Skip to content

Recover host name from the TLS ClientHello SNI when the target is an IP - #689

Merged
haga-rak merged 2 commits into
mainfrom
feature/sni-from-clienthello
Jun 11, 2026
Merged

haga-rak merged 2 commits into
mainfrom
feature/sni-from-clienthello

Conversation

@haga-rak

@haga-rak haga-rak commented Jun 9, 2026

Copy link
Copy Markdown
Owner

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 RecoverHostNameFromSni setting (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 TlsClientHelloParserTests and SniHostRecoveryProviderTests (parsing edge cases, replay integrity, rewrite + pin across providers, per-request exchanges and blind tunnels, rule precedence).

@haga-rak
haga-rak force-pushed the feature/sni-from-clienthello branch from f1ed15b to 0f349b3 Compare June 9, 2026 14:40
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
haga-rak force-pushed the feature/sni-from-clienthello branch from 0f349b3 to b8fb511 Compare June 9, 2026 14:48
…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.
@haga-rak
haga-rak merged commit 1d7136d into main Jun 11, 2026
4 of 6 checks passed
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.

1 participant