11 min read

The Packet’s Shortest Path: Redefining Terabit-Scale DDoS Mitigation with eBPF and XDP

Terabit-Scale DDoS Mitigation with eBPF and XDP

The Packet’s Shortest Path: Redefining Terabit-Scale DDoS Mitigation with eBPF and XDP

Imagine it’s 3:00 AM. Your edge network, a sprawling constellation of hundreds of PoPs (Points of Presence) scattered across the globe, is humming along at a comfortable 40% utilization. Suddenly, the monitoring dashboard turns a violent shade of crimson. A massive, volumetric UDP flood—peaking at 1.8 Terabits per second—is slamming into your infrastructure.

In the “old days” of networking, this was the point where hardware-based ASICs or expensive proprietary scrubbers would either save your life or fall over under the sheer state-tracking overhead. But in the modern hyper-scale era, we don’t just rely on specialized “black box” hardware anymore. We rely on the Programmable Data Plane.

At the heart of this revolution are two technologies that have transformed the Linux kernel from a bottleneck into a high-performance packet-processing engine: eBPF (Extended Berkeley Packet Filter) and XDP (eXpress Data Path).

Today, we’re going deep into the stack. We’re going to explore how we evolved from the slow, context-switching-heavy world of iptables to a world where we can drop millions of malicious packets per second on a single CPU core, all while keeping the “good” traffic flowing with microsecond latency.


The Legacy Wall: Why the Standard Kernel Stack Fails at Scale

To understand why eBPF and XDP are so revolutionary, we first have to acknowledge the “Kernel Tax.”

In a standard Linux networking environment, when a packet arrives at the Network Interface Card (NIC), it triggers an interrupt. The kernel then takes over, allocating a complex data structure called an sk_buff (socket buffer). This structure is feature-rich—it carries metadata about the packet, pointers for various layers of the OSI model, and timestamps.

However, this richness comes at a massive cost:

  1. Memory Allocation Overhead: Creating and destroying sk_buff structures for 100 million packets per second is an exercise in futility for the memory allocator.
  2. Context Switching: Moving data between kernel space and user space (where your sophisticated mitigation logic might live) involves expensive context switches and cache misses.
  3. The Netfilter Bottleneck: Tools like iptables or nftables sit deep in the networking stack. By the time a packet hits an iptables rule, the kernel has already done a significant amount of work. In a DDoS scenario, your CPU is often 100% utilized just parsing the packets before it even decides to drop them.

When you’re dealing with a Terabit-scale attack, the bottleneck isn’t the wire; it’s the interrupt handling and the protocol stack overhead. If you want to survive, you need to decide the fate of a packet as close to the hardware as possible.


Enter XDP: The “Fast Path” for the Modern Edge

XDP (eXpress Data Path) is the Linux kernel’s answer to high-performance packet processing. It provides a hook at the earliest possible point in the software stack: inside the network driver, right after the DMA (Direct Memory Access) transfer from the NIC.

Because XDP runs before the kernel allocates an sk_buff, it allows us to process packets with zero-copy efficiency.

The XDP Decision Tree

When an XDP program executes, it can return one of four primary actions for every single packet:

  • XDP_DROP: The packet is discarded immediately. The kernel never sees it. This is the holy grail for DDoS mitigation.
  • XDP_PASS: The packet is passed up to the regular Linux networking stack for normal processing.
  • XDP_TX: The packet is sent back out of the same interface it arrived on (useful for load balancers).
  • XDP_REDIRECT: The packet is bypassed to another NIC or a specialized AF_XDP socket in user space.

By running an eBPF program at the XDP hook, we can implement complex filtering logic—like checking for malformed headers, verifying source IPs against a massive blocklist, or rate-limiting specific protocols—all before the packet ever consumes “real” kernel resources.


The Architecture of a Terabit-Scale Mitigation System

Building a system that can withstand a 2Tbps attack requires more than just a fast hook; it requires a distributed, intelligent architecture. In a hyper-scale edge network, this typically looks like a three-tier defense.

Tier 1: The BGP Flowspec & Edge Router Layer

Before traffic even hits our Linux servers, we use BGP Flowspec to push coarse-grained rules to our upstream transit providers and edge routers. This is great for blocking massive, easily identifiable UDP reflection attacks. However, hardware routers have limited TCAM (Ternary Content-Addressable Memory) space. You can’t put 100,000 specific IP blocks in a router without it exploding.

Tier 2: The XDP “Shield”

This is where the magic happens. Every edge server in our PoP runs an XDP program. This program is fed by a global “threat intelligence” map.

  • The Data Plane: The eBPF program running in the kernel. It’s written in a restricted subset of C.
  • The Control Plane: A user-space daemon (often written in Go or Rust) that monitors traffic patterns and updates eBPF Maps.

Tier 3: The User-Space Analyzer (AF_XDP)

For traffic that is too complex for the XDP program to decide on immediately (e.g., sophisticated Layer 7 attacks), we use AF_XDP. This allows us to pass raw packet data into a high-level user-space application without the overhead of the standard kernel stack.


Diving into the Code: A Minimal XDP Dropper

What does this actually look like? Let’s look at a simplified eBPF program that drops all UDP traffic on port 123 (a common vector for NTP reflection attacks).

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>

SEC("xdp_mitigation")
int xdp_drop_ntp(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *iph = (void *)(eth + 1);
    if ((void *)(iph + 1) > data_end)
        return XDP_PASS;

    if (iph->protocol != IPPROTO_UDP)
        return XDP_PASS;

    struct udphdr *udph = (void *)(iph + 1);
    if ((void *)(udph + 1) > data_end)
        return XDP_PASS;

    // Target NTP Reflection (Port 123)
    if (udph->dest == bpf_htons(123)) {
        return XDP_DROP;
    }

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Why this is fast:

  1. Direct Memory Access: We are looking directly at the memory buffer where the NIC placed the packet.
  2. No Allocation: Notice there’s no malloc or skb_alloc.
  3. The Verifier: Before this code ever runs, the Linux kernel’s BPF Verifier checks it to ensure it can’t crash the kernel, doesn’t have infinite loops, and doesn’t access out-of-bounds memory. It’s sandboxed safety with native performance.

Engineering Curiosities: The Challenges of Scaling eBPF

While eBPF sounds like magic, implementing it at a hyper-scale edge introduces fascinating engineering challenges that you won’t find in a “Hello World” tutorial.

1. The Instruction Limit and Complexity

Until recently, eBPF programs were limited to 4,096 instructions (now much higher, but still finite). When you’re trying to implement complex stateful inspection—like tracking TCP sequences or calculating rolling hashes for entropy analysis—you hit these limits quickly. Solution: We use Tail Calls. This allows one eBPF program to call another, effectively daisy-chaining our mitigation logic into modular components (e.g., filter_icmp -> filter_udp -> rate_limit).

2. Cache Locality and “The Map Problem”

In a DDoS attack, you might want to check an incoming IP against a blocklist of 1 million entries. In eBPF, this is stored in a BPF_MAP_TYPE_HASH. At 100Gbps, every nanosecond counts. A hash map lookup involves a memory access. If that entry isn’t in the CPU’s L1/L2 cache, you’re waiting for a trip to main memory (DRAM). Multiply this by 100 million packets per second, and your “fast path” becomes a crawl. Engineering Fix: We often use LPM Trielists (Longest Prefix Match) for IP ranges and optimize for the “hot path” by keeping the most frequently hit rules in specialized per-CPU maps.

3. The JIT (Just-In-Time) Compiler

eBPF isn’t interpreted; it’s JIT-compiled into native machine code (x86_64 or ARM64) the moment it’s loaded. However, the way the compiler handles branching can impact performance. Modern mitigations often involve “branchless” programming techniques to keep the CPU’s pipeline full and avoid mispredictions during the chaos of an attack.


From Theory to Hyper-Scale: Managing the Global Fleet

A single server dropping packets is a tool. A thousand servers globally synchronized to drop an attack is a Platform.

In a hyper-scale network (think Cloudflare, Fastly, or Netflix), the eBPF programs aren’t static. They are dynamic entities.

  • The Feedback Loop: We use sampling (via sFlow or eBPF-based sampling) to send 1 out of every 10,000 packets to a centralized analysis cluster.
  • The Brain: The analysis cluster (running something like ClickHouse or a custom stream processor) detects the attack pattern—maybe it’s a specific packet length combined with a strange TCP option.
  • The Propagation: Within seconds, the analysis cluster generates a new eBPF bytecode fragment or updates a BPF Map entry and pushes it to the entire global edge via a high-speed pub/sub bus (like gRPC or specialized internal protocols).

This allows the network to “learn” and adapt to an attack in near real-time, shifting the defense from reactive to proactive.


The Tech Hype: Why is Everyone Talking About eBPF Now?

If you’ve been following the CNCF landscape or tech Twitter lately, eBPF is everywhere. But why now? The technology has existed in some form for years.

The hype is driven by the Convergence of Needs.

  1. Cloud-Native Networking: As we moved to Kubernetes, the overhead of iptables (which Kube-proxy used by default) became a massive bottleneck for service meshes.
  2. Observability: Companies realized they could use eBPF to trace every system call, every disk I/O, and every network packet with almost zero overhead, giving birth to tools like Cilium and Pixie.
  3. The Security Shift: With the rise of “Zero Trust,” we need to enforce security policies at the individual socket level, not just the perimeter. eBPF is the only way to do this at scale without killing application performance.

The “Substance” behind the hype is simple: eBPF makes the kernel programmable without the risk of a Kernel Panic. That is a fundamental shift in how we build infrastructure.


Performance Benchmarks: The Raw Numbers

To give you an idea of the scale we’re talking about:

  • Standard Kernel Stack (iptables): Might handle ~1-2 million packets per second (Mpps) per core before it starts dropping “good” traffic due to CPU exhaustion.
  • DPDK (Data Plane Development Kit): Can reach 20-40 Mpps, but it requires “polling” (using 100% CPU even when no traffic is present) and bypasses the kernel entirely, making it hard to integrate with standard tools.
  • XDP (eBPF): Can comfortably hit 15-25 Mpps per core while still staying within the kernel ecosystem.

When you aggregate this across a cluster of 40-core servers, you’re looking at a mitigation capacity of nearly 1 Billion packets per second per rack. This is how you stop Terabit-scale attacks without breaking a sweat.


The Future: Hardware Offloading and P4

Where do we go from here? The next frontier is Hardware Offload.

Modern SmartNICs (from vendors like NVIDIA/Mellanox, Intel, or Pensando) can now take an eBPF program and “offload” it directly to the NIC’s hardware silicon. This means the packet is dropped or redirected before it even travels across the PCIe bus to the CPU.

We’re also seeing a convergence with P4, a language specifically designed for programming networking hardware. The dream is a unified language where you write your mitigation logic once, and it runs on your routers (P4), your NICs (Hardware-offloaded eBPF), and your servers (XDP).

Final Thoughts for the Engineering Lead

If you are managing a high-growth edge network, the transition to a programmable data plane isn’t just an optimization—it’s a survival requirement.

The era of “buying a bigger box” to solve DDoS is over. The era of writing smarter code, closer to the metal, has begun. By leveraging eBPF and XDP, we’ve turned the network into a programmable, elastic, and incredibly resilient entity.

So, the next time that dashboard turns red at 3:00 AM, you can rest a little easier knowing your XDP programs are at the gate, silently discarding the noise and protecting the signal at the speed of light.


Engineering Checklist for Implementing XDP Mitigation:

  • Check Kernel Version: You need at least 4.18+, but 5.10+ is recommended for modern features like BTF (BPF Type Format).
  • Driver Support: Ensure your NICs (Mellanox, Intel i40e, etc.) support “Native XDP.”
  • Tooling: Familiarize yourself with libbpf and the clang/LLVM toolchain.
  • Monitoring: Use bpftool to inspect your maps and program status in real-time.
  • Safety First: Always have a “kill switch” in your user-space control plane to detach the XDP program if you accidentally write a logic bug that drops all traffic!

More to explore

Keep diving in