penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit ff623565dead20667277cc140dde701f71de0e32

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-29T01:43:18Z
subjectTrying some Windows AF_UNIX support
commit ff623565dead20667277cc140dde701f71de0e32
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-29T01:43:18Z

    Trying some Windows AF_UNIX support
---
 src/compat.h                 |  7 +++++++
 src/stream_conn.c            |  4 ----
 src/stream_listener.c        | 44 ++++++++++++++++++++++----------------------
 src/stream_route.c           | 12 ++++++------
 src/tinyproxy.c              |  2 +-
 tests/test_unix_listeners.py | 34 +++++++++++++++++++---------------
 6 files changed, 55 insertions(+), 48 deletions(-)

diff --git a/src/compat.h b/src/compat.h
index 573cd16..f69baee 100644
--- a/src/compat.h
+++ b/src/compat.h
@@ -9,6 +9,7 @@
 
 #include <winsock2.h>
 #include <ws2tcpip.h>
+#include <afunix.h>
 #include <process.h>
 #include <direct.h>
 #include <errno.h>
@@ -26,6 +27,12 @@
 
 #endif
 
+#ifdef _WIN32
+#define compat_unlink(path) _unlink(path)
+#else
+#define compat_unlink(path) unlink(path)
+#endif
+
 static inline int socket_err_is_retriable(int err)
 {
 #ifdef _WIN32
diff --git a/src/stream_conn.c b/src/stream_conn.c
index 1f5b4c0..c78c03a 100644
--- a/src/stream_conn.c
+++ b/src/stream_conn.c
@@ -130,9 +130,6 @@ static int connect_upstream(struct bufferevent *bev, const struct endpoint *ep)
 	}
 
 	case ENDPOINT_UNIX:
-#ifdef _WIN32
-		return -ENOTSUP;
-#else
 	{
 		struct sockaddr_un addr;
 
@@ -158,7 +155,6 @@ static int connect_upstream(struct bufferevent *bev, const struct endpoint *ep)
 
 		return 0;
 	}
-#endif
 
 	default:
 		return -ENOTSUP;
diff --git a/src/stream_listener.c b/src/stream_listener.c
index d1a6ed1..07e1c33 100644
--- a/src/stream_listener.c
+++ b/src/stream_listener.c
@@ -80,13 +80,6 @@ static int bind_tcp_stream_listener(struct stream_route_ctx *ctx)
 	return 0;
 }
 
-#ifdef _WIN32
-static int bind_unix_stream_listener(struct stream_route_ctx *ctx)
-{
-	(void)ctx;
-	return -EAFNOSUPPORT;
-}
-#else
 static int bind_unix_stream_listener(struct stream_route_ctx *ctx)
 {
 	const struct endpoint *ep = &ctx->route->listen;
@@ -113,17 +106,14 @@ static int bind_unix_stream_listener(struct stream_route_ctx *ctx)
 
 	memcpy(sa.sun_path, path, strlen(path) + 1);
 
-	/*
-	 * For listener sockets, stale socket files are common after crashes.
-	 * This is safe enough for our config-driven proxy: if the path exists
-	 * and is not ours, bind() would fail without this anyway.
-	 */
-	if (unlink(path) < 0 && errno != ENOENT) {
+	if (compat_unlink(path) < 0 && errno != ENOENT) {
+		int err = errno;
+
 		LOG_ERROR("failed to remove existing unix stream socket",
 			"line", _LOGV(ctx->route->line_no),
 			"path", _LOGV(path),
-			"err", _LOGV(strerror(errno)));
-		return -errno;
+			"err", _LOGV(strerror(err)));
+		return -err;
 	}
 
 	listener = evconnlistener_new_bind(
@@ -135,21 +125,31 @@ static int bind_unix_stream_listener(struct stream_route_ctx *ctx)
 		(struct sockaddr *)&sa,
 		sizeof(sa)
 	);
+
 	if (listener == NULL) {
-		int err = errno;
+		int sockerr = EVUTIL_SOCKET_ERROR();
+		int syserr = errno;
 
 		LOG_ERROR("failed to bind unix stream listener",
-			"line", _LOGV(ctx->route->line_no),
-			"path", _LOGV(path),
-			"err", _LOGV(strerror(err)));
-
-		return err ? -err : -EIO;
+				"line", _LOGV(ctx->route->line_no),
+				"path", _LOGV(path),
+				"sockerr", _LOGV(sockerr),
+				"sockerrstr", _LOGV(evutil_socket_error_to_string(sockerr)),
+				"errno", _LOGV(syserr),
+				"errnostr", _LOGV(strerror(syserr)));
+
+		if (sockerr != 0) {
+			return -sockerr;
+		}
+		if (syserr != 0) {
+			return -syserr;
+		}
+		return -EIO;
 	}
 
 	ctx->listener = listener;
 	return 0;
 }
-#endif
 
 int bind_stream_listener(struct stream_route_ctx *ctx)
 {
diff --git a/src/stream_route.c b/src/stream_route.c
index c901d2a..f80ac48 100644
--- a/src/stream_route.c
+++ b/src/stream_route.c
@@ -4,6 +4,7 @@
 
 #include "klog.h"
 #include "stream_route.h"
+#include "compat.h"
 #include "worker.h"
 #include "route.h"
 #include "stream_listener.h"
@@ -39,12 +40,10 @@ int start_stream_route(struct worker *w, const struct route *r,
 	return 0;
 }
 
-#ifndef _WIN32
 static void stop_unix_stream_route(struct stream_route_ctx *ctx)
 {
 	const struct route *r;
 	const char *path;
-	struct stat st;
 
 	if (ctx == NULL || ctx->route == NULL) {
 		return;
@@ -61,6 +60,9 @@ static void stop_unix_stream_route(struct stream_route_ctx *ctx)
 		return;
 	}
 
+#ifndef _WIN32
+	struct stat st;
+
 	if (lstat(path, &st) < 0) {
 		if (errno != ENOENT) {
 			LOG_WARN("failed to inspect unix stream listener socket",
@@ -77,15 +79,15 @@ static void stop_unix_stream_route(struct stream_route_ctx *ctx)
 			"path", _LOGV(path));
 		return;
 	}
+#endif
 
-	if (unlink(path) < 0) {
+	if (compat_unlink(path) < 0 && errno != ENOENT) {
 		LOG_WARN("failed to remove unix stream listener socket",
 			"line", _LOGV(r->line_no),
 			"path", _LOGV(path),
 			"err", _LOGV(strerror(errno)));
 	}
 }
-#endif
 
 void stop_stream_route(struct stream_route_ctx *ctx)
 {
@@ -97,9 +99,7 @@ void stop_stream_route(struct stream_route_ctx *ctx)
 		evconnlistener_free(ctx->listener);
 	}
 
-#ifndef _WIN32
 	stop_unix_stream_route(ctx);
-#endif
 
 	memset(ctx, 0, sizeof(*ctx));
 }
diff --git a/src/tinyproxy.c b/src/tinyproxy.c
index 2dc6e3b..fcea91f 100644
--- a/src/tinyproxy.c
+++ b/src/tinyproxy.c
@@ -4,8 +4,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include <signal.h>
+#include <unistd.h>
 
 #include "klog.h"
 #include "signals.h"
diff --git a/tests/test_unix_listeners.py b/tests/test_unix_listeners.py
index 35cf234..ccae43f 100644
--- a/tests/test_unix_listeners.py
+++ b/tests/test_unix_listeners.py
@@ -2,6 +2,7 @@ import asyncio
 import os
 import socket
 import sys
+import tempfile
 
 from .support import (
 	LISTEN_HOST,
@@ -10,17 +11,27 @@ from .support import (
 	run_tinyproxy_with_conf,
 )
 
+def unix_sock_path(name):
+	from pathlib import Path
 
-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"
+	if sys.platform == "win32":
+		d = Path("C:/tmp/tinyproxy-tests")
+		d.mkdir(parents=True, exist_ok=True)
+		return str(d / name).replace("\\", "/")
 
-UNIX_TO_UNIX_LISTEN_SOCK = "/tmp/test-ping.sock"
-UNIX_TO_UNIX_BACKEND_SOCK = "/tmp/test-pong.sock"
+	d = tempfile.mkdtemp(prefix="tinyproxy-")
+	return os.path.join(d, name)
 
-UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK = "/tmp/test-ping-dgram.sock"
-UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK = "/tmp/test-pong-dgram.sock"
+UNIX_STREAM_SOCK = unix_sock_path("test-listen.sock")
+UNIX_BUILTIN_SOCK = unix_sock_path("test-listen-2.sock")
+UNIX_DGRAM_SOCK = unix_sock_path("test-listen-dgram.sock")
+UNIX_DGRAM_BUILTIN_SOCK = unix_sock_path("test-listen-3.sock")
+
+UNIX_TO_UNIX_LISTEN_SOCK = unix_sock_path("test-ping.sock")
+UNIX_TO_UNIX_BACKEND_SOCK = unix_sock_path("test-pong.sock")
+
+UNIX_DGRAM_TO_UNIX_DGRAM_LISTEN_SOCK = unix_sock_path("test-ping-dgram.sock")
+UNIX_DGRAM_TO_UNIX_DGRAM_BACKEND_SOCK = unix_sock_path("test-pong-dgram.sock")
 
 def unlink_if_exists(path: str) -> None:
 	try:
@@ -28,7 +39,6 @@ def unlink_if_exists(path: str) -> None:
 	except FileNotFoundError:
 		pass
 
-
 async def echo_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
 	try:
 		while True:
@@ -88,8 +98,6 @@ def unix_dgram_roundtrip(sock_path: str, payload: bytes) -> bytes:
 
 
 async def test_unix_stream_to_tcp() -> None:
-	if sys.platform.startswith("win"):
-		raise SkipTest("Unix socks tests are skipped on Windows")
 	proxy_bin = os.environ.get("TINYPROXY_BIN")
 	if not proxy_bin:
 		raise SkipTest("TINYPROXY_BIN is not set")
@@ -138,8 +146,6 @@ async def test_unix_stream_to_tcp() -> None:
 
 
 async def test_unix_stream_to_builtin_client_addr() -> None:
-	if sys.platform.startswith("win"):
-		raise SkipTest("Unix socks tests are skipped on Windows")
 	proxy_bin = os.environ.get("TINYPROXY_BIN")
 	if not proxy_bin:
 		raise SkipTest("TINYPROXY_BIN is not set")
@@ -238,8 +244,6 @@ async def test_unix_dgram_to_builtin_client_addr() -> None:
 		unlink_if_exists(UNIX_DGRAM_BUILTIN_SOCK)
 
 async def test_unix_stream_to_unix_stream() -> None:
-	if sys.platform.startswith("win"):
-		raise SkipTest("Unix socks tests are skipped on Windows")
 	proxy_bin = os.environ.get("TINYPROXY_BIN")
 	if not proxy_bin:
 		raise SkipTest("TINYPROXY_BIN is not set")