Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

tn_xdp_probe — Tangent Networks XDP Hardware Capability Probe

Author: David Peter, Tangent Networks License: MIT Part of: tn_netdog_linux — Linux AF_XDP Inline IPS Deployment Toolchain


Overview

tn_xdp_probe is a zero-dependency C utility that authoritatively determines the AF_XDP capability of every physical network interface on a Linux host. It does this by performing real kernel-level socket bind probes against each hardware queue — not by reading driver names, ethtool flags, or any other indirect proxy.

Within tn_netdog_linux, it serves as the single source of truth for XDP hardware classification. tn_preflight.sh delegates all NIC capability decisions to this tool and interprets its JSON output rather than making its own ethtool-based guesses. The separation is intentional: shell scripts are the wrong place to be making kernel capability judgements.

Why not just check ethtool?

ethtool -k <iface> reports feature flags as the driver advertises them, not as the kernel actually honours them at bind time. A driver may advertise xdp: on but return EINVAL on bind due to a ring size constraint, firmware version mismatch, or LRO conflict. Conversely, igb reports GRO and GSO offloads as [fixed] — which looks like a limitation — but fully supports AF_XDP zero-copy. The only way to know for certain is to attempt the bind and observe the kernel's response. That is exactly what this tool does.


How It Works

The probe sequence runs once per interface and leaves no persistent state.

1. Capability Pre-check

Before touching any interface, the tool verifies that CAP_BPF is available by issuing a harmless BPF_PROG_GET_NEXT_ID syscall. If this returns EPERM, it exits immediately with a clear error and setcap instructions rather than producing confusing per-interface failures.

It also checks /proc/sys/vm/nr_hugepages and warns if no 2MB hugepages are allocated, since hugepage-backed UMEM significantly improves zero-copy DMA efficiency.

2. BPF Program Load

A minimal two-instruction XDP_PASS BPF program is compiled inline in the binary as raw bytecode — no libbpf, no .o file, no LLVM:

mov64 r0, XDP_PASS (2)
exit

This is the smallest valid XDP program. It passes every packet up the stack unchanged. It exists only to prove that an XDP program can be attached to the interface.

3. Attach Probe

The tool attempts to attach the XDP_PASS program using three methods in preference order:

Method Kernel Requirement Notes
BPF_LINK_CREATE ≥ 5.7 Preferred. Kernel manages fd lifecycle. Clean detach on close().
netlink DRV_MODE Any Legacy. Confirms native driver hook exists.
netlink SKB_MODE Any Generic path. Always succeeds. Means no native hook in driver.

If SKB_MODE is the only method that works, zero-copy probes are skipped for all queues — there is no native XDP hook to bind to.

After successful attach, the program is detached immediately. A RCU_FLUSH_US sleep follows to allow the kernel's RCU subsystem to release ring resources before the queue sweep begins.

If all three methods fail with EEXIST or EBUSY, the tool warns that an XDP program is already attached from a previous run or another process and prints the detach command.

4. Queue 0 Zero-Copy Pre-check

Before sweeping all queues, the tool probes queue 0 for XDP_ZEROCOPY. If it returns EOPNOTSUPP, zero-copy is skipped for the entire interface — the driver has no native ZC hook and probing every queue would just produce the same error repeatedly.

5. Full Queue Sweep

For each queue, two independent bind attempts are made:

  1. XDP_ZEROCOPY — NIC DMA-maps packet buffers directly into userspace memory. No kernel copy on the packet path. Best performance.
  2. XDP_COPY — Packets are copied between kernel and userspace rings. Functional for IPS but carries approximately 30% throughput overhead compared to zero-copy.

Each attempt uses a freshly allocated UMEM region that is munmap()'d before the next attempt. This is critical: AF_XDP sockets hold an exclusive reference to their UMEM, and reusing UMEM across probes produces false EBUSY errors from the kernel.

Between the ZC and CP probes on the same queue, a RCU_FLUSH_US sleep allows the kernel to fully release the zero-copy ring resources before the copy-mode bind. On EBUSY, up to EBUSY_RETRIES attempts are made with EBUSY_SLEEP_US delays between them.

6. Summary and Verdict

After all interfaces are probed, the tool prints:

  • A per-interface capability matrix showing best cap, worst cap, queue count, hugepage status, and attach path
  • A plain-language explanation of every capability level and what it means
  • An IPS deployment verdict for the machine as a whole
  • Per-interface action instructions
  • Resolution steps for every failure mode

With --json, a machine-readable summary is emitted instead for consumption by tn_preflight.sh.


Design Principles

No driver allowlists. There are no hardcoded lists of "qualified" or "unqualified" drivers. The hardware tells us what it can do; we listen and report. A future driver not known at compile time will be classified correctly by its bind behaviour.

Isolated UMEM per probe. Fresh allocation and munmap() for every bind attempt. No UMEM is shared between probes. This eliminates an entire class of false positives.

RCU discipline. Every attach, detach, and bind is followed by a grace period sleep. The Linux kernel's RCU subsystem needs time to propagate ring state changes. Probes that skip this produce intermittent EBUSY results that are impossible to reproduce reliably.

No system state modified. The tool is purely read-only from the operator's perspective. The BPF program is detached before the queue sweep begins. No interface configuration is changed. No files are written. Safe to run on a production system.

Zero external dependencies. Uses only standard Linux UAPI headers available in any kernel source tree. Links against nothing beyond libc. The binary can be copied to any x86-64 Debian host and run without installing anything.


Prerequisites

Build dependencies

On a minimal Debian or Ubuntu installation:

sudo apt-get update
sudo apt-get install --no-install-recommends build-essential ethtool

build-essential provides GCC and the standard library headers. The Linux UAPI kernel headers (linux/bpf.h, linux/if_xdp.h, linux/netlink.h, etc.) are included with the kernel source and are part of the standard include path on any Debian system. No separate linux-headers package is required.

ethtool is not required by the probe itself. It is listed as a dependency of tn_netdog_linux as a whole for other toolchain components.

Runtime privileges

The following Linux capabilities are required:

Capability Purpose
CAP_NET_RAW Open raw AF_XDP sockets and Netlink routing sockets
CAP_NET_ADMIN Attach and detach XDP programs to interfaces via Netlink
CAP_BPF Load inline eBPF programs via BPF_PROG_LOAD (kernel ≥ 5.8)
CAP_SYS_ADMIN Substitute for CAP_BPF on kernels < 5.8

The simplest approach is to run as root. To grant the minimum capabilities to the binary instead:

sudo setcap cap_net_raw,cap_net_admin,cap_bpf+ep ./tn_xdp_probe
./tn_xdp_probe

On kernels older than 5.8 where CAP_BPF does not exist, replace cap_bpf with cap_sys_admin.


HugePage Configuration (Recommended for Zero-Copy)

AF_XDP zero-copy performance depends on the NIC DMA engine being able to map large contiguous memory regions. 2MB hugepages satisfy this requirement efficiently; 4KB regular pages require many more DMA mapping entries and reduce throughput.

Hugepages are not required for the probe to function — it will fall back to regular pages automatically and note this in the output. They are recommended for production IPS deployments using XDP_ZEROCOPY.

Check current allocation

cat /proc/sys/vm/nr_hugepages
grep HugePages /proc/meminfo

Allocate for the current session

echo 512 | sudo tee /proc/sys/vm/nr_hugepages

This allocates 512 × 2MB = 1GB of hugepage memory. The allocation is lost on reboot.

Make the allocation persistent across reboots

echo "vm.nr_hugepages = 512" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

The appropriate number of hugepages depends on the number of AF_XDP sockets and queues your IPS deployment will use. Each socket with a 4MB UMEM (the default in this tool) consumes two 2MB hugepages. For a two-interface IPS pair with 4 queues each, 16 hugepages is a reasonable minimum; 512 leaves comfortable headroom.


Build

gcc -O2 -Wall -Wextra -o tn_xdp_probe tn_xdp_probe.c

To install system-wide as part of the tn_netdog_linux toolchain:

sudo install -m 0755 tn_xdp_probe /usr/local/bin/tn_xdp_probe

Usage

Usage: ./tn_xdp_probe [OPTIONS]

  --iface <name>   Probe only this interface (default: all physical NICs)
  --queues <n>     Max queues to probe per interface (default: all)
  --json           Emit machine-readable JSON (for tn_preflight.sh)
  --no-color       Disable ANSI colour output
  --help           Show this message

  Requires CAP_NET_RAW + CAP_NET_ADMIN + CAP_BPF, or root.

Examples

Probe all physical NICs:

sudo ./tn_xdp_probe

Probe a single interface:

sudo ./tn_xdp_probe --iface enp9s0

Probe one interface, limit to 4 queues:

sudo ./tn_xdp_probe --iface enp9s0 --queues 4

JSON output for script consumption:

sudo ./tn_xdp_probe --json
sudo ./tn_xdp_probe --json > xdp_audit.json

Disable colour (e.g. for log files):

sudo ./tn_xdp_probe --no-color | tee preflight.log

Colour is also disabled automatically when stdout is not a terminal.


JSON Output

When run with --json, the tool emits a structured JSON document and exits. No human-readable output is produced. This is the intended interface for tn_preflight.sh.

{
  "interfaces": [
    {
      "name": "enp9s0",
      "driver": "igb",
      "speed_mbps": "1000",
      "queues_found": 4,
      "queues_tested": 4,
      "attach_method": "BPF_LINK_CREATE",
      "best_cap": "Zero-Copy",
      "worst_cap": "Zero-Copy",
      "zc_queues": 4,
      "cp_queues": 4,
      "hugepages": false,
      "ips_capable": true,
      "ips_rating": "EXCELLENT"
    },
    {
      "name": "enp10s0",
      "driver": "igb",
      "speed_mbps": "1000",
      "queues_found": 4,
      "queues_tested": 4,
      "attach_method": "BPF_LINK_CREATE",
      "best_cap": "Zero-Copy",
      "worst_cap": "Zero-Copy",
      "zc_queues": 4,
      "cp_queues": 4,
      "hugepages": false,
      "ips_capable": true,
      "ips_rating": "EXCELLENT"
    }
  ]
}

JSON field reference

Field Type Description
name string Interface name as reported by the kernel
driver string Kernel driver module name from sysfs
speed_mbps string Link speed in Mbps, or "link-down" if interface is DOWN
queues_found integer Number of RX queues found in sysfs
queues_tested integer Number of queues actually probed (may be less if --queues is set)
attach_method string BPF attach path that succeeded: BPF_LINK_CREATE, netlink/DRV_MODE, netlink/SKB_MODE, or FAILED
best_cap string Best capability observed across all queues
worst_cap string Worst capability observed across all queues
zc_queues integer Number of queues that confirmed XDP_ZEROCOPY
cp_queues integer Number of queues that confirmed XDP_COPY
hugepages boolean Whether any queue obtained a hugepage-backed UMEM
ips_capable boolean true if best_cap is Copy-Mode or better
ips_rating string One-word deployment rating (see capability matrix below)

Capability Reference

Capability levels

The probe classifies each queue into one of the following capability levels. The levels are ordered from best to worst — the tool always reports the best and worst observed across all queues for each interface.

Level Bind Flag Meaning IPS Verdict
Zero-Copy XDP_ZEROCOPY NIC DMA-maps UMEM frames directly into userspace. No kernel copy on the packet path. Full line rate. IPS OK — preferred
Copy-Mode XDP_COPY Packets copied between kernel and userspace rings. Functional but ~30% throughput overhead vs zero-copy. IPS OK — acceptable
EBUSY Queue is held by another process after all retries. May resolve after reboot or clearing the holder. IPS WARN
EINVAL Driver precondition not met. Common causes: LRO enabled, ring size out of range, firmware too old. IPS FAIL
EPERM Missing CAP_BPF or CAP_NET_RAW. Tool must be run as root or with correct capabilities set. IPS FAIL
EOPNOTSUPP Driver has no native XDP hook. Only generic SKB-mode XDP is available, which does not support AF_XDP sockets. IPS FAIL
FAILED Socket or UMEM allocation failed before bind could be attempted. Check system memory and dmesg. IPS FAIL

Attach path meaning

The attach method recorded for each interface indicates which XDP data path is available:

Attach Method Meaning
BPF_LINK_CREATE Modern kernel (≥ 5.7). Native driver hook confirmed. Supports XDP_ZEROCOPY.
netlink/DRV_MODE Legacy attach path. Native driver hook confirmed. Supports XDP_ZEROCOPY.
netlink/SKB_MODE No native driver hook. Generic XDP only. XDP_ZEROCOPY will not be available. XDP_COPY may still work.
FAILED All attach methods failed. XDP is not functional on this interface.

Failure Resolution

EOPNOTSUPP — No native XDP hook

The driver does not implement the XDP receive hook (ndo_bpf). This interface cannot be used for inline AF_XDP IPS.

  • Verify the NIC is bound to its native driver and not vfio-pci or uio_pci_generic: dmesg | grep -i driver
  • Check the driver module is loaded: lsmod | grep <driver>
  • Known drivers with native XDP support: igb, ixgbe, i40e, ice, bnxt_en, mlx5_core, nfp, mvneta
  • Virtual/paravirtual drivers (virtio_net, vmxnet3, e1000) do not support native XDP; use as management ports only

EBUSY — Queue held after retries

Another AF_XDP socket is holding the queue, or a previous crashed process left a socket open.

# Identify the holder
ss -x
lsof | grep -i xdp

# Detach any existing XDP program from the interface
ip link set <iface> xdp off

# Check for OVS XDP programs
ovs-vsctl list interface

If nothing is found, a reboot will clear orphaned socket state.

EINVAL — Driver precondition not met

The driver rejected the bind due to a configuration conflict. Common causes:

# Disable LRO (Large Receive Offload) -- conflicts with XDP on many drivers
ethtool -K <iface> lro off

# Check dmesg for the specific driver error
dmesg | grep -i 'xdp\|invalid\|<driver name>'

# Verify ring size is a power of two and within driver limits
ethtool -g <iface>

FAILED — Setup error before bind

The probe could not allocate a socket or UMEM before the bind attempt.

# Check available memory
free -h

# Check system limits
ulimit -l       # locked memory limit (must be > UMEM_SIZE = 4MB)
ulimit -n       # open file limit

# Check dmesg for memory pressure or OOM events
dmesg | grep -iE 'oom|out of memory|alloc'

XDP program already attached

If another process (OVS, a previous tn_xdp_probe run, Suricata, etc.) left an XDP program attached, BPF_LINK_CREATE returns EBUSY or EEXIST. The tool prints a warning with the detach command:

ip link set <iface> xdp off

Integration with tn_preflight.sh

tn_preflight.sh calls tn_xdp_probe --json and parses the output to classify interfaces for IPS deployment. The integration works as follows:

# In tn_preflight.sh -- interface capability section
XDP_JSON=$(tn_xdp_probe --json 2>/dev/null) || {
    fail "tn_xdp_probe failed -- check CAP_BPF and interface state"
    exit 1
}

# Count IPS-capable interfaces
IPS_COUNT=$(printf '%s' "${XDP_JSON}" | \
    awk -F'"' '/"ips_capable": true/{count++} END{print count+0}')

if [ "${IPS_COUNT}" -ge 2 ]; then
    ok "IPS pair ready -- ${IPS_COUNT} XDP-capable interfaces found"
else
    fail "Need 2 XDP-capable interfaces for inline IPS -- found ${IPS_COUNT}"
fi

This keeps the capability classification logic entirely in the C tool where it belongs. The shell script interprets the verdict, not the kernel internals.


Tested Hardware

The following NICs have been verified to produce correct results with tn_xdp_probe. This list is informational — the tool does not use it internally.

Driver NIC Family Zero-Copy Notes
igb Intel I210, I211, I350 Yes GRO/GSO reported as [fixed] by ethtool — this is normal and not a limitation
ixgbe Intel X540, X550, 82599 Yes Disable LRO before probing: ethtool -K <iface> lro off
i40e Intel X710, XL710, XXV710 Yes
ice Intel E810 Yes Requires firmware ≥ 2.x
bnxt_en Broadcom NetXtreme-E Yes
mlx5_core Mellanox ConnectX-4/5/6 Yes Requires MLNX_OFED or upstream kernel ≥ 5.3
virtio_net KVM/QEMU virtio No Use for management only; PCI passthrough a physical NIC for IPS
vmxnet3 VMware No Use for management only
e1000 / e1000e Intel GbE (legacy) No Too old; no XDP hook
r8169 Realtek No Consumer NIC; no native XDP

Security Considerations

tn_xdp_probe requires elevated privileges and briefly touches the network data path of each probed interface. The following properties limit its attack surface:

  • Read-only by design. No files are written. No interface configuration is changed. No kernel state persists after the tool exits.
  • Minimal BPF program. The XDP_PASS bytecode is two instructions, GPL-licensed, and does nothing except return XDP_PASS. It cannot modify, drop, or redirect packets.
  • Immediate detach. The BPF program is detached before any socket bind probes begin. The data path is never in a modified state during the queue sweep.
  • No network transmission. The tool opens AF_XDP sockets to probe bind capability but never writes to TX rings or transmits any frames.
  • No external input parsed. The tool reads only sysfs files, /proc/sys/vm/nr_hugepages, and command-line arguments. No network input is parsed.

License

MIT License. See LICENSE for full text.

Copyright (c) 2026 David Peter, Tangent Networks

Part of the Tangent Networks tn_netdog_linux open source IPS deployment toolchain.

End of README.md

About

XDP Hardware Capability Probe

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages