fix(sniffing): re-dial upstream on HTTP Host change + surface no-SNI errors
sniffer_http: When DNS override directs multiple domains to the same proxy IP, the browser may reuse a keep-alive connection for a different host. The HTTP keep-alive loop now detects Host header changes and re-dials a new upstream connection with correct node selection, preventing CDN errors (Fastly unknown domain, CloudFront 403). sniffer_tls: Return a descriptive error when TLS ClientHello has no SNI, instead of silently returning nil. This makes the connection drop visible in logs and recorder output. Fixes go-gost/gost#479
This commit is contained in:
@@ -81,7 +81,9 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cc.Close()
|
defer func() { cc.Close() }()
|
||||||
|
|
||||||
|
upstreamHost := normalizeHost(ro.HTTP.Host, "80")
|
||||||
|
|
||||||
ho.log = log.WithFields(map[string]any{"src": cc.LocalAddr().String(), "dst": cc.RemoteAddr().String()})
|
ho.log = log.WithFields(map[string]any{"src": cc.LocalAddr().String(), "dst": cc.RemoteAddr().String()})
|
||||||
log = ho.log
|
log = ho.log
|
||||||
@@ -112,6 +114,41 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
|
|||||||
log.Trace(string(dump))
|
log.Trace(string(dump))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When DNS override directs multiple domains to the same proxy IP,
|
||||||
|
// the browser may reuse a keep-alive connection for a different host.
|
||||||
|
// Re-dial to ensure requests reach the correct upstream.
|
||||||
|
if reqHost := normalizeHost(req.Host, "80"); reqHost != "" && reqHost != upstreamHost {
|
||||||
|
cc.Close()
|
||||||
|
newNode, res, resolveErr := resolveHTTPNode(ctx, reqHost, req, &ho)
|
||||||
|
if resolveErr != nil {
|
||||||
|
ro.HTTP.StatusCode = res.StatusCode
|
||||||
|
res.Write(conn)
|
||||||
|
return resolveErr
|
||||||
|
}
|
||||||
|
dial := ho.dial
|
||||||
|
if dial == nil {
|
||||||
|
dial = (&net.Dialer{}).DialContext
|
||||||
|
}
|
||||||
|
newCC, dialErr := dial(ctx, "tcp", newNode.Addr)
|
||||||
|
if dialErr != nil {
|
||||||
|
return dialErr
|
||||||
|
}
|
||||||
|
newCC = tlsWrapConn(newCC, newNode.Options().TLS)
|
||||||
|
upstreamHost = reqHost
|
||||||
|
node = newNode
|
||||||
|
cc = newCC
|
||||||
|
ro.Host = reqHost
|
||||||
|
ro.SrcAddr = cc.LocalAddr().String()
|
||||||
|
ro.DstAddr = cc.RemoteAddr().String()
|
||||||
|
log = log.WithFields(map[string]any{
|
||||||
|
"host": reqHost,
|
||||||
|
"node": node.Name,
|
||||||
|
"dst": node.Addr,
|
||||||
|
"src": cc.LocalAddr().String(),
|
||||||
|
})
|
||||||
|
ho.log = log
|
||||||
|
}
|
||||||
|
|
||||||
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriteCloser(br, conn, conn), cc, node, req, &pStats, &ho); err != nil || shouldClose {
|
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriteCloser(br, conn, conn), cc, node, req, &pStats, &ho); err != nil || shouldClose {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOp
|
|||||||
if ho.log != nil {
|
if ho.log != nil {
|
||||||
ho.log.Debugf("no sni in clienthello from %s", conn.RemoteAddr())
|
ho.log.Debugf("no sni in clienthello from %s", conn.RemoteAddr())
|
||||||
}
|
}
|
||||||
return nil
|
return errors.New("tls: sni is empty, closing connection")
|
||||||
}
|
}
|
||||||
ro.Host = host
|
ro.Host = host
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, network string, conn net.Conn,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cc.Close()
|
defer func() { cc.Close() }()
|
||||||
|
|
||||||
|
upstreamHost := host
|
||||||
|
|
||||||
log = log.WithFields(map[string]any{"src": cc.LocalAddr().String(), "dst": cc.RemoteAddr().String()})
|
log = log.WithFields(map[string]any{"src": cc.LocalAddr().String(), "dst": cc.RemoteAddr().String()})
|
||||||
|
|
||||||
@@ -122,6 +124,26 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, network string, conn net.Conn,
|
|||||||
log.Trace(string(dump))
|
log.Trace(string(dump))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When DNS override directs multiple domains to the same proxy IP,
|
||||||
|
// the browser may reuse a keep-alive connection for a different host.
|
||||||
|
// Re-dial to ensure requests reach the correct upstream.
|
||||||
|
if reqHost := normalizeHost(req.Host, "80"); reqHost != upstreamHost {
|
||||||
|
cc.Close()
|
||||||
|
cc, err = dial(ctx, network, reqHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
upstreamHost = reqHost
|
||||||
|
ro.Host = reqHost
|
||||||
|
ro.SrcAddr = cc.LocalAddr().String()
|
||||||
|
ro.DstAddr = cc.RemoteAddr().String()
|
||||||
|
log = log.WithFields(map[string]any{
|
||||||
|
"host": reqHost,
|
||||||
|
"src": cc.LocalAddr().String(),
|
||||||
|
"dst": cc.RemoteAddr().String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriteCloser(br, conn, conn), cc, req, readTimeout, ro, &pStats, log); err != nil || shouldClose {
|
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriteCloser(br, conn, conn), cc, req, readTimeout, ro, &pStats, log); err != nil || shouldClose {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@@ -52,7 +53,7 @@ func (h *Sniffer) HandleTLS(ctx context.Context, network string, conn net.Conn,
|
|||||||
if log != nil {
|
if log != nil {
|
||||||
log.Debugf("no sni in clienthello from %s", conn.RemoteAddr())
|
log.Debugf("no sni in clienthello from %s", conn.RemoteAddr())
|
||||||
}
|
}
|
||||||
return nil
|
return errors.New("tls: sni is empty, closing connection")
|
||||||
}
|
}
|
||||||
ro.Host = host
|
ro.Host = host
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user