penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit 4eb647c97d6b989ac97314c1ed2da4200507f76b

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-30T07:28:20Z
subjectConfigurable worker count
commit 4eb647c97d6b989ac97314c1ed2da4200507f76b
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-30T07:28:20Z

    Configurable worker count
---
 docs/tinyproxy.1.scd      |  4 ++++
 docs/tinyproxy.conf.5.scd |  5 ++++-
 examples/tinyproxy.conf   |  5 +++--
 src/compat_cpu.c          | 33 +++++++++++++++++++++++++++++++++
 src/compat_cpu.h          |  6 ++++++
 src/klog.c                | 12 ++++++++++--
 src/klog.h                |  2 ++
 src/tinyproxy.c           | 37 ++++++++++++++++++++++++++++++++++---
 src/worker.c              |  1 +
 src/worker_pool.c         | 11 +++++++----
 src/x_builtins.c          | 22 ++++++++++++++++++----
 src/x_builtins.h          |  1 +
 12 files changed, 123 insertions(+), 16 deletions(-)

diff --git a/docs/tinyproxy.1.scd b/docs/tinyproxy.1.scd
index e05f1e6..56c74a5 100644
--- a/docs/tinyproxy.1.scd
+++ b/docs/tinyproxy.1.scd
@@ -31,6 +31,10 @@ Routes may be configured using a config file or directly from the command line w
 
 # OPTIONS
 
+*-w* <workers>
+	Number of worker threads. 0 = auto (one per CPU).
+	Default 1
+
 *-c* <config>
 	Load configuration file.
 
diff --git a/docs/tinyproxy.conf.5.scd b/docs/tinyproxy.conf.5.scd
index 02f599c..93e58f8 100644
--- a/docs/tinyproxy.conf.5.scd
+++ b/docs/tinyproxy.conf.5.scd
@@ -66,11 +66,14 @@ builtin close
 	- TCP: Accepts the connection and immediately closes it without reading or writing.
 	- UDP: this is equivalent to discard/drop because UDP has no connection to close.
 
-buitin http-ok
+buitin http_ok
 	Returns "HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\nConnection: close\\r\\n\\r\\nOK" then close.
 
 	Useful for probing
 
+buitin log_conn
+	Logs the connection.
+
 # EXAMPLES
 
 Forward HTTPS:
diff --git a/examples/tinyproxy.conf b/examples/tinyproxy.conf
index f556b7d..f2e93e9 100644
--- a/examples/tinyproxy.conf
+++ b/examples/tinyproxy.conf
@@ -18,14 +18,15 @@ listen tcp :12995  builtin client_addr
 listen tcp :12996  builtin hang         idle_timeout=3,connect_timeout=1
 listen tcp :12997  builtin discard
 listen tcp :12998  builtin close
-listen tcp :8080   builtin http-ok
+listen tcp :12999  builtin log_conn
+listen tcp :8080   builtin http_ok
 
 # Builtin UDP test backends.
 listen udp :12995  builtin client_addr
 listen udp :12996  builtin hang
 listen udp :12997  builtin discard
 listen udp :12998  builtin close
-listen udp :8080   builtin http-ok
+listen udp :8080   builtin http_ok
 
 # Static file TCP backends.
 listen tcp :14000  file examples/http-response.txt
diff --git a/src/compat_cpu.c b/src/compat_cpu.c
new file mode 100644
index 0000000..a7f4287
--- /dev/null
+++ b/src/compat_cpu.c
@@ -0,0 +1,33 @@
+#include "compat_cpu.h"
+
+#include <limits.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+int compat_cpu_count(void)
+{
+#ifdef _WIN32
+	SYSTEM_INFO si;
+
+	GetSystemInfo(&si);
+
+	if (si.dwNumberOfProcessors == 0 ||
+		si.dwNumberOfProcessors > INT_MAX) {
+		return 1;
+	}
+
+	return (int)si.dwNumberOfProcessors;
+#else
+	long n = sysconf(_SC_NPROCESSORS_ONLN);
+
+	if (n < 1 || n > INT_MAX) {
+		return 1;
+	}
+
+	return (int)n;
+#endif
+}
diff --git a/src/compat_cpu.h b/src/compat_cpu.h
new file mode 100644
index 0000000..4a3a77b
--- /dev/null
+++ b/src/compat_cpu.h
@@ -0,0 +1,6 @@
+#ifndef COMPAT_CPU_H
+#define COMPAT_CPU_H
+
+int compat_cpu_count(void);
+
+#endif
diff --git a/src/klog.c b/src/klog.c
index c053c03..158a2ae 100644
--- a/src/klog.c
+++ b/src/klog.c
@@ -7,6 +7,13 @@
 #include "klog.h"
 #include "route.h"
 
+static _Thread_local int klog_worker_id = -1;
+
+void klog_set_worker_id(int id)
+{
+	klog_worker_id = id;
+}
+
 static int localtime_compat(const time_t *t, struct tm *out)
 {
 #ifdef _WIN32
@@ -109,13 +116,14 @@ void log_at(char sev, const char *file, int line, const char *msg, ...)
 
 	strftime(tbuf, sizeof(tbuf), "%m%d %H:%M:%S", &tm);
 
-	fprintf(stderr, "%c%s.%06ld %7ld %s:%d] msg=",
+	fprintf(stderr, "%c%s.%06ld %7ld %s:%d] worker=%u msg=",
 		sev,
 		tbuf,
 		ts.tv_nsec / 1000,
 		(long)compat_getpid(),
 		file,
-		line);
+		line,
+		klog_worker_id);
 
 	print_quoted_value(msg);
 
diff --git a/src/klog.h b/src/klog.h
index 5601a5f..c0b73f1 100644
--- a/src/klog.h
+++ b/src/klog.h
@@ -31,6 +31,8 @@ struct log_value {
 	} v;
 };
 
+void klog_set_worker_id(int id);
+
 void log_at(char sev, const char *file, int line, const char *msg, ...);
 
 static inline struct log_value log_value_str(const char *v)
diff --git a/src/tinyproxy.c b/src/tinyproxy.c
index 7066cca..691c7a0 100644
--- a/src/tinyproxy.c
+++ b/src/tinyproxy.c
@@ -1,6 +1,7 @@
 #include <event2/event.h>
 
 #include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -10,6 +11,7 @@
 #include "klog.h"
 #include "signals.h"
 #include "file_conf.h"
+#include "compat_cpu.h"
 #include "worker_pool.h"
 #include "route.h"
 #include "route_runtime.h"
@@ -20,6 +22,7 @@ static void usage(FILE *out, const char *prog)
 		"usage: %s [-c config-file | -L route]\n"
 		"\n"
 		"options:\n"
+		"  -w N      Number of worker threads. 0 = auto (one per CPU). Default 1\n"
 		"  -c FILE   path to config file (default: tinyproxy.conf)\n"
 		"  -L ROUTE  add inline route, same syntax as config line\n"
 		"            may be specified multiple times\n"
@@ -45,6 +48,7 @@ static void prevent_socket_write_from_killing_process(void)
 int main(int argc, char **argv)
 {
 	prevent_socket_write_from_killing_process();
+	klog_set_worker_id(0);
 
 	int opt;
 	int exit_code = 1;
@@ -60,6 +64,8 @@ int main(int argc, char **argv)
 	int wsa_started = 0;
 #endif
 
+	size_t worker_count = 1;
+
 	const char *conf_path = NULL;
 	struct route *routes = NULL;
 	size_t route_count = 0;
@@ -71,8 +77,34 @@ int main(int argc, char **argv)
 	struct signal_events signals;
 	int signals_started = 0;
 
-	while ((opt = getopt(argc, argv, "c:L:h:v")) != -1) {
+	while ((opt = getopt(argc, argv, "c:L:w:h:v")) != -1) {
 		switch (opt) {
+			case 'w': {
+				char *end = NULL;
+				unsigned long n;
+
+				if (optarg == NULL || optarg[0] == '\0') {
+					LOG_ERROR("missing number after -w");
+					return 2;
+				}
+
+				errno = 0;
+				n = strtoul(optarg, &end, 10);
+
+				if (errno == ERANGE || end == optarg || *end != '\0' || n > INT_MAX) {
+					LOG_ERROR("invalid worker count", "value", _LOGV(optarg));
+					return 2;
+				}
+
+				worker_count = (int)n;
+
+				if(worker_count == 0) {
+					worker_count = compat_cpu_count();
+				}
+
+				break;
+			}
+
 			case 'c':
 				if (optarg == NULL || optarg[0] == '\0') {
 					LOG_ERROR("missing config path after -c");
@@ -219,11 +251,10 @@ int main(int argc, char **argv)
 	signals_started = 1;
 
 	struct worker_pool wpool;
-	size_t worker_count = 1;
 
 	rc = worker_pool_init(&wpool, worker_count);
 	if (rc != 0) {
-		LOG_ERROR("failed to init worker pool", "err", _LOGV(rc));
+		LOG_ERROR("failed to init worker pool", "err", _LOGV(strerror(rc)));
 		goto out;
 	}
 	worker_pool_ready = true;
diff --git a/src/worker.c b/src/worker.c
index ce9c753..126e272 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -99,6 +99,7 @@ static void worker_process_pending(struct worker *w)
 static void *worker_main(void *arg)
 {
 	struct worker *w = arg;
+	klog_set_worker_id(w->id);
 
 	event_base_dispatch(w->base);
 
diff --git a/src/worker_pool.c b/src/worker_pool.c
index 410a3b3..ac9c748 100644
--- a/src/worker_pool.c
+++ b/src/worker_pool.c
@@ -1,9 +1,10 @@
-#include "worker_pool.h"
-
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include "klog.h"
+#include "worker_pool.h"
+
 int worker_pool_init(struct worker_pool *pool, size_t count)
 {
 	size_t i;
@@ -22,7 +23,7 @@ int worker_pool_init(struct worker_pool *pool, size_t count)
 	pool->count = count;
 
 	for (i = 0; i < count; i++) {
-		int rc = worker_init(&pool->workers[i], (unsigned int)i);
+		int rc = worker_init(&pool->workers[i], (unsigned int)(i + 1));
 		if (rc != 0) {
 			while (i > 0) {
 				i--;
@@ -37,6 +38,8 @@ int worker_pool_init(struct worker_pool *pool, size_t count)
 		}
 	}
 
+	LOG_INFO("worker pool initialized", "workers", _LOGV(count));
+
 	return 0;
 }
 
@@ -118,4 +121,4 @@ struct worker *worker_pool_next(struct worker_pool *pool)
 	pool->next++;
 
 	return w;
-}
\ No newline at end of file
+}
diff --git a/src/x_builtins.c b/src/x_builtins.c
index ef0d233..5969250 100644
--- a/src/x_builtins.c
+++ b/src/x_builtins.c
@@ -1,9 +1,10 @@
-#include "x_builtins.h"
-
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
 
+#include "klog.h"
+#include "x_builtins.h"
+
 int x_builtin_parse(const char *s, enum x_builtin_upstream *out)
 {
 	if (s == NULL || out == NULL) {
@@ -25,11 +26,16 @@ int x_builtin_parse(const char *s, enum x_builtin_upstream *out)
 		return 0;
 	}
 
-	if (strcmp(s, "http-ok") == 0) {
+	if (strcmp(s, "http_ok") == 0) {
 		*out = X_BUILTIN_HTTP_OK;
 		return 0;
 	}
 
+	if (strcmp(s, "log_conn") == 0) {
+		*out = X_BUILTIN_LOG_CONN;
+		return 0;
+	}
+
 	if (strcmp(s, "close") == 0) {
 		*out = X_BUILTIN_CLOSE;
 		return 0;
@@ -53,7 +59,9 @@ const char *x_builtin_name(enum x_builtin_upstream builtin)
 	case X_BUILTIN_HANG:
 		return "hang";
 	case X_BUILTIN_HTTP_OK:
-		return "http-ok";
+		return "http_ok";
+	case X_BUILTIN_LOG_CONN:
+		return "log_conn";
 	case X_BUILTIN_CLOSE:
 		return "close";
 	case X_BUILTIN_NONE:
@@ -108,6 +116,12 @@ int x_builtin_handle(
 		res->action = X_BUILTIN_ACTION_DISCARD;
 		return 0;
 
+	case X_BUILTIN_LOG_CONN:
+		LOG_INFO("log_conn", "client_addr", _LOGV(req->client_addr));
+		res->data_len = 0;
+		res->action = X_BUILTIN_ACTION_CLOSE;
+		return 0;
+
 	case X_BUILTIN_CLOSE:
 		res->data_len = 0;
 		res->action = X_BUILTIN_ACTION_CLOSE;
diff --git a/src/x_builtins.h b/src/x_builtins.h
index e152874..bcc5a77 100644
--- a/src/x_builtins.h
+++ b/src/x_builtins.h
@@ -10,6 +10,7 @@ enum x_builtin_upstream {
 	X_BUILTIN_DISCARD,
 	X_BUILTIN_HANG,
 	X_BUILTIN_HTTP_OK,
+	X_BUILTIN_LOG_CONN,
 	X_BUILTIN_CLOSE,
 };