penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit 2555eb3a3beae0e468e7510b0bf8271637b0174a

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-06-05T17:11:48Z
subjectFixed builtin not properly closing connection
commit 2555eb3a3beae0e468e7510b0bf8271637b0174a
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-06-05T17:11:48Z

    Fixed builtin not properly closing connection
---
 examples/minecraft-bedrock-lan.conf |   3 +-
 src/stream_builtin.c                |   9 ++-
 src/stream_conn.c                   | 119 ++++++++++++++++++++++++++----------
 src/stream_conn.h                   |   1 +
 src/stream_pipe.c                   |  21 -------
 5 files changed, 95 insertions(+), 58 deletions(-)

diff --git a/examples/minecraft-bedrock-lan.conf b/examples/minecraft-bedrock-lan.conf
index dc474ca..1f162c9 100644
--- a/examples/minecraft-bedrock-lan.conf
+++ b/examples/minecraft-bedrock-lan.conf
@@ -1,4 +1,3 @@
-#
 # Expose a remote Minecraft Bedrock server as a local LAN server.
 #
 # Clients on the local network can discover the remote server from the
@@ -11,7 +10,7 @@
 # Replace 123.123.123.123:19132 with your server address.
 #
 udp :19132 udp 123.123.123.123:19132 broadcast_reply=listen
-#
+
 # Advertise a Minecraft Bedrock server outside the local broadcast subnet.
 #
 # tinyproxy receives LAN discovery broadcasts on :19132, forwards them to
diff --git a/src/stream_builtin.c b/src/stream_builtin.c
index 3713783..60a1617 100644
--- a/src/stream_builtin.c
+++ b/src/stream_builtin.c
@@ -2,6 +2,7 @@
 
 #include <string.h>
 
+#include "klog.h"
 #include "stream_builtin.h"
 #include "stream_conn.h"
 
@@ -17,10 +18,14 @@ static void builtin_client_read_cb(struct bufferevent *bev, void *arg)
 static void builtin_close_after_write_cb(struct bufferevent *bev, void *arg)
 {
 	conn_t *conn = arg;
+	struct evbuffer *output = bufferevent_get_output(bev);
 
-	if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) {
-		free_conn(conn);
+	if (evbuffer_get_length(output) != 0) {
+		return;
 	}
+
+	finish_client_write(conn);
+	conn->close_after_client_eof = true;
 }
 
 static const char *stream_client_addr_string(conn_t *conn, char *buf, size_t buf_len)
diff --git a/src/stream_conn.c b/src/stream_conn.c
index e9bb34b..86466ae 100644
--- a/src/stream_conn.c
+++ b/src/stream_conn.c
@@ -98,6 +98,27 @@ static void drain_client_then_close(conn_t *conn)
 	}
 }
 
+void finish_client_write(conn_t *conn)
+{
+	evutil_socket_t fd = bufferevent_getfd(conn->client);
+
+	if (fd >= 0) {
+#ifndef _WIN32
+		shutdown(fd, SHUT_WR);
+#else
+		shutdown(fd, SD_SEND);
+#endif
+	}
+
+	bufferevent_disable(conn->client, EV_WRITE);
+
+	/*
+	 * Keep EV_READ enabled so we can observe client EOF instead of
+	 * closing with unread data and causing RST on some platforms.
+	 */
+	bufferevent_enable(conn->client, EV_READ);
+}
+
 void stream_client_event_cb(struct bufferevent *bev, short events, void *arg)
 {
 	(void)bev;
@@ -106,40 +127,68 @@ void stream_client_event_cb(struct bufferevent *bev, short events, void *arg)
 	const struct route *r = conn->route;
 
 	if (events & BEV_EVENT_TIMEOUT) {
-		LOG_WARN("client connection timed out",
-				"listen", _LOGV_ENDPOINT(&r->listen),
-				"upstream", _LOGV_ENDPOINT(&r->upstream)
-				);
-		free_conn(conn);
-		return;
+		LOG_DEBUG("client connection timed out",
+			"listen", _LOGV_ENDPOINT(&r->listen),
+			"upstream", _LOGV_ENDPOINT(&r->upstream)
+		);
+		goto out_free;
 	}
 
 	if (events & BEV_EVENT_ERROR) {
 		int err = EVUTIL_SOCKET_ERROR();
 
 		if (conn->close_after_client_eof && err == ECONNRESET) {
-			LOG_INFO("client reset after response drain",
+			LOG_DEBUG("client reset after response drain",
 				"listen", _LOGV_ENDPOINT(&r->listen),
 				"upstream", _LOGV_ENDPOINT(&r->upstream)
 			);
-			free_conn(conn);
-			return;
+			goto out_free;
+		}
+
+		if (err == ECONNRESET) {
+			LOG_DEBUG("client reset connection",
+				"listen", _LOGV_ENDPOINT(&r->listen),
+				"upstream", _LOGV_ENDPOINT(&r->upstream)
+			);
+			goto out_free;
 		}
 
-		LOG_ERROR("client connection error",
+#ifdef EPIPE
+		if (err == EPIPE) {
+			LOG_DEBUG("client pipe closed",
+				"listen", _LOGV_ENDPOINT(&r->listen),
+				"upstream", _LOGV_ENDPOINT(&r->upstream)
+			);
+			goto out_free;
+		}
+#endif
+
+#ifdef ETIMEDOUT
+		if (err == ETIMEDOUT) {
+			LOG_DEBUG("client connection timed out",
+				"listen", _LOGV_ENDPOINT(&r->listen),
+				"upstream", _LOGV_ENDPOINT(&r->upstream)
+			);
+			goto out_free;
+		}
+#endif
+
+		LOG_WARN("client connection error",
 			"err", _LOGV(evutil_socket_error_to_string(err)),
 			"listen", _LOGV_ENDPOINT(&r->listen),
 			"upstream", _LOGV_ENDPOINT(&r->upstream)
 		);
-
-		free_conn(conn);
-		return;
+		goto out_free;
 	}
 
 	if (events & BEV_EVENT_EOF) {
-		free_conn(conn);
-		return;
+		goto out_free;
 	}
+
+	return;
+
+out_free:
+	free_conn(conn);
 }
 
 void stream_upstream_event_cb(struct bufferevent *bev, short events, void *arg)
@@ -147,7 +196,6 @@ void stream_upstream_event_cb(struct bufferevent *bev, short events, void *arg)
 	(void)bev;
 
 	conn_t *conn = arg;
-
 	const struct route *r = conn->route;
 
 	if (events & BEV_EVENT_CONNECTED) {
@@ -160,18 +208,16 @@ void stream_upstream_event_cb(struct bufferevent *bev, short events, void *arg)
 
 	if (events & BEV_EVENT_TIMEOUT) {
 		LOG_WARN("upstream connection timed out",
-				"listen", _LOGV_ENDPOINT(&r->listen),
-				"upstream", _LOGV_ENDPOINT(&r->upstream)
-				);
-		free_conn(conn);
-		return;
+			"listen", _LOGV_ENDPOINT(&r->listen),
+			"upstream", _LOGV_ENDPOINT(&r->upstream)
+		);
+		goto out_free;
 	}
 
 	if (events & BEV_EVENT_ERROR) {
 		int err = EVUTIL_SOCKET_ERROR();
 
 		if (client_has_pending_output(conn)) {
-
 			LOG_WARN("upstream error after response queued; draining client",
 				"err", _LOGV(evutil_socket_error_to_string(err)),
 				"client_output", _LOGV(bev_output_len(conn->client)),
@@ -179,30 +225,37 @@ void stream_upstream_event_cb(struct bufferevent *bev, short events, void *arg)
 				"listen", _LOGV_ENDPOINT(&r->listen),
 				"upstream", _LOGV_ENDPOINT(&r->upstream)
 			);
-
-			drain_client_then_close(conn);
-			return;
+			goto out_drain_client;
 		}
 
-		LOG_ERROR("upstream connection error",
+		LOG_WARN("upstream connection error",
 			"err", _LOGV(evutil_socket_error_to_string(err)),
 			"listen", _LOGV_ENDPOINT(&r->listen),
 			"upstream", _LOGV_ENDPOINT(&r->upstream)
 		);
-
-		free_conn(conn);
-		return;
+		goto out_free;
 	}
 
 	if (events & BEV_EVENT_EOF) {
 		if (client_has_pending_output(conn)) {
-			drain_client_then_close(conn);
-			return;
+			goto out_drain_client;
 		}
 
-		free_conn(conn);
-		return;
+		goto out_free;
 	}
+
+	LOG_DEBUG("unhandled upstream event",
+		"events", _LOGV(events),
+		"listen", _LOGV_ENDPOINT(&r->listen),
+		"upstream", _LOGV_ENDPOINT(&r->upstream)
+	);
+
+out_free:
+	free_conn(conn);
+	return;
+
+out_drain_client:
+	drain_client_then_close(conn);
 }
 
 static int connect_upstream(struct bufferevent *bev, const struct endpoint *ep)
diff --git a/src/stream_conn.h b/src/stream_conn.h
index 25b1673..320e2ef 100644
--- a/src/stream_conn.h
+++ b/src/stream_conn.h
@@ -12,6 +12,7 @@ int dispatch_client_fd(struct worker *w,
 	socklen_t addr_len);
 void free_conn(conn_t *conn);
 
+void finish_client_write(conn_t *conn);
 void set_client_idle_timeout(conn_t *conn, const struct route *r);
 
 void stream_client_event_cb(struct bufferevent *bev, short events, void *arg);
diff --git a/src/stream_pipe.c b/src/stream_pipe.c
index fc90233..daa2cdc 100644
--- a/src/stream_pipe.c
+++ b/src/stream_pipe.c
@@ -5,27 +5,6 @@
 #include "stream_conn.h"
 #include "stream_pipe.h"
 
-static void finish_client_write(conn_t *conn)
-{
-	evutil_socket_t fd = bufferevent_getfd(conn->client);
-
-	if (fd >= 0) {
-#ifndef _WIN32
-		shutdown(fd, SHUT_WR);
-#else
-		shutdown(fd, SD_SEND);
-#endif
-	}
-
-	bufferevent_disable(conn->client, EV_WRITE);
-
-	/*
-	 * Keep EV_READ enabled so we can observe client EOF instead of
-	 * closing with unread data and causing RST on some platforms.
-	 */
-	bufferevent_enable(conn->client, EV_READ);
-}
-
 void pipe_client_read_cb(struct bufferevent *client, void *arg)
 {
 	conn_t *conn = arg;