In-Kernel L7 Firewall with eBPF Hits 200ns Decisions

A firewall that inspects HTTP/2 headers and drops malicious traffic in under a microsecond—before the kernel even allocates a socket buffer. That's what the team at yeet.cx built, and it's already running in front of real enterprise traffic.

The key insight: use eBPF at the XDP hook (in the NIC driver) with an Aho-Corasick deterministic finite automaton (DFA) compiled from a JavaScript-defined policy. The result: p50 decision time under 200 nanoseconds, p95 around 775 nanoseconds, and p99 under a microsecond at low to moderate load. A single core handles roughly 5 million HTTP/2 header decisions per second, and that throughput doesn't degrade as you add rules.

Why the Kernel Can Do This Now

Historically, in-kernel L7 matching was impractical because the eBPF verifier rejects programs with unbounded loops. A 2024 study could only match about 48 bytes of payload—not enough for real HTTP/2 headers. Recent kernel work pushed that ceiling into the kilobytes. In May 2026, a team from ETH Zürich, NYU, Nvidia, and Politecnico di Milano published "Offloading L7 Policies to the Kernel" (L7FP), which surveyed 2,417 open-source projects and found 89% of deployed L7 policies could run in eBPF with no kernel changes. They also landed on Aho-Corasick DFAs to parse within verifier limits—the same approach yeet.cx's CEO Julian Goldstein independently chose.

How It Works

The firewall hooks at XDP, before the kernel creates a socket buffer or touches the network stack. It reassembles the TCP stream itself (necessary because HTTP/2 headers can span multiple segments) and then matches on HTTP/2 header values: :authority (host), User-Agent, source address, plus X-Forwarded-For and PROXY protocol when behind a trusted load balancer. Rules combine these with boolean logic:

host == "api.example.com" && src in 127.0.0.0/8 && ua ~ "Diffbot"

A common use case today is blocking crawlers by User-Agent (GPTBot, ClaudeBot, PerplexityBot, curl/, etc.) on specific hosts with source constraints.

The Aho-Corasick Automaton

Checking a User-Agent against a list of banned substrings one at a time would require scanning the header once per pattern—a nested loop that the eBPF verifier would reject. Instead, all UA substrings across all rules are compiled into a single Aho-Corasick automaton: a DFA that reads the header byte by byte and reports all matches in one pass. The scan is linear in header length and doesn't slow down as patterns are added. Ten patterns or ten thousand, same scan time.

The automaton uses 16-bit state IDs, supporting up to 32,767 states (roughly 4,000 User-Agent patterns in practice, with headroom to ~16,000 planned). Rule updates happen out-of-band: the automaton is recompiled and swapped atomically in a few seconds, off the packet path.

Handling HPACK

HTTP/2 compresses headers with HPACK, using a dynamic table. A client might send the User-Agent in full once and then reference it by index. A naive matcher looking at literal strings would miss it on subsequent requests. L7FP handles this by requiring every pod behind it to disable HPACK dynamic-table caching—fine for a service mesh, but not for an edge firewall facing arbitrary clients.

yeet.cx's firewall tracks the dynamic table live. The dashboard shows dynamic-table activity, header hits caught, and health signals like desyncs and mid-stream joins. As the article states: "Showing the failure mode is the point. A matcher that silently stops seeing a header is worse than one that tells you when it might."

Fragmentation and Threat Model

Because the firewall hooks at XDP, it sees individual TCP segments before kernel reassembly. Most HTTP/2 HEADERS frames fit in a single segment, but attackers can deliberately fragment headers to evade detection. The firewall handles this by reassembling the stream itself—and exposes counters for such attempts.

The threat model assumes adversarial clients, unlike academic fast paths that assume cooperative microservices. All counters are exposed from day one so you can watch evasion attempts.

Performance and Capacity

OperationApproximate Cost
L1 cache reference~1 ns
Main memory reference~100 ns
This firewall's p50 decision<200 ns
System call (getpid, round trip)~1–2 µs
Context switch~3–5 µs
Userspace proxy hop~1+ ms

These figures are at low to moderate load; the team is currently testing whether the tail stays flat at line rate. The constant-work-per-byte design suggests it will, but that's a prediction, not a settled result.

Operating the Firewall

The firewall exports Prometheus metrics and ships with Grafana dashboards showing:

  • Verdicts per second, blocks by rule and host
  • XDP funnel: packets hooked, passed, dropped
  • Aho-Corasick automaton state usage
  • BPF map usage, XDP program attachment status
  • Time since last rule reload and reload failure count
  • Source resolution (trusted X-Forwarded-For, PROXY protocol usage)

The dashboard header shows two signals: is it up, and is it enforcing.

Why It Matters

This isn't just another firewall. It's a production-hardened implementation that solves real problems (HPACK, fragmentation, adversarial clients) that academic work sidesteps. The ability to write policy in JavaScript, push via git, and have it enforced in the kernel with sub-microsecond overhead could change how developers think about edge security.

If you're blocking crawlers or enforcing L7 policies at scale, this is worth watching. The team is open about limitations—the tail latency at high load is still being measured—but the architecture is sound. Check out the source article for the full technical details.