Skip to content
LinkState
Go back

XDP is not automatically the cheapest fast path

Introduction to XDP and Kernel Networking

Overview of XDP and tc

eXpress Data Path (XDP) is a programmable hook that runs in the NIC driver’s receive path, before the kernel allocates an skb. An XDP program can return one of several actions: XDP_DROP, XDP_PASS, XDP_TX, or XDP_REDIRECT. Because it operates on the raw packet buffer (xdp_buff) supplied by the driver, it avoids the cost of skb allocation, checksum offload handling, and most of the generic networking stack when the packet is dropped or redirected entirely in hardware.

Traffic Control (tc) with the clsact qdisc attaches BPF programs to the ingress and egress points of a network device after the kernel has built an skb. tc can classify, police, shape, and redirect packets using the same BPF instruction set as XDP, but it works on a fully formed socket buffer, which means it incurs the cost of skb allocation, reference counting, and (unless bypassed) traversal of the core networking layers (e.g., netif_receive_skb, ip_rcv, tcp_v4_rcv). The advantage of tc is that it can be combined with traditional queuing disciplines (e.g., htb, fq_codel) and can manipulate packet metadata that XDP cannot see (e.g., socket marks, flow dissector results).

Kernel Networking Stack Basics

When a packet arrives at the NIC:

  1. The driver DMA‑writes the packet into a ring buffer and raises an interrupt or polls via NAPI.
  2. In the XDP path, the driver invokes the attached BPF program on the raw buffer. Depending on the return code:
    • XDP_DROP: packet is discarded, no skb is allocated.
    • XDP_PASS: driver clones the buffer into an skb and hands it to the stack.
    • XDP_TX: packet is transmitted directly (often via a redirect to another NIC queue).
    • XDP_REDIRECT: packet is steered to another XDP‑capable device or to a tc ingress queue.
  3. If the packet reaches the stack (XDP_PASS), the kernel builds an skb, runs GRO/LRO, invokes netif_receive_skb, then proceeds through L2/L3/L4 processing (e.g., eth_type_trans, ip_rcv, tcp_v4_rcv). At each layer, hooks such as Netfilter, socket filters, and tc ingress/egress can run.
  4. tc clsact programs are executed after the skb is formed: ingress tc runs just after netif_receive_skb (before L3 processing), egress tc runs just before dev_queue_xmit (after L3/L4 processing and after any outgoing qdiscs).

Thus, the fundamental difference is where the BPF program runs relative to skb allocation and the core networking layers. Any work that requires skb‑based metadata (socket lookup, flow classification, netfilter) forces a pass through the stack and erodes the raw‑packet advantage of XDP.


Performance Comparison Methodology

Workload Selection and Design

To evaluate where XDP’s theoretical advantage disappears, we construct workloads that stress the specific mechanisms that add overhead:

WorkloadPrimary StressReason for XDP/tc divergence
Pure dropCPU cycles per packetXDP can drop before skb allocation; tc must allocate skb then drop.
Redirect to another NIC queueInter‑queue copy/bounce bufferXDP redirect may use hardware steering or a shared page; tc redirect always involves an skb clone and dev_queue_xmit.
Map lookup + passBPF map latency + skb allocationBoth XDP and tc pay map cost, but XDP still avoids skb allocation only if the packet is dropped; a pass forces skb allocation.
Metadata enrichment (e.g., adding a custom header)Packet modification costXDP can modify in‑place if the packet is in linear memory and headroom exists; otherwise it must clone. tc always works on an skb, which guarantees headroom but adds allocation cost.
Observability hook (e.g., xdp_dump or tc bpf tracepoint)Extra BPF instructions + per‑packet tracingBoth incur BPF cost; XDP avoids skb allocation only if the packet is dropped after tracing.
Mixed pass/drop with rate limitingCombination of map lookups, conditional actions, and queuingtc can combine policing (tbf, htb) with classification in a single pass; XDP must either drop early or pass and let tc handle queuing, potentially duplicating work.

Each workload is implemented as a pair of XDP and tc BPF programs that perform the same logical function (e.g., drop packets with a specific VLAN ID). This isolates the overhead of the hook location.

Measurement Tools and Techniques

All measurements are repeated for at least 30 seconds of steady state after a 5‑second warm‑up, and results are reported as mean ± 95 % confidence interval.

Test Environment Setup and Configuration

ComponentSpecification
CPU2× Intel Xeon Platinum 8380 (Ice Lake), 40 cores / 80 threads, 2.3 GHz base, turbo up to 3.4 GHz
NUMA2 nodes, each with 20 cores, 96 GB DDR4
NICMellanox ConnectX‑6 DX (2× 100 GbE) – supports XDP hardware offload (devlink dev eswitch show) and tc offload via mlx5_core
KernelLinux 6.8.0‑rc5, compiled with CONFIG_BPF_SYSCALL=y, CONFIG_XDP_SOCKETS=y, CONFIG_CLS_ACT=y, CONFIG_NET_SCH_INGRESS=y
OSUbuntu 22.04 LTS, linux-image-6.8.0-rc5-generic
BPF toolchainlibbpf 1.2.0, clang 15.0.6, llvm-strip for BPF object size reduction
Test harnessCustom script that loads XDP/tc objects via ip link set dev eth0 xdp obj xdp_prog.o sec xdp, attaches tc clsact with tc qdisc add dev eth0 clsact, and binds BPF to ingress/egress with tc filter add dev eth0 ingress bpf da obj tc_prog.o sec tc_ingress.
IsolationIRQ affinity set to a dedicated core (echo 42 > /proc/irq/<irq_num>/smp_affinity_list), CPU frequency governor set to performance, turbo boost disabled for deterministic cycles (echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo).

Real‑World Workload Scenarios

Redirects and Their Impact on XDP Performance

XDP’s XDP_REDIRECT can be implemented in three ways:

  1. Device‑to‑device redirect via shared page (bpf_redirect_map with a devmap): the NIC driver shares a single page buffer between ingress and egress rings, avoiding a copy.
  2. Redirect to a tc ingress queue (bpf_redirect with BPF_F_INGRESS): the packet is cloned into an skb and injected into the tc ingress path of the target device.
  3. Redirect to a socket (bpf_redirect_to_sock): requires an skb and invokes the socket lookup path.

100 GbE ↔ 100 GbE redirect using a devmap

Redirect target is a tc ingress queue on the same device

Takeaway: XDP redirect wins only when the NIC supports hardware‑shared pages or when the redirect target can consume the raw buffer (e.g., another XDP‑enabled device). Redirecting to tc or to a socket erases much of the advantage because an skb must be created.


Maps and Metadata in XDP and Kernel Networking

Both XDP and tc can read/write BPF maps (hash, array, lru, etc.). The cost of a map lookup is dominated by:

Workload: hash‑map lookup of a 32‑bit flow key; drop if counter exceeds a threshold (rate‑limiting). Map resides in per‑CPU memory (BPF_F_NUMA_NODE set to the local node) to avoid remote accesses.

Results (10 M packets/sec, 64‑byte packets):

HookMap lookup cost (cycles)skb allocation (if pass?Total cycles/packet
XDP drop (map lookup + conditional drop)120No~260
XDP pass (map lookup + pass)120Yes (after XDP)~460
tc ingress drop (same map)130Yes (skb already allocated)~470
tc ingress pass (map lookup + pass)130Yes (skb already)~480

The difference between XDP pass and tc drop is only the cost of the skb allocation (~200 cycles). When the workload requires the packet to continue up the stack (e.g., to be delivered to a socket), both XDP and tc must pay the skb allocation cost, and the advantage of XDP shrinks to the few‑cycle difference in map helper overhead.

Metadata: XDP can read packet data directly from the raw buffer, but it cannot access socket‑level metadata (e.g., sk_mark, skb->tc_index) because no socket exists yet. If a program needs to make a decision based on, say, the SO_MARK set by an application, it must either:

In our tests, a workload that consulted sk_mark forced an XDP pass, adding ~200 cycles and eliminating the XDP advantage.


Pass Paths and Observability Hooks in XDP

Even a minimal XDP program that merely increments a per‑CPU counter and returns XDP_PASS incurs overhead:

No‑op XDP pass program (return XDP_PASS;):

Corresponding tc ingress no‑op (return TC_ACT_OK;):

Adding an observability hook such as bpf_trace_printk or a perf_event_output sample increases the cost linearly with the amount of data traced. For example, tracing a 64‑bit timestamp per packet added ~80 cycles to XDP and ~90 cycles to tc (extra cost mostly the BPF helper call and perf buffer enqueue). The relative penalty is similar, but because XDP already starts from a lower baseline, the absolute impact is more noticeable when aiming for sub‑microsecond per‑packet processing.


End of reviewed markdown.


Share this post on:

Previous Post
The packet matched before it was reassembled
Next Post
Hidden offloads that lie to your packet capture