commit 45da16e9451237ce75d2734ac3497046b41484b5
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-05-25T03:13:41Z |
| subject | Basic proxy protocol v2. NOT tested yet |
commit 45da16e9451237ce75d2734ac3497046b41484b5
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-05-25T03:13:41Z
Basic proxy protocol v2. NOT tested yet
---
Makefile | 3 +-
file_conf.c | 2 -
proxy_proto_v2.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
proxy_proto_v2.h | 35 +++++++++++++
tcp_route.c | 62 ++++++++++++++++++----
5 files changed, 246 insertions(+), 13 deletions(-)
diff --git a/Makefile b/Makefile
index 6fd8d6e..94761c3 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,8 @@ STRIP ?= strip
PROJECT_ROOT := $(CURDIR)
BIN_DIR := $(CURDIR)/bin
-SRC := tcp_route.c \
+SRC := proxy_proto_v2.c \
+ tcp_route.c \
file_conf.c \
tinyproxy.c
diff --git a/file_conf.c b/file_conf.c
index 4780bb1..2ef5ab1 100644
--- a/file_conf.c
+++ b/file_conf.c
@@ -1,5 +1,3 @@
-// file_conf.c
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/proxy_proto_v2.c b/proxy_proto_v2.c
new file mode 100644
index 0000000..8be5bdc
--- /dev/null
+++ b/proxy_proto_v2.c
@@ -0,0 +1,157 @@
+#include <event2/bufferevent.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "proxy_proto_v2.h"
+
+static void put_u16(unsigned char *p, uint16_t v)
+{
+ p[0] = (unsigned char)(v >> 8);
+ p[1] = (unsigned char)(v & 0xff);
+}
+
+int proxy_v2_build(
+ unsigned char *buf,
+ size_t buf_len,
+ const struct sockaddr *src,
+ socklen_t src_len,
+ const struct sockaddr *dst,
+ socklen_t dst_len,
+ int sock_type,
+ size_t *out_len
+) {
+ unsigned char fam_proto;
+ uint16_t addr_len;
+ unsigned char *p;
+
+ if (buf == NULL || src == NULL || dst == NULL || out_len == NULL) {
+ return -EINVAL;
+ }
+
+ if (src->sa_family != dst->sa_family) {
+ return -EAFNOSUPPORT;
+ }
+
+ if (sock_type == SOCK_STREAM) {
+ fam_proto = PP2_TRANS_STREAM;
+ } else if (sock_type == SOCK_DGRAM) {
+ fam_proto = PP2_TRANS_DGRAM;
+ } else {
+ return -EPROTONOSUPPORT;
+ }
+
+ switch (src->sa_family) {
+ case AF_INET:
+ if (src_len < sizeof(struct sockaddr_in) ||
+ dst_len < sizeof(struct sockaddr_in)) {
+ return -EINVAL;
+ }
+
+ addr_len = 12;
+
+ if (buf_len < 16 + addr_len) {
+ return -ENOSPC;
+ }
+
+ memcpy(buf, PROXY_V2_SIG, 12);
+ buf[12] = PP2_VERSION_CMD_PROXY;
+ buf[13] = PP2_FAM_INET | fam_proto;
+ put_u16(buf + 14, addr_len);
+
+ p = buf + 16;
+
+ const struct sockaddr_in *s4 = (const struct sockaddr_in *)src;
+ const struct sockaddr_in *d4 = (const struct sockaddr_in *)dst;
+
+ memcpy(p, &s4->sin_addr, 4);
+ p += 4;
+
+ memcpy(p, &d4->sin_addr, 4);
+ p += 4;
+
+ memcpy(p, &s4->sin_port, 2);
+ p += 2;
+
+ memcpy(p, &d4->sin_port, 2);
+ p += 2;
+
+ *out_len = 16 + addr_len;
+ return 0;
+
+ case AF_INET6:
+ if (src_len < sizeof(struct sockaddr_in6) ||
+ dst_len < sizeof(struct sockaddr_in6)) {
+ return -EINVAL;
+ }
+
+ addr_len = 36;
+
+ if (buf_len < 16 + addr_len) {
+ return -ENOSPC;
+ }
+
+ memcpy(buf, PROXY_V2_SIG, 12);
+ buf[12] = PP2_VERSION_CMD_PROXY;
+ buf[13] = PP2_FAM_INET6 | fam_proto;
+ put_u16(buf + 14, addr_len);
+
+ p = buf + 16;
+
+ const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *)src;
+ const struct sockaddr_in6 *d6 = (const struct sockaddr_in6 *)dst;
+
+ memcpy(p, &s6->sin6_addr, 16);
+ p += 16;
+
+ memcpy(p, &d6->sin6_addr, 16);
+ p += 16;
+
+ memcpy(p, &s6->sin6_port, 2);
+ p += 2;
+
+ memcpy(p, &d6->sin6_port, 2);
+ p += 2;
+
+ *out_len = 16 + addr_len;
+ return 0;
+
+ default:
+ return -EAFNOSUPPORT;
+ }
+}
+
+int proxy_v2_write_bufferevent(
+ struct bufferevent *bev,
+ const struct sockaddr *src,
+ socklen_t src_len,
+ const struct sockaddr *dst,
+ socklen_t dst_len,
+ int sock_type
+) {
+ unsigned char hdr[256];
+ size_t hdr_len = 0;
+
+ int rc = proxy_v2_build(
+ hdr,
+ sizeof(hdr),
+ src,
+ src_len,
+ dst,
+ dst_len,
+ sock_type,
+ &hdr_len
+ );
+ if (rc != 0) {
+ return rc;
+ }
+
+ if (bufferevent_write(bev, hdr, hdr_len) < 0) {
+ return -EIO;
+ }
+
+ return 0;
+}
diff --git a/proxy_proto_v2.h b/proxy_proto_v2.h
new file mode 100644
index 0000000..0a3ed47
--- /dev/null
+++ b/proxy_proto_v2.h
@@ -0,0 +1,35 @@
+#ifndef PROXY_PROTO_V2_H
+#define PROXY_PROTO_V2_H
+
+#define PROXY_V2_SIG "\r\n\r\n\0\r\nQUIT\n"
+
+#define PP2_VERSION_CMD_PROXY 0x21
+
+#define PP2_FAM_INET 0x10
+#define PP2_FAM_INET6 0x20
+#define PP2_FAM_UNIX 0x30
+
+#define PP2_TRANS_STREAM 0x01
+#define PP2_TRANS_DGRAM 0x02
+
+int proxy_v2_build(
+ unsigned char *buf,
+ size_t buf_len,
+ const struct sockaddr *src,
+ socklen_t src_len,
+ const struct sockaddr *dst,
+ socklen_t dst_len,
+ int sock_type,
+ size_t *out_len
+);
+
+int proxy_v2_write_bufferevent(
+ struct bufferevent *bev,
+ const struct sockaddr *src,
+ socklen_t src_len,
+ const struct sockaddr *dst,
+ socklen_t dst_len,
+ int sock_type
+);
+
+#endif
diff --git a/tcp_route.c b/tcp_route.c
index 0421b67..c09abaf 100644
--- a/tcp_route.c
+++ b/tcp_route.c
@@ -11,8 +11,11 @@
#include <string.h>
#include "file_conf.h"
+#include "proxy_proto_v2.h"
#include "tcp_route.h"
+#define PROXY_V2_SIG "\r\n\r\n\0\r\nQUIT\n"
+
static void free_conn(conn_t *conn) {
if (conn == NULL) {
return;
@@ -78,8 +81,6 @@ static void accept_cb(
void *arg
) {
(void)listener;
- (void)addr;
- (void)socklen;
struct listener_ctx *ctx = arg;
const struct route *r = ctx->route;
@@ -126,6 +127,14 @@ static void accept_cb(
return;
}
+ /*
+ * Do not read from the client yet.
+ *
+ * Otherwise client bytes may be copied into the upstream output buffer
+ * before the PROXY v2 header is queued.
+ */
+ bufferevent_disable(conn->client, EV_READ);
+
if (bufferevent_socket_connect(
conn->upstream,
(struct sockaddr *)&upstream_addr,
@@ -136,14 +145,47 @@ static void accept_cb(
return;
}
- /*
- * Important:
- * The upstream connection is async. libevent will emit BEV_EVENT_CONNECTED
- * later. This simple proxy enables client reading immediately.
- *
- * For PROXY v2, we need to write the PROXY header to
- * conn->upstream output before forwarding client bytes.
- */
+ if (r->send_proxy_v2) {
+ struct sockaddr_in local_addr;
+ socklen_t local_len = sizeof(local_addr);
+
+ memset(&local_addr, 0, sizeof(local_addr));
+
+ if (getsockname(client_fd, (struct sockaddr *)&local_addr, &local_len) < 0) {
+ perror("getsockname");
+ free_conn(conn);
+ return;
+ }
+
+ if (addr == NULL || socklen < (int)sizeof(struct sockaddr_in)) {
+ fprintf(stderr, "invalid client address\n");
+ free_conn(conn);
+ return;
+ }
+
+ if (((struct sockaddr *)addr)->sa_family != AF_INET ||
+ local_addr.sin_family != AF_INET) {
+ fprintf(stderr, "PROXY v2 currently only supports IPv4 TCP\n");
+ free_conn(conn);
+ return;
+ }
+
+ if (proxy_v2_write_bufferevent(
+ conn->upstream,
+ addr,
+ (socklen_t)socklen,
+ (struct sockaddr *)&local_addr,
+ local_len,
+ SOCK_STREAM
+ ) < 0) {
+ fprintf(stderr, "failed to write PROXY v2 header\n");
+ free_conn(conn);
+ return;
+ }
+ }
+
+ bufferevent_enable(conn->client, EV_READ | EV_WRITE);
+ bufferevent_enable(conn->upstream, EV_READ | EV_WRITE);
}
static void accept_error_cb(struct evconnlistener *listener, void *arg) {