9 min read

The Terabit Toll: Why Zero-Copy Serialization is the Only Way Forward for eBPF Gateways

Zero-Copy Serialization for eBPF Gateways

Imagine a firehose. Now imagine ten thousand of them. That is the reality of a Terabit-per-second (Tbps) gateway. At this scale, the traditional rules of networking—and even the "modern" rules of cloud-native development—don't just bend; they shatter.

When you are processing 1.5 billion packets per second, the "serialization tax" is no longer a minor overhead. It is a death sentence for your throughput. If your CPU spends even a handful of nanoseconds copying a packet header from one memory location to another, your latency spikes, your cache lines flush, and your expensive 400GbE NICs start dropping frames.

To survive at the Terabit edge, we have moved beyond simple packet filtering. We are now in the era of eBPF (Extended Berkeley Packet Filter), and more specifically, the era of Zero-Copy Serialization. This isn't just about writing fast code; it’s about architectural empathy for the CPU and the memory bus.

The Context: Why the Hype is Real (And Why It’s Hard)

For the last five years, eBPF has been the undisputed darling of the infrastructure world. From Cilium’s networking dominance to Cloudflare’s DoS mitigation, eBPF has moved the "programmable kernel" from a niche research project to a production powerhouse.

But as we push toward Terabit gateways—the massive aggregation points in data centers or ISP backbones—we’ve hit a wall. That wall is the von Neumann bottleneck. Moving data between the Network Interface Card (NIC), the kernel’s eBPF context, and a userspace control plane is expensive.

The "hype" around eBPF often skips over the messy reality of data movement. People talk about "kernel-level speed," but if your eBPF program parses a packet and then hands it to a userspace application via a standard socket, you’ve already lost. The cost of context switching and memory copying at 1Tbps is astronomical.

To solve this, the industry has pivoted toward Zero-Copy patterns. This is the art of manipulating data where it sits, using memory-mapped buffers and clever pointer arithmetic to ensure that a packet is touched exactly zero times by the CPU's "copy" instructions.


The Architecture of a Terabit Gateway

To understand why zero-copy is necessary, we have to look at the anatomy of a high-performance packet pipeline. A modern gateway isn't just a router; it's a stateful machine performing load balancing, DDoS protection, and telemetry.

1. The XDP (Express Data Path) Entry Point

The journey begins at XDP. XDP allows us to attach eBPF programs to the earliest possible point in the software stack: the NIC driver itself, before the kernel even allocates an sk_buff (the heavy metadata structure the Linux kernel usually uses for packets).

At this stage, the packet is just a raw chunk of memory in a DMA (Direct Memory Access) buffer.

2. The Serialization Problem

In a standard application, you might take that packet, deserialize it into a JSON object or a Protobuf message, and then process it.

  • Protobuf/JSON: Requires parsing, allocation of new memory, and copying of strings/fields.
  • The Cost: At 100Gbps, you have roughly 6.7 nanoseconds to process a 64-byte packet. A single cache miss takes ~100 nanoseconds. You cannot afford to parse.

3. The Zero-Copy Solution: AF_XDP and UMEM

The "Holy Grail" for Terabit gateways is AF_XDP. This is a specialized socket address family that allows us to map a region of memory (called UMEM) directly between the kernel and userspace.

When a packet arrives, the NIC places it into a frame in the UMEM. The eBPF program in the kernel decides where that packet goes. If it needs to go to userspace, it doesn't copy it. It simply passes a descriptor (a pointer and a length) to a ring buffer that the userspace app is watching.


Deep Dive: Zero-Copy Serialization Patterns

How do we actually structure our data so that both an eBPF kernel program and a C++/Rust userspace application can read it without any transformation? We use Fixed-Layout Memory Mapping.

Pattern A: The "Overlay" Pattern

Instead of deserializing a packet, we treat the raw memory as a structured type. This is effectively "Schema-on-Read."

// An eBPF-compatible struct representing our gateway's internal metadata
struct gateway_metadata {
    __u64 timestamp;
    __u32 flow_id;
    __u16 flags;
    __u8  action;
} __attribute__((packed));

// In the XDP program:
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_ABORTED;

// We "carve" space for metadata or read it directly from the packet
struct gateway_metadata *meta = (void *)(eth + 1);

By using __attribute__((packed)), we ensure the compiler doesn't add padding. The kernel and userspace now have a shared understanding of exactly which byte represents the flow_id.

Pattern B: The BPF Ring Buffer

The BPF Ring Buffer (introduced in Linux 5.8) is a game-changer for zero-copy telemetry. Older BPF "Perf Buffers" required a copy to move data from kernel to userspace. The Ring Buffer, however, uses memory-mapped pages that allow the kernel to write data and the userspace to read it with zero copies.

The key here is the Reservation API:

  1. Reserve space in the ring buffer.
  2. Write directly to that memory.
  3. Commit the write so userspace can see it.

This eliminates the need for a temporary buffer on the BPF stack, which is limited to a measly 512 bytes anyway.


Infrastructure Scale: Solving the "Terabit Toll"

When building a gateway at this scale (e.g., using a cluster of 400GbE-capable servers), the engineering challenges shift from "How do I write a feature?" to "How do I manage the CPU cache?"

Cache Locality and Data Alignment

In a Terabit gateway, the L1/L2 cache is your most precious resource. If your serialization pattern results in "pointer chasing" (where one memory address points to another, which points to another), you trigger a hardware prefetcher stall.

The Strategy: We use Data-Oriented Design (DOD). Instead of an "Array of Objects" (where each object has its own metadata), we use an "Object of Arrays" or highly aligned contiguous blocks. We align our structures to 64-byte cache lines. This ensures that when the CPU fetches a packet header, it also fetches the associated metadata in a single memory transaction.

BTF (BPF Type Format): The Secret Schema Engine

One of the most technical "under-the-hood" features that makes this work is BTF. Think of BTF as a high-performance, binary-encoded metadata format that describes the types used in a BPF program.

Because the kernel knows the exact layout of your structs (via BTF), it can perform CO-RE (Compile Once – Run Everywhere). This means your zero-copy gateway can run on different kernel versions with different internal struct offsets, and the BPF loader will automatically "relocate" the offsets at load time. This provides the safety of a schema with the speed of raw memory access.


Implementing the AF_XDP Zero-Copy Path

Let’s look at the actual code pattern for a Terabit-ready zero-copy handoff. This involves setting up the Fill Ring and the RX Ring.

The Userspace Setup (Rust/C++)

In userspace, we allocate a large chunk of memory and register it with the kernel as a UMEM.

// Registering UMEM for zero-copy
struct xsk_umem_config cfg = {
    .fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
    .comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
    .frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE,
    .frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM,
};

xsk_umem__create(&umem, buffer, size, &fill_ring, &comp_ring, &cfg);

The Kernel-Side "Steering"

The XDP program then uses a BPF_MAP_TYPE_XSKMAP to steer specific packets into the zero-copy socket.

SEC("xdp")
int xdp_sock_prog(struct xdp_md *ctx) {
    int index = bpf_get_smp_processor_id();

    // Check if there is a socket bound to this queue
    // If yes, redirect the packet directly to UMEM (Zero-Copy)
    return bpf_redirect_map(&xsks_map, index, XDP_PASS);
}

By using bpf_redirect_map, the packet data is never moved. The NIC driver simply updates the ownership of the memory page from the kernel's RX queue to the AF_XDP socket's RX ring.


Engineering Curiosities: The "False Sharing" Trap

At the scale of Terabit gateways, we run into a phenomenon called False Sharing. This happens when two different CPU cores are modifying different variables that happen to reside on the same 64-byte cache line.

Imagine Core 0 is updating a packet counter, and Core 1 is updating a timestamp for the same gateway. If those two values are adjacent in a struct, the hardware cache coherency protocol (like MESI) will bounce that cache line back and forth between the cores.

The Fix: We use the alignas(64) keyword or manual padding in our serialization structs. This ensures that per-CPU data stays truly local to that CPU’s cache, preventing the "Terabit Toll" from manifesting as a mysterious 30% performance drop.


Beyond the Hype: The Reality of Programmable ASICs

While eBPF is revolutionary, we must acknowledge the "Hardware Offload" context. Companies like NVIDIA (Mellanox) and Intel are increasingly moving eBPF logic onto the SmartNIC itself.

The "Zero-Copy Serialization" patterns we’ve discussed are actually becoming the interface between the FPGA/ASIC on the NIC and the host CPU. By standardizing on zero-copy memory layouts, we allow the NIC to perform the initial heavy lifting (like decapsulating VXLAN or Geneve headers) and write the results directly into host memory in a format the eBPF program expects.

This synergy—Hardware Offload + eBPF + Zero-Copy—is what actually enables a 1Tbps gateway. Software alone cannot do it; hardware alone is too rigid. The serialization pattern is the contract that binds them together.


The Performance Payoff

What does this look like in practice?

  • Standard Kernel Path: ~1-2 Million Packets Per Second (Mpps) per core.
  • XDP (Generic): ~4-5 Mpps per core.
  • XDP (Native + Zero-Copy): ~20-30+ Mpps per core.

When you multiply 30 Mpps by 32 or 64 cores, you finally reach the mathematical threshold required for Terabit networking. You aren't just saving memory; you are enabling a new class of infrastructure that was previously only possible with multi-million dollar proprietary hardware.

The Future: A World Without Copies

As we look toward 800GbE and 1.6Tbps standards, the CPU's role is shifting. It is no longer a data mover; it is a policy orchestrator.

The evolution of zero-copy serialization in eBPF tells us that the future of high-performance computing isn't just about faster clock speeds—it's about smarter data choreography. By eliminating the "copy," we treat memory not as a storage bin, but as a shared stage where the NIC, the kernel, and the application perform a perfectly synchronized dance.

For the engineers building the next generation of the internet, the mandate is clear: If you touch the data, you’ve already lost. Move the pointer, not the packet.


More to explore

Keep diving in