penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit bb6f1f61ed6eb7e29d73486d5cb15ebd3dd25427

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-26T13:42:54Z
subjectFixed log format and windows compat
commit bb6f1f61ed6eb7e29d73486d5cb15ebd3dd25427
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-26T13:42:54Z

    Fixed log format and windows compat
---
 Makefile            |   3 +-
 tcp_route.c         |   4 +-
 tests/test_proxy.py |  76 +++++++++++++++++++++-----------------
 tinyproxy.c         | 103 ++++++++++++++++++++++++++++++++--------------------
 4 files changed, 111 insertions(+), 75 deletions(-)

diff --git a/Makefile b/Makefile
index 577e644..cbec336 100644
--- a/Makefile
+++ b/Makefile
@@ -44,6 +44,7 @@ else ifeq ($(OS),Windows_NT)
 STATIC ?= 0
 LDFLAGS += -Wl,--gc-sections
 TEST_FLAGS :=
+TEST_FLAGS := CONCURRENCY=100 TOTAL=100 FD_LIMIT=512
 else
 STATIC ?= 1
 LDFLAGS += -Wl,--gc-sections
@@ -118,4 +119,4 @@ clean-libevent:
 distclean: clean clean-libevent
 	rm -rf $(BUILD_DIR)
 
-.PHONY: all clean clean-libevent distclean test strip
\ No newline at end of file
+.PHONY: all clean clean-libevent distclean test strip
diff --git a/tcp_route.c b/tcp_route.c
index 42c5164..d0d1cc6 100644
--- a/tcp_route.c
+++ b/tcp_route.c
@@ -65,7 +65,7 @@ static void event_cb(struct bufferevent *bev, short events, void *arg) {
 	if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
 		if (events & BEV_EVENT_ERROR) {
 			int err = EVUTIL_SOCKET_ERROR();
-			LOG_ERROR("connection error", "msg", _LOGV(evutil_socket_error_to_string(err)));
+			LOG_ERROR("connection error", "err", _LOGV(evutil_socket_error_to_string(err)));
 		}
 
 		free_conn(conn);
@@ -217,7 +217,7 @@ static void accept_error_cb(struct evconnlistener *listener, void *arg) {
 	struct event_base *base = arg;
 	int err = EVUTIL_SOCKET_ERROR();
 
-	LOG_ERROR("accept error", "msg", _LOGV(evutil_socket_error_to_string(err)));
+	LOG_ERROR("accept error", "err", _LOGV(evutil_socket_error_to_string(err)));
 
 	evconnlistener_free(listener);
 	event_base_loopexit(base, NULL);
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
index 8c52af3..f48bdb8 100755
--- a/tests/test_proxy.py
+++ b/tests/test_proxy.py
@@ -174,12 +174,16 @@ async def main_async(proxy_bin: str) -> int:
 		backlog=4096,
 	)
 
-	with tempfile.NamedTemporaryFile("w", suffix=".conf") as f:
-		f.write(f"{LISTEN_HOST}:{PROXY_PORT} {LISTEN_HOST}:{BACKEND_PORT} tcp\n")
-		f.flush()
+	conf_path = None
+	proxy = None
+
+	try:
+		fd, conf_path = tempfile.mkstemp(suffix=".conf", text=True)
+		with os.fdopen(fd, "w") as f:
+			f.write(f"{LISTEN_HOST}:{PROXY_PORT} {LISTEN_HOST}:{BACKEND_PORT} tcp\n")
 
 		proxy = subprocess.Popen(
-			[proxy_bin, "-c", f.name],
+			[proxy_bin, "-c", conf_path],
 			stdout=subprocess.PIPE,
 			stderr=subprocess.PIPE,
 			text=True,
@@ -192,43 +196,42 @@ async def main_async(proxy_bin: str) -> int:
 			raise RuntimeError(
 				f"proxy exited early with code {proxy.returncode}\n{stderr}"
 			)
-
-		try:
 			await wait_for_port(LISTEN_HOST, BACKEND_PORT)
 			await wait_for_port(LISTEN_HOST, PROXY_PORT)
 
-			print("running test_small_roundtrip...")
-			await test_small_roundtrip()
-			print("ok test_small_roundtrip")
+		print("running test_small_roundtrip...")
+		await test_small_roundtrip()
+		print("ok test_small_roundtrip")
 
-			print("running test_large_roundtrip...")
-			await test_large_roundtrip()
-			print("ok test_large_roundtrip")
+		print("running test_large_roundtrip...")
+		await test_large_roundtrip()
+		print("ok test_large_roundtrip")
 
-			print("running test_many_sequential_connections 1000...")
-			await test_many_sequential_connections(1000)
-			print("ok test_many_sequential_connections")
+		print("running test_many_sequential_connections 1000...")
+		await test_many_sequential_connections(1000)
+		print("ok test_many_sequential_connections")
 
-			total = int(os.environ.get("TOTAL", "10000"))
-			concurrency = int(os.environ.get("CONCURRENCY", "10000"))
-			payload_size = int(os.environ.get("PAYLOAD_SIZE", "1024"))
+		total = int(os.environ.get("TOTAL", "10000"))
+		concurrency = int(os.environ.get("CONCURRENCY", "10000"))
+		payload_size = int(os.environ.get("PAYLOAD_SIZE", "1024"))
 
-			print(
-				f"running test_concurrent_connections "
-				f"total={total} concurrency={concurrency} payload_size={payload_size}..."
-			)
+		print(
+			f"running test_concurrent_connections "
+			f"total={total} concurrency={concurrency} payload_size={payload_size}..."
+		)
 
-			await test_concurrent_connections(
-				total=total,
-				concurrency=concurrency,
-				payload_size=payload_size,
-			)
+		await test_concurrent_connections(
+			total=total,
+			concurrency=concurrency,
+			payload_size=payload_size,
+		)
 
-			print("ok test_concurrent_connections")
-			print("all tests passed")
-			return 0
+		print("ok test_concurrent_connections")
+		print("all tests passed")
+		return 0
 
-		finally:
+	finally:
+		if proxy is not None:
 			proxy.terminate()
 
 			try:
@@ -237,14 +240,21 @@ async def main_async(proxy_bin: str) -> int:
 				proxy.kill()
 				proxy.wait(timeout=2.0)
 
-			backend_server.close()
-			await backend_server.wait_closed()
+		backend_server.close()
+		await backend_server.wait_closed()
 
+		if proxy is not None:
 			stderr = proxy.stderr.read() if proxy.stderr else ""
 			if stderr:
 				print("\nproxy stderr:")
 				print(stderr)
 
+		if conf_path is not None:
+			try:
+				os.unlink(conf_path)
+			except FileNotFoundError:
+				pass
+
 
 def main() -> int:
 	if len(sys.argv) != 2:
diff --git a/tinyproxy.c b/tinyproxy.c
index e52de95..bbdf30f 100644
--- a/tinyproxy.c
+++ b/tinyproxy.c
@@ -27,6 +27,22 @@ int main(int argc, char **argv)
 {
 	const char *conf_path = "tinyproxy.conf";
 	int opt;
+	int exit_code = 1;
+	int rc;
+
+#ifdef _WIN32
+	int wsa_started = 0;
+#endif
+
+	struct route *routes = NULL;
+	size_t route_count = 0;
+
+	struct listener_ctx **tcp_ctxs = NULL;
+	size_t tcp_ctx_count = 0;
+
+	struct event_base *base = NULL;
+	struct signal_events signals;
+	int signals_started = 0;
 
 	while ((opt = getopt(argc, argv, "c:h:v")) != -1) {
 		switch (opt) {
@@ -58,43 +74,46 @@ int main(int argc, char **argv)
 		return 2;
 	}
 
-	struct route *routes = NULL;
-	size_t route_count = 0;
-
-	int rc = load_routes_from_file(conf_path, &routes, &route_count);
+	rc = load_routes_from_file(conf_path, &routes, &route_count);
 	if (rc != 0) {
-		LOG_ERROR("failed to load config", "path", conf_path, "msg", _LOGV(strerror(-rc)));
-		return 1;
+		LOG_ERROR("failed to load config",
+			"path", _LOGV(conf_path),
+			"err", _LOGV(strerror(-rc)));
+		goto out;
 	}
 
 	if (route_count == 0) {
 		LOG_ERROR("config has no routes", "path", _LOGV(conf_path));
-		free_routes(routes);
-		return 1;
+		goto out;
 	}
 
-	struct listener_ctx **tcp_ctxs = calloc(route_count, sizeof(*tcp_ctxs));
+	tcp_ctxs = calloc(route_count, sizeof(*tcp_ctxs));
 	if (tcp_ctxs == NULL) {
-		free_routes(routes);
-		return 1;
+		LOG_ERROR("calloc failed", "err", _LOGV(strerror(errno)));
+		goto out;
 	}
 
-	size_t tcp_ctx_count = 0;
+#ifdef _WIN32
+	WSADATA wsa;
+	rc = WSAStartup(MAKEWORD(2, 2), &wsa);
+	if (rc != 0) {
+		LOG_ERROR("WSAStartup failed", "err", _LOGV(rc));
+		goto out;
+	}
+	wsa_started = 1;
+#endif
 
-	struct event_base *base = event_base_new();
+	base = event_base_new();
 	if (base == NULL) {
 		LOG_ERROR("event_base_new failed");
-		return 1;
+		goto out;
 	}
 
-	struct signal_events signals;
-
 	if (setup_signal_handlers(base, &signals) != 0) {
 		LOG_ERROR("failed to setup signal handlers");
-		event_base_free(base);
-		free_routes(routes);
-		return 1;
+		goto out;
 	}
+	signals_started = 1;
 
 	struct worker w = {
 		.base = base,
@@ -113,8 +132,8 @@ int main(int argc, char **argv)
 			break;
 
 		case PROTO_UDP:
-			// rc = start_udp_route(r);
 			LOG_WARN("skipping udp route: Not implemented yet");
+			rc = 0;
 			break;
 
 		default:
@@ -126,38 +145,44 @@ int main(int argc, char **argv)
 		if (rc != 0) {
 			LOG_ERROR("failed to start route",
 				"line", _LOGV(r->line_no),
-				"listen_host", _LOGV(r->listen_port),
-				"listen_port", _LOGV(r->listen_host),
+				"listen_host", _LOGV(r->listen_host),
+				"listen_port", _LOGV(r->listen_port),
 				"upstream_host", _LOGV(r->upstream_host),
-				"upstream_port", _LOGV(r->upstream_port)
-			);
-
-			for (size_t j = 0; j < tcp_ctx_count; j++) {
-				free_tcp_route(tcp_ctxs[j]);
-			}
-
-			free(tcp_ctxs);
-			event_base_free(base);
-			free_routes(routes);
-			return 1;
+				"upstream_port", _LOGV(r->upstream_port));
+			goto out;
 		}
 	}
 
 	rc = event_base_dispatch(base);
+	if (rc != 0) {
+		LOG_ERROR("event loop failed", "err", _LOGV(rc));
+		goto out;
+	}
+
+	exit_code = 0;
 
+out:
 	for (size_t i = 0; i < tcp_ctx_count; i++) {
 		free_tcp_route(tcp_ctxs[i]);
 	}
 
-	free_signal_handlers(&signals);
+	if (signals_started) {
+		free_signal_handlers(&signals);
+	}
+
 	free(tcp_ctxs);
-	event_base_free(base);
+
+	if (base != NULL) {
+		event_base_free(base);
+	}
+
 	free_routes(routes);
 
-	if (rc != 0) {
-		LOG_ERROR("event loop failed", "msg", _LOGV(strerror(-rc)));
-		return 1;
+#ifdef _WIN32
+	if (wsa_started) {
+		WSACleanup();
 	}
+#endif
 
-	return 0;
+	return exit_code;
 }