Skip to content

Custom BPF Handlers

Kookiejarz edited this page Sep 2, 2026 · 1 revision

Custom BPF Handlers

Custom handlers work only with the XDP backend. Auto XDP exposes two BPF_PROG_TYPE_XDP tail-call tables:

  • Protocol slots, indexed by final IP protocol number 0–255, for GRE, ESP, SCTP, and other L4 protocols.
  • Port handlers, indexed by TCP or UDP destination port 1–65535, for application-specific validation.

Parsed-context ABI

Include handlers/xdp_slot_ctx.h and call get_slot_ctx(ctx) to reuse fields already parsed and validated by the main program:

struct xdp_slot_ctx {
    __u8   family;       /* IPv4=2, IPv6=10 */
    __u8   ip_proto;     /* final protocol after IPv6 extension headers */
    __u16  l3_offset;
    __u16  inner_offset; /* L4/payload offset */
    __be16 sport;
    __be16 dport;
    __u16  _pad;
    __u32  saddr[4];
    __u32  daddr[4];
};

static __always_inline struct xdp_slot_ctx *get_slot_ctx(struct xdp_md *ctx);

Native XDP reads packet metadata. Generic XDP falls back to the per-CPU slot_ctx_map. IPv4 uses only address word 0. Ports are in network byte order and are normally zero for protocol-level handlers.

Minimal protocol handler

// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "xdp_slot_ctx.h"

struct gre_hdr {
    __be16 flags;
    __be16 proto;
};

SEC("xdp/gre")
int xdp_gre_handler(struct xdp_md *ctx)
{
    struct xdp_slot_ctx *sc = get_slot_ctx(ctx);
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;
    struct gre_hdr *gre;

    if (!sc)
        return XDP_PASS;
    gre = data + sc->inner_offset;
    if ((void *)(gre + 1) > data_end)
        return XDP_PASS;
    if ((bpf_ntohs(gre->flags) & 0x7) != 0)
        return XDP_DROP;
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Every packet access still needs a verifier-provable data_end check. Whether an unrecognized/truncated optional format should pass or drop is part of the handler's security policy.

Build

The repository Makefile supplies architecture-specific includes and BPF flags:

make -C handlers

Equivalent direct compilation is approximately:

clang -O3 -g -target bpf -mcpu=v3 -fno-stack-protector \
  -Wall -Ihandlers -Ibpf/include -I/usr/include -I/usr/include/bpf \
  -c my_handler.c -o my_handler.o

The administration command also accepts a .c path and invokes the project compiler path. Validate verifier behavior on every target kernel and architecture.

Load and persist

# Built-ins
sudo axdp slot load gre
sudo axdp slot load esp
sudo axdp slot load sctp

# Custom IP protocol
sudo axdp slot load 47 ./my_gre_handler.o

# TCP/UDP destination ports
sudo axdp port-handler load tcp 25565 ./my_handler.o
sudo axdp port-handler load udp 53 ./dns_handler.c

sudo axdp slot list
sudo axdp port-handler list

The transactional load path:

  1. Compiles a .c input if necessary and loads a candidate program.
  2. Reads its program ID.
  3. Updates the relevant program-array entry and reads it back.
  4. Moves the candidate pin/file to the live name.
  5. Restores the old entry and pin if any step fails.
  6. Normally copies the persistent object under /etc/auto_xdp/handlers and updates TOML.

--no-config-update is for temporary experiments and does not survive restart.

Execution position

TCP/UDP port handlers run after base parsing and the main admission/rate policy for the port. Trusted or ACL-admitted UDP traffic can bypass a port handler. TCP handlers can use project-shared maps for multi-packet validation; the repository's Minecraft handler demonstrates pending dispatch, temporary validation state, and source blocking.

Built-in behavior

  • GRE: validates the minimum GRE header and rejects unsupported/unsafe version or flag combinations.
  • ESP: validates the minimum ESP structure and basic SPI constraints.
  • SCTP: uses configured permanent SCTP ports and shared conntrack maps.

With slots.default_action = "drop", any unhandled non-TCP/UDP/ICMP protocol is denied. Inventory tunnels and VPN protocols before enabling that policy.

Verification checklist

sudo axdp backend
sudo axdp slot list
sudo axdp port-handler list
sudo bpftool prog show
sudo bpftool map dump pinned /sys/fs/bpf/xdp_fw/proto_handlers
  • Test allow, deny, truncated, maximum-length, and state-transition cases.
  • Test IPv4/IPv6 where relevant and both native/generic XDP.
  • Verify a failed reload leaves the old handler active.
  • For stateful handlers, verify capacity, TTL, and cleanup.
  • Do not assume the handler runs under nftables fallback.

Clone this wiki locally