penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit c06b61a44ad84fd2fe61f49558d72c836c480fec

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-26T16:32:41Z
subjectFixed windows compat
commit c06b61a44ad84fd2fe61f49558d72c836c480fec
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-26T16:32:41Z

    Fixed windows compat
---
 compat_socket.h            |  9 +++++++
 proxy_proto_v2.c           | 18 ++++++++-----
 tests/support.py           |  8 ------
 tests/test_udp_proxy_v2.py | 65 ++++++++++++++++++++++++++++++----------------
 udp_route.c                | 10 +++----
 5 files changed, 67 insertions(+), 43 deletions(-)

diff --git a/compat_socket.h b/compat_socket.h
index d261224..b981691 100644
--- a/compat_socket.h
+++ b/compat_socket.h
@@ -20,4 +20,13 @@
 
 #endif
 
+static inline int socket_err_is_retriable(int err)
+{
+#ifdef _WIN32
+	return err == WSAEWOULDBLOCK || err == WSAEINTR;
+#else
+	return err == EAGAIN || err == EWOULDBLOCK || err == EINTR;
+#endif
+}
+
 #endif
diff --git a/proxy_proto_v2.c b/proxy_proto_v2.c
index 5318de1..3a60c99 100644
--- a/proxy_proto_v2.c
+++ b/proxy_proto_v2.c
@@ -24,13 +24,17 @@ int proxy_v2_build(
 	size_t *out_len
 ) {
 	unsigned char fam_proto;
-	uint16_t addr_len;
+	size_t addr_len;
 	unsigned char *p;
 
 	if (buf == NULL || src == NULL || dst == NULL || out_len == NULL) {
 		return -EINVAL;
 	}
 
+	if (src_len < 0 || dst_len < 0) {
+		return -EINVAL;
+	}
+
 	if (src->sa_family != dst->sa_family) {
 		return -EAFNOSUPPORT;
 	}
@@ -45,8 +49,8 @@ int proxy_v2_build(
 
 	switch (src->sa_family) {
 	case AF_INET:
-		if (src_len < sizeof(struct sockaddr_in) ||
-			dst_len < sizeof(struct sockaddr_in)) {
+		if ((size_t)src_len < sizeof(struct sockaddr_in) ||
+			(size_t)dst_len < sizeof(struct sockaddr_in)) {
 			return -EINVAL;
 		}
 
@@ -59,7 +63,7 @@ int proxy_v2_build(
 		memcpy(buf, PROXY_V2_SIG, 12);
 		buf[12] = PP2_VERSION_CMD_PROXY;
 		buf[13] = PP2_FAM_INET | fam_proto;
-		put_u16(buf + 14, addr_len);
+		put_u16(buf + 14, (uint16_t)addr_len);
 
 		p = buf + 16;
 
@@ -82,8 +86,8 @@ int proxy_v2_build(
 		return 0;
 
 	case AF_INET6:
-		if (src_len < sizeof(struct sockaddr_in6) ||
-			dst_len < sizeof(struct sockaddr_in6)) {
+		if ((size_t)src_len < sizeof(struct sockaddr_in6) ||
+			(size_t)dst_len < sizeof(struct sockaddr_in6)) {
 			return -EINVAL;
 		}
 
@@ -96,7 +100,7 @@ int proxy_v2_build(
 		memcpy(buf, PROXY_V2_SIG, 12);
 		buf[12] = PP2_VERSION_CMD_PROXY;
 		buf[13] = PP2_FAM_INET6 | fam_proto;
-		put_u16(buf + 14, addr_len);
+		put_u16(buf + 14, (uint16_t)addr_len);
 
 		p = buf + 16;
 
diff --git a/tests/support.py b/tests/support.py
index 6e031df..8a1bd37 100644
--- a/tests/support.py
+++ b/tests/support.py
@@ -48,7 +48,6 @@ class UDPClientProtocol(asyncio.DatagramProtocol):
 class UDPProxyV2EchoServerProtocol(asyncio.DatagramProtocol):
 	def __init__(self) -> None:
 		self.transport: asyncio.DatagramTransport | None = None
-		self.seen: asyncio.Future[None] | None = None
 		self.error: Exception | None = None
 		self.last_src: tuple[str, int] | None = None
 		self.last_dst: tuple[str, int] | None = None
@@ -56,8 +55,6 @@ class UDPProxyV2EchoServerProtocol(asyncio.DatagramProtocol):
 
 	def connection_made(self, transport: asyncio.BaseTransport) -> None:
 		self.transport = transport  # type: ignore[assignment]
-		loop = asyncio.get_running_loop()
-		self.seen = loop.create_future()
 
 	def datagram_received(self, data: bytes, addr) -> None:
 		self.last_raw = data
@@ -66,16 +63,11 @@ class UDPProxyV2EchoServerProtocol(asyncio.DatagramProtocol):
 			src, dst, payload = parse_proxy_v2_udp4_packet(data)
 		except Exception as exc:
 			self.error = exc
-			if self.seen is not None and not self.seen.done():
-				self.seen.set_exception(exc)
 			return
 
 		self.last_src = src
 		self.last_dst = dst
 
-		if self.seen is not None and not self.seen.done():
-			self.seen.set_result(None)
-
 		if self.transport is not None:
 			self.transport.sendto(payload, addr)
 
diff --git a/tests/test_udp_proxy_v2.py b/tests/test_udp_proxy_v2.py
index 7c26a67..fd81ac2 100644
--- a/tests/test_udp_proxy_v2.py
+++ b/tests/test_udp_proxy_v2.py
@@ -2,7 +2,6 @@ import asyncio
 import os
 
 from .support import (
-	BACKEND_PORT,
 	LISTEN_HOST,
 	PROXY_PORT,
 	SkipTest,
@@ -11,6 +10,9 @@ from .support import (
 	udp_proxy_roundtrip,
 )
 
+UDP_PROXY_V2_BACKEND_PORT = 41234
+
+
 async def test_tinyproxy_sends_proxy_v2_for_udp() -> None:
 	proxy_bin = os.environ.get("TINYPROXY_BIN")
 	if not proxy_bin:
@@ -18,11 +20,14 @@ async def test_tinyproxy_sends_proxy_v2_for_udp() -> None:
 
 	conf_text = (
 		f"{LISTEN_HOST}:{PROXY_PORT} "
-		f"{LISTEN_HOST}:{BACKEND_PORT} "
+		f"{LISTEN_HOST}:{UDP_PROXY_V2_BACKEND_PORT} "
 		f"udp proxy_v2\n"
 	)
 
-	async with run_udp_proxy_v2_echo_backend(LISTEN_HOST, BACKEND_PORT) as backend:
+	async with run_udp_proxy_v2_echo_backend(
+		LISTEN_HOST,
+		UDP_PROXY_V2_BACKEND_PORT,
+	) as backend:
 		async with run_tinyproxy_with_conf(
 			proxy_bin=proxy_bin,
 			conf_text=conf_text,
@@ -32,32 +37,46 @@ async def test_tinyproxy_sends_proxy_v2_for_udp() -> None:
 		):
 			payload = b"hello udp proxy v2\n"
 
-			echo_task = asyncio.create_task(udp_proxy_roundtrip(payload, timeout=3.0))
-
 			try:
-				got = await echo_task
-			except TimeoutError:
-				if backend.error is not None:
-					raw = backend.last_raw.hex(" ") if backend.last_raw else "<none>"
-					raise AssertionError(
-						f"backend failed to parse UDP PROXY v2: {backend.error}; raw={raw}"
-					) from backend.error
-
+				got = await udp_proxy_roundtrip(payload, timeout=3.0)
+			except TimeoutError as exc:
 				raw = backend.last_raw.hex(" ") if backend.last_raw else "<none>"
 				raise AssertionError(
-					f"timed out waiting for UDP proxy v2 echo; backend raw={raw}"
-				)
+					"timed out waiting for UDP proxy-v2 echo; "
+					f"backend_error={backend.error!r}; "
+					f"backend_last_src={backend.last_src!r}; "
+					f"backend_last_dst={backend.last_dst!r}; "
+					f"backend_raw={raw}"
+				) from exc
+
+			raw = backend.last_raw.hex(" ") if backend.last_raw else "<none>"
 
-			assert got == payload, f"udp proxy v2 roundtrip mismatch: {got!r}"
+			assert backend.error is None, (
+				f"backend failed to parse PROXY v2: {backend.error!r}; raw={raw}"
+			)
+			assert got == payload, (
+				f"udp proxy-v2 roundtrip mismatch: got={got!r} expected={payload!r}; "
+				f"backend_raw={raw}"
+			)
+			assert backend.last_src is not None, (
+				f"backend did not record PROXY v2 source; raw={raw}"
+			)
+			assert backend.last_dst is not None, (
+				f"backend did not record PROXY v2 destination; raw={raw}"
+			)
 
-			assert backend.error is None, f"backend failed to parse PROXY v2: {backend.error}"
-			assert backend.last_src is not None, "backend did not record PROXY v2 source"
-			assert backend.last_dst is not None, "backend did not record PROXY v2 destination"
+			assert backend.last_src[0] == LISTEN_HOST, (
+				f"bad proxy-v2 src addr: got={backend.last_src!r}; raw={raw}"
+			)
+			assert backend.last_dst[0] == LISTEN_HOST, (
+				f"bad proxy-v2 dst addr: got={backend.last_dst!r}; raw={raw}"
+			)
+			assert backend.last_dst[1] == PROXY_PORT, (
+				f"bad proxy-v2 dst port: got={backend.last_dst!r}; "
+				f"expected_port={PROXY_PORT}; raw={raw}"
+			)
 
-			assert backend.last_src[0] == LISTEN_HOST
-			assert backend.last_dst[0] == LISTEN_HOST
-			assert backend.last_dst[1] == PROXY_PORT
 
 TESTS = [
 	("test_tinyproxy_sends_proxy_v2_for_udp", test_tinyproxy_sends_proxy_v2_for_udp),
-]
+]
\ No newline at end of file
diff --git a/udp_route.c b/udp_route.c
index f44bf5a..4a4fc6e 100644
--- a/udp_route.c
+++ b/udp_route.c
@@ -134,11 +134,11 @@ static void upstream_read_cb(evutil_socket_t fd, short events, void *arg)
 		if (n < 0) {
 			int err = EVUTIL_SOCKET_ERROR();
 
-			if (err == EAGAIN || err == EWOULDBLOCK) {
+			if (socket_err_is_retriable(err)) {
 				return;
 			}
 
-			LOG_ERROR("udp upstream recv failed",
+			LOG_ERROR("udp recvfrom failed",
 				"err", _LOGV(evutil_socket_error_to_string(err))
 			);
 			return;
@@ -343,11 +343,11 @@ static void listen_read_cb(evutil_socket_t fd, short events, void *arg)
 		if (n < 0) {
 			int err = EVUTIL_SOCKET_ERROR();
 
-			if (err == EAGAIN || err == EWOULDBLOCK) {
+			if (socket_err_is_retriable(err)) {
 				return;
 			}
 
-			LOG_ERROR("udp recvfrom failed",
+			LOG_ERROR("udp upstream recv failed",
 				"err", _LOGV(evutil_socket_error_to_string(err))
 			);
 			return;
@@ -370,7 +370,7 @@ static void listen_read_cb(evutil_socket_t fd, short events, void *arg)
 		int rc = send_udp_payload_to_upstream(c, buf, (size_t)n);
 		if (rc < 0) {
 			LOG_ERROR("udp send to upstream failed",
-				"err", _LOGV(strerror(-rc))
+				"err", _LOGV(evutil_socket_error_to_string(-rc))
 			);
 			return;
 		}