2020-gfw-esni-blocking
findings extracted from this paper
-
Splitting the TLS ClientHello so that the first TCP segment is ≤4 bytes (less than the 5-byte TLS record header) defeats the GFW's ESNI detection with near 100% reliability. Geneva expressed this as `[TCP:flags:PA]-fragment{tcp:4:True}-|` (client-side) or a server-side window-size reduction to 4 bytes that forces the client to segment. This suggests the GFW's ESNI classifier cannot reassemble TCP segments across all protocol contexts.
-
Geneva discovered 6 client-side and 4 server-side TCP-layer evasion strategies against GFW ESNI blocking within 48 hours of training, all achieving near 100% reliability. Effective strategies include desynchronization attacks (triple SYN with corrupt sequence number, FIN+SYN flag confusion, TCB turnaround via pre-handshake SYN+ACK) and TCB teardown via corrupted-checksum RST injection. All strategies operate at the TCP layer and require no changes to the application sending ESNI.
-
The GFW's ESNI detector is keyed specifically to extension value `0xffce` (ESNI draft-01). Replacing `0xffce` with ECH draft values `0xff02`, `0xff03`, or `0xff04` produced no blocking as of August 2020. This indicates the GFW deployed a detector matching on a specific extension ID rather than detecting encrypted SNI generically.
-
The GFW blocks ESNI by dropping client-to-server packets whenever a TLS ClientHello containing the `0xffce` encrypted_server_name extension is sent over a completed TCP handshake. Unlike GFW censorship of SNI and HTTP (which uses RST injection to both endpoints), ESNI censorship uses unidirectional packet dropping with no injected packets. The blocking applies on all ports from 1 to 65535.
-
After a GFW ESNI block is triggered, residual censorship persists for 120–180 seconds (varying by vantage point), blocking all traffic on the same (srcIP, dstIP, dstPort) 3-tuple. Additional ESNI handshakes sent during the residual window do not reset the timer, and it takes at least 1 second for the GFW to enable blocking rules after the triggering packet.