Trabalhar com o DPDK

Esta página descreve como usar o kit de desenvolvimento de plano de dados (DPDK, na sigla em inglês) em instâncias do Compute Engine U4C.

Sobre o DPDK no AF_XDP

O kit de desenvolvimento de plano de dados (DPDK) é um framework para aplicativos com alto desempenho que exigem processamento de pacotes rápido, baixa latência e desempenho consistente. O DPDK ignora a pilha de rede do kernel do Linux e é executado diretamente no espaço do usuário. É possível executar o DPDK em instâncias U4C usando uma arquitetura AF_XDP.

O DPDK fornece um driver de modo de pesquisa AF_XDP (PMD, na sigla em inglês), que é um dispositivo virtual (vdev) que permite que aplicativos DPDK sejam executados no AF_XDP no modo de cópia ou de cópia zero. Para mais informações, consulte Driver de modo de pesquisa AF_XDP na documentação do DPDK. Ao contrário das implantações típicas no Compute Engine, o DPDK no AF_XDP em instâncias U4C não exige a configuração de VFIO, UIO ou dpdk-devbind.py.

O uso do DPDK com a solução ULL inclui suporte ao direcionamento de fluxo. É possível ignorar o hash de escalonamento do lado de recebimento (RSS, na sigla em inglês) padrão direcionando fluxos de tráfego específicos diretamente para uma fila de recebimento (RX) designada. A solução ULL oferece suporte ao direcionamento de fluxo de 3 tuplas (protocolo, endereço IP de destino e porta de destino) para tráfego de transmissão única e multicast ULL.

Antes de começar

Antes de trabalhar com o DPDK em instâncias do Compute Engine U4C, você precisa atender aos seguintes requisitos.

Criar uma instância U4C

Crie uma instância bare metal U4C, caso ainda não tenha feito isso. Consulte Criar ULL instâncias do Compute Engine.

Conectar-se à instância usando SSH

Conecte-se à instância usando SSH, caso ainda não tenha feito isso.

Mudar para o usuário raiz

Os comandos e scripts nos procedimentos a seguir modificam as configurações no nível do sistema, os parâmetros do kernel e as interfaces de rede. Para executá-los com sucesso, é necessário executá-los como o usuário raiz. É possível mudar para um shell raiz executão sudo su, ou adicionar sudo antes de executar comandos conforme necessário.

Instalar o DPDK na instância U4C

Para instalar o DPDK na instância U4C, siga estas etapas:

  1. Configure as dependências para a instalação do 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. Instale o DPDK.

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

    Substitua VERSION pela versão do DPDK que você quer instalar, como 26.07. Se necessário, consulte a página de download do DPDK.

  3. Para criar o DPDK com os exemplos:

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

Configurar interfaces de rede para AF_XDP

Para usar o AF_XDP na NIC virtual do Google (gVNIC), é necessário ajustar os recursos do driver padrão para preparar a interface de rede.

É possível realizar essas etapas manualmente ou usar um script de configuração automatizado. Selecione uma das seguintes guias:

Manual

Siga estas etapas para cada interface de rede que você quer configurar.

  1. Reduza as contagens de filas de RX e TX:a gVNIC usa por padrão o número máximo de filas de RX e TX com suporte, mas é necessário reduzir esse número pela metade para garantir que haja filas de TX suficientes para o tráfego normal do kernel.

    ethtool -L NIC_NAME rx NUM_SOCKETS \
    tx NUM_SOCKETS

    Substitua:

    • NIC_NAME: o nome do SO da interface de rede, como eth1.
    • NUM_SOCKETS: o número de soquetes AF_XDP a serem configurados. Defina um valor não maior que metade das filas máximas da interface. Para instâncias U4C, esse valor é normalmente 8 (metade das 16 filas padrão). É possível verificar as filas máximas executando ethtool -l NIC_NAME.
  2. Desative o GRO e o LRO de hardware:como a gVNIC não oferece suporte ao XDP de vários buffers, é necessário desativar a descarga de recebimento grande (LRO, na sigla em inglês) e a descarga de recebimento genérico (GRO, na sigla em inglês) de hardware:

    ethtool -K NIC_NAME rx-gro-hw off
    ethtool -K NIC_NAME lro off
  3. Reduza o comprimento do buffer de RX:por padrão, os drivers mais recentes postam buffers de 4 KB (4.096 bytes) na interface de rede para RX, mas o XDP exige um comprimento de buffer de 2048:

    ethtool -G NIC_NAME rx-buf-len 2048

Script

Como alternativa, é possível executar o seguinte script Bash para cada interface de rede que você quer configurar. O script prepara automaticamente uma determinada interface de rede para XDP, reduzindo as contagens de filas, desativando as descargas e ajustando o comprimento do buffer de RX:

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

Executar o aplicativo DPDK

Para usar o PMD AF_XDP, inclua a flag --vdev nos argumentos da camada de abstração de ambiente (EAL, na sigla em inglês) do aplicativo DPDK.

O comando de exemplo a seguir inclui vários parâmetros importantes. Para informações detalhadas sobre a configuração e os parâmetros, consulte Driver de modo de pesquisa AF_XDP na documentação do DPDK.

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

Substitua:

  • DPDK_APPLICATION: o binário do aplicativo DPDK a ser executado.
  • PCIE_BDF: o endereço PCI da interface de rede. É possível encontrar esse valor executando ethtool -i NIC_NAME e verificando o valor bus-info, como 0000:00:04.0.
  • NIC_NAME: o nome do SO da interface de rede, como eth1.
  • NUM_SOCKETS: o número de soquetes AF_XDP a serem abertos. Cada soquete é anexado a um único par de filas. Esse valor precisa corresponder à contagem de filas configurada na interface.
  • START_QUEUE: o índice da fila inicial para os soquetes AF_XDP.
  • XDP_PROG: um programa XDP personalizado a ser executado em pacotes recebidos. Se omitido, o DPDK usará o programa XDP padrão fornecido por libxdp.
  • APPLICATION_ARGS: argumentos específicos do aplicativo DPDK.

Usar recursos do driver

Esta seção fornece informações de uso para direcionamento de fluxo e marcação de data/hora de RX.

Direcionamento de fluxo

É possível usar o direcionamento de fluxo com o XDP para direcionar pacotes de aplicativos a um subconjunto específico de filas, deixando as filas restantes para o tráfego do kernel. As seções a seguir descrevem duas abordagens que podem ser usadas para a programação de regras de fluxo.

Direcionamento de fluxo pré-programado (recomendado)

Como a programação de fluxo on-the-fly usando chamadas ioctl não é compatível com o PMD AF_XDP e exige a modificação do aplicativo DPDK, recomendamos pré-programar as regras de fluxo usando ethtool antes de iniciar o aplicativo.

Por exemplo, para programar uma regra de fluxo que direciona o tráfego UDP IPv4 para a fila 0:

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

Substitua:

  • NIC_NAME: o nome do SO da interface de rede, como eth1.
  • DST_IP: o endereço IP de destino do tráfego a ser direcionado.
  • DST_PORT: a porta de destino do tráfego a ser direcionado.

Programar regras de fluxo on-the-fly

O PMD AF_XDP não oferece suporte à programação de fluxo on-the-fly. Para usar esse método, é necessário modificar o aplicativo DPDK para enviar manualmente chamadas ioctl ethtool.

Recomendamos evitar essa abordagem,a menos que o aplicativo processe um grande número de conexões temporárias. A gVNIC oferece suporte a até 20.000 regras de direcionamento de fluxo de 3 tuplas. Se você precisar de menos regras e souber os endereços IP e as portas de destino com antecedência, pré-programe as regras.

Se você precisar programar regras dinamicamente, consulte o exemplo de código C a seguir:

Abrir para ver o exemplo de código 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);
  ...
}

Programação de RSS

Ao usar o direcionamento de fluxo, o RSS pode ajudar a fornecer um melhor isolamento de tráfego. Assim como o direcionamento de fluxo, é possível configurar o RSS usando ethtool. Recomendamos configurar o RSS antes de executar o aplicativo.

O exemplo a seguir mostra como configurar o RSS para trabalhar com o 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

Marcação de data/hora de RX

Embora a marcação de data/hora de RX não seja compatível com o repositório DPDK upstream para o PMD AF_XDP, é possível usar a marcação de data/hora de RX fazendo o seguinte:

  1. Aplique os patches necessários à árvore de origem do DPDK e recompilando a origem do DPDK e o aplicativo. Esses patches adicionam suporte para rte_eth_read_clock e carimbos de data/hora de RX em mbufs recebidos.

  2. Use um programa XDP que carregue o carimbo de data/hora nos metadados.

  3. Forneça os seguintes parâmetros vdev AF_XDP adicionais ao iniciar o aplicativo DPDK:

    • xdp_meta_rx_ts_offset: o deslocamento de bytes do início dos metadados XDP em que o valor do carimbo de data/hora de RX de 64 bits está localizado.
    • xdp_meta_valid_hint_offset: (opcional) o deslocamento de bytes que abrange um campo de flag de 1 byte que indica se o carimbo de data/hora é válido.
    • xdp_meta_rx_ts_valid_mask: (opcional) a máscara de bits usada para extrair os bits de flag válidos.

      Se ctx for o início dos metadados, o valor de (ctx->data_meta + xdp_meta_valid_hint_offset) & xdp_meta_rx_ts_valid_mask descreve se o carimbo de data/hora é válido.

A seguir

  • Para sincronizar o relógio da instância com o relógio da NIC física do servidor host, consulte Configurar a hora exata.