penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit 5a216402d143727f172518e036b61f9c545ee752

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-28T23:08:18Z
subjectAdded 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