penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit 178260079860545d4d09e44d85eaa71af33b0c22

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-25T21:57:50Z
subjectAdded klog.c
commit 178260079860545d4d09e44d85eaa71af33b0c22
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-25T21:57:50Z

    Added klog.c
---
 Dockerfile  | 21 +++++++++++++++++++++
 Makefile    | 53 +++++++++++++++++++++++++++++++++++++----------------
 README.md   |  4 ++--
 file_conf.c |  3 ++-
 klog.c      | 33 +++++++++++++++++++++++++++++++++
 klog.h      | 10 ++++++++++
 tcp_route.c | 26 +++++++++++++-------------
 tinyproxy.c | 29 ++++++++++++++---------------
 8 files changed, 132 insertions(+), 47 deletions(-)

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..62e9594
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,21 @@
+FROM alpine:3.23 AS build
+
+RUN apk add --no-cache build-base libevent-dev
+
+RUN mkdir /src
+WORKDIR /src
+
+COPY [ "*.c", "*.h", "*.conf", "Makefile", "/src" ]
+
+RUN make all
+
+COPY tests ./tests
+RUN make test
+
+FROM alpine:3.23
+
+RUN apk add --no-cache libevent
+COPY --from=build /src/bin/tinyproxy /usr/bin/tinyproxy
+COPY --from=build /src/tinyproxy.conf /etc/tinyproxy.conf
+
+CMD [ "tinyproxy", "-c", "/etc/tinyproxy.conf" ]
diff --git a/Makefile b/Makefile
index 61ca864..50a34f8 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,8 @@ STRIP ?= strip
 PROJECT_ROOT := $(CURDIR)
 BIN_DIR := $(CURDIR)/bin
 
-SRC := proxy_proto_v2.c \
+SRC := klog.c \
+	   proxy_proto_v2.c \
 	   route.c \
 	   tcp_route.c \
        file_conf.c \
@@ -18,33 +19,39 @@ BIN := $(BIN_DIR)/tinyproxy
 
 BUILD_DIR := $(PROJECT_ROOT)/build
 
-LIBEVENT_SRC := $(PROJECT_ROOT)/libevent2
+# Optional. Set this only when you want to build vendored libevent.
+# Example:
+#   make LIBEVENT_SRC=$(PROJECT_ROOT)/libevent2
+LIBEVENT_SRC ?=
+
 LIBEVENT_PREFIX := $(BUILD_DIR)/libevent-install
 LIBEVENT_CORE_A := $(LIBEVENT_PREFIX)/lib/libevent_core.a
 
 CFLAGS ?= -Os -Wall -Wextra -ffunction-sections -fdata-sections
-CPPFLAGS += -I$(LIBEVENT_PREFIX)/include
-LDFLAGS += -L$(LIBEVENT_PREFIX)/lib
 
-ifeq ($(shell uname),Darwin)
-LDFLAGS += -Wl,-dead_strip
-TEST_FLAGS := CONCURRENCY=1000 TOTAL=1000 FD_LIMIT=2560
+ifeq ($(strip $(LIBEVENT_SRC)),)
+# System libevent, e.g. Alpine libevent-dev
+LIBEVENT_CPPFLAGS := $(shell pkg-config --cflags libevent_core)
+LIBEVENT_LDFLAGS  := $(shell pkg-config --libs-only-L libevent_core)
+LIBEVENT_LDLIBS   := $(shell pkg-config --libs-only-l --libs-only-other libevent_core)
+LIBEVENT_DEPS     :=
 else
-LDFLAGS += -Wl,--gc-sections
-TEST_FLAGS :=
+# Vendored libevent
+LIBEVENT_CPPFLAGS := -I$(LIBEVENT_PREFIX)/include
+LIBEVENT_LDFLAGS  := -L$(LIBEVENT_PREFIX)/lib
+LIBEVENT_LDLIBS   := -levent_core
+LIBEVENT_DEPS     := $(LIBEVENT_CORE_A)
 endif
 
-LDLIBS += -levent_core
+CPPFLAGS += $(LIBEVENT_CPPFLAGS)
+LDFLAGS  += $(LIBEVENT_LDFLAGS)
+LDLIBS   += $(LIBEVENT_LDLIBS)
 
-all: $(BIN)
-
-$(BIN): $(SRC) $(LIBEVENT_CORE_A)
+$(BIN): $(SRC) $(LIBEVENT_DEPS)
 	mkdir -p $(BIN_DIR)
 	$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(SRC) $(LDFLAGS) $(LDLIBS)
 
-strip: $(BIN)
-	$(STRIP) $(BIN)
-
+ifneq ($(strip $(LIBEVENT_SRC)),)
 $(LIBEVENT_CORE_A):
 	cd $(LIBEVENT_SRC) && \
 		AR=$(AR) RANLIB=$(RANLIB) ./configure \
@@ -60,6 +67,20 @@ $(LIBEVENT_CORE_A):
 		--disable-shared
 	AR=$(AR) RANLIB=$(RANLIB) $(MAKE) -C $(LIBEVENT_SRC)
 	AR=$(AR) RANLIB=$(RANLIB) $(MAKE) -C $(LIBEVENT_SRC) install
+endif
+
+ifeq ($(shell uname),Darwin)
+LDFLAGS += -Wl,-dead_strip
+TEST_FLAGS := CONCURRENCY=1000 TOTAL=1000 FD_LIMIT=2560
+else
+LDFLAGS += -Wl,--gc-sections
+TEST_FLAGS :=
+endif
+
+all: $(BIN)
+
+strip: $(BIN)
+	$(STRIP) $(BIN)
 
 test: $(BIN)
 	$(TEST_FLAGS) python3 tests/test_proxy.py $(BIN)
diff --git a/README.md b/README.md
index bbdfa93..304a0ea 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@ tinyproxy
 
 This is a work in progress project.
 
-## Rules of thumb
-1. Work as a simple inet utils
+## Rule of thumb
+1. Should work like an inet utils
 1. Be simple and effective
 1. Runs in foreground
 1. Uses libevent2 (for readability and future maintainance)
diff --git a/file_conf.c b/file_conf.c
index 0d12b82..5d86348 100644
--- a/file_conf.c
+++ b/file_conf.c
@@ -5,6 +5,7 @@
 #include <ctype.h>
 #include <errno.h>
 
+#include "klog.h"
 #include "file_conf.h"
 
 #define MAX_LINE_LEN 512
@@ -194,7 +195,7 @@ int load_routes_from_file(const char *path, struct route **routes_out, size_t *c
 
 		struct route *r = &routes[count];
 		if (parse_route_line(line, r) != 0) {
-			fprintf(stderr, "%s:%u: invalid route config\n", path, line_no);
+			LOG_ERROR("%s:%u: invalid route config", path, line_no);
 			fclose(fp);
 			free(routes);
 			return -EINVAL;
diff --git a/klog.c b/klog.c
new file mode 100644
index 0000000..451316a
--- /dev/null
+++ b/klog.c
@@ -0,0 +1,33 @@
+#include "klog.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+void log_at(char sev, const char *file, int line, const char *fmt, ...)
+{
+	struct timespec ts;
+	struct tm tm;
+	char tbuf[32];
+
+	clock_gettime(CLOCK_REALTIME, &ts);
+	localtime_r(&ts.tv_sec, &tm);
+
+	strftime(tbuf, sizeof(tbuf), "%m%d %H:%M:%S", &tm);
+
+	fprintf(stderr, "%c%s.%06ld %7ld %s:%d] ",
+		sev,
+		tbuf,
+		ts.tv_nsec / 1000,
+		(long)getpid(),
+		file,
+		line);
+
+	va_list ap;
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	fputc('\n', stderr);
+}
diff --git a/klog.h b/klog.h
new file mode 100644
index 0000000..b4c24b5
--- /dev/null
+++ b/klog.h
@@ -0,0 +1,10 @@
+#ifndef KLOG_H
+#define KLOG_H
+
+void log_at(char sev, const char *file, int line, const char *fmt, ...);
+
+#define LOG_INFO(...)  log_at('I', __FILE__, __LINE__, __VA_ARGS__)
+#define LOG_WARN(...)  log_at('W', __FILE__, __LINE__, __VA_ARGS__)
+#define LOG_ERROR(...) log_at('E', __FILE__, __LINE__, __VA_ARGS__)
+
+#endif
diff --git a/tcp_route.c b/tcp_route.c
index 84cbdeb..fcd5240 100644
--- a/tcp_route.c
+++ b/tcp_route.c
@@ -10,6 +10,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "klog.h"
 #include "file_conf.h"
 #include "proxy_proto_v2.h"
 #include "route.h"
@@ -64,8 +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();
-			fprintf(stderr, "connection error: %s\n",
-					evutil_socket_error_to_string(err));
+			LOG_ERROR("connection error: %s", evutil_socket_error_to_string(err));
 		}
 
 		free_conn(conn);
@@ -119,7 +119,7 @@ static void worker_adopt_client_fd(struct worker *w, struct accepted_client *ac)
 	upstream_addr.sin_port = htons(r->upstream_port);
 
 	if (inet_pton(AF_INET, r->upstream_host, &upstream_addr.sin_addr) != 1) {
-		fprintf(stderr, "invalid upstream address\n");
+		LOG_ERROR("invalid upstream address");
 		free_conn(conn);
 		return;
 	}
@@ -137,7 +137,7 @@ static void worker_adopt_client_fd(struct worker *w, struct accepted_client *ac)
 			(struct sockaddr *)&upstream_addr,
 			sizeof(upstream_addr)
 		) < 0) {
-		fprintf(stderr, "upstream connect failed\n");
+		LOG_ERROR("upstream connect failed");
 		free_conn(conn);
 		return;
 	}
@@ -155,14 +155,14 @@ static void worker_adopt_client_fd(struct worker *w, struct accepted_client *ac)
 		}
 
 		if (0 < ac->peer_addr_len) {
-			fprintf(stderr, "invalid client address\n");
+			LOG_ERROR("invalid client address");
 			free_conn(conn);
 			return;
 		}
 
 		if (ac->peer_addr.ss_family != AF_INET ||
 			local_addr.sin_family != AF_INET) {
-			fprintf(stderr, "PROXY v2 currently only supports IPv4 TCP\n");
+			LOG_ERROR("PROXY v2 currently only supports IPv4 TCP");
 			free_conn(conn);
 			return;
 		}
@@ -175,7 +175,7 @@ static void worker_adopt_client_fd(struct worker *w, struct accepted_client *ac)
 			local_len,
 			SOCK_STREAM
 		) < 0) {
-			fprintf(stderr, "failed to write PROXY v2 header\n");
+			LOG_ERROR("failed to write PROXY v2 header");
 			free_conn(conn);
 			return;
 		}
@@ -217,8 +217,7 @@ static void accept_error_cb(struct evconnlistener *listener, void *arg) {
 	struct event_base *base = arg;
 	int err = EVUTIL_SOCKET_ERROR();
 
-	fprintf(stderr, "accept error: %s\n",
-			evutil_socket_error_to_string(err));
+	LOG_ERROR("accept error: %s", evutil_socket_error_to_string(err));
 
 	evconnlistener_free(listener);
 	event_base_loopexit(base, NULL);
@@ -235,7 +234,7 @@ int start_tcp_route(
 	listen_addr.sin_port = htons(r->listen_port);
 
 	if (inet_pton(AF_INET, r->listen_host, &listen_addr.sin_addr) != 1) {
-		fprintf(stderr, "invalid listen address: %s\n", r->listen_host);
+		LOG_ERROR("invalid listen address: %s", r->listen_host);
 		return -EINVAL;
 	}
 
@@ -259,7 +258,7 @@ int start_tcp_route(
 	);
 
 	if (ctx->listener == NULL) {
-		fprintf(stderr, "evconnlistener_new_bind failed\n");
+		LOG_ERROR("evconnlistener_new_bind failed");
 		free(ctx);
 		return -EADDRINUSE;
 	}
@@ -270,11 +269,12 @@ int start_tcp_route(
 
 	route_options_str(r, opts, sizeof(opts));
 
-	fprintf(stderr, "line %u, listening on %s:%u, forwarding to %s:%u%s%s\n",
+	LOG_INFO("msg=\"route started\" line=%u listen_host=%s listen_port=%u upstream_host=%s upstream_port=%u%s%s",
 		r->line_no,
 		r->listen_host, r->listen_port,
 		r->upstream_host, r->upstream_port,
-		opts[0] ? ", options: " : "", opts
+		opts[0] ? " options=" : "",
+		opts[0] ? opts : ""
 	);
 
 	*out = ctx;
diff --git a/tinyproxy.c b/tinyproxy.c
index 6eb89ca..171e483 100644
--- a/tinyproxy.c
+++ b/tinyproxy.c
@@ -6,6 +6,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "klog.h"
 #include "file_conf.h"
 #include "tcp_route.h"
 
@@ -30,7 +31,7 @@ int main(int argc, char **argv)
 		switch (opt) {
 		case 'c':
 			if (optarg == NULL || optarg[0] == '\0') {
-				fprintf(stderr, "missing config path after -c\n");
+				LOG_ERROR("missing config path after -c");
 				return 2;
 			}
 			conf_path = optarg;
@@ -51,7 +52,7 @@ int main(int argc, char **argv)
 	}
 
 	if (optind != argc) {
-		fprintf(stderr, "unexpected argument: %s\n", argv[optind]);
+		LOG_ERROR("unexpected argument: %s", argv[optind]);
 		usage(stderr, argv[0]);
 		return 2;
 	}
@@ -61,13 +62,12 @@ int main(int argc, char **argv)
 
 	int rc = load_routes_from_file(conf_path, &routes, &route_count);
 	if (rc != 0) {
-		fprintf(stderr, "failed to load config %s: %s\n",
-			conf_path, strerror(-rc));
+		LOG_ERROR("failed to load config %s: %s", conf_path, strerror(-rc));
 		return 1;
 	}
 
 	if (route_count == 0) {
-		fprintf(stderr, "config %s has no routes\n", conf_path);
+		LOG_ERROR("config %s has no routes", conf_path);
 		free_routes(routes);
 		return 1;
 	}
@@ -82,7 +82,7 @@ int main(int argc, char **argv)
 
 	struct event_base *base = event_base_new();
 	if (base == NULL) {
-		fprintf(stderr, "event_base_new failed\n");
+		LOG_ERROR("event_base_new failed");
 		return 1;
 	}
 
@@ -104,22 +104,21 @@ int main(int argc, char **argv)
 
 		case PROTO_UDP:
 			// rc = start_udp_route(r);
-			fprintf(stderr, "skipping udp route: Not implemented yet\n");
+			LOG_WARN("skipping udp route: Not implemented yet");
 			break;
 
 		default:
-			fprintf(stderr, "route %zu has unknown protocol\n", i);
+			LOG_ERROR("route %zu has unknown protocol", i);
 			rc = -EINVAL;
 			break;
 		}
 
 		if (rc != 0) {
-			fprintf(stderr,
-					"failed to start route %zu: %s:%u -> %s:%u: %s\n",
-					i,
-					r->listen_host, r->listen_port,
-					r->upstream_host, r->upstream_port,
-					strerror(-rc));
+			LOG_ERROR("msg=\"failed to start route\" line=%u listen_host=%s listen_port=%u upstream_host=%s upstream_port=%u",
+				r->line_no,
+				r->listen_host, r->listen_port,
+				r->upstream_host, r->upstream_port
+			);
 
 			for (size_t j = 0; j < tcp_ctx_count; j++) {
 				free_tcp_route(tcp_ctxs[j]);
@@ -143,7 +142,7 @@ int main(int argc, char **argv)
 	free_routes(routes);
 
 	if (rc != 0) {
-		fprintf(stderr, "event loop failed: %s\n", strerror(-rc));
+		LOG_ERROR("event loop failed: %s", strerror(-rc));
 		return 1;
 	}