sni-spoofing-rust: How a Rust Tool Gaslights DPI With One Packet That Never Reaches the Server

A deep dive into the out-of-window SNI injection trick, the raw-socket plumbing behind it, and why the project is more about TCP state than TLS itself.

9 min read • View on GitHub • More from therealaleph

A wide black-ink editorial scene shows a client on the left launching a forged TLS ClientHello toward a DPI box, while the real origin server on the right receives nothing. A packet marked with a fake SNI is visible to the inspector, but it bounces off a TCP receive-window boundary before reaching the server, explaining how the tool splits inspection from delivery.
The trick is not stronger encryption. It is forcing the middlebox and the endpoint to disagree about what arrived.
Key Takeaways

The most interesting thing about sni-spoofing-rust is not that it speaks TLS. It is that it sends one TLS ClientHello to two different audiences, and only one of them is meant to believe it.

A DPI middlebox sees a forged SNI and makes a judgment early. The real server sees a packet whose sequence number places it outside the receive window, so it drops the payload before TLS ever becomes relevant. The bypass lives in that split.

The packet the server never sees

The tool’s signature move is simple to state and tricky to execute. It fabricates a TLS ClientHello, patches in a chosen SNI, then sends that packet with a sequence number that makes it look valid to an inspector but invalid to the endpoint.

That matters because many censorship systems want to decide fast. They inspect early bytes, infer a hostname, and then allow or block the flow before the application ever finishes negotiating. If the server never accepts the forged packet, but the DPI already acted on it, the attacker has found a crack in the timing model.

A close-up engraving-style scene shows a hand assembling a TLS ClientHello template on a workbench while another hand turns a sequence-number dial past a marked receive-window boundary. The visual explains that the tool edits both the SNI payload and the TCP placement of the packet at the same time.
This is packet surgery, not just SNI rewriting. The payload and the sequence number are coordinated.

Why sequence numbers matter more than TLS branding

The real lesson is not SNI itself. It is the gap between what an observer can parse and what the endpoint will accept.

In practice, the tool depends on a mismatch in how aggressively different network actors validate state. The DPI wants to understand the flow early. The server enforces TCP correctness strictly. If the fake packet lands in the inspector’s field of view but outside the server’s accepted window, the two systems diverge.

// Conceptual sequence placement
let fake_seq = isn.wrapping_add(1).wrapping_sub(fake_payload.len() as u32);

// The forged ClientHello is visible to inspection,
// but it is positioned so the origin drops it.

That is why the story is more interesting than generic SNI spoofing. The hostname alone is not the exploit. The exploit is making inspection and delivery disagree about the same bytes.

How the Rust code is split across the stack

The repo is organized like a layered attack surface. `main.rs` starts the sniffer and the listener. `handler.rs` coordinates each connection. `relay.rs` carries the actual traffic once the deception has done its job.

The low-level work lives under `src/sniffer/` and `src/packet/`. That split is the key design choice. One side watches and injects. The other side builds packets, computes checksums, and patches TLS bytes by hand.

// High-level shape of the architecture
main
  ├─ sniffer thread
  │   ├─ capture backend
  │   └─ forged packet injector
  ├─ connection handler
  │   └─ register conn tuple
  └─ relay
      └─ bidirectional tokio copy

That architecture keeps the repo small without making it simplistic. It is still an async proxy at heart, but one with a very sharp packet-level prelude.

Raw sockets, native filters, and no libpcap

The capture layer avoids heavyweight packet-capture abstractions. On Linux it uses `AF_PACKET` and a hand-written BPF filter. On macOS it uses `/dev/bpf*` and the relevant ioctls. The point is to keep packet selection close to the kernel and keep the Rust side focused on the logic that matters.

That choice pays off in two ways. It reduces overhead, and it makes the code feel like infrastructure rather than a wrapper around infrastructure. The backend does the minimum necessary to observe the connection tuple and wake up only when the right traffic appears.

ProjectMechanismScopeOperational burdenWhy it matters
sni-spoofing-rustOut-of-window forged ClientHello plus async relayNarrow and surgicalLow to moderateOptimized for one bypass trick
Xray-core with RealityProtocol-level camouflage inside a larger proxy ecosystemBroadModerate to highA full platform with a bigger operational footprint
snirectTransparent proxy that modifies SNI through MITMFocused but proxy-centricModeratePragmatic, but requires a different trust model

The comparison is revealing. This repo is smaller than Xray, and less of a full platform than snirect. That is not a weakness by itself. It just means the design center is a single exploit path, not an all-purpose censorship stack.

Why this is smaller than Xray, but more surgical

Xray’s Reality protocol hides in a broader camouflage system. Snirect works as a transparent proxy and leans on a different trust story. `sni-spoofing-rust` is narrower. It does one thing with a high degree of precision: manipulate what the middlebox believes about a packet that the endpoint will never accept.

That narrowness is attractive if you care about footprint and control. It is less attractive if you want a turnkey ecosystem. The repo is not trying to be the ecosystem. It is trying to make one network lie cleanly.

Xray’s Reality protocol hides the SNI by mimicking a major site’s TLS handshake.

That quote is the useful baseline. This repo does not try to outdo Reality on disguise. It uses a different lever entirely, one that depends on sequence math and the assumptions middleboxes make when they inspect TCP streams.

What the code hints at, and what it still lacks

The code structure is clean enough to suggest a thoughtful author. The separation of capture, packet crafting, and relay is good engineering. So is the use of Rust traits and async channels to keep the pieces loosely coupled.

But the project still feels young. There is no deep testing story visible here, and the design reads as a sharp prototype more than a hardened production system. That is not a criticism of the idea. It is a reminder that network subversion is easy to imagine and hard to operationalize safely.