Bypassing DPI with eBPF sock_ops
- title
- Bypassing DPI with eBPF sock_ops
- type
- summary
- summary
- Bora Tanrıkulu builds gecit — eBPF sock_ops on Linux for in-kernel SNI desync, gVisor + TUN as the "tiny VPN" fallback on macOS/Windows
- tags
- networking, censorship-circumvention, dpi, ebpf, tls, linux, macos, windows
- sources
- bora-bypassing-dpi-with-ebpf
- created
- 2026-05-06
- updated
- 2026-05-06
A late-April-2026 design post on 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 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:
bpf_setsockopt(skops, IPPROTO_TCP, TCP_MAXSEG, &mss, ...)clamps MSS to 88 for that one socket. The app has no idea.skops->snd_nxtandskops->rcv_nxtare read off the kernel's socket struct, plus the 5-tuple, and packed into a perf event.- 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. 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;
tracerouteis 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)