From d5dcc4d6feccd49f393f03fceef6a768ce81e16a Mon Sep 17 00:00:00 2001 From: echoowall Date: Sun, 12 Jul 2026 03:39:36 +0800 Subject: [PATCH] splithttp: bind sendThrough=origin to the real per-connection local IP The XHTTP inbound set every accepted connection's LocalAddr to the listener's address (h.localAddr = l.listener.Addr()). On a wildcard listener that is the unspecified address ("[::]" / "0.0.0.0"). sendThrough "origin" derives the outbound gateway from inbound.Local, which comes from conn.LocalAddr(). So with XHTTP every connection's egress was bound to the wildcard, collapsing all entry IPs onto one (often IPv6) source address and breaking source-in-source-out on multi-IP hosts (and failing outright when that address has no route). Read the concrete per-connection local address from the request context (http.LocalAddrContextKey), which net/http populates with the address the client actually connected to. Fall back to the listener address when the key is absent (e.g. HTTP/3, where net/http does not set it). --- transport/internet/splithttp/hub.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/transport/internet/splithttp/hub.go b/transport/internet/splithttp/hub.go index 557f8a54d..b06633afb 100644 --- a/transport/internet/splithttp/hub.go +++ b/transport/internet/splithttp/hub.go @@ -373,11 +373,23 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req Reader: request.Body, ResponseWriter: writer, } + // Use the concrete local address this request actually arrived on, + // not the listener's wildcard address (e.g. "[::]" / "0.0.0.0"). Go's + // net/http stores the per-connection local address in the request + // context under LocalAddrContextKey. Without this, sendThrough "origin" + // (which derives the outbound gateway from inbound.Local) reads the + // wildcard and pins every connection's egress to a single address, + // breaking source-in-source-out on multi-IP hosts. Fall back to the + // listener address when the key is absent (e.g. HTTP/3). + localAddr := h.localAddr + if la, ok := request.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && la != nil { + localAddr = la + } conn := splitConn{ writer: httpSC, reader: httpSC, remoteAddr: remoteAddr, - localAddr: h.localAddr, + localAddr: localAddr, } if sessionId != "" { // if not stream-one conn.reader = currentSession.uploadQueue