
September 03, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: September 2026
When standard Linux networking stacks become the bottleneck for high-throughput or low-latency workloads, XDP: High-Performance Packet Processing provides a programmable path to handle traffic directly inside the network driver. Unlike traditional firewalling that processes packets after significant kernel overhead, XDP executes eBPF bytecode at the earliest possible ingress point, enabling drop, pass, redirect, or transmit decisions in microseconds. For engineers managing DDoS mitigation, load balancing, or telemetry at scale, understanding this layer is now essential infrastructure knowledge, much like understanding Ubuntu server setup fundamentals was a decade ago.
How does XDP: High-Performance Packet Processing differ from traditional Linux networking?
Traditional Linux networking relies on the New API (NAPI) polling mechanism, where packets are received into SKB (Socket Buffer) structures before traversing netfilter, TC, and eventually reaching userspace. Each step involves memory allocation, metadata parsing, and potential context switches. At 10Gbps+ speeds, this per-packet overhead saturates CPU cores long before NIC bandwidth is exhausted.
XDP changes this equation fundamentally. The eBPF program executes while the packet still resides in the NIC's DMA ring buffer, before any SKB allocation occurs. This means dropped malicious traffic consumes almost zero CPU cycles, and forwarded packets can be redirected to another interface without ever touching the general-purpose networking stack. The tradeoff is reduced flexibility: you cannot access socket layers, connection tracking, or complex protocol state without explicitly passing packets up to the slower TC or netfilter layers.
In practice, I have seen XDP reduce CPU utilization by 60–80% for volumetric attack mitigation compared to equivalent iptables rules on the same hardware. The key insight is that XDP operates on raw packet data with bounded helper functions, verified by the kernel's eBPF verifier to guarantee safety. You cannot crash the kernel or leak memory, but you also cannot call arbitrary kernel functions. This constraint is what makes it production-safe for always-on traffic handling.
What are the three XDP operating modes and when should you use each?
XDP supports three distinct attachment modes, each with different performance characteristics and hardware requirements. Choosing incorrectly leads to either wasted performance potential or deployment failures on incompatible hardware.
Native XDP (Driver Mode)
The eBPF program runs directly in the NIC driver's receive path, before any kernel networking code executes. This is the true XDP: High-Performance Packet Processing mode, capable of handling 10–40 Mpps per core depending on packet size and program complexity. Requirements include a supported NIC driver (most Intel, Mellanox, Broadcom, and Netronome drivers in kernel 6.x+) and typically kernel 4.18 or newer. On Ubuntu 24.04 LTS with kernel 6.8+, native XDP works out-of-the-box for most server-grade NICs.
Offload XDP (Hardware Mode)
The eBPF bytecode is compiled to hardware-specific instructions and executed directly on the NIC's embedded processor. This achieves true line-rate processing independent of host CPU, but only SmartNICs like Netronome Agilio or NVIDIA BlueField support it. Program complexity is severely limited, and debugging requires vendor-specific toolchains. Reserve this for specialized appliances where host CPU must remain completely untouched.
Generic XDP (SKB Mode)
A fallback mode that attaches the XDP program after SKB allocation, mimicking the XDP API but without performance benefits. Useful only for development, testing, or hardware lacking native support. Never deploy generic mode expecting high-performance results; benchmark carefully against native mode to quantify the gap, which often exceeds 10x.
| Mode | Hook Point | Max Throughput | Hardware Requirement | Use Case |
|---|---|---|---|---|
| Native | Driver RX ring | 10–40 Mpps/core | Supported NIC driver | Production filtering, LB |
| Offload | NIC hardware | Line-rate (100G+) | SmartNIC required | Appliance, zero-CPU |
| Generic | Post-SKB alloc | 1–3 Mpps/core | None | Dev/test only |
How do you write and attach a basic XDP program safely in 2026?
Modern XDP development uses libbpf and CO-RE (Compile Once – Run Everywhere) to avoid kernel header dependencies. The following example drops all UDP traffic on port 53 except from a trusted source, a common pattern for DNS amplification defense.
// xdp_dns_filter.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#define ETH_P_IP 0x0800
#define IPPROTO_UDP 17
SEC("xdp")
int xdp_dns_filter(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 *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol != IPPROTO_UDP)
return XDP_PASS;
struct udphdr *udp = (void *)((char *)ip + sizeof(*ip));
if ((void *)(udp + 1) > data_end)
return XDP_PASS;
__u16 dport = bpf_ntohs(udp->dest);
if (dport == 53) {
/* Allow only from 203.0.113.10 */
if (ip->saddr == bpf_htonl(0xCBD3710A))
return XDP_PASS;
return XDP_DROP;
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL"; Compile with clang and attach using bpftool or a loader like xdp-loader from the xdp-tools package:
# Build with BTF and CO-RE support
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \
-I/usr/include/bpf -c xdp_dns_filter.c -o xdp_dns_filter.o
# Attach to interface in native mode
sudo xdp-loader load -m native eth0 xdp_dns_filter.o
# Verify attachment and mode
sudo xdp-loader status eth0 Critical safety practices: always test in generic mode first on a non-production interface, use XDP_PASS as your default action during development, and implement a kill switch (e.g., a BPF map flag checked at program entry) to disable filtering without detaching the program. On production systems I maintain, every XDP program includes a global enabled map entry that defaults to 0; operators flip it to 1 only after confirming correct behavior via packet counters.
When should you choose AF_XDP over XDP for userspace packet processing?
AF_XDP is a socket family introduced alongside XDP that allows zero-copy packet transfer between the NIC driver and userspace applications. While XDP excels at in-kernel filtering and redirection, AF_XDP shines when your application logic is too complex for eBPF constraints—custom protocols, deep packet inspection, or integration with existing userspace frameworks.
The architecture uses UMEM shared memory regions mapped between kernel and userspace, eliminating copy overhead entirely. Combined with XDP_REDIRECT, packets can flow from NIC to userspace at near-native XDP speeds. However, AF_XDP introduces complexity: you manage ring buffers, handle busy-polling or interrupt coalescing tuning, and accept that your application now owns packet processing responsibility previously handled by the kernel.
Choose AF_XDP when:
- Your per-packet logic exceeds eBPF instruction limits or helper availability
- You need to integrate with DPDK-like workflows without abandoning kernel networking
- Telemetry or logging requires structured data export impractical via BPF maps
- Existing application codebases cannot be rewritten as eBPF programs
Stick with pure XDP when filtering, simple modification, or redirection suffices. The operational simplicity of an in-kernel program outweighs AF_XDP's flexibility for most security and routing use cases. For deeper context on integrating high-performance components into broader system architecture, see modern application architecture patterns which share similar tradeoff reasoning around performance boundaries.
How do you monitor, debug, and troubleshoot XDP programs in production?
Observability for XDP differs fundamentally from traditional networking tools. tcpdump and Wireshark operate post-XDP, so they cannot see dropped packets. Instead, rely on these approaches:
- BPF maps for counters: Every production XDP program should maintain per-action counters (drop, pass, redirect, error) in a BPF_MAP_TYPE_ARRAY or PERCPU_ARRAY. Query with
bpftool map dumpor expose via Prometheus exporter. - bpftrace for ad-hoc tracing: Attach probes to XDP helper calls or map updates to trace specific flows without recompiling. Example:
bpftrace -e 'tracepoint:xdp/xdp_exception { printf("err=%d\n", args->act); }' - perf/ring buffer events: Sample packets matching criteria into a BPF ring buffer for userspace analysis. Essential for debugging false positives in filter rules.
- ethtool statistics: Many NICs expose XDP-specific counters (
rx_xdp_drop,rx_xdp_redirect) via ethtool. Cross-reference these with BPF map counters to detect verifier rejections or driver-level issues. - xdp-bench and xdp-monitor: Part of xdp-tools, these utilities provide standardized throughput measurement and event monitoring specifically designed for XDP validation.
A common mistake I have encountered during server hardening projects is deploying XDP filters without adequate monitoring, then discovering weeks later that legitimate traffic was silently dropped. Always validate counter increments against expected traffic patterns immediately after deployment, and set alerts on anomalous drop rate increases.
Practical Next Steps for Adopting XDP
Start with a non-critical interface in generic mode to validate your eBPF logic, then migrate to native mode once confident. Invest time in building reusable scaffolding: kill switches, counter maps, and CI-based verifier testing pay dividends across every future XDP program. Remember that XDP: High-Performance Packet Processing is a surgical tool, not a replacement for layered defense; combine it with traditional netfilter for stateful rules and application-layer inspection. If you are evaluating whether XDP fits your infrastructure or need assistance implementing high-performance packet handling on Linux systems, reach out to discuss your specific requirements.









