penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit 45fe164d2fc9f7155fb4e2965f700c1790021327

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-28T19:06:01Z
subjectRefactor some more
commit 45fe164d2fc9f7155fb4e2965f700c1790021327
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-28T19:06:01Z

    Refactor some more
---
 devtools/cflow.sh    |  64 +++++++++++++++++++++++++++++
 src/datagram_route.c | 112 +++++++++++++++++++++++++++++++--------------------
 src/endpoint.c       |  36 +++++++++++++++++
 src/endpoint.h       |   6 ++-
 src/file_conf.c      |   4 +-
 src/route_runtime.c  |  82 +++++++++++++++++++++++++++----------
 src/route_runtime.h  |   2 +
 src/stream_route.c   |  55 +++++++++++++++++--------
 src/tinyproxy.c      |  10 +++++
 9 files changed, 286 insertions(+), 85 deletions(-)

diff --git a/devtools/cflow.sh b/devtools/cflow.sh
new file mode 100755
index 0000000..5c28e76
--- /dev/null
+++ b/devtools/cflow.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+set -eu
+
+MAIN="${1:-main}"
+
+IGNORE='
+LOG_ERROR
+LOG_INFO
+LOG_WARN
+calloc
+clock_gettime
+fclose
+feof
+ferror
+fgets
+fopen
+fprintf
+fputc
+fputs
+fread
+free
+getpid
+htons
+inet_ntop
+inet_pton
+isspace
+localtime_compat
+localtime_r
+localtime_s
+malloc
+memcmp
+memcpy
+memmove
+memset
+ntohs
+printf
+putc
+putchar
+puts
+realloc
+snprintf
+strchr
+strcmp
+strcpy
+strdup
+strerror
+strftime
+strlen
+strncmp
+strrchr
+strtok_r
+strtol
+strtoul
+time
+trim
+unlink
+'
+
+IGNORE_RE="$(printf '%s\n' "$IGNORE" \
+	| sed '/^[[:space:]]*$/d' \
+	| paste -sd '|' -)"
+
+cflow --brief --main="$MAIN" src/*.c |
+	grep -Ev "^[[:space:]]*(${IGNORE_RE})\\("
diff --git a/src/datagram_route.c b/src/datagram_route.c
index d05f6f7..47b2aea 100644
--- a/src/datagram_route.c
+++ b/src/datagram_route.c
@@ -581,22 +581,14 @@ static void listen_read_cb(evutil_socket_t fd, short events, void *arg)
 	}
 }
 
-int start_datagram_route(
-	struct worker *w,
-	const struct route *r,
-	struct datagram_route_ctx *ctx
-) {
-	if (r->listen.kind != ENDPOINT_INET) {
-		LOG_ERROR("unsupported udp listen endpoint",
-			"listen", _LOGV_ENDPOINT(&r->listen)
-		);
-		return -ENOTSUP;
-	}
+static int prepare_datagram_route(struct datagram_route_ctx *ctx)
+{
+	const struct route *r = ctx->route;
 
 	if (r->upstream.kind != ENDPOINT_INET &&
 		r->upstream.kind != ENDPOINT_BUILTIN &&
 		r->upstream.kind != ENDPOINT_UNIX_DGRAM) {
-		LOG_ERROR("unsupported udp upstream endpoint",
+		LOG_ERROR("unsupported datagram upstream endpoint",
 			"upstream", _LOGV_ENDPOINT(&r->upstream)
 		);
 		return -ENOTSUP;
@@ -614,7 +606,15 @@ int start_datagram_route(
 		}
 	}
 
+	return 0;
+}
+
+static int bind_udp_datagram_listener(struct datagram_route_ctx *ctx)
+{
+	const struct route *r = ctx->route;
 	struct sockaddr_in listen_addr;
+	int one = 1;
+
 	memset(&listen_addr, 0, sizeof(listen_addr));
 
 	listen_addr.sin_family = AF_INET;
@@ -627,16 +627,6 @@ int start_datagram_route(
 		return -EINVAL;
 	}
 
-	memset(ctx, 0, sizeof(*ctx));
-	if (ctx == NULL) {
-		return -ENOMEM;
-	}
-
-	ctx->base = w->base;
-	ctx->worker = w;
-	ctx->route = r;
-	ctx->listen_fd = -1;
-
 	ctx->listen_fd = socket(AF_INET, SOCK_DGRAM, 0);
 	if (ctx->listen_fd < 0) {
 		int err = EVUTIL_SOCKET_ERROR();
@@ -644,20 +634,16 @@ int start_datagram_route(
 		LOG_ERROR("udp listen socket failed",
 			"err", _LOGV(evutil_socket_error_to_string(err))
 		);
-		memset(ctx, 0, sizeof(*ctx));
-		return -errno;
+		return -err;
 	}
 
 	evutil_make_socket_closeonexec(ctx->listen_fd);
 
 	if (evutil_make_socket_nonblocking(ctx->listen_fd) < 0) {
 		LOG_ERROR("evutil_make_socket_nonblocking failed");
-		evutil_closesocket(ctx->listen_fd);
-		free(ctx);
 		return -EINVAL;
 	}
 
-	int one = 1;
 	setsockopt(
 		ctx->listen_fd,
 		SOL_SOCKET,
@@ -676,8 +662,6 @@ int start_datagram_route(
 		LOG_ERROR("udp bind failed",
 			"err", _LOGV(evutil_socket_error_to_string(err))
 		);
-		evutil_closesocket(ctx->listen_fd);
-		free(ctx);
 		return -EADDRINUSE;
 	}
 
@@ -694,8 +678,6 @@ int start_datagram_route(
 		LOG_ERROR("udp getsockname failed",
 			"err", _LOGV(evutil_socket_error_to_string(err))
 		);
-		evutil_closesocket(ctx->listen_fd);
-		free(ctx);
 		return -EINVAL;
 	}
 
@@ -708,24 +690,63 @@ int start_datagram_route(
 	);
 	if (ctx->listen_ev == NULL) {
 		LOG_ERROR("event_new failed for udp listener");
-		evutil_closesocket(ctx->listen_fd);
-		free(ctx);
 		return -ENOMEM;
 	}
 
 	if (event_add(ctx->listen_ev, NULL) < 0) {
 		LOG_ERROR("event_add failed for udp listener");
-		event_free(ctx->listen_ev);
-		evutil_closesocket(ctx->listen_fd);
-		free(ctx);
 		return -EINVAL;
 	}
 
+	return 0;
+}
+
+int start_datagram_route(
+	struct worker *w,
+	const struct route *r,
+	struct datagram_route_ctx *ctx
+) {
+	int rc;
 	char opts[128];
 
+	if (ctx == NULL) {
+		return -EINVAL;
+	}
+
+	memset(ctx, 0, sizeof(*ctx));
+
+	ctx->base = w->base;
+	ctx->worker = w;
+	ctx->route = r;
+	ctx->listen_fd = -1;
+
+	rc = prepare_datagram_route(ctx);
+	if (rc != 0) {
+		memset(ctx, 0, sizeof(*ctx));
+		return rc;
+	}
+
+	switch (r->listen.kind) {
+	case ENDPOINT_INET:
+		rc = bind_udp_datagram_listener(ctx);
+		break;
+
+	default:
+		LOG_ERROR("unsupported datagram listen endpoint",
+			"listen", _LOGV_ENDPOINT(&r->listen)
+		);
+		rc = -ENOTSUP;
+		break;
+	}
+
+	if (rc != 0) {
+		stop_datagram_route(ctx);
+		return rc;
+	}
+
 	route_options_str(&r->opts, opts, sizeof(opts));
 
-	LOG_INFO("udp route started",
+	LOG_INFO("datagram route started",
 		"line", _LOGV(r->line_no),
 		"listen", _LOGV_ENDPOINT(&r->listen),
 		"upstream", _LOGV_ENDPOINT(&r->upstream),
@@ -741,20 +762,23 @@ void stop_datagram_route(struct datagram_route_ctx *ctx)
 		return;
 	}
 
+	while (ctx->clients != NULL) {
+		struct udp_client *c = ctx->clients;
+		ctx->clients = c->next;
+		c->next = NULL;
+		free_udp_client(c);
+	}
+
 	if (ctx->listen_ev != NULL) {
 		event_free(ctx->listen_ev);
+		ctx->listen_ev = NULL;
 	}
 
 	if (ctx->listen_fd >= 0) {
 		evutil_closesocket(ctx->listen_fd);
-	}
-
-	struct udp_client *c = ctx->clients;
-	while (c != NULL) {
-		struct udp_client *next = c->next;
-		free_udp_client(c);
-		c = next;
+		ctx->listen_fd = -1;
 	}
 
 	memset(ctx, 0, sizeof(*ctx));
+	ctx->listen_fd = -1;
 }
diff --git a/src/endpoint.c b/src/endpoint.c
index 740936d..53ca6fc 100644
--- a/src/endpoint.c
+++ b/src/endpoint.c
@@ -1,3 +1,4 @@
+#include <stdbool.h>
 #include <stdio.h>
 #include <errno.h>
 
@@ -41,4 +42,39 @@ int endpoint_to_string(const struct endpoint *ep, char *buf, size_t buf_len)
 	}
 }
 
+bool endpoint_is_stream(const struct endpoint *ep)
+{
+	if (ep == NULL) {
+		return false;
+	}
+
+	switch (ep->proto) {
+	case PROTO_TCP:
+	case PROTO_UNIX_STREAM:
+		return true;
+
+	default:
+		return false;
+	}
+}
 
+bool endpoint_is_datagram(const struct endpoint *ep)
+{
+	if (ep == NULL) {
+		return false;
+	}
+
+	switch (ep->proto) {
+	case PROTO_UDP:
+	case PROTO_UNIX_DGRAM:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+bool endpoint_is_listenable(const struct endpoint *ep)
+{
+	return endpoint_is_stream(ep) || endpoint_is_datagram(ep);
+}
diff --git a/src/endpoint.h b/src/endpoint.h
index 8ffe14f..867e92a 100644
--- a/src/endpoint.h
+++ b/src/endpoint.h
@@ -8,7 +8,7 @@
 enum endpoint_proto {
 	PROTO_TCP,
 	PROTO_UDP,
-	PROTO_UNIX,
+	PROTO_UNIX_STREAM,
 	PROTO_UNIX_DGRAM,
 	PROTO_FILE,
 	PROTO_BUILTIN,
@@ -37,4 +37,8 @@ struct endpoint {
 
 int endpoint_to_string(const struct endpoint *ep, char *buf, size_t buf_len);
 
+bool endpoint_is_stream(const struct endpoint *ep);
+bool endpoint_is_datagram(const struct endpoint *ep);
+bool endpoint_is_listenable(const struct endpoint *ep);
+
 #endif
diff --git a/src/file_conf.c b/src/file_conf.c
index 776299c..2b3f8e1 100644
--- a/src/file_conf.c
+++ b/src/file_conf.c
@@ -91,7 +91,7 @@ static int parse_proto(const char *s, enum endpoint_proto *out)
 	}
 
 	if (strcmp(s, "unix") == 0) {
-		*out = PROTO_UNIX;
+		*out = PROTO_UNIX_STREAM;
 		return 0;
 	}
 
@@ -250,7 +250,7 @@ static int parse_endpoint(enum endpoint_proto proto, const char *s, struct endpo
 
 		return 0;
 
-	case PROTO_UNIX:
+	case PROTO_UNIX_STREAM:
 		path = s;
 
 		if (strncmp(path, "unix:", 5) == 0) {
diff --git a/src/route_runtime.c b/src/route_runtime.c
index 089457c..87b0244 100644
--- a/src/route_runtime.c
+++ b/src/route_runtime.c
@@ -4,12 +4,61 @@
 #include "route.h"
 #include "route_runtime.h"
 
+int validate_route(const struct route *r)
+{
+	if (r == NULL) {
+		return -EINVAL;
+	}
+
+	if (!endpoint_is_listenable(&r->listen)) {
+		LOG_ERROR("route listen endpoint is not listenable",
+			"line", _LOGV(r->line_no),
+			"listen", _LOGV_ENDPOINT(&r->listen)
+		);
+		return -EINVAL;
+	}
+
+	if (endpoint_is_stream(&r->listen)) {
+		if (endpoint_is_stream(&r->upstream) ||
+		    r->upstream.proto == PROTO_FILE ||
+		    r->upstream.proto == PROTO_BUILTIN) {
+			return 0;
+		}
+
+		LOG_ERROR("unsupported stream route upstream",
+			"line", _LOGV(r->line_no),
+			"listen", _LOGV_ENDPOINT(&r->listen),
+			"upstream", _LOGV_ENDPOINT(&r->upstream)
+		);
+		return -EINVAL;
+	}
+
+	if (endpoint_is_datagram(&r->listen)) {
+		if (endpoint_is_datagram(&r->upstream) ||
+		    r->upstream.proto == PROTO_BUILTIN) {
+			return 0;
+		}
+
+		LOG_ERROR("unsupported datagram route upstream",
+			"line", _LOGV(r->line_no),
+			"listen", _LOGV_ENDPOINT(&r->listen),
+			"upstream", _LOGV_ENDPOINT(&r->upstream)
+		);
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
 int start_route(struct worker *w, const struct route *r, struct route_ctx *ctx)
 {
-	int rc = 0;
-	switch (r->listen.proto) {
+	int rc;
 
-	case PROTO_TCP:
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->route = r;
+	ctx->kind = ROUTE_CTX_NONE;
+
+	if (endpoint_is_stream(&r->listen)) {
 		ctx->kind = ROUTE_CTX_STREAM;
 
 		rc = start_stream_route(w, r, &ctx->u.stream);
@@ -19,35 +68,26 @@ int start_route(struct worker *w, const struct route *r, struct route_ctx *ctx)
 		}
 
 		return rc;
+	}
 
-	case PROTO_UDP:
+	if (endpoint_is_datagram(&r->listen)) {
 		ctx->kind = ROUTE_CTX_DATAGRAM;
 
 		rc = start_datagram_route(w, r, &ctx->u.datagram);
 		if (rc != 0) {
 			ctx->kind = ROUTE_CTX_NONE;
-			memset(&ctx->u.stream, 0, sizeof(ctx->u.datagram));
+			memset(&ctx->u.datagram, 0, sizeof(ctx->u.datagram));
 		}
 
 		return rc;
+	}
 
-	/* New approach
-	case ENDPOINT_PROTO_TCP:
-	case ENDPOINT_PROTO_UNIX_STREAM:
-		return start_stream_listener(w, r, ctx);
-
-	case ENDPOINT_PROTO_UDP:
-	case ENDPOINT_PROTO_UNIX_DGRAM:
-		return start_datagram_listener(w, r, ctx);
-	*/
+	LOG_ERROR("route listen endpoint is not listenable",
+		"line", _LOGV(r->line_no),
+		"listen", _LOGV_ENDPOINT(&r->listen)
+	);
 
-	default:
-		LOG_ERROR("route listen endpoint is not listenable",
-			"line", _LOGV(r->line_no),
-			"listen", _LOGV_ENDPOINT(&r->listen)
-		);
-		return -EINVAL;
-	}
+	return -EINVAL;
 }
 
 void stop_route(struct route_ctx *ctx)
diff --git a/src/route_runtime.h b/src/route_runtime.h
index fb8b359..a192f36 100644
--- a/src/route_runtime.h
+++ b/src/route_runtime.h
@@ -21,6 +21,8 @@ struct route_ctx {
 	} u;
 };
 
+int validate_route(const struct route *r);
+
 int start_route(struct worker *w, const struct route *r, struct route_ctx *ctx);
 void stop_route(struct route_ctx *ctx);
 
diff --git a/src/stream_route.c b/src/stream_route.c
index 7ff1c84..6c782a2 100644
--- a/src/stream_route.c
+++ b/src/stream_route.c
@@ -660,30 +660,21 @@ static void accept_error_cb(struct evconnlistener *listener, void *arg)
 	event_base_loopexit(ctx->accept_base, NULL);
 }
 
-int start_stream_route(
-	struct worker *w,
-	const struct route *r,
-	struct stream_route_ctx *ctx)
+static int bind_tcp_stream_listener(struct stream_route_ctx *ctx)
 {
+	const struct route *r = ctx->route;
 	struct sockaddr_in listen_addr;
 
-	memset(ctx, 0, sizeof(*ctx));
-
 	memset(&listen_addr, 0, sizeof(listen_addr));
 	listen_addr.sin_family = AF_INET;
 	listen_addr.sin_port = htons(r->listen.port);
 
 	if (inet_pton(AF_INET, r->listen.host, &listen_addr.sin_addr) != 1) {
 		LOG_ERROR("invalid listen address",
-			"listen", _LOGV_ENDPOINT(&r->listen)
-		);
+			"listen", _LOGV_ENDPOINT(&r->listen));
 		return -EINVAL;
 	}
 
-	ctx->accept_base = w->base;
-	ctx->worker = w;
-	ctx->route = r;
-
 	ctx->listener = evconnlistener_new_bind(
 		ctx->accept_base,
 		accept_cb,
@@ -695,23 +686,53 @@ int start_stream_route(
 	);
 
 	if (ctx->listener == NULL) {
-		LOG_ERROR("evconnlistener_new_bind failed");
-		memset(ctx, 0, sizeof(*ctx));
+		LOG_ERROR("evconnlistener_new_bind failed",
+			"listen", _LOGV_ENDPOINT(&r->listen));
 		return -EADDRINUSE;
 	}
 
-	evconnlistener_set_error_cb(ctx->listener, accept_error_cb);
+	return 0;
+}
 
+int start_stream_route(struct worker *w, const struct route *r,
+	struct stream_route_ctx *ctx)
+{
+	int rc;
 	char opts[128];
 
+	memset(ctx, 0, sizeof(*ctx));
+
+	ctx->accept_base = w->base;
+	ctx->worker = w;
+	ctx->route = r;
+
+	switch (r->listen.proto) {
+	case PROTO_TCP:
+		rc = bind_tcp_stream_listener(ctx);
+		break;
+
+	default:
+		LOG_ERROR("stream listen protocol not implemented yet",
+			"line", _LOGV(r->line_no),
+			"listen", _LOGV_ENDPOINT(&r->listen));
+		rc = -ENOTSUP;
+		break;
+	}
+
+	if (rc != 0) {
+		memset(ctx, 0, sizeof(*ctx));
+		return rc;
+	}
+
+	evconnlistener_set_error_cb(ctx->listener, accept_error_cb);
+
 	route_options_str(&r->opts, opts, sizeof(opts));
 
 	LOG_INFO("route started",
 		"line", _LOGV(r->line_no),
 		"listen", _LOGV_ENDPOINT(&r->listen),
 		"upstream", _LOGV_ENDPOINT(&r->upstream),
-		"options", _LOGV(opts[0] ? opts : "")
-	);
+		"options", _LOGV(opts[0] ? opts : ""));
 
 	return 0;
 }
diff --git a/src/tinyproxy.c b/src/tinyproxy.c
index eb146e9..2dc6e3b 100644
--- a/src/tinyproxy.c
+++ b/src/tinyproxy.c
@@ -221,6 +221,16 @@ int main(int argc, char **argv)
 	for (size_t i = 0; i < route_count; i++) {
 		const struct route *r = &routes[i];
 
+		rc = validate_route(r);
+		if (rc != 0) {
+			LOG_ERROR("invalid route",
+					"line", _LOGV(r->line_no),
+					"listen", _LOGV_ENDPOINT(&r->listen),
+					"upstream", _LOGV_ENDPOINT(&r->upstream)
+					);
+			goto out;
+		}
+
 		rc = start_route(&w, r, &route_ctxs[route_ctx_count]);
 		if (rc == 0) {
 			route_ctx_count++;