TLS Desync via Fake ClientHello
- title
- TLS Desync via Fake ClientHello
- type
- concept
- summary
- Send a low-TTL fake ClientHello with a benign SNI before the real one, so the DPI logs the wrong hostname and waves the connection through
- tags
- networking, censorship-circumvention, dpi, tls, desync
- created
- 2026-05-06
- updated
- 2026-05-06
A client-side technique for getting past SNI-inspecting DPI middleboxes without a tunnel. Instead of hiding what you're connecting to, you confuse the middlebox about what it already saw.
Mechanism
The same TCP connection carries two ClientHello packets, in order:
- A fake ClientHello with
SNI = www.google.com(or any whitelisted hostname), TTL set low โ typically 8. - The real ClientHello with the actual SNI, normal TTL.
The fake reaches the in-path DPI box, which records "this connection is going to google.com" and stops paying attention. The TTL expires before the packet reaches the real server, so the server never sees it. The real ClientHello arrives at the server normally, but the DPI's per-connection state is already poisoned โ by the time it sees the real SNI, it's already decided to let the flow through.
Two assumptions have to hold:
- The DPI inspects only the first ClientHello-shaped TCP segment. If it tracks the whole flow, the real SNI catches up.
- TTL is tunable enough to fit between the middlebox and the server. TSPU and similar boxes sit at the ISP, so a TTL of 8 lands somewhere between them and any non-trivial Internet destination.
tracerouteis the tuning tool.
Companion: MSS clamping for fragmentation
The technique is usually paired with MSS clamping. The kernel is told to use a small TCP_MAXSEG (88 bytes in gecit's default), which forces the real ClientHello to fragment across multiple TCP segments. Naive DPI parses only the first segment, where the SNI extension is now truncated. So even a DPI that would re-examine the flow can't find the SNI field. This is the same trick zapret/GoodbyeDPI/ByeDPI rely on, and it works against a different class of middlebox than the desync โ many real deployments need both.
Sequence and ack numbers must be real
The fake has to ship with the live connection's snd_nxt / rcv_nxt. DPI boxes that bother to track sequence space drop fakes that don't fit the window. On Linux, bpf_sock_ops exposes those fields directly via the BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB callback. On platforms without an in-kernel hook, you have to scrape SYN-ACKs off the wire with pcap, which is racier and fails more often.
Where the technique sits in the bypass landscape
This is a client-side bypass: you do not get a foreign exit IP. The destination IP is unchanged, and the censor's IP-block list is untouched. So this only works against blocks where the censor's only signal is the SNI. Against the CIDR whitelist regime that TSPU runs on Russian mobile carriers, fake-ClientHello desync alone fails โ you need a tunnel to a whitelisted IP.
Against an SNI-only block โ typical of corporate networks, hotel Wi-Fi, university filters, and many national-level filters that aren't quite as aggressive as TSPU โ desync plus MSS clamping can be enough on its own. This is the bracket gecit is built for.
Implementations
- gecit โ eBPF
sock_opson Linux, TUN+gVisor on macOS/Windows; default fake SNIwww.google.com, default TTL 8, default MSS 88. - zapret / GoodbyeDPI / ByeDPI โ the older family of client-side desync tools, mentioned in russia-vpn-bypass-state-2026-04 as orthogonal companions to a real tunnel. They use NFQUEUE / WinDivert hooks rather than eBPF, which is why none of them have a clean in-kernel Linux story.
Related
- domain-fronting โ sister technique that lies to the DPI via inner-vs-outer hostname mismatch on a CDN, rather than packet-level desync
- tspu โ DPI system this technique partially defeats; the SNI inspection layer specifically
- whitelist-internet-blocking โ the layer this technique cannot defeat (fakes don't move your exit IP)
- russia-vpn-bypass-state-2026-04 โ places this in the larger Russian-circumvention picture