Skip to content

splithttp: bind sendThrough=origin to the real per-connection local IP#6476

Closed
echoowall wants to merge 2 commits into
XTLS:mainfrom
echoowall:fix-xhttp-origin-xtls
Closed

splithttp: bind sendThrough=origin to the real per-connection local IP#6476
echoowall wants to merge 2 commits into
XTLS:mainfrom
echoowall:fix-xhttp-origin-xtls

Conversation

@echoowall

Copy link
Copy Markdown

Problem

With sendThrough: "origin" (source-in-source-out), the outbound gateway is derived from inbound.Local, which is set from conn.LocalAddr() of the accepted connection (app/proxyman/inbound/worker.go).

For the XHTTP (splithttp) inbound, every accepted connection's LocalAddr is set to h.localAddr, which is the listener address (l.listener.Addr()). On a wildcard listener that is the unspecified address ([::] or 0.0.0.0).

Consequences on a multi-IP host:

  • sendThrough: origin reads the wildcard [::] for every connection, so all egress is bound to a single (often IPv6) source address regardless of which local IP the client actually connected to — source-in-source-out silently stops working.
  • On hosts whose [::] has no route, the outbound fails entirely: the REALITY handshake and VLESS tunnel come up, but no data flows (network is unreachable).

TCP inbounds are unaffected because worker.go already uses the real conn.LocalAddr(); only XHTTP substitutes the listener address.

Fix

Read the concrete per-connection local address from the request context via 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), preserving current behavior there.

Verification

Confirmed under HTTP/2 on a wildcard [::] listener that request.Context().Value(http.LocalAddrContextKey) returns the concrete IP the client connected to (different entry IP → different value), whereas l.listener.Addr() is always [::].

End-to-end on a 5-IP host running sendThrough: origin + VLESS/REALITY/XHTTP: before the patch all egress bound [::] (broken on a host without IPv6 route); after the patch each entry IP egresses via the matching source IP (.38→.38, .49→.49, …), all reachable.

echoowall and others added 2 commits July 12, 2026 03:39
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).
@Fangliding

Copy link
Copy Markdown
Member

哎 小修复

@RPRX

RPRX commented Jul 16, 2026

Copy link
Copy Markdown
Member

@Fangliding 能合吗

@Fangliding

Copy link
Copy Markdown
Member

似乎可可
这种东西不知道为什么golang不放request里要放value ctx有点迷惑

@RPRX

RPRX commented Jul 16, 2026

Copy link
Copy Markdown
Member

其它传输层没这问题吗

@RPRX

RPRX commented Jul 17, 2026

Copy link
Copy Markdown
Member

@Fangliding

@RPRX

RPRX commented Jul 20, 2026

Copy link
Copy Markdown
Member

风扇旅游去了吗

@Fangliding

Fangliding commented Jul 20, 2026

Copy link
Copy Markdown
Member

有时候上午爬起来看一堆乱七八糟的平台消息然后吃东西去了就忘了

至于其他传输 只有从 流/io对 重组出来的conn会丢 ws和httpupgrade都是原生listener返回的原本的conn grpc也是h2 based也会丢 不过应该找一下

@Fangliding

Copy link
Copy Markdown
Member

grpc确实有 我改一下吧

@Fangliding Fangliding closed this Jul 20, 2026
@Fangliding Fangliding mentioned this pull request Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants