Skip to content

XDP Notes

Shane Alcock edited this page Aug 3, 2026 · 16 revisions

Notes on Libtrace XDP Support

This page describes the Libtrace XDP support that has been added in libtrace 4.0.14. This format is considered experimental.

XDP (eXpress Data Path) is an eBPF based high-performance data path designed as an alternative to DPDK. Libtrace uses XDP to redirect packets directly to userspace applications bypassing the kernel IP stack altogether. XDP is only available for Linux (and requires a relatively recent kernel).

Warning: Running Libtrace with XDP will redirect all traffic on the configured interface to Libtrace. This prevents the kernel IP stack from seeing the packet altogether; do not use XDP to capture from the interface that is being used to connect to the server (i.e. over ssh) or you will lose connectivity to the server.

XDP requirements

To build libtrace with XDP support, you will need to have the libbpf-dev and libelf-dev libraries.

Kernel version >= 4.18 is needed read support.
Kernel version >= 5.1 is needed for write support.

libelf-dev packages are available for Debian / Ubuntu and Centos / RHEL based distros and can be installed with:

Debian / Ubuntu:
sudo apt install libelf-dev    

Centos / RHEL:
sudo yum install elfutils-libelf-devel    

Libbpf >= 0.0.6 packages are not currently available to all distros and are best compiled from source with:

cd /usr/local/src
git clone https://github.com/libbpf/libbpf
cd libbpf/src
make
sudo make install
echo "/usr/lib64" | sudo tee -a /etc/ld.so.conf.d/lib64.conf
sudo ldconfig

Libtrace can now be compiled by following Building from source. XDP compilation support can be confirmed with a configure output which will include:

configure: Compiled with XDP capture support: Yes

If configure does not indicate this passing the configure flag --with-xdp should reveal more information on what requirements are missing.

Note: Kernel versions < 5.4 may complain about missing XDP headers and/or undeclared libbpf definitions. If this happens include the headers from libbpf in the libtrace configure command like (replace /usr/local/src/ with the path to your libbpf git clone if you cloned to a different directory):

./configure CFLAGS="-I/usr/local/src/libbpf/include/uapi"

Libtrace eBPF program

Libtrace ships with a precompiled eBPF program which is used by Libtrace to perform RSS hashing and statistics counting. If you wish to build this yourself you are required to install the XDP requirements above along with clang >= 3.4.0, llvm >= 3.7.1 and gcc-multilib with:

Debian / Ubuntu:
sudo apt install clang llvm gcc-multilib

Centos / RHEL:
sudo yum install clang llvm

Note: LLVM's tool 'llc' must support target 'bpf', list version and supported targets with command:

llc --version

Configure will now detect the requirements to build the eBPF program. This can be confirmed by the output of configure which will include:

configure: XDP Libtrace BPF program will be compiled: Yes

If configure does not indicate this passing the configure flag --enable-ebpf-build should reveal more information on what requirements are missing.

Note: Kernel versions < 5.4 may complain about undeclared identifiers. If this happens include the UAPI headers from libbpf in the libtrace configure command like (replace /usr/local/src/ with the path to your libbpf git clone if you cloned to a different directory):

./configure CFLAGS="-I/usr/local/src/libbpf/include/uapi"

Custom eBPF program

Libtrace supports loading a custom eBPF program instead of the one supplied by Libtrace. A custom eBPF program is loaded by supplying the program file along with the program name within the XDP URI such as:

xdp:/path/to/ebpf/program:programname:<interface>

Note: Loading a custom eBPF program will inhibit RSS hashing and statistics counting from being performed by eBPF and will instead be handled by Libtrace itself which may have a performance impact. If a user wants to add custom features to the eBPF program it is recommended to base their program on the Libtrace supplied eBPF program so these features are still performed by the eBPF program.

RSS Fanout Issue on Intel i40e/X710 NICs

This write-up was generously provided by @jason-sirius via Issue #222.

Summary

When using xdp: (and likely any multi-queue format relying on NIC RSS) on Intel X710/XL710 NICs with the in-tree i40e driver, hardware RSS may only ever deliver packets to the lower half of the receive queues. Parallel libtrace applications sized to the NIC's reported queue count will have half their processing threads silently idle. Nothing errors and every software-visible setting looks correct, which makes this miserable to diagnose — we lost several days to it on our own AF_XDP stack and are sharing the findings (plus a small detection patch) in case they save someone else the trip.

Symptoms

With N combined channels configured and traffic with plenty of flow entropy, ethtool -S <if> shows per-queue RX counters like:

rx-0.packets: 773,707      rx-8.packets:  0
rx-1.packets: 774,313      rx-9.packets:  0
...                        ...
rx-7.packets: 773,752      rx-15.packets: 0

Everything a user would normally check is consistent and correct the whole time:

  • ethtool -l reports 16 combined channels
  • ethtool -x shows a 512-entry indirection table evenly covering all 16 queues
  • /sys/class/net/<if>/queues/ shows rx-0..rx-15
  • 4-tuple Toeplitz hashing confirmed via ethtool -n <if> rx-flow-hash tcp4
  • (under AF_XDP) all 16 XSK buffer pools register in dmesg

Root cause

The i40e firmware LLDP agent applies a default FCoE-aware DCB configuration at startup: two traffic classes, splitting the queue pairs evenly. From the driver's debugfs VSI dump:

tc_config: numtc = 2, enabled_tc = 0x3
tc_config: tc = 0, qoffset = 0, qcount = 8, netdev_tc = 0
tc_config: tc = 1, qoffset = 8, qcount = 8, netdev_tc = 1

i40e hardware RSS distributes within a traffic class, not across TC boundaries. All normal traffic lands in TC0, so RSS only ever uses queues 0–7. The kernel's cached view (channels, indirection table, sysfs) does not reflect the hardware TC partition.

Observed on: Intel XL710 4-port 10GbE [8086:1572], firmware 9.30/9.130.73618, in-tree i40e on kernel 5.15.0-181-generic. The agent's state is visible via ethtool --show-priv-flags <if> → disable-fw-lldp : off.

Fix / work-around

ethtool --set-priv-flags <if> disable-fw-lldp on

This is a persistent NVM write (survives reboots and driver reloads, but is per-device — replacement NICs regress). The driver collapses to a single TC immediately, no reload needed:

tc_config: numtc = 1, enabled_tc = 0x1
tc_config: tc = 0, qoffset = 0, qcount = 16, netdev_tc = 0

After the fix all 16 queues receive traffic within ~1.5% of each other.

Effect on libtrace

linux_xdp_pstart_input() sizes perpkt_thread_count to the NIC's reported max queues and starts one stream per queue. On an affected NIC, threads 8–15 are fed by queues that hardware RSS never selects, so they run but process nothing — no error, no drop counter, just silently missing parallelism (and, for HASHER_BALANCE expectations, a skewed load distribution). The same applies to any RSS-dependent parallel format on this hardware.

From version 4.0.35 onwards, libtrace will print a warning if it detects that the FW LLDP setting is enabled during initialization of an XDP input interface.

Clone this wiki locally