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.
This commit is contained in:
ginuerzh
2026-07-26 22:27:44 +08:00
parent 0ffc000d0d
commit c23a2f1e53
+25 -4
View File
@@ -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
}
}