DPDK (Data Plane Development Kit) is a set of libraries and poll-mode drivers that let an application receive and send packets directly from the network card, bypassing the Linux kernel network stack. Instead of waiting for interrupts, a DPDK application dedicates CPU cores that constantly poll the NIC queues, and it stores packets in huge page memory. This is how virtual switches, routers, firewalls and packet generators reach millions of packets per second per core.

In this tutorial you will install DPDK from the Ubuntu 24.04 repositories, reserve huge pages, hand a spare network interface to DPDK, forward traffic with dpdk-testpmd, compile a minimal DPDK program, and return the interface to Linux afterwards.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS on x86_64 with at least 4 GB of RAM and 2 or more CPU cores.
  • A non-root user with sudo privileges.
  • A second network interface that you do not use for SSH. Once an interface is bound to DPDK it disappears from Linux, so binding the interface you are connected through cuts your session. A private network interface on a CubePath VPS, or a spare port on a dedicated server, works well.
  • Console access to the server as a fallback.

DPDK supports many NICs through its own drivers: Intel (ixgbe, i40e, ice), NVIDIA Mellanox (mlx5), Broadcom and the virtio NICs used by KVM virtual machines. Real line-rate performance requires a physical supported NIC on bare metal; on a virtual machine DPDK works and is perfect for learning and functional testing.

Step 1 - Installing DPDK

Ubuntu 24.04 packages DPDK 23.11 LTS, including the runtime tools and development files:

sudo apt update
sudo apt install dpdk dpdk-dev libdpdk-dev pkg-config build-essential

Verify that the tools used in this tutorial are available and check the version:

which dpdk-testpmd dpdk-devbind.py dpdk-hugepages.py
pkg-config --modversion libdpdk
/usr/bin/dpdk-testpmd
/usr/bin/dpdk-devbind.py
/usr/bin/dpdk-hugepages.py
23.11.1

Step 2 - Reserving huge pages

DPDK allocates its packet buffers from huge pages so the NIC can access them by physical address and the CPU suffers fewer TLB misses. The dpdk-hugepages.py helper reserves pages and mounts the hugetlbfs filesystem in one command. Reserve 1 GB of 2 MB pages:

sudo dpdk-hugepages.py -p 2M --setup 1G
dpdk-hugepages.py --show
Node Pages Size Total
0    512   2Mb    1Gb

Hugepages mounted on /dev/hugepages

This reservation is lost at reboot. To make it permanent, add vm.nr_hugepages = 512 to a file in /etc/sysctl.d/; Ubuntu mounts /dev/hugepages automatically at boot.

Step 3 - Loading the vfio-pci driver

DPDK talks to the NIC from user space through the kernel's vfio-pci driver, which gives a process safe access to a PCI device. On bare metal with the IOMMU enabled this is fully isolated. Check whether your server has an active IOMMU:

ls /sys/kernel/iommu_groups/ | wc -l

If the number is greater than 0, the IOMMU is active. Load the driver:

sudo modprobe vfio-pci

On bare metal where the result is 0, enable the IOMMU in the BIOS (VT-d or AMD-Vi) and add intel_iommu=on iommu=pt (Intel) or iommu=pt (AMD) to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub, then run sudo update-grub and reboot.

On a virtual machine without a virtual IOMMU the result is 0 and cannot be changed from inside the guest. Load VFIO in no-IOMMU mode instead:

sudo modprobe vfio enable_unsafe_noiommu_mode=1
sudo modprobe vfio-pci
cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
Y

Step 4 - Binding the spare interface to DPDK

List the network devices and the driver each one uses:

dpdk-devbind.py --status-dev net
Network devices using kernel driver
===================================
0000:00:03.0 'Virtio network device 1000' if=eth0 drv=virtio-pci unused=vfio-pci *Active*
0000:00:04.0 'Virtio network device 1000' if=eth1 drv=virtio-pci unused=vfio-pci

The *Active* flag marks an interface with routes in use; that is your SSH interface, so leave it alone. In this example the spare interface is eth1 at PCI address 0000:00:04.0. Take it down and bind it to vfio-pci:

sudo ip link set dev eth1 down
sudo dpdk-devbind.py --bind=vfio-pci 0000:00:04.0
dpdk-devbind.py --status-dev net
Network devices using DPDK-compatible driver
============================================
0000:00:04.0 'Virtio network device 1000' drv=vfio-pci unused=virtio-pci

Network devices using kernel driver
===================================
0000:00:03.0 'Virtio network device 1000' if=eth0 drv=virtio-pci unused=vfio-pci *Active*

eth1 no longer appears in ip link; the device now belongs to DPDK. The tool refuses to bind an active interface unless forced, which protects you from cutting your own connection.

Step 5 - Running dpdk-testpmd

dpdk-testpmd is DPDK's reference application for testing ports and forwarding. The arguments before -- belong to the DPDK Environment Abstraction Layer (EAL), the ones after it to testpmd:

  • -l 0-1: use CPU cores 0 and 1 (core 0 for control, core 1 for polling).
  • -a 0000:00:04.0: only use this PCI device.
  • -i: start an interactive prompt.
sudo dpdk-testpmd -l 0-1 -a 0000:00:04.0 -- -i
EAL: Detected CPU lcores: 2
EAL: Probe PCI driver: net_virtio (1af4:1000) device: 0000:00:04.0 (socket -1)
Configuring Port 0 (socket 0)
Port 0: 52:54:00:AB:CD:EF
Checking link statuses...
Done
testpmd>

Show the port details, including link state and MAC address:

testpmd> show port info 0

Set the forwarding mode to receive only, start the polling core and let it run while traffic arrives on the private network (for example, run ping to any address in that subnet from another server on the same network):

testpmd> set fwd rxonly
testpmd> start

After a few seconds, show the counters:

testpmd> show port stats 0
  ######################## NIC statistics for port 0  ########################
  RX-packets: 1482       RX-missed: 0          RX-bytes:  94848
  RX-errors: 0
  RX-nombuf:  0
  TX-packets: 0          TX-errors: 0          TX-bytes:  0

  Throughput (since last show)
  Rx-pps:          102          Rx-bps:        52224
  Tx-pps:            0          Tx-bps:            0
  ############################################################################

Increasing RX-packets proves that DPDK is receiving directly from the NIC. Note that the remote ping gets no replies: nothing in the kernel owns this interface any more, and rxonly just counts and discards packets. Stop and exit:

testpmd> stop
testpmd> quit

While testpmd runs, core 1 stays at 100% usage in top even without traffic. That is how poll-mode drivers work, and why DPDK applications get dedicated, isolated cores in production.

Step 6 - Building a minimal DPDK application

DPDK applications link against the libraries through pkg-config. This small program initializes the EAL and reports the ports it found, which is the first thing every DPDK application does:

mkdir -p ~/dpdk-hello && cd ~/dpdk-hello
nano main.c
#include <stdio.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_debug.h>

int main(int argc, char **argv)
{
    int ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "EAL initialization failed\n");

    uint16_t ports = rte_eth_dev_count_avail();
    printf("DPDK ports available: %u\n", ports);

    uint16_t port_id;
    RTE_ETH_FOREACH_DEV(port_id) {
        struct rte_ether_addr mac;
        if (rte_eth_macaddr_get(port_id, &mac) == 0)
            printf("Port %u MAC: " RTE_ETHER_ADDR_PRT_FMT "\n",
                   port_id, RTE_ETHER_ADDR_BYTES(&mac));
    }

    rte_eal_cleanup();
    return 0;
}

Compile it with the flags that pkg-config provides:

gcc -O2 main.c -o dpdk-hello $(pkg-config --cflags --libs libdpdk)

Run it with the same EAL arguments as testpmd:

sudo ./dpdk-hello -l 0 -a 0000:00:04.0
EAL: Detected CPU lcores: 2
EAL: Probe PCI driver: net_virtio (1af4:1000) device: 0000:00:04.0 (socket -1)
DPDK ports available: 1
Port 0 MAC: 52:54:00:AB:CD:EF

From here, a real application configures RX and TX queues with rte_eth_rx_queue_setup() and rte_eth_tx_queue_setup(), starts the port, and runs a loop calling rte_eth_rx_burst() and rte_eth_tx_burst() on each dedicated core. The DPDK "skeleton" sample in the official documentation is the next step.

Step 7 - Returning the interface to Linux

When you are done, give the NIC back to its kernel driver (virtio-pci here; use the unused= value shown earlier, such as ixgbe or i40e, on physical NICs) and bring it up:

sudo dpdk-devbind.py --bind=virtio-pci 0000:00:04.0
sudo ip link set dev eth1 up
ip -br addr show eth1

If the interface was configured with netplan, run sudo netplan apply to restore its address. Release the huge pages if you do not need them:

sudo dpdk-hugepages.py --clear

Troubleshooting

EAL: No free 2048 kB hugepages reported. The pool is empty or used by another process. Check with dpdk-hugepages.py --show and reserve pages again as in Step 2.

EAL: Cannot open /dev/vfio/noiommu-0 or vfio-pci: probe failed. No IOMMU is active and no-IOMMU mode is off. Load vfio with enable_unsafe_noiommu_mode=1 before vfio-pci, as in Step 3, or enable the IOMMU on bare metal.

dpdk-devbind.py says the device is active and not modified. You are trying to bind the interface with routes, likely your SSH interface. Pick the spare interface instead.

testpmd shows Link status: down or no RX packets. Confirm that traffic actually reaches that network (check the other host's ARP table and routes) and that you bound the right PCI address. On physical NICs, verify the cable and that the NIC model appears in DPDK's supported hardware list.

Conclusion

You installed DPDK 23.11 on Ubuntu 24.04, reserved huge pages, moved a NIC from the kernel to vfio-pci, received packets with dpdk-testpmd and built your first program against the DPDK libraries. For production use, pair DPDK with isolated CPU cores and a persistent huge page pool, keep the NIC and the polling cores on the same NUMA node, and explore applications built on DPDK such as Open vSwitch with DPDK, VPP or the dpdk-pktgen traffic generator.