Recover host name from the TLS ClientHello SNI when the target is an IP - #689
Merged
Merged
Conversation
haga-rak
force-pushed
the
feature/sni-from-clienthello
branch
from
June 9, 2026 14:40
f1ed15b to
0f349b3
Compare
Under full-system / SOCKS5 capture the connect authority is often a raw IP, so the generated certificate gets an IP CN and the recorded host is the IP. The real host name is in the client's TLS ClientHello SNI. Add an opt-in RecoverHostNameFromSni setting (default off). When enabled and the connect target is an IP, both ingress providers (SOCKS5 and transparent CONNECT) peek the ClientHello up front, parse the SNI, and adopt it for the recorded authority and the upstream SNI while pinning the original IP via RemoteHostIp, so the upstream connection still targets it exactly with no extra DNS and no reroute. The peeked bytes are replayed, so the handshake (decrypt) or the tunnel (blind) is byte-for-byte intact. Because the host name is known before the blind/decrypt split, blind tunnels also get the host name recorded. Parsing is bounded and never throws: no SNI, ECH, malformed or over-large input falls back to the IP authority. The peek is bounded by a timeout so a silent client on a non-TLS blind tunnel forwards normally instead of stalling. Surfaced on the CLI as --recover-host-from-sni.
haga-rak
force-pushed
the
feature/sni-from-clienthello
branch
from
June 9, 2026 14:48
0f349b3 to
b8fb511
Compare
…ection The pin was set on the provisional exchange context only, while the exchanges that actually reach upstream get a fresh context from the downstream pipe, so decrypt mode re-resolved the SNI host through DNS. A decorating context builder now pins the original IP on every context created for the connection, unless a rule already forced a remote IP. The ClientHello peek also runs before the exchange context is built and the context is created with the recovered authority, so rules evaluated at OnAuthorityReceived scope match the host name instead of the IP. The flag is passed to the source providers at construction.
This was referenced Jun 22, 2026
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Under full-system / SOCKS5 capture the connect authority is often a raw IP: the certificate gets an IP CN and the recorded host is the IP, while the real name sits in the client's TLS ClientHello SNI.
With the opt-in
RecoverHostNameFromSnisetting (CLI--recover-host-from-sni, default off), both ingress providers (SOCKS5 and transparent CONNECT) peek the ClientHello when the target is an IP and adopt the SNI for the recorded authority, the certificate and the upstream SNI. The peek runs before the exchange context is built, so authority-scope rules match the host name, and the original IP is pinned on every exchange context of the connection, so upstream targets the same IP with no extra DNS (a rule-forced remote IP wins over the pin). Peeked bytes are replayed, keeping the handshake or blind tunnel byte-for-byte intact, and the parser never throws: no SNI, ECH or malformed input falls back to the IP authority. With the flag off the paths are unchanged.Covered by
TlsClientHelloParserTestsandSniHostRecoveryProviderTests(parsing edge cases, replay integrity, rewrite + pin across providers, per-request exchanges and blind tunnels, rule precedence).