penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

commit 69f3fd924bbc616b248d37a80860c95f56668c35

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-05-27T19:19:42Z
subjectBasic fuzzing for tcp_route
commit 69f3fd924bbc616b248d37a80860c95f56668c35
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-05-27T19:19:42Z

    Basic fuzzing for tcp_route
---
 .gitignore                                |   1 +
 Makefile                                  |  95 +-----
 README.md                                 |   2 -
 fuzz/Makefile                             |  87 +++++
 fuzz/fuzz_file_conf.c                     | 124 +++++++
 fuzz/fuzz_proxy_v2_build.c                | 130 ++++++++
 fuzz/fuzz_tcp_route.c                     | 111 +++++++
 fuzz/seeds/tcp_route/basic                |   1 +
 fuzz/seeds/tcp_route/chunked_weird        |   8 +
 fuzz/seeds/tcp_route/ff                   |   1 +
 fuzz/seeds/tcp_route/hex_junk             |   2 +
 fuzz/seeds/tcp_route/http                 |   3 +
 fuzz/seeds/tcp_route/http2_preface        |   4 +
 fuzz/seeds/tcp_route/http_connect         |   3 +
 fuzz/seeds/tcp_route/http_get             |   3 +
 fuzz/seeds/tcp_route/http_post            |   5 +
 fuzz/seeds/tcp_route/huge_request_line    |   3 +
 fuzz/seeds/tcp_route/long_header          |   4 +
 fuzz/seeds/tcp_route/many_headers         | 515 ++++++++++++++++++++++++++++++
 fuzz/seeds/tcp_route/nuls                 | Bin 0 -> 8 bytes
 fuzz/seeds/tcp_route/percent_encoded_junk |   3 +
 fuzz/seeds/tcp_route/proxyish             | Bin 0 -> 13 bytes
 fuzz/seeds/tcp_route/smuggling_shape_1    |   9 +
 fuzz/seeds/tcp_route/ssh_banner           |   1 +
 fuzz/seeds/tcp_route/tls_clienthello_like | Bin 0 -> 43 bytes
 mk/common.mk                              |  48 +++
 mk/libevent.mk                            |  45 +++
 src/klog.c                                |   4 +
 src/klog.h                                |   1 +
 src/{signal.c => signals.c}               |   2 +-
 src/{signal.h => signals.h}               |   0
 src/tcp_route.c                           |  37 +++
 src/tcp_route.h                           |  13 +
 src/tinyproxy.c                           |   2 +-
 34 files changed, 1173 insertions(+), 94 deletions(-)

diff --git a/.gitignore b/.gitignore
index 63ce10f..e01bc0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 bin/
 build/
+corpus/
 libevent2/
 __pycache__
 *.log
diff --git a/Makefile b/Makefile
index 9807c82..5361c96 100644
--- a/Makefile
+++ b/Makefile
@@ -1,76 +1,13 @@
-export
-
-CC ?= cc
-AR ?= ar
-RANLIB ?= ranlib
-STRIP ?= strip
-PKG_CONFIG ?= pkg-config
+.DEFAULT_GOAL := all
 
 PROJECT_ROOT := $(CURDIR)
-SRC_DIR := src
-BIN_DIR := $(CURDIR)/bin
-BUILD_DIR := $(PROJECT_ROOT)/build
-
-BIN := $(BIN_DIR)/tinyproxy
-
-SRC := $(wildcard $(SRC_DIR)/*.c)
-
-EXEEXT :=
-WINDOWS_LDLIBS :=
 
-ifeq ($(OS),Windows_NT)
-EXEEXT := .exe
-WINDOWS_LDLIBS += -lws2_32
-endif
+include mk/common.mk
+include mk/libevent.mk
 
 BIN := $(BIN_DIR)/tinyproxy$(EXEEXT)
 
-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
-
-UNAME_S := $(shell uname)
-
-ifeq ($(UNAME_S),Darwin)
-STATIC ?= 0
-LDFLAGS += -Wl,-dead_strip
-TEST_FLAGS := CONCURRENCY=1000 TOTAL=1000 FD_LIMIT=2560
-else ifeq ($(OS),Windows_NT)
-STATIC ?= 0
-LDFLAGS += -Wl,--gc-sections
-TEST_FLAGS :=
-TEST_FLAGS := CONCURRENCY=100 TOTAL=100 FD_LIMIT=512
-else
-STATIC ?= 1
-LDFLAGS += -Wl,--gc-sections
-TEST_FLAGS :=
-endif
-
-ifeq ($(STATIC),1)
-LDFLAGS += -static
-PKG_CONFIG_STATIC := --static
-else
-PKG_CONFIG_STATIC :=
-endif
-
-ifeq ($(strip $(LIBEVENT_SRC)),)
-LIBEVENT_CPPFLAGS := $(shell $(PKG_CONFIG) --cflags libevent_core 2>/dev/null)
-LIBEVENT_LDFLAGS  :=
-LIBEVENT_LDLIBS   := $(shell $(PKG_CONFIG) $(PKG_CONFIG_STATIC) --libs libevent_core 2>/dev/null)
-LIBEVENT_DEPS     :=
-else
-LIBEVENT_CPPFLAGS := -I$(LIBEVENT_PREFIX)/include
-LIBEVENT_LDFLAGS  := -L$(LIBEVENT_PREFIX)/lib
-LIBEVENT_LDLIBS   := -levent_core
-LIBEVENT_DEPS     := $(LIBEVENT_CORE_A)
-endif
-
-CPPFLAGS += $(LIBEVENT_CPPFLAGS)
-LDFLAGS  += $(LIBEVENT_LDFLAGS)
-LDLIBS   += $(LIBEVENT_LDLIBS)
-LDLIBS   += $(WINDOWS_LDLIBS)
+SRC := $(wildcard $(SRC_DIR)/*.c)
 
 all: $(BIN)
 
@@ -78,24 +15,6 @@ $(BIN): $(SRC) $(LIBEVENT_DEPS)
 	mkdir -p $(BIN_DIR)
 	$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(SRC) $(LDFLAGS) $(LDLIBS)
 
-ifneq ($(strip $(LIBEVENT_SRC)),)
-$(LIBEVENT_CORE_A):
-	cd $(LIBEVENT_SRC) && \
-		AR=$(AR) RANLIB=$(RANLIB) ./configure \
-		--prefix="$(LIBEVENT_PREFIX)" \
-		--disable-openssl \
-		--disable-samples \
-		--disable-libevent-regress \
-		--disable-debug-mode \
-		--disable-malloc-replacement \
-		--disable-thread-support \
-		--disable-dependency-tracking \
-		--enable-static \
-		--disable-shared
-	AR=$(AR) RANLIB=$(RANLIB) $(MAKE) -C $(LIBEVENT_SRC)
-	AR=$(AR) RANLIB=$(RANLIB) $(MAKE) -C $(LIBEVENT_SRC) install
-endif
-
 strip: $(BIN)
 ifeq ($(UNAME_S),Darwin)
 	$(error strip target is not supported on macOS; build without stripping, or strip inside the Linux/Alpine container)
@@ -109,11 +28,7 @@ test: $(BIN)
 clean:
 	rm -rf $(BIN_DIR)
 
-clean-libevent:
-	-$(MAKE) -C $(LIBEVENT_SRC) distclean
-	rm -rf $(LIBEVENT_PREFIX)
-
 distclean: clean clean-libevent
 	rm -rf $(BUILD_DIR)
 
-.PHONY: all clean clean-libevent distclean test strip
+.PHONY: all clean distclean test strip
diff --git a/README.md b/README.md
index 49e79cb..374ad48 100644
--- a/README.md
+++ b/README.md
@@ -142,5 +142,3 @@ These are the major features I want to implement before calling it done. (going
 * Forwarding
   * IPv6 (planned for v0.2.x)
   * unix socks (listen) (planned for v0.2.x)
-* Security
-  * fuzzing (planned for v0.1.x)
diff --git a/fuzz/Makefile b/fuzz/Makefile
new file mode 100644
index 0000000..cf43612
--- /dev/null
+++ b/fuzz/Makefile
@@ -0,0 +1,87 @@
+PROJECT_ROOT := $(abspath ..)
+
+# Sanitizers + static linking are a bad combo. Must be before common.mk.
+STATIC := 0
+
+include $(PROJECT_ROOT)/mk/common.mk
+include $(PROJECT_ROOT)/mk/libevent.mk
+
+CC ?= clang
+
+OUT := $(PROJECT_ROOT)/bin/fuzz
+CORPUS := $(PROJECT_ROOT)/corpus
+SEEDS := seeds
+
+TARGETS := proxy_v2_build file_conf tcp_route
+BINS := $(addprefix $(OUT)/fuzz_,$(TARGETS))
+
+FUZZ_CFLAGS ?= -g -O1 -DFUZZ -fsanitize=fuzzer,address,undefined
+FUZZ_LDFLAGS ?= -fsanitize=fuzzer,address,undefined
+
+COMMON_FUZZ_SRC := \
+	$(SRC_DIR)/klog.c
+
+.PHONY: all run clean FORCE
+.PRECIOUS: $(OUT)/fuzz_%
+
+all: $(BINS)
+
+$(OUT):
+	mkdir -p $(OUT)
+
+$(OUT)/fuzz_proxy_v2_build: \
+	fuzz_proxy_v2_build.c \
+	$(SRC_DIR)/proxy_proto_v2.c \
+	| $(OUT)
+	$(CC) $(CFLAGS) $(FUZZ_CFLAGS) $(CPPFLAGS) \
+		$(SRC_DIR)/proxy_proto_v2.c \
+		fuzz_proxy_v2_build.c \
+		-o $@ \
+		$(LDFLAGS) $(FUZZ_LDFLAGS) $(LDLIBS)
+
+$(OUT)/fuzz_file_conf: \
+	fuzz_file_conf.c \
+	$(SRC_DIR)/file_conf.c \
+	$(SRC_DIR)/klog.c \
+	| $(OUT)
+	$(CC) $(CFLAGS) $(FUZZ_CFLAGS) $(CPPFLAGS) \
+		$(SRC_DIR)/file_conf.c \
+		$(SRC_DIR)/klog.c \
+		fuzz_file_conf.c \
+		-o $@ \
+		$(LDFLAGS) $(FUZZ_LDFLAGS) $(LDLIBS)
+
+$(OUT)/fuzz_tcp_route: \
+	fuzz_tcp_route.c \
+	$(SRC_DIR)/tcp_route.c \
+	$(SRC_DIR)/proxy_proto_v2.c \
+	$(SRC_DIR)/klog.c \
+	$(LIBEVENT_DEPS) \
+	| $(OUT)
+	$(CC) $(CFLAGS) $(FUZZ_CFLAGS) $(CPPFLAGS) \
+		$(SRC_DIR)/tcp_route.c \
+		$(SRC_DIR)/proxy_proto_v2.c \
+		$(SRC_DIR)/route.c \
+		$(SRC_DIR)/klog.c \
+		fuzz_tcp_route.c \
+		-o $@ \
+		$(LDFLAGS) $(FUZZ_LDFLAGS) $(LDLIBS)
+
+seed-%: FORCE
+	mkdir -p $(CORPUS)/$*
+	if [ -d "$(SEEDS)/$*" ]; then \
+		cp "$(SEEDS)/$*"/* "$(CORPUS)/$*"/ 2>/dev/null || true; \
+	fi
+
+run-%: $(OUT)/fuzz_% seed-%
+	$(OUT)/fuzz_$* \
+		$(CORPUS)/$* \
+		-max_total_time=$${MAX_TOTAL_TIME:-60} \
+		-max_len=$${MAX_LEN:-8192}
+
+run: $(addprefix run-,$(TARGETS))
+
+clean:
+	rm -rf $(OUT)
+
+FORCE:
diff --git a/fuzz/fuzz_file_conf.c b/fuzz/fuzz_file_conf.c
new file mode 100644
index 0000000..24351b8
--- /dev/null
+++ b/fuzz/fuzz_file_conf.c
@@ -0,0 +1,124 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "file_conf.h"
+
+static void check_endpoint(const struct endpoint *ep)
+{
+	switch (ep->kind) {
+	case ENDPOINT_INET:
+		if (ep->port == 0) {
+			abort();
+		}
+		if (ep->host[0] == '\0') {
+			abort();
+		}
+		break;
+
+	case ENDPOINT_UNIX:
+	case ENDPOINT_UNIX_DGRAM:
+		if (ep->path[0] == '\0') {
+			abort();
+		}
+		break;
+
+	default:
+		abort();
+	}
+}
+
+static void check_route(const struct route *r)
+{
+	check_endpoint(&r->listen);
+	check_endpoint(&r->upstream);
+
+	switch (r->proto) {
+	case PROTO_TCP:
+	case PROTO_UDP:
+		break;
+	default:
+		abort();
+	}
+
+	if (r->line_no == 0) {
+		abort();
+	}
+
+	if (r->opts.idle_timeout_sec <= 0) {
+		abort();
+	}
+
+	if (r->opts.connect_timeout_sec <= 0) {
+		abort();
+	}
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+	/*
+	 * Keep the fuzzer fast. file_conf.c has MAX_LINE_LEN=512, so this is
+	 * already enough to exercise normal, multiline, and overlong cases.
+	 */
+	if (size > 8192) {
+		return 0;
+	}
+
+	char path[] = "/tmp/tinyproxy-file-conf-fuzz-XXXXXX";
+	int fd = mkstemp(path);
+	if (fd < 0) {
+		return 0;
+	}
+
+	size_t off = 0;
+	while (off < size) {
+		ssize_t n = write(fd, data + off, size - off);
+		if (n < 0) {
+			if (errno == EINTR) {
+				continue;
+			}
+
+			close(fd);
+			unlink(path);
+			return 0;
+		}
+
+		if (n == 0) {
+			break;
+		}
+
+		off += (size_t)n;
+	}
+
+	close(fd);
+
+	struct route *routes = NULL;
+	size_t count = 0;
+
+	int rc = load_routes_from_file(path, &routes, &count);
+
+	unlink(path);
+
+	if (rc == 0) {
+		if (count > 0 && routes == NULL) {
+			abort();
+		}
+
+		for (size_t i = 0; i < count; i++) {
+			check_route(&routes[i]);
+		}
+
+		free_routes(routes);
+	} else {
+		/*
+		 * On failure, load_routes_from_file() should not transfer ownership.
+		 * Keep this loose because callers should not rely on output values
+		 * after error unless the API explicitly promises that.
+		 */
+	}
+
+	return 0;
+}
diff --git a/fuzz/fuzz_proxy_v2_build.c b/fuzz/fuzz_proxy_v2_build.c
new file mode 100644
index 0000000..2861e51
--- /dev/null
+++ b/fuzz/fuzz_proxy_v2_build.c
@@ -0,0 +1,130 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "../src/proxy_proto_v2.h"
+
+static uint16_t read_u16(const uint8_t *p)
+{
+	return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+	if (size < 1) {
+		return 0;
+	}
+
+	struct sockaddr_storage src;
+	struct sockaddr_storage dst;
+	unsigned char buf[128];
+	size_t out_len = 999999;
+
+	memset(&src, 0, sizeof(src));
+	memset(&dst, 0, sizeof(dst));
+	memset(buf, 0xa5, sizeof(buf));
+
+	int family = data[0] % 3;
+	int sock_type = (size > 1 && (data[1] & 1)) ? SOCK_DGRAM : SOCK_STREAM;
+
+	if (family == 0) {
+		struct sockaddr_in *s = (struct sockaddr_in *)&src;
+		struct sockaddr_in *d = (struct sockaddr_in *)&dst;
+
+		s->sin_family = AF_INET;
+		d->sin_family = AF_INET;
+
+		if (size > 2) {
+			memcpy(&s->sin_addr, data + 2, size - 2 > 4 ? 4 : size - 2);
+		}
+		if (size > 6) {
+			memcpy(&d->sin_addr, data + 6, size - 6 > 4 ? 4 : size - 6);
+		}
+		if (size > 10) {
+			memcpy(&s->sin_port, data + 10, size - 10 > 2 ? 2 : size - 10);
+		}
+		if (size > 12) {
+			memcpy(&d->sin_port, data + 12, size - 12 > 2 ? 2 : size - 12);
+		}
+
+		size_t buf_len = size % sizeof(buf);
+
+		int rc = proxy_v2_build(
+			buf,
+			buf_len,
+			(const struct sockaddr *)s,
+			sizeof(*s),
+			(const struct sockaddr *)d,
+			sizeof(*d),
+			sock_type,
+			&out_len
+		);
+
+		if (rc == 0) {
+			if (out_len > buf_len) __builtin_trap();
+			if (out_len != 28) __builtin_trap();
+			if (memcmp(buf, PROXY_V2_SIG, 12) != 0) __builtin_trap();
+			if (read_u16(buf + 14) != 12) __builtin_trap();
+		}
+	} else if (family == 1) {
+		struct sockaddr_in6 *s = (struct sockaddr_in6 *)&src;
+		struct sockaddr_in6 *d = (struct sockaddr_in6 *)&dst;
+
+		s->sin6_family = AF_INET6;
+		d->sin6_family = AF_INET6;
+
+		if (size > 2) {
+			memcpy(&s->sin6_addr, data + 2, size - 2 > 16 ? 16 : size - 2);
+		}
+		if (size > 18) {
+			memcpy(&d->sin6_addr, data + 18, size - 18 > 16 ? 16 : size - 18);
+		}
+		if (size > 34) {
+			memcpy(&s->sin6_port, data + 34, size - 34 > 2 ? 2 : size - 34);
+		}
+		if (size > 36) {
+			memcpy(&d->sin6_port, data + 36, size - 36 > 2 ? 2 : size - 36);
+		}
+
+		size_t buf_len = size % sizeof(buf);
+
+		int rc = proxy_v2_build(
+			buf,
+			buf_len,
+			(const struct sockaddr *)s,
+			sizeof(*s),
+			(const struct sockaddr *)d,
+			sizeof(*d),
+			sock_type,
+			&out_len
+		);
+
+		if (rc == 0) {
+			if (out_len > buf_len) __builtin_trap();
+			if (out_len != 52) __builtin_trap();
+			if (memcmp(buf, PROXY_V2_SIG, 12) != 0) __builtin_trap();
+			if (read_u16(buf + 14) != 36) __builtin_trap();
+		}
+	} else {
+		struct sockaddr *s = (struct sockaddr *)&src;
+		struct sockaddr *d = (struct sockaddr *)&dst;
+
+		s->sa_family = AF_UNSPEC;
+		d->sa_family = AF_UNSPEC;
+
+		(void)proxy_v2_build(
+			buf,
+			size % sizeof(buf),
+			s,
+			sizeof(*s),
+			d,
+			sizeof(*d),
+			sock_type,
+			&out_len
+		);
+	}
+
+	return 0;
+}
diff --git a/fuzz/fuzz_tcp_route.c b/fuzz/fuzz_tcp_route.c
new file mode 100644
index 0000000..1e6ac2e
--- /dev/null
+++ b/fuzz/fuzz_tcp_route.c
@@ -0,0 +1,111 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <event2/event.h>
+
+#include "route.h"
+#include "tcp_route.h"
+#include "klog.h"
+
+static void set_nonblock(int fd)
+{
+	int flags = fcntl(fd, F_GETFL, 0);
+	if (flags >= 0) {
+		(void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+	}
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+	if (size == 0 || size > 8192) {
+		return 0;
+	}
+
+	struct event_base *base = event_base_new();
+	if (base == NULL) {
+		return 0;
+	}
+
+	int sv[2];
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) {
+		event_base_free(base);
+		return 0;
+	}
+
+	set_nonblock(sv[0]);
+	set_nonblock(sv[1]);
+
+	struct route r;
+	memset(&r, 0, sizeof(r));
+
+	r.proto = PROTO_TCP;
+	r.line_no = 1;
+
+	/*
+	 * listen endpoint is not used by the fuzz seam, but keep it sane.
+	 */
+	r.listen.kind = ENDPOINT_INET;
+	snprintf(r.listen.host, sizeof(r.listen.host), "127.0.0.1");
+	r.listen.port = 12345;
+
+	/*
+	 * Important: this should be something connect_upstream() can handle.
+	 * For a first fuzzer, intentionally point it at a closed port.
+	 * That fuzzes failure paths without needing an upstream server.
+	 */
+	r.upstream.kind = ENDPOINT_INET;
+	snprintf(r.upstream.host, sizeof(r.upstream.host), "127.0.0.1");
+	r.upstream.port = 9;
+
+	r.opts.proxy_v2 = (data[0] & 1) != 0;
+	r.opts.keep_alive = (data[0] & 2) != 0;
+	r.opts.idle_timeout_sec = 1;
+	r.opts.connect_timeout_sec = 1;
+
+	struct sockaddr_storage peer;
+	memset(&peer, 0, sizeof(peer));
+
+	struct sockaddr_un *sun = (struct sockaddr_un *)&peer;
+	sun->sun_family = AF_UNIX;
+
+	if (tcp_route_adopt_client_for_fuzz(
+		    base,
+		    &r,
+		    sv[1],
+		    &peer,
+		    sizeof(*sun)) != 0) {
+		close(sv[0]);
+		close(sv[1]);
+		event_base_free(base);
+		return 0;
+	}
+
+	/*
+	 * Ownership of sv[1] was transferred to bufferevent with
+	 * BEV_OPT_CLOSE_ON_FREE, so don't close sv[1] manually after success.
+	 */
+	sv[1] = -1;
+
+	(void)write(sv[0], data, size);
+
+	struct timeval tv;
+	tv.tv_sec = 0;
+	tv.tv_usec = 1000;
+
+	event_base_loopexit(base, &tv);
+	event_base_dispatch(base);
+
+	close(sv[0]);
+	event_base_free(base);
+
+	return 0;
+}
diff --git a/fuzz/seeds/tcp_route/basic b/fuzz/seeds/tcp_route/basic
new file mode 100644
index 0000000..b6fc4c6
--- /dev/null
+++ b/fuzz/seeds/tcp_route/basic
@@ -0,0 +1 @@
+hello
\ No newline at end of file
diff --git a/fuzz/seeds/tcp_route/chunked_weird b/fuzz/seeds/tcp_route/chunked_weird
new file mode 100644
index 0000000..c93708b
--- /dev/null
+++ b/fuzz/seeds/tcp_route/chunked_weird
@@ -0,0 +1,8 @@
+POST / HTTP/1.1
+Host: x
+Transfer-Encoding: chunked
+
+ffffffff
+AAAA
+0
+
diff --git a/fuzz/seeds/tcp_route/ff b/fuzz/seeds/tcp_route/ff
new file mode 100644
index 0000000..8663f7d
--- /dev/null
+++ b/fuzz/seeds/tcp_route/ff
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/fuzz/seeds/tcp_route/hex_junk b/fuzz/seeds/tcp_route/hex_junk
new file mode 100644
index 0000000..e6ac26e
--- /dev/null
+++ b/fuzz/seeds/tcp_route/hex_junk
@@ -0,0 +1,2 @@
+GARBAGE  4Vx
+
diff --git a/fuzz/seeds/tcp_route/http b/fuzz/seeds/tcp_route/http
new file mode 100644
index 0000000..79b7788
--- /dev/null
+++ b/fuzz/seeds/tcp_route/http
@@ -0,0 +1,3 @@
+GET / HTTP/1.1
+Host: x
+
diff --git a/fuzz/seeds/tcp_route/http2_preface b/fuzz/seeds/tcp_route/http2_preface
new file mode 100644
index 0000000..0d3b835
--- /dev/null
+++ b/fuzz/seeds/tcp_route/http2_preface
@@ -0,0 +1,4 @@
+PRI * HTTP/2.0
+
+SM
+
diff --git a/fuzz/seeds/tcp_route/http_connect b/fuzz/seeds/tcp_route/http_connect
new file mode 100644
index 0000000..6ccaef5
--- /dev/null
+++ b/fuzz/seeds/tcp_route/http_connect
@@ -0,0 +1,3 @@
+CONNECT example.com:443 HTTP/1.1
+Host: example.com:443
+
diff --git a/fuzz/seeds/tcp_route/http_get b/fuzz/seeds/tcp_route/http_get
new file mode 100644
index 0000000..ac056bf
--- /dev/null
+++ b/fuzz/seeds/tcp_route/http_get
@@ -0,0 +1,3 @@
+GET / HTTP/1.1
+Host: example
+
diff --git a/fuzz/seeds/tcp_route/http_post b/fuzz/seeds/tcp_route/http_post
new file mode 100644
index 0000000..db86c3a
--- /dev/null
+++ b/fuzz/seeds/tcp_route/http_post
@@ -0,0 +1,5 @@
+POST / HTTP/1.1
+Host: example
+Content-Length: 4
+
+AAAA
\ No newline at end of file
diff --git a/fuzz/seeds/tcp_route/huge_request_line b/fuzz/seeds/tcp_route/huge_request_line
new file mode 100644
index 0000000..f191012
--- /dev/null
+++ b/fuzz/seeds/tcp_route/huge_request_line
@@ -0,0 +1,3 @@
+GET /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.1
+Host: x
+
diff --git a/fuzz/seeds/tcp_route/long_header b/fuzz/seeds/tcp_route/long_header
new file mode 100644
index 0000000..64d398b
--- /dev/null
+++ b/fuzz/seeds/tcp_route/long_header
@@ -0,0 +1,4 @@
+GET / HTTP/1.1
+Host: x
+X-Fuzz: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+
diff --git a/fuzz/seeds/tcp_route/many_headers b/fuzz/seeds/tcp_route/many_headers
new file mode 100644
index 0000000..9a5f604
--- /dev/null
+++ b/fuzz/seeds/tcp_route/many_headers
@@ -0,0 +1,515 @@
+GET / HTTP/1.1
+Host: x
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+X-A: B
+
diff --git a/fuzz/seeds/tcp_route/nuls b/fuzz/seeds/tcp_route/nuls
new file mode 100644
index 0000000..1b1cb4d
Binary files /dev/null and b/fuzz/seeds/tcp_route/nuls differ
diff --git a/fuzz/seeds/tcp_route/percent_encoded_junk b/fuzz/seeds/tcp_route/percent_encoded_junk
new file mode 100644
index 0000000..ea8166e
--- /dev/null
+++ b/fuzz/seeds/tcp_route/percent_encoded_junk
@@ -0,0 +1,3 @@
+GET /%00%0d%0a%ff HTTP/1.1
+Host: x
+
diff --git a/fuzz/seeds/tcp_route/proxyish b/fuzz/seeds/tcp_route/proxyish
new file mode 100644
index 0000000..71fd8ad
Binary files /dev/null and b/fuzz/seeds/tcp_route/proxyish differ
diff --git a/fuzz/seeds/tcp_route/smuggling_shape_1 b/fuzz/seeds/tcp_route/smuggling_shape_1
new file mode 100644
index 0000000..0558ef0
--- /dev/null
+++ b/fuzz/seeds/tcp_route/smuggling_shape_1
@@ -0,0 +1,9 @@
+POST / HTTP/1.1
+Host: x
+Content-Length: 4
+Transfer-Encoding: chunked
+
+0
+
+GET /x HTTP/1.1
+
diff --git a/fuzz/seeds/tcp_route/ssh_banner b/fuzz/seeds/tcp_route/ssh_banner
new file mode 100644
index 0000000..59de39a
--- /dev/null
+++ b/fuzz/seeds/tcp_route/ssh_banner
@@ -0,0 +1 @@
+SSH-2.0-OpenSSH_9.0
diff --git a/fuzz/seeds/tcp_route/tls_clienthello_like b/fuzz/seeds/tcp_route/tls_clienthello_like
new file mode 100644
index 0000000..c4032d7
Binary files /dev/null and b/fuzz/seeds/tcp_route/tls_clienthello_like differ
diff --git a/mk/common.mk b/mk/common.mk
new file mode 100644
index 0000000..fee5a6d
--- /dev/null
+++ b/mk/common.mk
@@ -0,0 +1,48 @@
+export
+
+CC ?= cc
+AR ?= ar
+RANLIB ?= ranlib
+STRIP ?= strip
+PKG_CONFIG ?= pkg-config
+
+PROJECT_ROOT ?= $(CURDIR)
+SRC_DIR ?= $(PROJECT_ROOT)/src
+BIN_DIR ?= $(PROJECT_ROOT)/bin
+BUILD_DIR ?= $(PROJECT_ROOT)/build
+
+EXEEXT :=
+WINDOWS_LDLIBS :=
+
+ifeq ($(OS),Windows_NT)
+EXEEXT := .exe
+WINDOWS_LDLIBS += -lws2_32
+endif
+
+CFLAGS ?= -Os -Wall -Wextra -ffunction-sections -fdata-sections
+CPPFLAGS += -I$(SRC_DIR)
+
+UNAME_S := $(shell uname)
+
+ifeq ($(UNAME_S),Darwin)
+STATIC ?= 0
+LDFLAGS += -Wl,-dead_strip
+TEST_FLAGS := CONCURRENCY=1000 TOTAL=1000 FD_LIMIT=2560
+else ifeq ($(OS),Windows_NT)
+STATIC ?= 0
+LDFLAGS += -Wl,--gc-sections
+TEST_FLAGS := CONCURRENCY=100 TOTAL=100 FD_LIMIT=512
+else
+STATIC ?= 1
+LDFLAGS += -Wl,--gc-sections
+TEST_FLAGS :=
+endif
+
+ifeq ($(STATIC),1)
+LDFLAGS += -static
+PKG_CONFIG_STATIC := --static
+else
+PKG_CONFIG_STATIC :=
+endif
+
+LDLIBS += $(WINDOWS_LDLIBS)
\ No newline at end of file
diff --git a/mk/libevent.mk b/mk/libevent.mk
new file mode 100644
index 0000000..61d7f68
--- /dev/null
+++ b/mk/libevent.mk
@@ -0,0 +1,45 @@
+LIBEVENT_SRC ?=
+LIBEVENT_PREFIX := $(BUILD_DIR)/libevent-install
+LIBEVENT_CORE_A := $(LIBEVENT_PREFIX)/lib/libevent_core.a
+
+ifeq ($(strip $(LIBEVENT_SRC)),)
+LIBEVENT_CPPFLAGS := $(shell $(PKG_CONFIG) --cflags libevent_core 2>/dev/null)
+LIBEVENT_LDFLAGS  :=
+LIBEVENT_LDLIBS   := $(shell $(PKG_CONFIG) $(PKG_CONFIG_STATIC) --libs libevent_core 2>/dev/null)
+LIBEVENT_DEPS     :=
+else
+LIBEVENT_CPPFLAGS := -I$(LIBEVENT_PREFIX)/include
+LIBEVENT_LDFLAGS  := -L$(LIBEVENT_PREFIX)/lib
+LIBEVENT_LDLIBS   := -levent_core
+LIBEVENT_DEPS     := $(LIBEVENT_CORE_A)
+endif
+
+CPPFLAGS += $(LIBEVENT_CPPFLAGS)
+LDFLAGS  += $(LIBEVENT_LDFLAGS)
+LDLIBS   += $(LIBEVENT_LDLIBS)
+
+ifneq ($(strip $(LIBEVENT_SRC)),)
+$(LIBEVENT_CORE_A):
+	cd $(LIBEVENT_SRC) && \
+		AR=$(AR) RANLIB=$(RANLIB) ./configure \
+		--prefix="$(LIBEVENT_PREFIX)" \
+		--disable-openssl \
+		--disable-samples \
+		--disable-libevent-regress \
+		--disable-debug-mode \
+		--disable-malloc-replacement \
+		--disable-thread-support \
+		--disable-dependency-tracking \
+		--enable-static \
+		--disable-shared
+	AR=$(AR) RANLIB=$(RANLIB) $(MAKE) -C $(LIBEVENT_SRC)
+	AR=$(AR) RANLIB=$(RANLIB) $(MAKE) -C $(LIBEVENT_SRC) install
+endif
+
+.PHONY: clean-libevent
+
+clean-libevent:
+ifneq ($(strip $(LIBEVENT_SRC)),)
+	-$(MAKE) -C $(LIBEVENT_SRC) distclean
+endif
+	rm -rf $(LIBEVENT_PREFIX)
\ No newline at end of file
diff --git a/src/klog.c b/src/klog.c
index d0f1c18..2c358ba 100644
--- a/src/klog.c
+++ b/src/klog.c
@@ -118,6 +118,10 @@ static void print_log_value(struct log_value value)
 
 void log_at(char sev, const char *file, int line, const char *msg, ...)
 {
+#ifdef FUZZ
+	return;
+#endif
+
 	struct timespec ts;
 	struct tm tm;
 	char tbuf[32];
diff --git a/src/klog.h b/src/klog.h
index 5a74183..1920cb0 100644
--- a/src/klog.h
+++ b/src/klog.h
@@ -1,6 +1,7 @@
 #ifndef KLOG_H
 #define KLOG_H
 
+#include <stdbool.h>
 #include <stdint.h>
 
 struct endpoint;
diff --git a/src/signal.c b/src/signals.c
similarity index 98%
rename from src/signal.c
rename to src/signals.c
index efb0fb6..6ac9cb0 100644
--- a/src/signal.c
+++ b/src/signals.c
@@ -1,4 +1,4 @@
-#include "signal.h"
+#include "signals.h"
 
 #include <signal.h>
 #include <string.h>
diff --git a/src/signal.h b/src/signals.h
similarity index 100%
rename from src/signal.h
rename to src/signals.h
diff --git a/src/tcp_route.c b/src/tcp_route.c
index 0ac1451..827c684 100644
--- a/src/tcp_route.c
+++ b/src/tcp_route.c
@@ -468,3 +468,40 @@ void free_tcp_route(struct tcp_route_ctx *ctx)
 
 	free(ctx);
 }
+
+#ifdef FUZZ
+int tcp_route_adopt_client_for_fuzz(
+	struct event_base *base,
+	const struct route *r,
+	evutil_socket_t client_fd,
+	const struct sockaddr_storage *peer_addr,
+	socklen_t peer_addr_len
+) {
+	if (base == NULL || r == NULL || client_fd < 0) {
+		return -1;
+	}
+
+	struct worker w;
+	memset(&w, 0, sizeof(w));
+
+	w.base = base;
+	w.id = 0;
+
+	struct accepted_client ac;
+	memset(&ac, 0, sizeof(ac));
+
+	ac.fd = client_fd;
+	ac.route = r;
+
+	if (peer_addr != NULL &&
+	    peer_addr_len > 0 &&
+	    peer_addr_len <= sizeof(ac.peer_addr)) {
+		memcpy(&ac.peer_addr, peer_addr, peer_addr_len);
+		ac.peer_addr_len = peer_addr_len;
+	}
+
+	worker_adopt_client_fd(&w, &ac);
+
+	return 0;
+}
+#endif
diff --git a/src/tcp_route.h b/src/tcp_route.h
index 6b3fb89..ec9b8b1 100644
--- a/src/tcp_route.h
+++ b/src/tcp_route.h
@@ -2,6 +2,9 @@
 #define TCP_ROUTE_H
 
 #include "route.h"
+#ifdef FUZZ
+#include "compat_socket.h"
+#endif
 
 struct tcp_route_ctx;
 
@@ -12,3 +15,13 @@ int start_tcp_route(
 
 void free_tcp_route(struct tcp_route_ctx *ctx);
 #endif
+
+#ifdef FUZZ
+int tcp_route_adopt_client_for_fuzz(
+	struct event_base *base,
+	const struct route *r,
+	evutil_socket_t client_fd,
+	const struct sockaddr_storage *peer_addr,
+	socklen_t peer_addr_len
+);
+#endif
diff --git a/src/tinyproxy.c b/src/tinyproxy.c
index 1d2db66..2c3d6f2 100644
--- a/src/tinyproxy.c
+++ b/src/tinyproxy.c
@@ -7,7 +7,7 @@
 #include <unistd.h>
 
 #include "klog.h"
-#include "signal.h"
+#include "signals.h"
 #include "file_conf.h"
 #include "tcp_route.h"
 #include "udp_route.h"