使用 DPDK

本頁說明如何在 U4C Compute Engine 執行個體上使用資料平面開發套件 (DPDK)。

關於透過 AF_XDP 的 DPDK

資料平面開發套件 (DPDK) 是一種框架,適用於需要快速封包處理、低延遲和穩定效能的效能密集型應用程式。DPDK 會略過 Linux 核心網路堆疊,直接在使用者空間中執行。您可以使用 AF_XDP 架構,在 U4C 執行個體上執行 DPDK。

DPDK 提供 AF_XDP 輪詢模式驅動程式 (PMD),這是一種虛擬裝置 (vdev),可讓 DPDK 應用程式在 AF_XDP 上以複製模式或零複製模式執行。詳情請參閱 DPDK 說明文件中的「AF_XDP Poll Mode Driver」。與在 Compute Engine 上部署的典型應用程式不同,在 U4C 執行個體上透過 AF_XDP 使用 DPDK 時,不需要設定 VFIO、UIO 或 dpdk-devbind.py

搭配 ULL 解決方案使用 DPDK 時,可支援流量導向。您可以導向特定流量,直接將流量傳送至指定接收佇列 (RX),藉此略過預設的接收端縮放 (RSS) 雜湊。ULL 解決方案支援 3 元組流量轉向 (通訊協定、目的地 IP 位址和目的地通訊埠),適用於 ULL 單點傳播和多點傳播流量。

事前準備

在 U4C Compute Engine 執行個體上使用 DPDK 之前,請務必符合下列條件。

建立 U4C 執行個體

如果尚未建立 U4C 裸機執行個體,請先建立。請參閱「建立 ULL Compute Engine 執行個體」。

使用 SSH 連線至執行個體

如果尚未連線,請使用 SSH 連線至執行個體

切換為超級使用者

下列程序中的指令和指令碼會修改系統層級設定、核心參數和網路介面。如要順利執行這些指令,您必須以超級使用者身分執行。您可以執行 sudo su 切換至根層級殼層,或視需要先新增 sudo 再執行指令。

在 U4C 執行個體上安裝 DPDK

如要在 U4C 執行個體上安裝 DPDK,請按照下列步驟操作:

  1. 設定 DPDK 安裝作業的依附元件:

    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. 安裝 DPDK。

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

    VERSION 替換為要安裝的 DPDK 版本,例如 26.07。如有需要,請參閱「 DPDK 下載」頁面。

  3. 如要使用範例建構 DPDK,請執行下列操作:

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

為 AF_XDP 設定網路介面

如要在 Google 虛擬 NIC (gVNIC) 上使用 AF_XDP,您必須調整預設驅動程式功能,準備網路介面。

您可以手動執行這些步驟,也可以使用自動設定指令碼。選取下列任一分頁標籤:

手動

針對要設定的每個網路介面,執行下列步驟。

  1. 減少 RX 和 TX 佇列計數:gVNIC 預設會使用支援的 RX 和 TX 佇列最大數量,但您必須將此數量減半,確保有足夠的 TX 佇列可供正常核心流量使用。

    ethtool -L NIC_NAME rx NUM_SOCKETS \
    tx NUM_SOCKETS

    更改下列內容:

    • NIC_NAME:網路介面的 OS 名稱,例如 eth1
    • NUM_SOCKETS:要設定的 AF_XDP 插槽數量。請將此值設為介面佇列上限的一半以下。 對於 U4C 執行個體,這通常是 8 (預設 16 個佇列的一半)。如要驗證佇列上限,請執行 ethtool -l NIC_NAME
  2. 停用硬體 GRO 和 LRO:由於 gVNIC 不支援多緩衝區 XDP,因此您必須停用大量接收卸載 (LRO) 和硬體通用接收卸載 (GRO):

    ethtool -K NIC_NAME rx-gro-hw off
    ethtool -K NIC_NAME lro off
  3. 縮短 RX 緩衝區長度:根據預設,較新的驅動程式會將 4 KB (4,096 位元組) 的緩衝區發布至網路介面以供 RX 使用,但 XDP 需要的緩衝區長度為 2048

    ethtool -G NIC_NAME rx-buf-len 2048

腳本

或者,您也可以針對要設定的每個網路介面執行下列 Bash 指令碼。指令碼會減少佇列計數、停用卸載,並調整 RX 緩衝區長度,自動準備 XDP 的指定網路介面:

#!/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

執行 DPDK 應用程式

如要使用 AF_XDP PMD,請在 DPDK 應用程式的環境抽象層 (EAL) 引數中加入 --vdev 標記。

以下範例指令包含幾個重要參數。如要進一步瞭解設定和參數,請參閱 DPDK 說明文件中的「AF_XDP Poll Mode Driver」。

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

更改下列內容:

  • DPDK_APPLICATION:要執行的 DPDK 應用程式二進位檔。
  • PCIE_BDF:網路介面的 PCI 位址。您可以執行 ethtool -i NIC_NAME 並檢查 bus-info 值 (例如 0000:00:04.0),找出這個值。
  • NIC_NAME:網路介面的 OS 名稱,例如 eth1
  • NUM_SOCKETS:要開啟的 AF_XDP 通訊端數量。每個通訊端都會附加至單一佇列配對。這個值必須與您在介面上設定的佇列數量相符。
  • START_QUEUE:AF_XDP 插座的起始佇列索引。
  • XDP_PROG:在收到的封包上執行的自訂 XDP 程式。如果省略這個標記,DPDK 會使用 libxdp 提供的預設 XDP 程式。
  • APPLICATION_ARGS:DPDK 應用程式專屬的引數。

使用駕駛人功能

本節提供流量轉向和 RX 時間戳記的使用資訊。

流程導引

您可以使用 XDP 的流量導向功能,將應用程式封包導向特定佇列子集,將其餘佇列留給核心流量。以下各節說明兩種可用於流程規則程式設計的方法。

節目前流程導向 (建議)

AF_XDP PMD 不支援使用 ioctl 呼叫進行即時流程程式設計,且需要修改 DPDK 應用程式,因此建議您在啟動應用程式前,先使用 ethtool 預先設定流程規則。

舉例來說,如要編寫流程規則,將 IPv4 UDP 流量導向佇列 0,請執行下列指令:

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

更改下列內容:

  • NIC_NAME:網路介面的 OS 名稱,例如 eth1
  • DST_IP:要轉送的流量目的地 IP 位址。
  • DST_PORT:要轉送流量的目標通訊埠。

即時設定程式流程規則

AF_XDP PMD 不支援即時流程程式設計。如要使用這個方法,您必須修改 DPDK 應用程式,手動傳送 ethtool ioctl 呼叫。

除非應用程式處理大量暫時性連線,否則建議您避免使用這種方法。gVNIC 最多支援 20,000 個 3 元組流量轉送規則;如果您需要的規則較少,且事先知道目的地 IP 位址和連接埠,請改為預先編寫規則。

如果必須動態設定規則,請參閱下列 C 程式碼範例:

展開即可查看 C 程式碼範例

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 節目

使用流量導向時,RSS 有助於提供更完善的流量隔離功能。與流程導向相同,您可以使用 ethtool 設定 RSS。建議您先設定 RSS,再執行應用程式。

以下範例說明如何設定 RSS,以便與 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 時間戳記

雖然上游 DPDK 存放區的 AF_XDP PMD 不支援 RX 時間戳記,但您可以透過下列方式使用 RX 時間戳記:

  1. 必要修補程式套用至 DPDK 來源樹狀結構,然後重新編譯 DPDK 來源和應用程式。這些修補程式會在收到的 mbufs 中新增對 rte_eth_read_clock 和 RX 時間戳記的支援。

  2. 使用可將時間戳記載入中繼資料的 XDP 程式。

  3. 啟動 DPDK 應用程式時,請提供下列額外的 AF_XDP vdev 參數:

    • xdp_meta_rx_ts_offset:從 XDP 中繼資料開頭算起的位元組偏移,64 位元 RX 時間戳記值位於該處。
    • xdp_meta_valid_hint_offset:(選用) 位元組偏移,涵蓋 1 個位元組的旗標欄位,指出時間戳記是否有效。
    • xdp_meta_rx_ts_valid_mask:(選用) 用於擷取有效旗標位元的位元遮罩。

      如果 ctx 是中繼資料的開頭,則 (ctx->data_meta + xdp_meta_valid_hint_offset) & xdp_meta_rx_ts_valid_mask 的值會說明時間戳記是否有效。

後續步驟

  • 如要將執行個體時鐘與主機伺服器的實體 NIC 時鐘同步,請參閱「設定準確時間」。