2026-07-24
XDP (eXpress Data Path) runs your eBPF program on the NIC driver's receive path before Linux allocates an skb. You get raw packet bytes, and you return XDP_PASS, XDP_DROP, XDP_TX, or XDP_REDIRECT. At 10 Gbps line rate that's ~14.88 million packets/second per core, so this path lives or dies on avoiding overhead. The catch: the eBPF verifier will refuse to load your program unless it can statically prove that every packet-byte access is in bounds.
The context struct struct xdp_md gives you two pointers: data and data_end. Both are __u32 in the UAPI but the verifier tracks them as tagged pointer types (PTR_TO_PACKET and PTR_TO_PACKET_END). When you write:
void *data = (void *)(long)ctx->data;void *end = (void *)(long)ctx->data_end;if (data + 14 > end) return XDP_DROP;struct ethhdr *eth = data; // now legal to touch eth->h_proto...the verifier walks both branches of the if. On the false branch, it narrows the range known about data: it now knows data + 14 <= end, so accessing 14 bytes at data is safe. Skip the check, and the load fails with invalid access to packet, off=0 size=14, R1(id=0,off=0,r=0) — the r=0 means "range proven safe is zero bytes."
Concrete example — parsing an IPv4 header: after checking the Ethernet header, you must re-check before touching struct iphdr. The verifier does not carry range info across an assignment to a new pointer without a fresh comparison:
struct iphdr *ip = data + 14;if ((void *)(ip + 1) > end) return XDP_DROP;if (ip->ihl < 5) return XDP_DROP; // verifier tracks scalar range tooif ((void *)ip + (ip->ihl * 4) > end) return XDP_DROP;That last check is the subtle one: ip->ihl is a 4-bit field, so the verifier knows it's 0–15, and combined with the >= 5 check it proves the multiplication won't overflow. Without the < 5 check first, some kernels reject the arithmetic entirely.
Rule of thumb: every N-byte packet read needs a ptr + N > data_end check in the same basic block, or in a dominating block whose bound the verifier can still see. Function calls, loops, and helper returns can invalidate your proven range — after bpf_xdp_adjust_head(), all prior PTR_TO_PACKET pointers become SCALAR_VALUE and you must re-derive them from ctx->data.
ptr + N > data_end comparison in the same control-flow region.
