From c23a2f1e53fdb053fb36bbfc901ad8a455a9e40c Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sun, 26 Jul 2026 22:27:44 +0800 Subject: [PATCH] fix(redirect/tcp): sniffingFallback with empty SNI falls through to raw forwarding When sniffing detects TLS but the ClientHello has no SNI, HandleTLS errors with 'tls: sni is empty, closing connection'. Previously this error was returned unconditionally even with sniffingFallback enabled. Now, bytes consumed by the sniffer are captured via io.TeeReader and replayed through io.MultiReader when sniffingFallback is true, allowing the connection to fall through to raw forwarding to the original dstAddr. --- handler/redirect/tcp/handler.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/handler/redirect/tcp/handler.go b/handler/redirect/tcp/handler.go index 2d1620e..014f45b 100644 --- a/handler/redirect/tcp/handler.go +++ b/handler/redirect/tcp/handler.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "crypto/tls" + "io" "net" "strings" "time" @@ -224,11 +225,16 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han ReadTimeout: h.md.readTimeout, } - conn = xnet.NewReadWriteConn(br, conn, conn) + // Capture bytes consumed by the sniffer so we can replay + // them when sniffingFallback is enabled. + origConn := conn + capture := new(bytes.Buffer) + sniffConn := xnet.NewReadWriteConn(io.TeeReader(br, capture), origConn, origConn) + + var sniffErr error switch proto { case sniffing.ProtoHTTP: - ro.Time = time.Time{} - return sniffer.HandleHTTP(ctx, "tcp", conn, + sniffErr = sniffer.HandleHTTP(ctx, "tcp", sniffConn, sniffing.WithService(h.options.Service), sniffing.WithDial(dial), sniffing.WithDialTLS(dialTLS), @@ -236,8 +242,12 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han sniffing.WithRecorderObject(ro), sniffing.WithLog(log), ) + if sniffErr == nil { + ro.Time = time.Time{} + return nil + } case sniffing.ProtoTLS: - return sniffer.HandleTLS(ctx, ro.Network, conn, + sniffErr = sniffer.HandleTLS(ctx, ro.Network, sniffConn, sniffing.WithService(h.options.Service), sniffing.WithDial(dial), sniffing.WithDialTLS(dialTLS), @@ -245,6 +255,17 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han sniffing.WithRecorderObject(ro), sniffing.WithLog(log), ) + if sniffErr == nil { + return nil + } + } + + if h.md.sniffingFallback { + log.Debugf("sniffing(%s) failed, falling back: %v", proto, sniffErr) + conn = xnet.NewReadWriteConn(io.MultiReader(capture, br), origConn, origConn) + // fall through to raw forwarding below + } else { + return sniffErr } }