# Bypassing DPI with eBPF sock_ops

A late-April-2026 design post on [[bora-blog|bora.sh]] about [[gecit]], a system-level DPI bypass tool that injects a fake TLS ClientHello before the real one to confuse SNI-inspecting middleboxes. The interesting half of the article isn't the technique — it's the platform comparison. The same job takes a few hundred lines of Linux eBPF and a userspace TCP stack on macOS and Windows.

## The technique

When a TLS connection opens, the middlebox is reading the SNI off your ClientHello and dropping connections whose hostname is on its list. gecit's bypass is to send two ClientHellos: a fake one first, with `SNI = www.google.com` and TTL 8; the real one second. The fake reaches the in-path DPI box (which records "google.com" and waves the connection through), then expires before reaching the server. The real ClientHello arrives at the server, but the DPI's per-connection state is already poisoned. Companion trick: clamp `TCP_MAXSEG` to 88 bytes so the kernel fragments the real ClientHello, splitting the SNI extension across segments — naive DPI parsers only look at the first one. See [[tls-desync-fake-clienthello]].

This is a client-side bypass: your destination IP doesn't change. So it defeats SNI-only DPI but not [[whitelist-internet-blocking|CIDR whitelisting]] — it sits in the same orthogonal-tool slot as zapret / GoodbyeDPI / ByeDPI in [[russia-vpn-bypass-state-2026-04]].

## Linux: eBPF is the differentiator

On Linux, the bypass takes a `sock_ops` BPF program attached to a cgroup. The `BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB` callback fires the moment an outgoing TCP connection finishes its three-way handshake — connection up, app hasn't said anything yet. From inside that callback, three things happen:

1. `bpf_setsockopt(skops, IPPROTO_TCP, TCP_MAXSEG, &mss, ...)` clamps MSS to 88 for that one socket. The app has no idea.
2. `skops->snd_nxt` and `skops->rcv_nxt` are read off the kernel's socket struct, plus the 5-tuple, and packed into a perf event.
3. A Go goroutine in userspace reads the event and writes a fake ClientHello to a raw socket with `IP_HDRINCL`, same 5-tuple, same seq/ack, TTL 8.

After that, the connection runs entirely in-kernel. Bulk traffic is zero-overhead. The `sock_ops` callback gives gecit three properties no other Linux network hook gives you all at once: synchronous per-connection callback at exactly the right TCP-state transition, cgroup-level attachment so even apps that ignore proxy settings are covered, and in-kernel state mutation via `bpf_setsockopt`. See [[ebpf-sock-ops]].

## macOS and Windows: building a tiny VPN

macOS has no eBPF. DTrace, Endpoint Security, and the MAC framework can observe but not splice fake TCP into a live connection. A Network Extension would work but needs an Apple-approved entitlement, which is not realistic for a small open-source tool. The author's first attempt was an HTTP CONNECT proxy on `127.0.0.1:8443`; browsers honored it, Discord didn't, and plenty of apps don't, so that died.

The shipped solution is a TUN device backed by gVisor's userspace TCP/IP stack via [`sing-tun`](https://github.com/SagerNet/sing-tun). Traffic to port 443 is routed to the TUN, gVisor terminates the handshake locally, gecit opens its own connection to the real server, sends the fake ClientHello, then forwards the real one. Sequence numbers come from sniffing SYN-ACKs on the physical NIC with pcap, since macOS doesn't expose `snd_nxt`/`rcv_nxt` to userspace.

Windows uses the same TUN+gVisor architecture but with two extra headaches. TCP raw sockets have been blocked since Vista, so packet injection goes through Npcap's `pcap_sendpacket`, which means gecit builds full Ethernet frames itself — including the gateway MAC, which it has to scrape from the ARP table. And the standard Windows DPI-bypass driver, WinDivert, has had an expired code-signing certificate since 2023 (Defender flags it, some systems refuse to load it), so gecit uses WireGuard's WinTUN instead. Npcap can't be redistributed without an OEM license, so users have to install it themselves.

The cost on both non-Linux platforms is the same: every packet now crosses the kernel-user boundary twice, where on Linux only the fake injection itself does. Same overhead profile as a VPN, no remote exit. The author's framing line: *"the gap isn't a slope, it's a step. eBPF lets you reach into the kernel TCP stack at exactly the right point with no scaffolding around it. On the other platforms, you end up building a tiny VPN to do what a short BPF program does in-kernel."*

## DNS, sandboxes, and the limits of userspace tweaks

gecit also runs a local DoH server on `127.0.0.1:53` and points the system resolver at it; encrypted DNS to the upstream of choice. The chicken-and-egg problem (the DoH client needs DNS to find its upstream) is solved by pinning upstream hostnames to IPs at startup.

The Flatpak example is the post's small philosophical kick. Each Flatpak runs in its own sandbox with its own DNS resolution, ignoring `/etc/resolv.conf`. So gecit's DNS bypass doesn't reach inside Flatpak — but the DPI bypass still works there, *because eBPF runs in the kernel below every sandbox*. Same tool, two mechanisms; the kernel one composes through namespaces and the userspace one doesn't. It's an instructive contrast for thinking about [[sandboxing-ai-agents]] and [[matryoshka-isolation]] — kernel hooks reach down through container/namespace stacks in ways userspace tweaks can't.

## Rough edges from the post

- DPI behavior is network-specific. Some boxes drop, some send RSTs. Default TTL of 8 works on most networks; `traceroute` is the tuning tool when it doesn't.
- Fakes with placeholder seq/ack values get rejected outright. On macOS/Windows, when pcap fails to catch the SYN-ACK, the fake ships with garbage and the DPI just shrugs.
- macOS DNS is per-network-service; gecit picks the active service off the default route to handle USB tethering correctly.

## Related

- [[gecit]] — the tool itself
- [[bora-blog]] — author's blog
- [[tls-desync-fake-clienthello]] — the bypass technique extracted as a concept
- [[ebpf-sock-ops]] — the Linux kernel hook extracted as a concept
- [[tspu]] — the Russian DPI system; gecit-style desync defeats SNI inspection but not the CIDR whitelist
- [[russia-vpn-bypass-state-2026-04]] — places this technique in the wider circumvention picture (it sits in the "orthogonal tool" slot, like zapret / ByeDPI)
- [[domain-fronting]] — sister technique that also lies to the DPI about the destination, but at the HTTP layer
- [[whitelist-internet-blocking]] — the bypass tier this technique cannot defeat
- [[little-snitch-linux]] — different eBPF-based networking tool (different hooks, different goal)
