# 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 BITS` 的 `128` 项的索引映射表,X710 PF RETA 为一个包含位宽`6 BITS` 的 `256` 项的索引映射表,位宽决定了 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](../../_static/rss_block_diagram.png) **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 ```