XDP Packet Processing

XDP Fundamentals and Packet Parsing

XDP (eXpress Data Path) - packet processing at the driver level, before entering the kernel stack. The fastest eBPF hook point.

XDP Fundamentals and Packet Parsing

Lab: eBPF XDP Fundamentals

What you will learn in this lab:

  • XDP modes: Generic (test), Native (production), Offload (NIC)
  • XDP actions:
    • XDP_PASS - forward the packet to the kernel stack
    • XDP_DROP - drop the packet (fastest)
    • XDP_TX - send the packet back out the same interface
    • XDP_REDIRECT - redirect to another interface
    • XDP_ABORTED - error, drop the packet + trace
  • struct xdp_md context (data, data_end, data_meta)
  • Ethernet header parsing (struct ethhdr)
  • IPv4/IPv6 header parsing
  • TCP/UDP header parsing
  • ICMP parsing
  • Byte order: bpf_ntohs(), bpf_htons()

Packet Parsing Pattern

SEC("xdp")
int xdp_parser(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if (data + sizeof(*eth) > data_end)
        return XDP_PASS;

    if (eth->h_proto == bpf_htons(ETH_P_IP)) {
        struct iphdr *ip = data + sizeof(*eth);
        if ((void *)(ip + 1) > data_end)
            return XDP_PASS;
        // IPv4 packet processing...
    }
    return XDP_PASS;
}

Full parse chain: Ethernet → IP → TCP/UDP

Extending the basic pattern above into a complete L3/L4 parser:

SEC("xdp")
int xdp_full_parse(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    // L2: Ethernet
    struct ethhdr *eth = data;
    if (data + sizeof(*eth) > data_end)
        return XDP_PASS;

    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return XDP_PASS;  // not IPv4, skip

    // L3: IPv4
    struct iphdr *ip = data + sizeof(*eth);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    // L4: TCP or UDP
    void *l4 = (void *)ip + (ip->ihl * 4);  // IP header can be variable length

    if (ip->protocol == IPPROTO_TCP) {
        struct tcphdr *tcp = l4;
        if ((void *)(tcp + 1) > data_end)
            return XDP_PASS;
        // tcp->dest has the destination port (network byte order)
        if (tcp->dest == bpf_htons(80))
            return XDP_DROP;  // drop HTTP traffic
    } else if (ip->protocol == IPPROTO_UDP) {
        struct udphdr *udp = l4;
        if ((void *)(udp + 1) > data_end)
            return XDP_PASS;
        // udp->dest has the destination port
        if (udp->dest == bpf_htons(53))
            return XDP_DROP;  // drop DNS traffic
    }

    return XDP_PASS;
}

Notice the pattern: every pointer dereference requires a bounds check against data_end. The verifier enforces this — skip any check and your program will not load.

xdp-tutorial: packet01-parsing/ - do it yourself!

How to work through xdp-tutorial:

  1. Read README.org first (understand the assignments)
  2. Read xdp_prog_kern.c, inspect parsing_helpers.h
  3. Do the assignments yourself
  4. If you get stuck, check the packet-solutions/ folder

Prerequisite reading: XDP Paper (~15 pages)

Inspect in xdp-tutorial: setup_dependencies.org - dependencies (clang, llvm, libelf, kernel headers, bpftool, libxdp, libbpf).