UPF 收包单队列解决方案

介绍

RSS 介绍

RSS

RSS(Receive Side Scaling 接收端报文缩放) 网卡分流机制

RSS 是一种网卡驱动技术,可在多处理器系统中的多个CPU之间有效分配网络接收处理;接收方缩放(RSS)也称为多队列接收,它在多个基于硬件的接收队列之间分配网络接收处理,从而允许多个CPU处理入站流量。RSS可用于缓解单个CPU过载导致的接收中断处理瓶颈,并减少网络延迟。

它的作用是在每个传入的数据包上发出带有预定义哈希键的哈希函数。哈希函数将数据包的IP地址,协议(UDP或TCP)和端口(5个元组)作为键并计算哈希值。哈希值的多个最低有效位(LSB)用于索引间接表RETA(Redirection Table)

RSS RETA

间接表中的值用于将接收到的数据分配给CPU。(82599 RETA 为一个包含位宽 4 BITS128 项的索引映射表,X710 PF RETA 为一个包含位宽6 BITS256 项的索引映射表,位宽决定了 82599 最高支持 16 队列分流,X710 PF 最高支持 64 队列分流。)

以 82599 为例,RETA 表为 128 项的索引映射表,当设置入队列为 4 queues 时,将索引填充到 128 项表中即

[0,1,2,3,0,1,2,3,0,1,2,3,.....]

通过计算的 Hash 值的后 7 位索引来选择对应的 Queue;

针对不同的 Packet flow type 也可以划分不同的 Region 来绑定可用的 queue 范围,如 DPDK 基于 X710 中如下配置,将 flowtype 23 绑定对应的 10-25 Queue,flowtype 26 绑定到 1-8 Queue。然后根据计算的 Hash 值选择 Queue。

testpmd> set port 0 queue-region region_id 0 queue_start_index 1 queue_num 8
testpmd> set port 0 queue-region region_id 1 queue_start_index 10 queue_num 16

testpmd> set port 0 queue-region region_id 0 flowtype 26
testpmd> set port 0 queue-region region_id 1 flowtype 23

X710 RSS block diagram

RSS 开关对比

当未开启 RSS 负载分流时,所有报文只会从一个硬件队列来收包。开启 RSS 负载分流时,RSS 根据报文的 L3 层 L4 层信息进行 Hash Function 计算 rss hash,rss hash 有效低位值映射到 RSS output index。无法解释的报文,rss hash 和 RSS output index 的设置为 0。

DPDK 场景下,为不同的收包队列绑定不同的 Worker CPU,通过将来自网卡的接收处理分配到多个 CPU 中来处理,减轻处理压力,有效减少 rx-miss 丢包问题。

工具介绍

testpmd

TestPMD 是一个使用 DPDK 软件包分发的参考应用程序。其主要目的是在网络接口的以太网端口之间转发数据包。此外,用户还可以用TestPMD尝试一些不同驱动程序的功能,例如RSS。

安装

下载

$ git clone -b v20.08 https://github.com/DPDK/dpdk.git

编译安装

$ cd dpdk/usertools
$ ./dpdk-setup.sh
$ 41 x86_64-native-linux-gcc

运行

$ ./dpdk-devbing.py -b vfio-pci 0000:00:03.0
$ cd dpdk/x86_64-native-linux-gcc
// rxq 和 txq 都需要
$ ./app/testpmd -c f -n 4 -- -i --pkt-filter-mode=perfect --port-topology=chained --txq=16   --rxq=16

scapy

Scapy 是一个 Python 程序,使用户能够发送,嗅探和剖析并伪造网络数据包。此功能允许构建可以探测,扫描或攻击网络的工具。Scapy 是一个功能强大的交互式数据包操作程序。它能够伪造或解码大量协议的数据包,通过线路发送,捕获它们,匹配请求和回复等等。Scapy 可以轻松处理大多数经典任务,如扫描,跟踪路由,探测,单元测试,攻击或网络发现。它可以取代 hping,arpspoof,arp-sk,arping,p0f 甚至是 Nmap,tcpdump 和tshark 的某些部分。

我们可以使用 scapy 构造 GTPU 报文,包括其中的 Extension Header,Teid 等内容。

安装
$ git clone https://github.com/secdev/scapy.git

安装

$ sudo python3 setup.py install

运行

$ scapy

问题描述

UPF N3 接口报文协议为 GTPU,单基站场景下,GTPU 隧道两端为固定的基站 IP 和 UPF N3 IP 和 2152 Port,无法根据内层信息进行 Hash 计算,所以当 UPF 收包时,RSS 通常计算的五元组得到的 RSS Hash 结果一样,选择的 Queue 一样,导致所有处理都到一个 CPU 上进行处理。

当高负载情况下,导致网卡收包出现大量 rx-miss 丢包现象。

解决方案

为避免外层五元组相同导致 RSS Hash 一样,我们选择计算 GTPU 的内层五元组或者与 UE 一一对应的 TEID。

Intel X710 可以通过使用 GTP DDP Profile 来支持计算 Field 的选择。下载地址

https://www.intel.com/content/www/us/en/download/19667/intel-ethernet-controller-x710-xxv710-xl710-adapters-dynamic-device-personalization-gtp-ext-package.html?wapkw=GTP

网卡通过加载该 profile 支持 GTP 报文解析,目前支持的 packet classification types 有

  22: GTPU IPV4
  23: GTPU IPV6
  24: GTPU
  25: IPV4 GTPC
  38: IPV6 GTPC
  12: IPV4 PFCP Session
  13: IPV4 PFCP
  14: IPV6 PFCP Session
  15: IPV6 PFCP
  16: GTPU GTPEH85 UL IPV4			// 上行带 Extenstion Header  GTPU
  17: GTPU GTPEH85 UL IPV6
  18: GTPU GTPEH85 IPV4
  19: GTPU GTPEH85 IPV6

根据 《Dynamic_Device_Personalization_GTP_Ext_Headers_1_0.pdf》中 Fields 的定义

44-45 GTPU Teid
9-16  Inner IPv4

使用见下面的测试内容。

方案测试

测试场景如下,有对 i40e driver 和固件进行升级。

root@upfastri:~# ethtool -i ens3
driver: i40e
version: 2.17.4					// 升级
firmware-version: 7.10 0x80006459 1.2547.0		// 升级
expansion-rom-version: 
bus-info: 0000:00:03.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: yes

i40e 升级,下载 i40e-2.17.4.tar

https://www.intel.com/content/www/us/en/download/18026/intel-network-adapter-driver-for-pcie-40-gigabit-ethernet-network-connections-under-linux.html

$ tar -xvf i40e-2.17.4.tar 
$ cd i40e-2.17.4/src
$ make install
$ modprobe i40e

使用 scapy 发送如下报文进行测试

Source IP                1.1.1.1
Destination IP           2.2.2.2
IP Protocol              17 (UDP)
GTP Source Port          2152
GTP Destination Port     2152
GTP Message type         0xFF
GTP Tunnel id            0x2-0xffffffff random
GTP PDU Type			 1	(UL PDU Session)
GTP QFI					 1
-- Inner IPv4 Configuration --------------
Source IP                3.3.3.1-255 random
Destination IP           4.4.4.1-255 random
IP Protocol              17 (UDP)
UDP Source Port          2000
UDP Destination Port     3000

未使用 DDP 时

testpmd 启动并配置所有 rss

$ ./app/testpmd -c f -n 4 -- -i --pkt-filter-mode=perfect --port-topology=chained --txq=16   --rxq=16
$ set verbose 1
$ port config all rss all
$ port start 0
$ start

scapy 启动并发包

$ scapy
// 支持 GTPU 和 SessionContainer
$ from scapy.contrib.gtp import GTP_U_Header as GTP_U_Header
$ from scapy.contrib.gtp import GTPPDUSessionContainer as GTPPDUSessionContainer

发送四个测试报文,其中内层源和目的 IP 不同,Teid 不同。

Source IP                1.1.1.1
Destination IP           2.2.2.2
IP Protocol              17 (UDP)
GTP Source Port          2152
GTP Destination Port     2152
GTP Message type         0xFF
GTP Tunnel id            0x2					0x3
GTP PDU Type			 1	(UL PDU Session)
GTP QFI					 1
-- Inner IPv4 Configuration --------------
Source IP                3.3.3.1				3.3.3.2
Destination IP           4.4.4.1				4.4.4.2
IP Protocol              17 (UDP)
UDP Source Port          2000
UDP Destination Port     3000

$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xa6675169 - RSS queue=0x9

只修改 TEID
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x3)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xa6675169 - RSS queue=0x9

只修改内层源 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.2',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xa6675169 - RSS queue=0x9

只修改内层目的 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.2')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xa6675169 - RSS queue=0x9

可以看到,RSS Hash 的计算结果和 Queue 选择和 Teid 和 内层源目的 IP 无关,无法选择不同的 Queue。

使用 gtp-ext.pkg DDP

默认计算方法

testpmd 启动并计算 Hash

$ ./app/testpmd -c f -n 4 -- -i --pkt-filter-mode=perfect --port-topology=chained --txq=16   --rxq=16
$ set verbose 1

// 加载 DDP profile
$ ddp add 0 /root/gtp-ext.pkg,/root/gtp-ext-pkg.bak

// 关联 packet classification type 和 Flow type,16 pctype 为 GTPU GTPEH85 UL IPV4,23 flowtype 为 RTE_ETH_FLOW_GTPU; pctype 默认 hash TEID + 内层源 IP。
$ port config 0 pctype mapping update 16 23

$ port start 0

// 为 PORT 配置 flowtype 为 23 计算 RSS hash
$ port config all rss 23
$ start

scapy 启动并发包,见上节。

发送相同四个报文

Source IP                1.1.1.1
Destination IP           2.2.2.2
IP Protocol              17 (UDP)
GTP Source Port          2152
GTP Destination Port     2152
GTP Message type         0xFF
GTP Tunnel id            0x2					0x3
GTP PDU Type			 1	(UL PDU Session)
GTP QFI					 1
-- Inner IPv4 Configuration --------------
Source IP                3.3.3.1				3.3.3.2
Destination IP           4.4.4.1				4.4.4.2
IP Protocol              17 (UDP)
UDP Source Port          2000
UDP Destination Port     3000

$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x28017f79 - RSS queue=0x9

只修改 TEID
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x3)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xf33b8a54 - RSS queue=0x4

只修改内层源 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.2',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x1ff44360 - RSS queue=0x0 

只修改内层目的 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.2')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x28017f79 - RSS queue=0x9

可以看到,RSS Hash 的计算结果和 Queue 选择和 Teid 和 内层源 IP 有关,当修改 TEID 和内层源 IP 时可以获取到不同 Hash 值和 Queue。

根据 TEID 计算方法

testpmd 启动并配置 rss 根据 TEID 计算 Hash

$ ./app/testpmd -c f -n 4 -- -i --pkt-filter-mode=perfect --port-topology=chained --txq=16   --rxq=16
$ set verbose 1

// 加载 DDP profile
$ ddp add 0 /root/gtp-ext.pkg,/root/gtp-ext-pkg.bak

// 关联 packet classification type 和 Flow type,16 pctype 为 GTPU GTPEH85 UL IPV4,23 flowtype 为 RTE_ETH_FLOW_GTPU; hash_inset 为 teid (44-45)
$ port config 0 pctype mapping update 16 23
$ port config 0 pctype 16 hash_inset clear all
$ port config 0 pctype 16 hash_inset set field 44
$ port config 0 pctype 16 hash_inset set field 45

$ port start 0

// 为 PORT 配置 flowtype 为 23 计算 RSS hash
$ port config all rss 23
$ start

scapy 启动并发包,见上节。

发送相同四个报文

Source IP                1.1.1.1
Destination IP           2.2.2.2
IP Protocol              17 (UDP)
GTP Source Port          2152
GTP Destination Port     2152
GTP Message type         0xFF
GTP Tunnel id            0x2					0x3
GTP PDU Type			 1	(UL PDU Session)
GTP QFI					 1
-- Inner IPv4 Configuration --------------
Source IP                3.3.3.1				3.3.3.2
Destination IP           4.4.4.1				4.4.4.2
IP Protocol              17 (UDP)
UDP Source Port          2000
UDP Destination Port     3000

$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xed531408 - RSS queue=0x8

只修改 TEID
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x3)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x37f53c19 - RSS queue=0x9

只修改内层源 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.2',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xed531408 - RSS queue=0x8

只修改内层目的 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.2')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0xed531408 - RSS queue=0x8

可以看到,RSS Hash 的计算结果和 Queue 选择只和 Teid 有关,当修改 TEID 时可以获取到不同 Hash 值和 Queue。

根据内层源目的 IP 进行计算

testpmd 启动并配置 rss 根据内层源目的 IP 计算 Hash

$ ./app/testpmd -c f -n 4 -- -i --pkt-filter-mode=perfect --port-topology=chained --txq=16   --rxq=16
$ set verbose 1

// 加载 DDP profile
$ ddp add 0 /root/gtp-ext.pkg,/root/gtp-ext-pkg.bak

// 关联 packet classification type 和 Flow type,16 pctype 为 GTPU GTPEH85 UL IPV4,23 flowtype 为 RTE_ETH_FLOW_GTPU; hash_inset 为内层源目的 IP (9-16)
$ port config 0 pctype mapping update 16 23
$ port config 0 pctype 16 hash_inset clear all
$ port config 0 pctype 16 hash_inset set field 9
$ port config 0 pctype 16 hash_inset set field 10
$ port config 0 pctype 16 hash_inset set field 11
$ port config 0 pctype 16 hash_inset set field 12
$ port config 0 pctype 16 hash_inset set field 13
$ port config 0 pctype 16 hash_inset set field 14
$ port config 0 pctype 16 hash_inset set field 15
$ port config 0 pctype 16 hash_inset set field 16

$ port start 0

// 为 PORT 配置 flowtype 为 23 计算 RSS hash
$ port config all rss 23
$ start

scapy 启动并发包,见上节。

发送相同四个报文

Source IP                1.1.1.1
Destination IP           2.2.2.2
IP Protocol              17 (UDP)
GTP Source Port          2152
GTP Destination Port     2152
GTP Message type         0xFF
GTP Tunnel id            0x2					0x3
GTP PDU Type			 1	(UL PDU Session)
GTP QFI					 1
-- Inner IPv4 Configuration --------------
Source IP                3.3.3.1				3.3.3.2
Destination IP           4.4.4.1				4.4.4.2
IP Protocol              17 (UDP)
UDP Source Port          2000
UDP Destination Port     3000

$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x74cda611 - RSS queue=0x1

只修改 TEID
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x3)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x74cda611 - RSS queue=0x1

只修改内层源 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.2',dst='4.4.4.1')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x2949adb2 - RSS queue=0x2

只修改内层目的 IP
$ p=Ether(src='11:11:22:22:33:33',dst='44:44:55:55:66:66')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=2152,dport=2152)/GTP_U_Header(teid=0x2)/GTPPDUSessionContainer(QFI=1,type=1)/IP(src='3.3.3.1',dst='4.4.4.2')/UDP(sport=2000,dport=3000)/Raw('x'*20)
$ sendp(p, iface='ens3')
得到 RSS Hash 结算结果和 Queue 选择
src=11:11:22:22:33:33 - dst=44:44:55:55:66:66 - type=0x0800 - length=106 - nb_segs=1 - RSS hash=0x5b307da3 - RSS queue=0x3

可以看到,RSS Hash 的计算结果和 Queue 选择只和内层源目的 IP 有关,当修改内层源目的 IP 时可以获取到不同 Hash 值和 Queue。

VPP 集成配置 DDP 代码

DDP配置对带拓展字段GTPU报文内层IP+TEID进行RSS

testpmd> port stop 0                                          # 配置前停止 port
testpmd> set verbose 1                                        # 设置 log 登记
testpmd> ddp add 0 /root/gtp-ext.pkg,/root/gtp-ext-pkg.bak    # 添加带 GTPU 拓展头模板文件 
testpmd> port config 0 pctype mapping update 16 23            # 更新 hash 报文字段和 flow 对应 mapping,16 为报文GTPU-EXT,23 为 flow GTPU
testpmd> port start 0                                         # 启动 port
testpmd> port config all rss 23                               # 配置对 GTPU RSS
testpmd> start                                                # 运行 testpmd
                                                                        入口函数
port stop 0                                                             rte_eth_dev_stop
ddp add 0 /root/gtp-ext.pkg,/root/gtp-ext-pkg.bak                       cmd_ddp_add_parsed
port config 0 pctype mapping update 16 23                               cmd_pctype_mapping_update_parsed
port start 0                                                            rte_eth_dev_start
port config all rss 23                                                  cmd_config_rss_parsed

VPP集成

// 在 dpdk/lib/librte_cmdline/cmdline.c 添加配置 DDP 接口函数

uint8_t *
open_file(const char *file_path, uint32_t *size)
{
	int fd = open(file_path, O_RDONLY);
	off_t pkg_size;
	uint8_t *buf = NULL;
	int ret = 0;
	struct stat st_buf;
	if (size)
		*size = 0;
	if (fd == -1) {
		printf("%s: Failed to open %s\n", __func__, file_path);
		return buf;
	}
	if ((fstat(fd, &st_buf) != 0)){// || (!S_ISREG(st_buf.st_mode))) {
		close(fd);
		printf("%s: File operations failed\n", __func__);
		return buf;
	}
	pkg_size = st_buf.st_size;
	if (pkg_size < 0) {
		close(fd);
		printf("%s: File operations failed\n", __func__);
		return buf;
	}
	buf = (uint8_t *)malloc(pkg_size);
	if (!buf) {
		close(fd);
		printf("%s: Failed to malloc memory\n",	__func__);
		return buf;
	}
	ret = read(fd, buf, pkg_size);
	if (ret < 0) {
		close(fd);
		printf("%s: File read operation failed\n", __func__);
		close_file(buf);
		return NULL;
	}
	if (size)
		*size = pkg_size;
	close(fd);
	return buf;
}

int
save_file(const char *file_path, uint8_t *buf, uint32_t size)
{
	FILE *fh = fopen(file_path, "wb");
	if (fh == NULL) {
		printf("%s: Failed to open %s\n", __func__, file_path);
		return -1;
	}
	if (fwrite(buf, 1, size, fh) != size) {
		fclose(fh);
		printf("%s: File write operation failed\n", __func__);
		return -1;
	}
	fclose(fh);
	return 0;
}

int
close_file(uint8_t *buf)
{
	if (buf) {
		free((void *)buf);
		return 0;
	}
	return -1;
}

int
eth_dev_info_get_print_err(uint16_t port_id,
					struct rte_eth_dev_info *dev_info)
{
	int ret;
	ret = rte_eth_dev_info_get(port_id, dev_info);
	if (ret != 0)
		printf("Error during getting device (port %u) info: %s\n",
				port_id, strerror(-ret));
	return ret;
}

int cmdline_ddp(uint16_t port_id)
{
    uint8_t *buff;
	uint32_t size;
	char *filepath;
	char *file_fld[2];
	int file_num;
    int ret = -ENOTSUP;

#ifdef RTE_LIBRTE_I40E_PMD
    /*port stop all*/
    rte_eth_dev_stop((uint16_t)port_id);

    /*ddp add 0 /root/gtp-ext.pkg,/root/gtp-ext-pkg.bak*/            
    filepath = strdup("/root/gtp-ext.pkg,/root/gtp-ext-pkg.bak");
	if (filepath == NULL) {
	  return 0;
	}
    file_fld[0] = "/root/gtp-ext.pkg";
    file_fld[1] = "/root/gtp-ext-pkg.bak";
    file_num = 2;
	buff = open_file(file_fld[0], &size);
	if (!buff) {
      free((void *)filepath);
      return 0;
    }
	if (ret == -ENOTSUP)
      ret = rte_pmd_i40e_process_ddp_package((uint16_t)port_id,
					       buff, size,
					       1);
    if (ret == -EEXIST)
      ;
    else if (ret < 0)
      ;
    else if (file_num == 2)
      save_file(file_fld[1], buff, size);

    close_file(buff);
    free((void *)filepath);

    /*port config 0 pctype mapping update 16 23*/
	struct rte_pmd_i40e_flow_type_mapping mapping;
	unsigned int i;
	unsigned int nb_item;
	unsigned int pctype_list[64];
    nb_item = 1;
    pctype_list[0] = 16;
    pctype_list[1] = 0;
	mapping.flow_type = 23;
	for (i = 0, mapping.pctype = 0ULL; i < nb_item; i++)
		mapping.pctype |= (1ULL << pctype_list[i]);
	ret = rte_pmd_i40e_flow_type_mapping_update(port_id,
						&mapping,1,0);
	switch (ret) {
	case 0:
		break;
	case -EINVAL:
		break;
	case -ENODEV:
		break;
	case -ENOTSUP:
		break;
	default:
        ;
	}
 
    /*port start*/
    rte_eth_dev_start(port_id);

    /*port config all rss 23*/
    struct rte_eth_rss_conf rss_conf = { .rss_key_len = 0, };
	struct rte_eth_dev_info dev_info = { .flow_type_rss_offloads = 0, };
	int all_updated = 1;
	int diag;
    rss_conf.rss_hf = 1ULL << 23;
    rss_conf.rss_key = NULL;
    struct rte_eth_rss_conf local_rss_conf;
    ret = eth_dev_info_get_print_err(port_id, &dev_info);
	if (ret != 0)
		return;
	local_rss_conf = rss_conf;
	local_rss_conf.rss_hf = rss_conf.rss_hf &
		dev_info.flow_type_rss_offloads;
	if (local_rss_conf.rss_hf != rss_conf.rss_hf) {
		//PMD_DRV_LOG(ERR,"Port %u modified RSS hash function based on hardware support,"
		//		"requested:%#"PRIx64" configured:%#"PRIx64"\n",
		//		port_id, rss_conf.rss_hf, local_rss_conf.rss_hf);
	  }
	diag = rte_eth_dev_rss_hash_update(port_id, &local_rss_conf);
	if (diag < 0) {
		all_updated = 0;
		//PMD_DRV_LOG(ERR,"Configuration of RSS hash at ethernet port %d "
		//		"failed with error (%d): %s.\n",
		//		port_id, -diag, strerror(-diag));
	  }
#endif  
    return 0;
}

// upf-astri.git/src/plugins/dpdkdevice/init.c
// 在 VPP 初始化 DPDK 时,添加调用配置 DPP
static clib_error_t *
dpdk_lib_init (dpdk_main_t * dm)
{
...
  /* *INDENT-OFF* */
  RTE_ETH_FOREACH_DEV(i)
    {
 ...      
        /*set ddp hash func*/
        if(devconf->num_rx_queues > 1)
          {
             cmdline_ddp(i);
          }
 ...
}

  /* *INDENT-ON* */

  return 0;
}

验证:

root@upf:~# vppctl
uranus> show logging
2021/11/12 17:47:27:925 notice     dpdk           Device with port_id=1 already stopped
2021/11/12 17:47:27:925 notice     dpdk           Device with port_id=2 already stopped
2021/11/12 17:47:27:925 notice     dpdk           rte_pmd_i40e_process_ddp_package(): Profile already exists.  # 第二次添加带 GTPU 拓展头模板文件,表明 DDP 已配置成功
2021/11/12 17:47:30:012 notice     dpdk           Device with port_id=1 already started
2021/11/12 17:47:30:012 notice     dpdk           Device with port_id=2 already started

注:DDP 在 82599 网卡不支持,在 X710 固件版本 6.0.1 以上可用,目前以上代码为 一汽现场版本临时修改,后续开发 DDP 正式版本,并提交到代码库

参考

https://www.cnblogs.com/dream397/p/13835075.html
<<332464-710_Series_Datasheet_v_3_9.pdf>>
https://www.intel.com/content/www/us/en/developer/articles/technical/dynamic-device-personalization-for-intel-ethernet-700-series.html
https://www.intel.com/content/www/us/en/download/19667/intel-ethernet-controller-x710-xxv710-xl710-adapters-dynamic-device-personalization-gtp-ext-package.html?wapkw=GTP