Work with DPDK

This page describes how to use the Data Plane Development Kit (DPDK) on U4 Compute Engine instances.

About DPDK over AF_XDP

The Data Plane Development Kit (DPDK) is a framework for performance-intensive applications that require fast packet processing, low latency, and consistent performance. DPDK bypasses the Linux kernel network stack and runs directly in the user space. You can run DPDK on U4 instances by using an AF_XDP architecture.

DPDK provides an AF_XDP Poll Mode Driver (PMD), which is a virtual device (vdev) that lets DPDK applications run on top of AF_XDP in either copy mode or zero-copy mode. For more information, see AF_XDP Poll Mode Driver in the DPDK documentation. Unlike typical deployments on Compute Engine, DPDK over AF_XDP on U4 instances doesn't require configuring VFIO, UIO, or dpdk-devbind.py.

Using DPDK with ULL Solution includes support for flow steering. You can bypass default Receive Side Scaling (RSS) hashing by steering specific traffic flows directly to a designated receive queue (RX). ULL Solution supports 3-tuple flow steering (protocol, destination IP address, and destination port) for ULL unicast and multicast traffic.

Before you begin

Before you work with DPDK on U4 Compute Engine instances, you must meet the following requirements.

Create a U4 instance

If you haven't already, create a U4 Compute Engine instance by using one of the following procedures:

Connect to your instance by using SSH

If you haven't already, connect to your instance by using SSH.

Switch to the root user

The commands and scripts in the following procedures modify system-level settings, kernel parameters, and network interfaces. To run them successfully, you must execute them as the root user. You can switch to a root shell by running sudo su, or add sudo before running commands as needed.

Install DPDK on your U4 instance

To install DPDK on your U4 instance, follow these steps:

  1. Configure the dependencies for DPDK installation:

    apt-get update && apt-get upgrade -yq
    apt-get install -yq build-essential ninja-build python3-pip \
        linux-headers-$(uname -r) pkg-config libnuma-dev
    pip install pyelftools meson
    
  2. Install DPDK.

    wget https://fast.dpdk.org/rel/dpdk-VERSION.tar.xz
    tar xvf dpdk-VERSION.tar.xz
    cd dpdk-VERSION

    Replace VERSION with the DPDK version that you want to install, such as 26.07. If needed, see the DPDK Download page.

  3. To build DPDK with the examples:

    meson setup -Dexamples=all build
    ninja -C build install; ldconfig
    

Configure network interfaces for AF_XDP

To use AF_XDP on Google Virtual NIC (gVNIC), you must adjust default driver features to prepare the network interface.

You can perform these steps manually or use an automated configuration script. Select one of the following tabs:

Manual

Follow these steps for each network interface that you want to configure.

  1. Reduce the RX and TX queue counts: gVNIC defaults to using the maximum supported number of RX and TX queues, but you must reduce this by half to ensure there are enough TX queues for normal kernel traffic.

    ethtool -L NIC_NAME rx NUM_SOCKETS \
    tx NUM_SOCKETS

    Replace the following:

    • NIC_NAME: the OS name of the network interface, such as eth1.
    • NUM_SOCKETS: the number of AF_XDP sockets to configure. Set this to a value no larger than half of the maximum queues for the interface. For U4P and U4C instances, this is typically 8 (half of the default 16 queues). For U4S instances, it is up to 8 depending on the machine size. You can verify the maximum queues by running ethtool -l NIC_NAME.
  2. Disable hardware GRO and LRO: Because gVNIC doesn't support multi-buffer XDP, you must disable Large Receive Offload (LRO) and hardware Generic Receive Offload (GRO):

    ethtool -K NIC_NAME rx-gro-hw off
    ethtool -K NIC_NAME lro off
  3. Reduce the RX buffer length: By default, newer drivers post 4 KB (4,096-byte) buffers to the network interface for RX, but XDP requires a buffer length of 2048:

    ethtool -G NIC_NAME rx-buf-len 2048

Script

Alternatively, you can run the following Bash script for each network interface that you want to configure. The script automatically prepares a given network interface for XDP by reducing the queue counts, disabling offloads, and adjusting the RX buffer length:

#!/bin/bash
# Usage example: NUM_SOCKETS=8 prep_xdp.sh eth0

DEV=$1
NUM_SOCKETS=${NUM_SOCKETS=1}

# Reduce RX/TX queue counts to the number of AF_XDP sockets
ethtool -L $DEV rx $NUM_SOCKETS tx $NUM_SOCKETS

# Disable LRO/HW-gro
OFFLOAD=$(ethtool -k $DEV | \
grep "rx-gro-hw\|large-receive-offload" | \
grep -v fixed | cut -d ":" -f 1)
ethtool -K $DEV ${OFFLOAD} off

# Reduce RX buffer length to 2048
ethtool -G $DEV rx-buf-len 2048

Run your DPDK application

To use the AF_XDP PMD, include the --vdev flag in the Environment Abstraction Layer (EAL) arguments of your DPDK application.

The following example command includes several key parameters. For detailed information about setup and parameters, see AF_XDP Poll Mode Driver in the DPDK documentation.

DPDK_APPLICATION -a PCIE_BDF \
  --vdev=net_af_xdp,iface=NIC_NAME,queue_count=NUM_SOCKETS,start_queue=START_QUEUE,xdp_prog=XDP_PROG \
  -- APPLICATION_ARGS

Replace the following:

  • DPDK_APPLICATION: the DPDK application binary to run.
  • PCIE_BDF: the PCI address of the network interface. You can find this value by running ethtool -i NIC_NAME and checking the bus-info value, such as 0000:00:04.0.
  • NIC_NAME: the OS name of the network interface, such as eth1.
  • NUM_SOCKETS: the number of AF_XDP sockets to open. Each socket attaches to a single queue pair. This value must match the queue count that you configured on the interface.
  • START_QUEUE: the starting queue index for the AF_XDP sockets.
  • XDP_PROG: a custom XDP program to run on received packets. If omitted, DPDK uses the default XDP program provided by libxdp.
  • APPLICATION_ARGS: arguments specific to your DPDK application.

Use driver features

This section provides usage information for flow steering and RX timestamping.

Flow steering

You can use flow steering with XDP to steer application packets to a specific subset of queues, leaving the remaining queues for kernel traffic. The following sections describe two approaches that you can use for flow rule programming.

Pre-program flow steering (recommended)

Because on-the-fly flow programming using ioctl calls isn't supported by the AF_XDP PMD and requires modifying your DPDK application, we recommend pre-programming your flow rules by using ethtool before starting your application.

For example, to program a flow rule that steers IPv4 UDP traffic to queue 0:

ethtool -N NIC_NAME flow-type udp4 \
  dst-ip DST_IP dst-port DST_PORT action 0 loc 0

Replace the following:

  • NIC_NAME: the OS name of the network interface, such as eth1.
  • DST_IP: the destination IP address of the traffic to steer.
  • DST_PORT: the destination port of the traffic to steer.

Program flow rules on the fly

The AF_XDP PMD doesn't support on-the-fly flow programming. To use this method, you must modify your DPDK application to manually send ethtool ioctl calls.

We recommend avoiding this approach unless your application handles a large number of ephemeral connections. gVNIC supports up to 20,000 3-tuple flow steering rules; if you require fewer rules and know your destination IP addresses and ports in advance, pre-program your rules instead.

If you must program rules dynamically, refer to the following C code example:

Expand to view C code example

struct flow_rule_info {
  uint32_t src_ip;
  uint32_t dst_ip;
  uint16_t src_port;
  uint16_t dst_port;
  uint32_t target_queue;
  uint32_t rule_id;
}

int add_flow_rule(const char *ifname,
                  struct flow_rule_info *rule_info,
                  bool is_5tuple) {
  struct ethtool_rxnfc cmd;
  struct ifreq ifr;
  int fd;

  fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (fd < 0) {
    fprintf(stderr, "Failed to open socket: %s", strerror(fd));
    return -1;
  }

  memset(&cmd, 0, sizeof(cmd));
  memset(&ifr, 0, sizeof(ifr));

  cmd.cmd = ETHTOOL_SRXCLSRLINS;
  cmd.fs.flow_type = UDP_V4_FLOW;

  cmd.fs.h_u.udp_ip4_spec.ip4dst = rule_info->dst_ip;
  cmd.fs.h_u.udp_ip4_spec.pdst = htons(rule_info->dst_port);
  cmd.fs.m_u.udp_ip4_spec.ip4dst = 0xFFFFFFFF;
  cmd.fs.m_u.udp_ip4_spec.pdst = 0xFFFF;

  if (is_5tuple) {
    cmd.fs.h_u.udp_ip4_spec.ip4src = rule_info->src_ip;
    cmd.fs.h_u.udp_ip4_spec.psrc = htons(rule_info->src_port);
    cmd.fs.m_u.udp_ip4_spec.ip4src = 0xFFFFFFFF;
    cmd.fs.m_u.udp_ip4_spec.psrc = 0xFFFF;
  }

  cmd.fs.ring_cookie = rule_info->target_queue;
  cmd.fs.location = rule_info->rule_id;

  strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
  ifr.ifr_data = (void *)&cmd;

  int ret = ioctl(fd, SIOCETHTOOL, &ifr);
  if (ret)
    fprintf(stderr, "Failed to send ioctl: %s\n", strerror(errno));
  close(fd);

  return ret;
}

int try_add_xdp_flow_rule(int port, struct flow_rule_info *rule_info) {
  char dev_name[RTE_ETH_NAME_MAX_LEN];
  char *af_xdp_driver = "net_af_xdp";
  struct rte_eth_dev_info dev_info;
  int err;

  if (!rte_eth_dev_is_valid_port(port)) return -1;

  err = rte_eth_dev_info_get(port, &dev_info);
  if (err) {
    fprintf(stderr, "Error getting info for port %d: %s\n", port,
            strerror(err));
    return retval;
  }

  if (strncmp(dev_info.driver_name, af_xdp_driver,
              strlen(af_xdp_driver)) != 0) {
    fprintf(stderr, "Not an AF_XDP vdev!\n");
    return -EINVAL;
  }

  err = rte_eth_dev_get_name_by_port(port, dev_name);
  if (retval) {
    fprintf(stderr, "Failed to get dev_name: %s\n", strerror(err));
    return retval;
  }

  /* replace ens4 with correct ifname; must be passed in as application argument */
  err = add_flow_rule("ens4", rule_info));
  if (err)
    fprintf("Failed to add flow rule: %s\n", strerror(err));
  return err;
}

/* From somewhere in the application: */
int application_func(...) {
  ...

  struct flow_rule_info *rule_info {
    .src_ip = 0x0a000001,
    .dst_ip = 0x0a000002,
    .src_port = 0x1110,
    .uint16_t dst_port = 0x1011,
    .uint32_t target_queue = 0,
    .uint32_t rule_id = 1,
  };
  try_add_xdp_flow_rule(xdp_port_id, &rule_info, /*is_5tuple=*/false);
  ...
}

RSS programming

When using flow steering, RSS can help provide better traffic isolation. As with flow steering, you can configure RSS by using ethtool. We recommend configuring RSS before running your application.

The following example shows how to configure RSS to work with AF_XDP:

# Example: Kernel queues 0-3, XDP queues 4-7
NUM_SOCKETS=4 bash prep_xdp.sh eth0

# Program flow rules
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_0 action 4 loc 0
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_1 action 5 loc 1
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_2 action 6 loc 2
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_3 action 7 loc 3
# If there are more ports that the application polls on, flow rules can be added in a round-robin fashion in a script.

# Program RSS
ethtool -X eth0 start 0 equal 4

# Run the application
./path/to/application -a 0000:00:03.0 --vdev net_af_xdp,iface=eth0,start_queue=4,queue_count=4 -- APPLICATION_ARGS

RX timestamping

Although RX timestamping isn't supported in the upstream DPDK repository for the AF_XDP PMD, you can use RX timestamping by doing the following:

  1. Apply the required patches to the DPDK source tree and recompile the DPDK source and your application. These patches add support for rte_eth_read_clock and RX timestamps in received mbufs.

  2. Use an XDP program that loads the timestamp into the metadata.

  3. Provide the following additional AF_XDP vdev parameters when starting your DPDK application:

    • xdp_meta_rx_ts_offset: the byte offset from the start of the XDP metadata where the 64-bit RX timestamp value is located.
    • xdp_meta_valid_hint_offset: (Optional) the byte offset covering a 1-byte flag field indicating whether the timestamp is valid.
    • xdp_meta_rx_ts_valid_mask: (Optional) the bitmask used to extract the valid flag bits.

      If ctx is the start of the metadata, then the value of (ctx->data_meta + xdp_meta_valid_hint_offset) & xdp_meta_rx_ts_valid_mask describes whether the timestamp is valid.

What's next