Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

XDP: High-Performance Packet Processing

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.

Traditional StackNIC RX RingSKB Alloc + NAPINetfilter / TCSocket / AppXDP PathNIC RX RingXDP eBPF HookOptional SKBApp / RedirectHigh overhead per packetNear-zero drop cost
Traditional Linux networking allocates SKBs before filtering; XDP: High-Performance Packet Processing intercepts at the driver level before allocation.

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.

ModeHook PointMax ThroughputHardware RequirementUse Case
NativeDriver RX ring10–40 Mpps/coreSupported NIC driverProduction filtering, LB
OffloadNIC hardwareLine-rate (100G+)SmartNIC requiredAppliance, zero-CPU
GenericPost-SKB alloc1–3 Mpps/coreNoneDev/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.

Write C/eBPFCO-RE + libbpfclang CompileBTF + RelocsKernel VerifierSafety ProofAttach NativeKill Switch MapMonitor Countersbpftrace / bpftoolTest GenericNon-prod ifaceUnit Testbpf_test_runCI PipelineVerifier + TestsSafe Deployment LoopNever attach untested XDP to production interfaces
XDP development workflow emphasizing verification, generic-mode testing, and kill-switch patterns before native production attachment.

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.

Kernel SpaceNIC DriverXDP_REDIRECTto AF_XDP SocketUMEM Region(Shared Memory)RX Fill RingTX Complete RingPacket BuffersUserspace AppAF_XDP SocketCustom LogicProtocol / DPIZero-Copy Data PathNo memcpy between kernel and userspace
AF_XDP uses shared UMEM regions and ring buffers to enable zero-copy packet transfer between NIC driver and userspace applications.

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:

  1. 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 dump or expose via Prometheus exporter.
  2. 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); }'
  3. perf/ring buffer events: Sample packets matching criteria into a BPF ring buffer for userspace analysis. Essential for debugging false positives in filter rules.
  4. 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.
  5. 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.

Frequently Asked Questions

XDP is an eBPF-based hook in the Linux kernel allowing packet processing at the earliest possible driver stage, bypassing the traditional network stack for significantly higher throughput and lower latency.

Use XDP when filtering millions of packets per second where netfilter overhead causes CPU saturation; stick to nftables for complex stateful rules or when standard tooling suffices under 500k PPS.

Requires Linux kernel 5.15+ for stable BTF support and NIC drivers with native XDP hooks; generic mode works everywhere but offers minimal performance gains over standard socket filtering.

DPDK achieves higher raw throughput by bypassing the kernel entirely, but XDP integrates with existing Linux networking, requires no dedicated cores, and allows mixing high-speed filtering with normal socket traffic.

Yes, Rust with Aya or Ebpf libraries is increasingly popular for memory safety; Python and Go have bindings but typically compile to eBPF bytecode via LLVM, still requiring understanding of kernel constraints.

XDP programs cannot loop indefinitely, have limited stack space (512 bytes), lack dynamic memory allocation, and cannot call arbitrary kernel functions. Complex logic often requires splitting work between XDP for fast-path filtering and userspace or TC hooks for stateful processing. In my experience building high-traffic infrastructure, hitting these limits usually signals the need for a hybrid architecture rather than forcing everything into XDP.

Always load XDP programs with the XDP_FLAGS_UPDATE_IF_NOEXIST flag first and implement an automatic unload timer or watchdog process. Test in SKB (generic) mode before switching to native driver mode. Keep an out-of-band console access method available. On production servers I manage, I deploy XDP changes through CI pipelines that include automated rollback triggers if SSH connectivity drops after loading a new program.

Use bpftool to inspect loaded programs and maps, perf or bpftrace for tracing XDP execution paths, and xdpdump for live packet capture at the XDP layer. Kernel logs via dmesg show verifier rejections and runtime errors. For complex issues, I rely on eBPF CO-RE with BTF to get readable symbol names instead of raw addresses. Avoid printk inside hot paths as it destroys performance; use ring buffers or perf events for observability.

XDP allows adjusting packet headers via xdp_adjust_head and redirecting packets between interfaces using bpf_redirect_map with devmap or cpumap. However, not all NICs support every redirect operation in native mode. Verify driver capabilities before deployment. When building DDoS mitigation systems, I frequently combine XDP_DROP for obvious attack traffic with XDP_PASS for legitimate flows needing full stack processing, reserving XDP_TX and redirects only for specific forwarding use cases.

XDP programs run with kernel-level privileges and can crash the system if buggy. Only load signed or verified programs, restrict CAP_BPF and CAP_NET_ADMIN capabilities, and audit all map accesses. Unprivileged BPF should remain disabled on servers. In legal-tech platforms handling sensitive client data, I treat XDP code with the same review rigor as kernel modules: mandatory code review, staged rollout, and continuous monitoring for unexpected behavior or resource exhaustion.

XDP works on physical or virtual NICs but faces challenges with veth pairs and overlay networks. Cilium and Calico use XDP effectively by attaching to host devices and managing pod traffic through eBPF. Container-native XDP requires careful coordination with CNI plugins. For Kubernetes clusters I have configured, native XDP performs best on bare-metal nodes with SR-IOV passthrough, while cloud deployments often benefit more from TC-layer eBPF due to virtualization constraints.

Write eBPF code in C or Rust, compile with clang targeting bpf, verify with bpftool or cilium/ebpf test harnesses, then load via libbpf or ip link set. Store programs in version control alongside userspace loaders. Deploy through configuration management or CI/CD with health checks. My standard pipeline includes unit tests with bpf_test_run, integration tests in isolated VMs, and canary deployment to one node before fleet-wide rollout. Never skip the verifier testing phase.

Use xdp-bench from the kernel samples, pktgen-dpdk for synthetic loads, or real traffic mirroring with tcpdump comparison. Measure PPS, CPU utilization, and tail latency separately. Account for NIC queue distribution and IRQ affinity. Benchmarks on idle systems mislead; always test under realistic mixed workloads. When optimizing legal-service portals during peak filing seasons, I found that synthetic benchmarks overstated gains by 30-40% compared to actual production traffic patterns with varied packet sizes.

Verifier rejects typically stem from unbounded loops, out-of-bounds memory access, missing NULL checks after map lookups, or exceeding instruction limits. Enable verifier logs with libbpf_set_print or bpftool prog load -v to see detailed rejection reasons. Structure code with explicit bounds checking and avoid pointer arithmetic without validation. In practice, most verifier failures I encounter come from insufficient input validation on packet data boundaries or incorrect map value size declarations.

XDP development requires specialized expertise costing Rs 150,000-400,000 (~USD 1,100-3,000) for initial implementation in Nepal markets, versus Rs 80,000-200,000 monthly for additional server capacity. XDP pays off when existing hardware handles the workload but software inefficiency causes bottlenecks. For smaller deployments under 1M PPS, hardware upgrades or load balancers often prove more economical. Reserve XDP investment for scenarios where scaling hardware becomes prohibitively expensive or physically impossible.

Share this article

What I've Built

Products I Build & Run

Legal-tech and language-services platforms I designed, built and operate — each running in production for real clients across Nepal and Australia.

More in the making — I keep shipping tools for Nepal's legal, language and digital work. Got an idea worth building?

Quick Contact Options
Choose how you want to connect me: