commit 5a216402d143727f172518e036b61f9c545ee752
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-05-28T23:08:18Z |
| subject | Added tests for unix socks |
commit 5a216402d143727f172518e036b61f9c545ee752
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-05-28T23:08:18Z
Added tests for unix socks
---
.gitignore | 1 +
README.md | 70 +--------
docs/tinyproxy.1.scd | 3 +-
docs/tinyproxy.7.scd | 80 ++++++++++
mk/man.mk | 7 +-
tests/run_tests.py | 2 +
tests/support.py | 19 ++-
tests/test_unix_listeners.py | 344 +++++++++++++++++++++++++++++++++++++++++++
tinyproxy.conf | 8 +-
9 files changed, 457 insertions(+), 77 deletions(-)
diff --git a/.gitignore b/.gitignore
index 654fdc0..5c24ce0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ __pycache__
# Generated man pages from scdoc
docs/*.1
docs/*.5
+docs/*.7
docs/*.8
# Prerequisites
diff --git a/README.md b/README.md
index a3f93a1..48d9eec 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
An L4 proxy designed to act as a tiny transparent shim.
-## Rule of thumb
+## Philosophy
1. Should feel like an inet utility.
2. Be simple and effective.
@@ -33,73 +33,6 @@ tinyproxy -c tinyproxy.conf
check tinyproxy.conf for a more complete setup
-## Supported protocols
-
-### TCP
-
-TCP routes accept client connections and forward bytes to the configured upstream.
-
-Each accepted client connection creates one upstream connection.
-
-### UDP
-
-UDP routes listen for datagrams and forward them to the configured upstream.
-
-UDP is connectionless, but the proxy keeps a small per-client mapping internally so upstream replies can be sent back to the correct original client address.
-
-The current UDP implementation is intentionally simple:
-
-- IPv4 only for now.
-- One upstream UDP socket per observed client address.
-- Idle client mappings are expired after a timeout.
-- No attempt is made to provide delivery guarantees.
-- No attempt is made to reassemble fragmented UDP traffic.
-
-## Proxy Protocol v2
-
-The `proxy_v2` option makes tinyproxy send a Proxy Protocol v2 header to the upstream server.
-
-This is useful when the upstream server needs to know the original client address and port, but the proxy itself would otherwise hide that information.
-
-### TCP Proxy Protocol v2
-
-For TCP routes, tinyproxy writes one Proxy Protocol v2 header before forwarding the client stream.
-
-The upstream must understand Proxy Protocol v2. If it does not, the upstream will see binary header bytes before the application payload and will likely fail.
-
-### UDP Proxy Protocol v2
-
-For UDP routes, tinyproxy prepends a Proxy Protocol v2 header to each forwarded client datagram.
-
-The upstream receives:
-
-```text
-PROXY_V2_HEADER + ORIGINAL_UDP_PAYLOAD
-```
-
-For IPv4 UDP, the header uses:
-
-```text
-version/command = PROXY
-family/proto = INET/DGRAM
-address length = 12
-```
-
-This behavior is intended to match the useful subset of Proxy Protocol v2 used by load balancers that need to preserve client address information for UDP services.
-
-AWS documents Proxy Protocol v2 support on Network Load Balancer target groups, and its AWS networking blog notes that UDP is supported even though the walkthrough focuses on TCP:
-
-- [AWS: Preserving client IP address with Proxy protocol v2 and Network Load Balancer](https://aws.amazon.com/blogs/networking-and-content-delivery/preserving-client-ip-address-with-proxy-protocol-v2-and-network-load-balancer/)
-- [AWS: Target groups for your Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html)
-
-tinyproxy adds a Proxy Protocol v2 header so the upstream can recover:
-
-- source IP
-- source port
-- destination IP
-- destination port
-- transport protocol
-
## Non-goals
- TLS termination
@@ -125,4 +58,3 @@ These are the major features I want to implement before calling it done. (going
* pthread support (planned for v0.2.x)
* Forwarding
* IPv6 (planned for v0.2.x)
- * unix socks (listen) (planned for v0.2.x)
diff --git a/docs/tinyproxy.1.scd b/docs/tinyproxy.1.scd
index 5c84b13..e05f1e6 100644
--- a/docs/tinyproxy.1.scd
+++ b/docs/tinyproxy.1.scd
@@ -17,7 +17,8 @@ Supported endpoint types:
- UDP
- UNIX stream sockets
- UNIX datagram sockets
-- file:// backends
+- file backends
+- Some builtin backends
Features:
- PROXY protocol v2
diff --git a/docs/tinyproxy.7.scd b/docs/tinyproxy.7.scd
new file mode 100644
index 0000000..a5df9db
--- /dev/null
+++ b/docs/tinyproxy.7.scd
@@ -0,0 +1,80 @@
+tinyproxy-protocols(7)
+
+# NAME
+
+tinyproxy-protocols - protocol behavior and forwarding semantics
+
+# DESCRIPTION
+
+This document describes how tinyproxy handles supported transport protocols
+and Proxy Protocol v2 forwarding behavior.
+
+# TCP
+
+TCP routes accept client connections and forward bytes to the configured upstream.
+
+Each accepted client connection creates one upstream connection.
+
+# UDP
+
+UDP routes listen for datagrams and forward them to the configured upstream.
+
+UDP is connectionless, but the proxy keeps a small per-client mapping internally so upstream replies can be sent back to the correct original client address.
+
+The current UDP implementation is intentionally simple:
+
+- IPv4 only for now.
+- One upstream UDP socket per observed client address.
+- Idle client mappings are expired after a timeout.
+- No attempt is made to provide delivery guarantees.
+- No attempt is made to reassemble fragmented UDP traffic.
+
+# Proxy Protocol V2
+
+tinyproxy adds a Proxy Protocol v2 header so the upstream can recover:
+
+- source IP
+- source port
+- destination IP
+- destination port
+- transport protocol
+
+The `proxy_v2` option makes tinyproxy send a Proxy Protocol v2 header to the upstream server.
+
+This is useful when the upstream server needs to know the original client address and port, but the proxy itself would otherwise hide that information.
+
+## TCP Proxy Protocol v2
+
+For TCP routes, tinyproxy writes one Proxy Protocol v2 header before forwarding the client stream.
+
+The upstream must understand Proxy Protocol v2. If it does not, the upstream will see binary header bytes before the application payload and will likely fail.
+
+## UDP Proxy Protocol v2
+
+For UDP routes, tinyproxy prepends a Proxy Protocol v2 header to each forwarded client datagram.
+
+The upstream receives:
+
+```
+PROXY_V2_HEADER + ORIGINAL_UDP_PAYLOAD
+```
+
+For IPv4 UDP, the header uses:
+
+```
+version/command = PROXY
+family/proto = INET/DGRAM
+address length = 12
+```
+
+This behavior is intended to match the useful subset of Proxy Protocol v2 used by load balancers that need to preserve client address information for UDP services.
+
+AWS documents Proxy Protocol v2 support on Network Load Balancer target groups, and its AWS networking blog notes that UDP is supported even though the walkthrough focuses on TCP:
+
+<https://aws.amazon.com/blogs/networking-and-content-delivery/preserving-client-ip-address-with-proxy-protocol-v2-and-network-load-balancer/>
+
+<https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html>
+
+# SEE ALSO
+
+tinyproxy(1), tinyproxy.conf(5)
diff --git a/mk/man.mk b/mk/man.mk
index 1d626fd..49eda49 100644
--- a/mk/man.mk
+++ b/mk/man.mk
@@ -1,7 +1,8 @@
SCDOC ?= scdoc
MAN_SCD := docs/tinyproxy.1.scd \
- docs/tinyproxy.conf.5.scd
+ docs/tinyproxy.conf.5.scd \
+ docs/tinyproxy.7.scd
MAN := $(MAN_SCD:.scd=)
MAN_DIST := $(PROJECT_ROOT)/dist
@@ -16,14 +17,18 @@ docs/%: docs/%.scd
install-man: man
mkdir -p $(MAN_DIST)/usr/share/man/man1
mkdir -p $(MAN_DIST)/usr/share/man/man5
+ mkdir -p $(MAN_DIST)/usr/share/man/man7
install -Dm644 docs/tinyproxy.1 \
$(MAN_DIST)/usr/share/man/man1/tinyproxy.1
install -Dm644 docs/tinyproxy.conf.5 \
$(MAN_DIST)/usr/share/man/man5/tinyproxy.conf.5
+ install -Dm644 docs/tinyproxy.7 \
+ $(MAN_DIST)/usr/share/man/man7/tinyproxy.7
uninstall-man:
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/tinyproxy.1
rm -f $(DESTDIR)$(PREFIX)/share/man/man5/tinyproxy.conf.5
+ rm -f $(DESTDIR)$(PREFIX)/share/man/man7/tinyproxy.7
clean-man:
rm -f $(MAN)
diff --git a/tests/run_tests.py b/tests/run_tests.py
index 1123e56..dea7a12 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -20,6 +20,7 @@ from . import test_udp_basic
from . import test_udp_idle_timeout
from . import test_haproxy_proxy_v2
from . import test_udp_proxy_v2
+from . import test_unix_listeners
TCP_PROXY_TEST_MODULES = [
@@ -32,6 +33,7 @@ UDP_PROXY_TEST_MODULES = [
]
STANDALONE_TEST_MODULES = [
+ test_unix_listeners,
test_haproxy_proxy_v2,
test_udp_proxy_v2,
test_tcp_backpressure,
diff --git a/tests/support.py b/tests/support.py
index c617f7d..c388468 100644
--- a/tests/support.py
+++ b/tests/support.py
@@ -335,11 +335,11 @@ async def run_echo_backend(
server.close()
await server.wait_closed()
-
@asynccontextmanager
async def run_tinyproxy_with_conf(
proxy_bin: str,
- conf_text: str,
+ conf_text: str | None = None,
+ args: list[str] | None = None,
listen_host: str = LISTEN_HOST,
listen_port: int = PROXY_PORT,
proto: str = "tcp",
@@ -348,10 +348,20 @@ async def run_tinyproxy_with_conf(
proxy = None
try:
- conf_path = write_temp_file(conf_text, ".conf")
+ cmd = [proxy_bin]
+
+ if conf_text is not None:
+ conf_path = write_temp_file(conf_text, ".conf")
+ cmd += ["-c", conf_path]
+
+ if args:
+ cmd += args
+
+ if conf_text is None and not args:
+ raise ValueError("run_tinyproxy_with_conf requires conf_text or args")
proxy = subprocess.Popen(
- [proxy_bin, "-c", conf_path],
+ cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
@@ -385,7 +395,6 @@ async def run_tinyproxy_with_conf(
except FileNotFoundError:
pass
-
@asynccontextmanager
async def run_default_tcp_tinyproxy(proxy_bin: str):
conf_text = (
diff --git a/tests/test_unix_listeners.py b/tests/test_unix_listeners.py
new file mode 100644
index 0000000..df026c3
--- /dev/null
+++ b/tests/test_unix_listeners.py
@@ -0,0 +1,344 @@
+import asyncio
+import os
+import socket
+
+from .support import (
+ LISTEN_HOST,
+ BACKEND_PORT,
+ SkipTest,
+ run_tinyproxy_with_conf,
+)
+
+
+UNIX_STREAM_SOCK = "/tmp/test-listen.sock"
+UNIX_BUILTIN_SOCK = "/tmp/test-listen-2.sock"
+UNIX_DGRAM_SOCK = "/tmp/test-listen-dgram.sock"
+UNIX_DGRAM_BUILTIN_SOCK = "/tmp/test-listen-3.sock"
+
+UNIX_TO_UNIX_LISTEN_SOCK = "/tmp/test-ping.sock"
+UNIX_TO_UNIX_BACKEND_SOCK = "/tmp/test-pong.sock"
+
+UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK = "/tmp/test-ping-dgram.sock"
+UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK = "/tmp/test-pong-dgram.sock"
+
+def unlink_if_exists(path: str) -> None:
+ try:
+ os.unlink(path)
+ except FileNotFoundError:
+ pass
+
+
+async def echo_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
+ try:
+ while True:
+ data = await reader.read(65536)
+ if not data:
+ break
+
+ writer.write(data)
+ await writer.drain()
+ finally:
+ writer.close()
+ await writer.wait_closed()
+
+
+async def close_writer(writer: asyncio.StreamWriter) -> None:
+ try:
+ writer.close()
+ await writer.wait_closed()
+ except ConnectionResetError:
+ pass
+
+
+async def open_unix_connection(path: str):
+ return await asyncio.open_unix_connection(path)
+
+
+async def udp_echo_server(host: str, port: int):
+ loop = asyncio.get_running_loop()
+
+ class EchoProtocol(asyncio.DatagramProtocol):
+ def connection_made(self, transport):
+ self.transport = transport
+
+ def datagram_received(self, data, addr):
+ self.transport.sendto(data, addr)
+
+ return await loop.create_datagram_endpoint(
+ lambda: EchoProtocol(),
+ local_addr=(host, port),
+ )
+
+
+def unix_dgram_roundtrip(sock_path: str, payload: bytes) -> bytes:
+ client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+ client.settimeout(3.0)
+
+ client_path = f"/tmp/tinyproxy-test-client-{os.getpid()}-{id(client)}.sock"
+ unlink_if_exists(client_path)
+
+ try:
+ client.bind(client_path)
+ client.sendto(payload, sock_path)
+ return client.recv(65536)
+ finally:
+ client.close()
+ unlink_if_exists(client_path)
+
+
+async def test_unix_stream_to_tcp() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ unlink_if_exists(UNIX_STREAM_SOCK)
+
+ conf_text = (
+ f"listen unix {UNIX_STREAM_SOCK} tcp {LISTEN_HOST}:{BACKEND_PORT}\n"
+ )
+
+ backend_server = await asyncio.start_server(
+ echo_handler,
+ LISTEN_HOST,
+ BACKEND_PORT,
+ backlog=128,
+ )
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ proto="unix",
+ ):
+ reader, writer = await open_unix_connection(UNIX_STREAM_SOCK)
+
+ try:
+ payload = b"hello over unix stream\n"
+
+ writer.write(payload)
+ await writer.drain()
+
+ got = await asyncio.wait_for(
+ reader.readexactly(len(payload)),
+ timeout=3.0,
+ )
+
+ assert got == payload, (
+ f"unix stream roundtrip mismatch: got={got!r} expected={payload!r}"
+ )
+ finally:
+ await close_writer(writer)
+ finally:
+ backend_server.close()
+ await backend_server.wait_closed()
+ unlink_if_exists(UNIX_STREAM_SOCK)
+
+
+async def test_unix_stream_to_builtin_client_addr() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ unlink_if_exists(UNIX_BUILTIN_SOCK)
+
+ conf_text = (
+ f"listen unix {UNIX_BUILTIN_SOCK} builtin client_addr\n"
+ )
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ proto="unix",
+ ):
+ reader, writer = await open_unix_connection(UNIX_BUILTIN_SOCK)
+
+ try:
+ got = await asyncio.wait_for(reader.read(65536), timeout=3.0)
+
+ assert got, "expected builtin client_addr response"
+ assert b"unix" in got.lower() or b"unknown" in got.lower() or got.strip(), (
+ f"unexpected builtin client_addr response: {got!r}"
+ )
+ finally:
+ await close_writer(writer)
+ finally:
+ unlink_if_exists(UNIX_BUILTIN_SOCK)
+
+
+async def test_unix_dgram_to_udp() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ unlink_if_exists(UNIX_DGRAM_SOCK)
+
+ conf_text = (
+ f"listen unix-dgram {UNIX_DGRAM_SOCK} udp {LISTEN_HOST}:{BACKEND_PORT}\n"
+ )
+
+ transport, _ = await udp_echo_server(LISTEN_HOST, BACKEND_PORT)
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ proto="unix-dgram",
+ ):
+ payload = b"hello over unix datagram\n"
+
+ got = await asyncio.to_thread(
+ unix_dgram_roundtrip,
+ UNIX_DGRAM_SOCK,
+ payload,
+ )
+
+ assert got == payload, (
+ f"unix dgram roundtrip mismatch: got={got!r} expected={payload!r}"
+ )
+ finally:
+ transport.close()
+ unlink_if_exists(UNIX_DGRAM_SOCK)
+
+
+async def test_unix_dgram_to_builtin_client_addr() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ unlink_if_exists(UNIX_DGRAM_BUILTIN_SOCK)
+
+ conf_text = (
+ f"listen unix-dgram {UNIX_DGRAM_BUILTIN_SOCK} builtin client_addr\n"
+ )
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ proto="unix-dgram",
+ ):
+ got = await asyncio.to_thread(
+ unix_dgram_roundtrip,
+ UNIX_DGRAM_BUILTIN_SOCK,
+ b"ignored\n",
+ )
+
+ assert got, "expected builtin client_addr datagram response"
+ finally:
+ unlink_if_exists(UNIX_DGRAM_BUILTIN_SOCK)
+
+async def test_unix_stream_to_unix_stream() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ unlink_if_exists(UNIX_TO_UNIX_LISTEN_SOCK)
+ unlink_if_exists(UNIX_TO_UNIX_BACKEND_SOCK)
+
+ conf_text = (
+ f"listen unix {UNIX_TO_UNIX_LISTEN_SOCK} "
+ f"unix {UNIX_TO_UNIX_BACKEND_SOCK}\n"
+ )
+
+ backend_server = await asyncio.start_unix_server(
+ echo_handler,
+ path=UNIX_TO_UNIX_BACKEND_SOCK,
+ backlog=128,
+ )
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ proto="unix",
+ ):
+ reader, writer = await open_unix_connection(UNIX_TO_UNIX_LISTEN_SOCK)
+
+ try:
+ payload = b"hello unix to unix\n"
+
+ writer.write(payload)
+ await writer.drain()
+
+ got = await asyncio.wait_for(
+ reader.readexactly(len(payload)),
+ timeout=3.0,
+ )
+
+ assert got == payload, (
+ f"unix-to-unix roundtrip mismatch: "
+ f"got={got!r} expected={payload!r}"
+ )
+ finally:
+ await close_writer(writer)
+ finally:
+ backend_server.close()
+ await backend_server.wait_closed()
+ unlink_if_exists(UNIX_TO_UNIX_LISTEN_SOCK)
+ unlink_if_exists(UNIX_TO_UNIX_BACKEND_SOCK)
+
+async def unix_dgram_echo_server(path: str):
+ loop = asyncio.get_running_loop()
+
+ class EchoProtocol(asyncio.DatagramProtocol):
+ def connection_made(self, transport):
+ self.transport = transport
+
+ def datagram_received(self, data, addr):
+ self.transport.sendto(data, addr)
+
+ return await loop.create_datagram_endpoint(
+ lambda: EchoProtocol(),
+ local_addr=path,
+ family=socket.AF_UNIX,
+ )
+
+async def test_unix_dgram_to_unix_dgram() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ unlink_if_exists(UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK)
+ unlink_if_exists(UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK)
+
+ conf_text = (
+ f"listen unix-dgram {UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK} "
+ f"unix-dgram {UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK}\n"
+ )
+
+ transport, _ = await unix_dgram_echo_server(
+ UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK,
+ )
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ proto="unix-dgram",
+ ):
+ payload = b"hello unix-dgram to unix-dgram\n"
+
+ got = await asyncio.to_thread(
+ unix_dgram_roundtrip,
+ UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK,
+ payload,
+ )
+
+ assert got == payload, (
+ f"unix-dgram-to-unix-dgram roundtrip mismatch: "
+ f"got={got!r} expected={payload!r}"
+ )
+ finally:
+ transport.close()
+ unlink_if_exists(UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK)
+ unlink_if_exists(UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK)
+
+TESTS = [
+ ("test_unix_stream_to_tcp", test_unix_stream_to_tcp),
+ ("test_unix_stream_to_builtin_client_addr", test_unix_stream_to_builtin_client_addr),
+ ("test_unix_dgram_to_udp", test_unix_dgram_to_udp),
+ ("test_unix_dgram_to_builtin_client_addr", test_unix_dgram_to_builtin_client_addr),
+ ("test_unix_stream_to_unix_stream", test_unix_stream_to_unix_stream),
+ ("test_unix_dgram_to_unix_dgram", test_unix_dgram_to_unix_dgram),
+]
diff --git a/tinyproxy.conf b/tinyproxy.conf
index f944557..701b49c 100644
--- a/tinyproxy.conf
+++ b/tinyproxy.conf
@@ -40,7 +40,7 @@ listen tcp :80 tcp 10.0.1.1:80
listen tcp :443 tcp 10.0.1.1:443 proxy_v2,keep_alive
# TCP listener to UNIX stream backend.
-listen tcp :12990 unix /tmp/test.sock
+listen tcp :12990 unix /tmp/test.sock
# UDP datagram forwarding.
listen udp :12345 udp 10.0.1.1:12345 idle_timeout=10,connect_timeout=17
@@ -64,3 +64,9 @@ listen udp :12998 builtin close
# Static file TCP backends.
listen tcp :14000 file examples/http-response.txt
listen tcp :14001 file /does/not/exist
+
+listen unix /tmp/test-listen.sock tcp 127.0.0.1:12991
+listen unix /tmp/test-listen-2.sock builtin client_addr
+listen unix-dgram /tmp/test-listen.sock udp 127.0.0.1:12991
+listen unix-dgram /tmp/test-listen-3.sock builtin client_addr
+listen unix /tmp/test-ping.sock unix /tmp/test-pong.sock