penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit f2f1d2f7907b5fe0c49c283a0907d74cb2b328cb

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-27T14:38:40Z
subjectudp upstream support unix-dgram socket
commit f2f1d2f7907b5fe0c49c283a0907d74cb2b328cb
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-27T14:38:40Z

    udp upstream support unix-dgram socket
---
 Makefile        |   1 +
 compat_socket.h |   1 +
 env.c           |  16 +++++
 env.h           |   6 ++
 file_conf.c     |  16 ++++-
 klog.c          |   6 +-
 route.h         |   3 +-
 tcp_route.c     |   8 +--
 tinyproxy.conf  |  13 ++--
 udp_route.c     | 186 +++++++++++++++++++++++++++++++++++++++++++++-----------
 10 files changed, 206 insertions(+), 50 deletions(-)

diff --git a/Makefile b/Makefile
index fe9ef1f..d037068 100644
--- a/Makefile
+++ b/Makefile
@@ -16,6 +16,7 @@ SRC := klog.c \
        route.c \
        tcp_route.c \
        udp_route.c \
+	   env.c \
        file_conf.c \
        tinyproxy.c
 
diff --git a/compat_socket.h b/compat_socket.h
index b981691..4c39529 100644
--- a/compat_socket.h
+++ b/compat_socket.h
@@ -14,6 +14,7 @@
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <unistd.h>
diff --git a/env.c b/env.c
new file mode 100644
index 0000000..08b9381
--- /dev/null
+++ b/env.c
@@ -0,0 +1,16 @@
+#include "env.h"
+
+#include <stdlib.h>
+
+#define TINYPROXY_DEFAULT_RUNTIME_DIR "/tmp/tinyproxy"
+
+const char *tinyproxy_runtime_dir(void)
+{
+	const char *v = getenv("TINYPROXY_RUNTIME_DIR");
+
+	if (v != NULL && v[0] != '\0') {
+		return v;
+	}
+
+	return TINYPROXY_DEFAULT_RUNTIME_DIR;
+}
diff --git a/env.h b/env.h
new file mode 100644
index 0000000..b6c2632
--- /dev/null
+++ b/env.h
@@ -0,0 +1,6 @@
+#ifndef ENV_H
+#define ENV_H
+
+const char *tinyproxy_runtime_dir(void);
+
+#endif
diff --git a/file_conf.c b/file_conf.c
index 3a867e9..62a5e20 100644
--- a/file_conf.c
+++ b/file_conf.c
@@ -213,9 +213,23 @@ static int parse_endpoint(const char *s, struct endpoint *ep)
 		ep->kind = ENDPOINT_UNIX;
 		strcpy(ep->path, path);
 		return 0;
+	} else if (strncmp(s, "unix-dgram:", 11) == 0) {
+		path = s + 11;
+
+		if (path[0] == '\0') {
+			return -1;
+		}
+
+		if (strlen(path) >= sizeof(ep->path)) {
+			return -1;
+		}
+
+		ep->kind = ENDPOINT_UNIX_DGRAM;
+		strcpy(ep->path, path);
+		return 0;
 	}
 
-	ep->kind = ENDPOINT_TCP;
+	ep->kind = ENDPOINT_INET;
 
 	if (split_host_port(s, ep->host, sizeof(ep->host), &ep->port) != 0) {
 		return -1;
diff --git a/klog.c b/klog.c
index e44bd2e..d0f1c18 100644
--- a/klog.c
+++ b/klog.c
@@ -65,7 +65,7 @@ static void log_print_endpoint(FILE *out, const struct endpoint *ep)
 	}
 
 	switch (ep->kind) {
-	case ENDPOINT_TCP:
+	case ENDPOINT_INET:
 		fprintf(out, "%s:%u", ep->host, ep->port);
 		return;
 
@@ -73,6 +73,10 @@ static void log_print_endpoint(FILE *out, const struct endpoint *ep)
 		fprintf(out, "unix:%s", ep->path);
 		return;
 
+	case ENDPOINT_UNIX_DGRAM:
+		fprintf(out, "unix-dgram:%s", ep->path);
+		return;
+
 	default:
 		fputs("<unknown>", out);
 		return;
diff --git a/route.h b/route.h
index c755bb6..586f0b2 100644
--- a/route.h
+++ b/route.h
@@ -25,8 +25,9 @@ struct route_options {
 };
 
 enum endpoint_kind {
-	ENDPOINT_TCP,
+	ENDPOINT_INET,
 	ENDPOINT_UNIX,
+	ENDPOINT_UNIX_DGRAM,
 };
 
 struct endpoint {
diff --git a/tcp_route.c b/tcp_route.c
index aea2bab..5394c46 100644
--- a/tcp_route.c
+++ b/tcp_route.c
@@ -9,10 +9,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#ifndef _WIN32
-#include <sys/un.h>
-#endif
-
 #include "klog.h"
 #include "file_conf.h"
 #include "compat_socket.h"
@@ -175,7 +171,7 @@ static int connect_upstream(struct bufferevent *bev, const struct endpoint *ep)
 	}
 
 	switch (ep->kind) {
-	case ENDPOINT_TCP: {
+	case ENDPOINT_INET: {
 		struct sockaddr_in addr;
 
 		memset(&addr, 0, sizeof(addr));
@@ -288,7 +284,7 @@ static void worker_adopt_client_fd(struct worker *w, struct accepted_client *ac)
 		return;
 	}
 
-	if (r->opts.keep_alive && r->upstream.kind == ENDPOINT_TCP) {
+	if (r->opts.keep_alive && r->upstream.kind == ENDPOINT_INET) {
 		evutil_socket_t upstream_fd = bufferevent_getfd(conn->upstream);
 
 		if (upstream_fd >= 0) {
diff --git a/tinyproxy.conf b/tinyproxy.conf
index ff974fe..67fa8f9 100644
--- a/tinyproxy.conf
+++ b/tinyproxy.conf
@@ -1,6 +1,7 @@
-# listen      upstream            proto  option
-:80           10.0.1.1:80         tcp
-:443          10.0.1.1:443        tcp    proxy_v2,keep_alive
-:12345        10.0.1.1:12345      udp    idle_timeout=10,connect_timeout=17
-:19132        10.0.1.1:19132      udp    proxy_v2
-:12990        unix:/tmp/test.sock tcp
+# listen  upstream                   proto  option
+:80       10.0.1.1:80                tcp
+:443      10.0.1.1:443               tcp    proxy_v2,keep_alive
+:12990    unix:/tmp/test.sock        tcp
+:12345    10.0.1.1:12345             udp    idle_timeout=10,connect_timeout=17
+:19132    10.0.1.1:19132             udp    proxy_v2
+:12998    unix-dgram:/tmp/testd.sock udp
diff --git a/udp_route.c b/udp_route.c
index f6d777b..5bc9690 100644
--- a/udp_route.c
+++ b/udp_route.c
@@ -6,8 +6,10 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+#include <sys/stat.h>
 
 #include "klog.h"
+#include "env.h"
 #include "file_conf.h"
 #include "compat_socket.h"
 #include "proxy_proto_v2.h"
@@ -24,6 +26,8 @@ struct udp_client {
 	evutil_socket_t fd;
 	struct event *ev;
 
+	char unix_local_path[108];
+
 	struct sockaddr_storage client_addr;
 	socklen_t client_addr_len;
 
@@ -77,6 +81,10 @@ static void free_udp_client(struct udp_client *c)
 		evutil_closesocket(c->fd);
 	}
 
+	if (c->unix_local_path[0] != '\0') {
+		unlink(c->unix_local_path);
+	}
+
 	free(c);
 }
 
@@ -167,6 +175,116 @@ static void upstream_read_cb(evutil_socket_t fd, short events, void *arg)
 	}
 }
 
+static int connect_udp_upstream(struct udp_client *c, const struct endpoint *upstream)
+{
+	if (upstream == NULL) {
+		return -EINVAL;
+	}
+
+	switch (upstream->kind) {
+	case ENDPOINT_INET: {
+		struct sockaddr_in upstream_addr;
+
+		memset(&upstream_addr, 0, sizeof(upstream_addr));
+		upstream_addr.sin_family = AF_INET;
+		upstream_addr.sin_port = htons(upstream->port);
+
+		if (inet_pton(AF_INET, upstream->host, &upstream_addr.sin_addr) != 1) {
+			return -EINVAL;
+		}
+
+		c->fd = socket(AF_INET, SOCK_DGRAM, 0);
+		if (c->fd < 0) {
+			return -EVUTIL_SOCKET_ERROR();
+		}
+
+		if (connect(
+				c->fd,
+				(const struct sockaddr *)&upstream_addr,
+				sizeof(upstream_addr)
+			) < 0) {
+			return -EVUTIL_SOCKET_ERROR();
+		}
+
+		return 0;
+	}
+
+	case ENDPOINT_UNIX_DGRAM:
+#ifdef _WIN32
+		return -ENOTSUP;
+#else
+	{
+		struct sockaddr_un local_addr;
+		struct sockaddr_un upstream_addr;
+
+		if (upstream->path[0] == '\0') {
+			return -EINVAL;
+		}
+
+		if (strlen(upstream->path) >= sizeof(upstream_addr.sun_path)) {
+			return -ENAMETOOLONG;
+		}
+
+		c->fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+		if (c->fd < 0) {
+			return -EVUTIL_SOCKET_ERROR();
+		}
+
+		memset(&local_addr, 0, sizeof(local_addr));
+		local_addr.sun_family = AF_UNIX;
+
+		// UNIX_DGRAM sock for reply
+		const char *runtime_dir = tinyproxy_runtime_dir();
+
+		int n = snprintf(
+			c->unix_local_path,
+			sizeof(c->unix_local_path),
+			"%s/udp-%ld-%p.sock",
+			runtime_dir,
+			(long)getpid(),
+			(void *)c
+		);
+
+		if (n < 0 || (size_t)n >= sizeof(c->unix_local_path)) {
+			return -ENAMETOOLONG;
+		}
+
+		if (strlen(c->unix_local_path) >= sizeof(local_addr.sun_path)) {
+			return -ENAMETOOLONG;
+		}
+
+		unlink(c->unix_local_path);
+		strcpy(local_addr.sun_path, c->unix_local_path);
+
+		if (bind(
+				c->fd,
+				(const struct sockaddr *)&local_addr,
+				sizeof(local_addr)
+			) < 0) {
+			return -EVUTIL_SOCKET_ERROR();
+		}
+
+		memset(&upstream_addr, 0, sizeof(upstream_addr));
+		upstream_addr.sun_family = AF_UNIX;
+		strcpy(upstream_addr.sun_path, upstream->path);
+
+		if (connect(
+				c->fd,
+				(const struct sockaddr *)&upstream_addr,
+				sizeof(upstream_addr)
+			) < 0) {
+			return -EVUTIL_SOCKET_ERROR();
+		}
+
+		return 0;
+	}
+#endif
+
+	default:
+		return -ENOTSUP;
+	}
+}
+
 static struct udp_client *create_udp_client(
 	struct udp_route_ctx *ctx,
 	const struct sockaddr_storage *client_addr,
@@ -185,12 +303,11 @@ static struct udp_client *create_udp_client(
 	c->client_addr_len = client_addr_len;
 	c->last_seen = time(NULL);
 
-	c->fd = socket(AF_INET, SOCK_DGRAM, 0);
-	if (c->fd < 0) {
-		int err = EVUTIL_SOCKET_ERROR();
-
-		LOG_ERROR("udp upstream socket failed",
-			"err", _LOGV(evutil_socket_error_to_string(err))
+	int rc = connect_udp_upstream(c, &r->upstream);
+	if (rc < 0) {
+		LOG_ERROR("udp upstream connect failed",
+			"upstream", _LOGV_ENDPOINT(&r->upstream),
+			"err", _LOGV(evutil_socket_error_to_string(-rc))
 		);
 		free_udp_client(c);
 		return NULL;
@@ -202,34 +319,6 @@ static struct udp_client *create_udp_client(
 		return NULL;
 	}
 
-	struct sockaddr_in upstream_addr;
-	memset(&upstream_addr, 0, sizeof(upstream_addr));
-
-	upstream_addr.sin_family = AF_INET;
-	upstream_addr.sin_port = htons(r->upstream.port);
-
-	if (inet_pton(AF_INET, r->upstream.host, &upstream_addr.sin_addr) != 1) {
-		LOG_ERROR("invalid udp upstream address",
-			"upstream", _LOGV_ENDPOINT(&r->upstream)
-		);
-		free_udp_client(c);
-		return NULL;
-	}
-
-	if (connect(
-			c->fd,
-			(const struct sockaddr *)&upstream_addr,
-			sizeof(upstream_addr)
-		) < 0) {
-		int err = EVUTIL_SOCKET_ERROR();
-
-		LOG_ERROR("udp upstream connect failed",
-			"err", _LOGV(evutil_socket_error_to_string(err))
-		);
-		free_udp_client(c);
-		return NULL;
-	}
-
 	c->ev = event_new(ctx->base, c->fd, EV_READ | EV_PERSIST, upstream_read_cb, c);
 	if (c->ev == NULL) {
 		LOG_ERROR("event_new failed for udp upstream");
@@ -346,7 +435,7 @@ static void listen_read_cb(evutil_socket_t fd, short events, void *arg)
 				return;
 			}
 
-			LOG_ERROR("udp upstream recv failed",
+			LOG_ERROR("udp listen recv failed",
 				"err", _LOGV(evutil_socket_error_to_string(err))
 			);
 			return;
@@ -381,6 +470,33 @@ int start_udp_route(
 	const struct route *r,
 	struct udp_route_ctx **out
 ) {
+	if (r->listen.kind != ENDPOINT_INET) {
+		LOG_ERROR("unsupported udp listen endpoint",
+			"listen", _LOGV_ENDPOINT(&r->listen)
+		);
+		return -ENOTSUP;
+	}
+
+	if (r->upstream.kind != ENDPOINT_INET &&
+		r->upstream.kind != ENDPOINT_UNIX_DGRAM) {
+		LOG_ERROR("unsupported udp upstream endpoint",
+			"upstream", _LOGV_ENDPOINT(&r->upstream)
+		);
+		return -ENOTSUP;
+	}
+
+	if (r->upstream.kind == ENDPOINT_UNIX_DGRAM) {
+		const char *runtime_dir = tinyproxy_runtime_dir();
+
+		if (mkdir(runtime_dir, 0755) < 0 && errno != EEXIST) {
+			LOG_ERROR("failed to create unix-dgram runtime dir",
+				"dir", _LOGV(runtime_dir),
+				"err", _LOGV(strerror(errno))
+			);
+			return -errno;
+		}
+	}
+
 	struct sockaddr_in listen_addr;
 	memset(&listen_addr, 0, sizeof(listen_addr));