Linux用户态协议栈与DPDK构建高性能应用
一、dpdk环境开启
这里使用了已经搭建好的dpdk环境,dpdk的搭建过程网上有很多教程可以参考,后面有空再做一篇dpdk环境搭建文章吧!
(1)检查网卡状态
ifconfig
ping一下网卡IP,确定网卡是可以连通的。
(2)查看是不是支持多队列网卡
cat /proc/interrupts | grep eth0
多队列网卡是支持多队列中断的;只支持一个中断是无法使用dpdk的。
(3)导出dpdk环境变量
cd dpdk路径
# 如dpdk/dpdk-stable-19.08.2/
#切换root权限
sudo su
export RTE_SDK=dpdk路径
export RTE_TARGET=x86_64-native-linux-gcc
可以做成shell脚本。
(4)配置dpdk
./usertools/dpdk-setup.sh
依次执行:
43(加载DPDK UIO 模块,即插入driver)
44(加载VFIO模块,也是一种driver)
45(加载KNI模块,将一些数据写回内核)
46(设置巨页,可以不需要频繁页交换,512)
47(设置巨页,可512)
49(执行之前需要eth0 down掉,执行sudo ifconfig eth0 down,使绑定dpdk)pci地址=对应eth0的(如0000:03:00.0)
60(退出)
执行过程如下:
------------------------------------------------------------------------------
RTE_SDK exported as /home/king/share/dpdk/dpdk-stable-19.08.2
------------------------------------------------------------------------------
----------------------------------------------------------
Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] arm64-armada-linuxapp-gcc
[2] arm64-armada-linux-gcc
[3] arm64-armv8a-linuxapp-clang
[4] arm64-armv8a-linuxapp-gcc
[5] arm64-armv8a-linux-clang
[6] arm64-armv8a-linux-gcc
[7] arm64-bluefield-linuxapp-gcc
[8] arm64-bluefield-linux-gcc
[9] arm64-dpaa2-linuxapp-gcc
[10] arm64-dpaa2-linux-gcc
[11] arm64-dpaa-linuxapp-gcc
[12] arm64-dpaa-linux-gcc
[13] arm64-octeontx2-linuxapp-gcc
[14] arm64-octeontx2-linux-gcc
[15] arm64-stingray-linuxapp-gcc
[16] arm64-stingray-linux-gcc
[17] arm64-thunderx2-linuxapp-gcc
[18] arm64-thunderx2-linux-gcc
[19] arm64-thunderx-linuxapp-gcc
[20] arm64-thunderx-linux-gcc
[21] arm64-xgene1-linuxapp-gcc
[22] arm64-xgene1-linux-gcc
[23] arm-armv7a-linuxapp-gcc
[24] arm-armv7a-linux-gcc
[25] i686-native-linuxapp-gcc
[26] i686-native-linuxapp-icc
[27] i686-native-linux-gcc
[28] i686-native-linux-icc
[29] ppc_64-power8-linuxapp-gcc
[30] ppc_64-power8-linux-gcc
[31] x86_64-native-bsdapp-clang
[32] x86_64-native-bsdapp-gcc
[33] x86_64-native-freebsd-clang
[34] x86_64-native-freebsd-gcc
[35] x86_64-native-linuxapp-clang
[36] x86_64-native-linuxapp-gcc
[37] x86_64-native-linuxapp-icc
[38] x86_64-native-linux-clang
[39] x86_64-native-linux-gcc
[40] x86_64-native-linux-icc
[41] x86_x32-native-linuxapp-gcc
[42] x86_x32-native-linux-gcc
----------------------------------------------------------
Step 2: Setup linux environment
----------------------------------------------------------
[43] Insert IGB UIO module
[44] Insert VFIO module
[45] Insert KNI module
[46] Setup hugepage mappings for non-NUMA systems
[47] Setup hugepage mappings for NUMA systems
[48] Display current Ethernet/Baseband/Crypto device settings
[49] Bind Ethernet/Baseband/Crypto device to IGB UIO module
[50] Bind Ethernet/Baseband/Crypto device to VFIO module
[51] Setup VFIO permissions
----------------------------------------------------------
Step 3: Run test application for linux environment
----------------------------------------------------------
[52] Run test application ($RTE_TARGET/app/test)
[53] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)
----------------------------------------------------------
Step 4: Other tools
----------------------------------------------------------
[54] List hugepage info from /proc/meminfo
----------------------------------------------------------
Step 5: Uninstall and system cleanup
----------------------------------------------------------
[55] Unbind devices from IGB UIO or VFIO driver
[56] Remove IGB UIO module
[57] Remove VFIO module
[58] Remove KNI module
[59] Remove hugepage mappings
[60] Exit Script
Option: 43
Unloading any existing DPDK UIO module
Loading uio module
Loading DPDK UIO module
Press enter to continue ...
......
Option: 44
Unloading any existing VFIO module
Loading VFIO module
chmod /dev/vfio
OK
Press enter to continue ...
......
Option: 45
Unloading any existing DPDK KNI module
Loading DPDK KNI module
Press enter to continue ...
......
Option: 46
Removing currently reserved hugepages
Unmounting /mnt/huge and removing directory
Input the number of 1048576kB hugepages
Example: to have 128MB of hugepages available in a 2MB huge page system,
enter '64' to reserve 64 * 2MB pages
Number of pages: 512
Reserving hugepages
Creating /mnt/huge and mounting as hugetlbfs
Press enter to continue ...
......
Option: 47
Removing currently reserved hugepages
Unmounting /mnt/huge and removing directory
Input the number of 1048576kB hugepages for each node
Example: to have 128MB of hugepages available per node in a 2MB huge page system,
enter '64' to reserve 64 * 2MB pages on each node
Number of pages for node0: 512
Reserving hugepages
Creating /mnt/huge and mounting as hugetlbfs
Press enter to continue ...
......
Option: 49
Network devices using kernel driver
===================================
0000:02:01.0 '82545EM Gigabit Ethernet Controller (Copper) 100f' if=eth2 drv=e1000 unused=igb_uio,vfio-pci *Active*
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper) 100f' if=eth3 drv=e1000 unused=igb_uio,vfio-pci *Active*
0000:03:00.0 'VMXNET3 Ethernet Controller 07b0' if=eth0 drv=vmxnet3 unused=igb_uio,vfio-pci
0000:0b:00.0 'VMXNET3 Ethernet Controller 07b0' if=eth1 drv=vmxnet3 unused=igb_uio,vfio-pci *Active*
No 'Baseband' devices detected
==============================
No 'Crypto' devices detected
============================
No 'Eventdev' devices detected
==============================
No 'Mempool' devices detected
=============================
No 'Compress' devices detected
==============================
No 'Misc (rawdev)' devices detected
===================================
Enter PCI address of device to bind to IGB UIO driver: 0000:03:00.0
OK
Press enter to continue ...
......
Option: 60
二、Windowe下配置静态IP表
需要管理员权限
(1)查看要加入的静态表接口
arp -a
示例结果如下,可以看到0x13这个位置,后面步骤用到。
接口: 192.168.2.130 --- 0x13
Internet 地址 物理地址 类型
192.168.0.20 00-17-16-07-b1-14 动态
192.168.0.25 00-00-74-f8-0f-65 动态
192.168.0.60 00-1e-67-6e-d4-c8 动态
192.168.0.62 00-15-5d-00-29-01 动态
192.168.0.80 00-00-5e-00-01-82 动态
192.168.0.116 04-d4-c4-8f-03-d7 动态
192.168.0.120 90-09-d0-0a-39-8b 动态
192.168.0.128 18-c0-4d-5e-30-05 动态
192.168.0.150 90-23-b4-b8-62-63 动态
192.168.0.152 b8-cb-29-b1-82-5b 动态
192.168.0.180 0c-c4-7a-79-21-8a 动态
192.168.2.42 30-5a-3a-5a-63-cd 动态
192.168.2.154 00-0e-c6-5c-39-34 动态
192.168.2.227 18-c0-4d-de-e8-9d 动态
192.168.3.111 30-b4-9e-76-e6-60 动态
192.168.3.166 2c-56-dc-dc-d5-45 动态
192.168.4.191 d8-5e-d3-20-7a-53 动态
192.168.5.0 18-c0-4d-9b-65-fb 动态
192.168.7.31 fc-aa-14-a2-e7-4a 动态
192.168.7.98 18-c0-4d-de-dd-be 动态
192.168.7.146 00-0c-29-39-a8-c4 动态
192.168.7.234 18-c0-4d-cc-b7-da 动态
192.168.7.248 d4-5d-64-d2-b7-23 动态
192.168.7.253 50-81-40-f3-ed-90 动态
192.168.8.1 70-8c-b6-ee-02-12 动态
192.168.8.11 00-11-04-01-19-4d 动态
192.168.8.17 00-11-04-01-01-c5 动态
192.168.11.12 d4-5d-64-3c-5c-fa 动态
192.168.11.21 e0-70-ea-f1-0b-77 动态
192.168.11.45 0c-9d-92-85-52-d4 动态
192.168.11.92 40-8d-5c-a8-08-00 动态
192.168.11.95 04-42-1a-eb-b5-00 动态
192.168.11.138 00-0e-c6-80-04-fa 动态
192.168.11.202 98-29-a6-65-c9-2c 动态
192.168.11.225 18-c0-4d-57-59-58 动态
192.168.16.124 18-c0-4d-50-1e-da 动态
192.168.17.140 d8-5e-d3-2a-56-78 动态
192.168.17.174 70-5a-0f-4d-c7-e8 动态
192.168.17.196 00-24-1d-9c-f2-15 动态
192.168.17.199 38-d5-47-1c-5c-fb 动态
192.168.20.188 e4-e7-49-ff-f0-9c 动态
192.168.255.255 ff-ff-ff-ff-ff-ff 静态
224.0.0.2 01-00-5e-00-00-02 静态
224.0.0.22 01-00-5e-00-00-16 静态
224.0.0.251 01-00-5e-00-00-fb 静态
224.0.0.252 01-00-5e-00-00-fc 静态
224.0.1.60 01-00-5e-00-01-3c 静态
224.0.6.151 01-00-5e-00-06-97 静态
224.100.100.100 01-00-5e-64-64-64 静态
224.200.200.200 01-00-5e-48-c8-c8 静态
229.111.112.12 01-00-5e-6f-70-0c 静态
233.233.233.233 01-00-5e-69-e9-e9 静态
234.200.200.200 01-00-5e-48-c8-c8 静态
239.102.144.50 01-00-5e-66-90-32 静态
239.192.152.143 01-00-5e-40-98-8f 静态
239.193.3.64 01-00-5e-41-03-40 静态
239.193.4.69 01-00-5e-41-04-45 静态
239.193.5.133 01-00-5e-41-05-85 静态
239.193.21.194 01-00-5e-41-15-c2 静态
239.193.21.222 01-00-5e-41-15-de 静态
239.193.21.241 01-00-5e-41-15-f1 静态
239.255.102.18 01-00-5e-7f-66-12 静态
239.255.255.250 01-00-5e-7f-ff-fa 静态
239.255.255.251 01-00-5e-7f-ff-fb 静态
239.255.255.253 01-00-5e-7f-ff-fd 静态
239.255.255.254 01-00-5e-7f-ff-fe 静态
(2)查看适配器
netsh i i show in
示例结果如下:
Idx Met MTU 状态 名称
--- ---------- ---------- ------------ ---------------------------
1 75 4294967295 connected Loopback Pseudo-Interface 1
19 35 1500 connected 以太网 2
5 35 1500 connected VMware Network Adapter VMnet1
15 35 1500 connected VMware Network Adapter VMnet8
39 35 1500 connected VMware Network Adapter VMnet2
可以看到,上面的0x13=19对应的网络接口是以太网。
(3)新添静态IP
netsh -c i i add neighbors 19 192.168.7.199 38-d5-47-1c-5c-fb
注意要确定MAC地址的正确性。
(4)检查是否添加成功
arp -a
(5)如果需要清除静态表,执行:
netsh i i delete neighbors 接口号
# 比如18就是接口号
三、DPDK API介绍
dpdk提供丰富的api,具体的api介绍可以参考官网API文档。这里介绍一下部分常用的API。
3.1、struct rte_memzone结构体
原型:
#include <rte_memzone.h>
struct rte_memzone{
char name [RTE_MEMZONE_NAMESIZE];
rte_iova_t iova;
size_t len;
uint64_t hugepage_sz;
int32_t socket_id;
uint32_t flags;
void * addr;
uint64_t addr_64;
};
成员描述:
成员 | 含义 |
---|---|
name | 内存分区名称。 |
iova | 开始的输入输出地址。 |
addr | 开始的虚拟地址。 |
addr_64 | 确保addr总是64 bits。 |
len | memzone的长度。 |
hugepage_sz | 底层内存的页大小。 |
socket_id | NUMA socket ID。 |
flags | memzone的标识 |
3.2、struct rte_mempool结构体
原型:
#include <rte_mempool.h>
struct rte_mempool{
char name [RTE_MEMPOOL_NAMESIZE];
void *pool_config;
const struct rte_memzone *mz;
unsigned int flags;
int socket_id;
uint32_t size;
uint32_t cache_size;
uint32_t elt_size;
uint32_t header_size;
uint32_t trailer_size;
unsigned private_data_size;
int32_t ops_index;
struct rte_mempool_cache *local_cache;
uint32_t populated_size;
struct rte_mempool_objhdr_list elt_list;
uint32_t nb_mem_chunks;
struct rte_mempool_memhdr_list mem_list;
void *pool_data;
uint64_t pool_id;
}
成员描述:
成员 | 含义 |
---|---|
name | 内存池的名称。 |
pool_data | 用于存储对象的环或池。 |
pool_id | 外部内存标识符。 |
pool_config | 可选参数。 |
mz | 分配池的Memzone。 |
flags | 内存池的标志。 |
socket_id | 创建时传递的套接字id。 |
size | 内存池的最大大小。 |
cache_size | 每内核默认本地缓存的大小。 |
elt_size | 元素大小。 |
header_size | 元素之前的头大小。 |
trailer_size | 元素之后的尾大小。 |
private_data_size | 私有数据的大小。 |
ops_index | 内存池操作结构的rte_mempool_ops_table数组的索引,该数组包含回调函数指针。在这里使用索引而不是回调函数的指针,以方便任何可能想要使用这个内存池的辅助进程。 |
local_cache | Per-lcore本地缓存 |
populated_size | 填充对象的数量。 |
elt_list | 池中的元素列表 |
nb_mem_chunks | 内存块数量 |
mem_list | 内存块列表 |
3.3、struct rte_eth_dev_info结构体
一种用于检索以太网设备上下文信息的结构,如设备的控制驱动程序等。
原型:
#include <rte_ethdev.h>
struct rte_eth_dev_info{
struct rte_device * device;
const char * driver_name;
unsigned int if_index;
uint16_t min_mtu;
uint16_t max_mtu;
const uint32_t * dev_flags;
uint32_t min_rx_bufsize;
uint32_t max_rx_pktlen;
uint32_t max_lro_pkt_size;
uint16_t max_rx_queues;
uint16_t max_tx_queues;
uint32_t max_mac_addrs;
uint32_t max_hash_mac_addrs;
uint16_t max_vfs;
uint16_t max_vmdq_pools;
struct rte_eth_rxseg_capa rx_seg_capa;
uint64_t rx_offload_capa;
uint64_t tx_offload_capa;
uint64_t rx_queue_offload_capa;
uint64_t tx_queue_offload_capa;
uint16_t reta_size;
uint8_t hash_key_size;
uint64_t flow_type_rss_offloads;
struct rte_eth_rxconf default_rxconf;
struct rte_eth_txconf default_txconf;
uint16_t vmdq_queue_base;
uint16_t vmdq_queue_num;
uint16_t vmdq_pool_base;
struct rte_eth_desc_lim rx_desc_lim;
struct rte_eth_desc_lim tx_desc_lim;
uint32_t speed_capa;
uint16_t nb_rx_queues;
uint16_t nb_tx_queues;
struct rte_eth_dev_portconf default_rxportconf;
struct rte_eth_dev_portconf default_txportconf;
uint64_t dev_capa;
struct rte_eth_switch_info switch_info;
uint64_t reserved_64s [2];
void * reserved_ptrs [2];
};
3.4、struct rte_eth_conf结构体
用于配置以太网端口的结构。根据Rx多队列模式,可能需要额外的高级配置设置。
#include <rte_ethdev.h>
struct rte_eth_conf{
uint32_t link_speeds;
struct rte_eth_rxmode rxmode;
struct rte_eth_txmode txmode;
uint32_t lpbk_mode;
struct {
struct rte_eth_rss_conf rss_conf;
struct rte_eth_vmdq_dcb_conf vmdq_dcb_conf;
struct rte_eth_dcb_rx_conf dcb_rx_conf;
struct rte_eth_vmdq_rx_conf vmdq_rx_conf;
} rx_adv_conf;
union {
struct rte_eth_vmdq_dcb_tx_conf vmdq_dcb_tx_conf;
struct rte_eth_dcb_tx_conf dcb_tx_conf;
struct rte_eth_vmdq_tx_conf vmdq_tx_conf;
} tx_adv_conf;
uint32_t dcb_capability_en;
struct rte_eth_fdir_conf fdir_conf;
struct rte_eth_intr_conf intr_conf;
};
3.5、struct rte_eth_rxconf结构体
用于配置以太网端口Rx环的结构。位置lib/ethdev/rte_ethdev.h。
原型:
#include <rte_ethdev.h>
rte_eth_rxconf{
struct rte_eth_thresh rx_thresh;// 接收环阈值寄存器
uint16_t rx_free_thresh;//驱动Rx描述符的释放
uint8_t rx_drop_en;//如果没有可用的描述符,则丢弃数据包
uint8_t rx_deferred_start;//不要使用 rte_eth_dev_start()开始排队
uint16_t rx_nseg;//数组中的rx_seg描述数
uint16_t share_group;//在 Rx 域和交换机域中共享组索引。非零值启用 ,零值禁用。
uint16_t share_qid;//组中的共享 Rx 队列 ID
uint64_t offloads;//卸载,每队列 Rx 卸载要使用 RTE_ETH_RX_OFFLOAD_* 标志进行设置。
union rte_eth_rxseg * rx_seg;//指向整个数据包的分段描述数组。数组元素是连续 Rx 段的属性。
uint64_t reserved_64s [2];//保留
void * reserved_ptrs [2];//保留
};
3.6、struct rte_eth_txconf结构体
用于配置以太网端口的发送环的结构。
原型:
#include <rte_ethdev.h>
struct rte_eth_txconf{
struct rte_eth_thresh tx_thresh;//发送环阈值寄存器
uint16_t tx_rs_thresh;//驱动TXD上RS为的设置
uint16_t tx_free_thresh;//用于配置以太网端口的发送环的结构。
uint8_t tx_deferred_start;//不要使用 rte_eth_dev_start()开始排
uint64_t offloads;//卸载
uint64_t reserved_64s [2];//保留
void * reserved_ptrs [2];//保留
};
3.7、rte_eal_init()
EAL配置API定义在rte_eal.h。
函数原型:
#include <rte_eal.h>
int rte_eal_init(int argc, char **argv);
此函数是初始化环境抽象层 (EAL)。仅在 MAIN lcore 上执行,尽可能在应用程序的 main() 函数中执行;它将 WORKER lcore 置于 WAIT 状态。
成功则返回大于或等于0的值,即分析的参数数。失败则返回-1,并且rte_errno设置故障原因的值。在一些情况下,为清除部分问题,可能需要重启应用程序。
参数:
参数 | 含义 |
---|---|
argc | 非负值。如果它大于 0,则 argv[0] 到 argv[argc]的数组成员应包含指向字符串的指针。 |
argv | 字符串数组。数组的内容以及数组所指向的字符串都可以由此函数修改。 |
rte_errno返回的错误码:
值 | 含义 |
---|---|
EACCES | 表示存在权限问题。 |
EAGAIN | 指示总线或系统资源不可用,可以再次尝试设置。 |
EALREADY | 表示rte_eal_init函数已被调用,无法再次调用。 |
EFAULT | 指示在内存配置中找不到 tailq 配置名称。 |
EINVAL | 指示无效参数被传递为 argv/argc。 |
ENOMEM | 表示故障可能是由内存不足情况引起的。 |
ENODEV | 指示内存设置问题。 |
ENOTSUP | 指示 EAL 无法在此系统上初始化。 |
EPROTO | 指示 PCI 总线不存在,或者 eal 无法读取。 |
ENOEXEC | 表示服务核心未能成功启动。 |
3.8、rte_exit()
立即终止 应用程序,打印错误消息并将exit_code返回到 shell。此函数不会有返回。
函数原型:
#include <rte_common.h>
__rte_noreturn void rte_exit(int exit_code,const char * format,... );
参数 | 含义 |
---|---|
exit_code | 应用程序要返回的退出代码 |
format | 用于打印消息的格式字符串。这可以包括 printf 格式字符,这些字符将使用函数的任何其他参数进行扩展。 |
3.9、rte_memzone_reserve()
函数原型:
#include <rte_memzone.h>
const struct rte_memzone* rte_memzone_reserve (
const char * name,
size_t len,
int socket_id,
unsigned flags
);
保留一部分物理内存。此函数保留一些内存,并返回指向正确填充的 memzone 描述符的指针。如果无法完成分配,则返回 NULL。
注意:
len 设置为 0 的 memzone 将仅尝试从已可用的内存中分配 memzone。它不会触发任何新的分配。当保留len设置为0的memzones时,最好也设置一个有效的socket_id。支持将socket_id设置为SOCKET_ID_ANY,但可能不会产生预期的结果。具体来说,生成的 memzone 不一定是可用的最大 memzone,而是与调用预留的 lcore 相对应的套接字 ID 上可用的最大 memzone。
参数:
参数 | 含义 |
---|---|
name | memzone的名称。如果它已经存在,则该函数将失败并返回 NULL。 |
len | 要保留的内存的大小。如果为 0,则将保留最大的连续区域。 |
socket_id | NUMA 情况下的套接字标识符。如果保留区域没有 NUMA 约束,则可以SOCKET_ID_ANY该值。 |
flags | 参数用于请求从特定大小的大页面中获取 memzone。 |
flags取值:
标识 | 含义 |
---|---|
RTE_MEMZONE_2MB | 保留 2MB 页面 |
RTE_MEMZONE_1GB | 保留 1GB 页面 |
RTE_MEMZONE_16MB | 保留 16MB 页面 |
RTE_MEMZONE_16GB | 保留 16GB 页面 |
RTE_MEMZONE_256KB | 保留 256KB 页起 |
RTE_MEMZONE_256MB | 保留 256MB 页面 |
RTE_MEMZONE_512MB | 保留 512MB 页面 |
RTE_MEMZONE_4GB | 保留自 4GB 页面 |
RTE_MEMZONE_SIZE_HINT_ONLY | 如果请求的页面大小不可用,则允许使用备用页面大小。如果未设置此标志,则该函数将在大小不可用请求时返回错误。 |
RTE_MEMZONE_IOVA_CONTIG | 确保保留的 memzone 与 IOVA 相容。在分配用于硬件环等的内存时,应使用此选项。 |
成功则返回指向正确填充的只读 memzone 描述符的指针,如果失败时返回 NULL,并适当地设置rte_errno。
错误码:
错误码 | 含义 |
---|---|
E_RTE_NO_CONFIG | 函数无法获取指向rte_config结构的指针 |
E_RTE_SECONDARY | 函数从辅助流程实例调用 |
EINVAL | 无效参数 |
ENOSPC | memzone的最大数量已经分配 |
EEXIST | 已存在同名的memzone |
ENOMEM | 没有找到合适的内存区域来创建memzone |
3.10、rte_mempool_create()
函数原型:
struct rte_mempool* rte_mempool_create ( const char * name,
unsigned n,
unsigned elt_size,
unsigned cache_size,
unsigned private_data_size,
rte_mempool_ctor_t *mp_init,
void *mp_init_arg,
rte_mempool_obj_cb_t *obj_init,
void *obj_init_arg,
int socket_id,
unsigned flags
);
在内存中创建一个名为name的新内存池。这个函数使用rte_memzone_reserve()来分配内存。该池包含n个elt_size的元素。它的大小设为n。
参数 | 含义 |
---|---|
name | 内存池的名称。 |
n | 内存池中的元素数。内存池的最佳大小(就内存使用而言):n = (2^q - 1)。 |
elt_size | 每个元素的大小。 |
cache_size | 每核对象缓存的大小。如果cache_size不为零,则rte_mempool库将尝试通过维护每 lcore 对象缓存来限制对公共无锁池的访问。此参数必须低于或等于 RTE_MEMPOOL_CACHE_MAX_SIZE 和 n/1.5。建议选择cache_size,使其具有“n模cache_size == 0”:如果不是这种情况,则某些元素将始终留在池中,永远不会被使用。当然,对 per-lcore 表的访问速度比多生产者/使用者池更快。如果 cache_size 参数设置为 0,则可以禁用缓存;它可用于避免丢失缓存中的对象。 |
private_data_size | 内存池结构后附加的私有数据的大小。这对于在内存池结构之后存储一些私有数据非常有用,例如对于rte_mbuf_pool。 |
mp_init | 一个函数指针,用于在对象初始化之前初始化池。如果需要,用户可以在此函数中初始化私有数据。如果不需要,此参数可以为 NULL。 |
mp_init_arg | 指向可在内存池构造函数中使用的数据的不透明指针。 |
obj_init | 在池初始化时为每个对象调用的函数指针。如果需要,用户可以在对象中设置一些元数据。如果不需要,此参数可以为 NULL。obj_init() 函数将 mempool 指针、init_arg、对象指针和对象编号作为参数。 |
obj_init_arg | 指向数据的不透明指针,可用作每次调用对象构造函数的参数。 |
socket_id | socket_id参数是 NUMA 情况下的套接字标识符。如果保留区域没有 NUMA 约束,则可以SOCKET_ID_ANY该值。 |
flags | 标记符 |
flags标记符说明:
标记符 | 含义 |
---|---|
RTE_MEMPOOL_F_NO_SPREAD | 默认情况下,对象地址分布在 RAM 中的通道之间:池分配器将根据硬件配置在对象之间添加填充。有关详细信息,请参阅内存对齐约束。如果设置了此标志,则分配器将仅将它们与缓存行对齐。 |
RTE_MEMPOOL_F_NO_CACHE_ALIGN | 默认情况下,返回的对象是缓存对齐的。此标志将删除此约束,并且对象之间不会存在任何填充。此标志表示RTE_MEMPOOL_F_NO_SPREAD。 |
RTE_MEMPOOL_F_SP_PUT | 如果设置了此标志,则使用 rte_mempool_put() 或 rte_mempool_put_bulk() 时的默认行为为“单生产者”。否则,就是“多生产者”。 |
RTE_MEMPOOL_F_SC_GET | 如果设置了此标志,则使用 rte_mempool_get() 或 rte_mempool_get_bulk() 时的默认行为为“单使用者”。否则,就是“多消费者”。 |
RTE_MEMPOOL_F_NO_IOVA_CONTIG | 如果设置,分配的对象在 IO 内存中不一定是连续的。 |
成功时返回指向新分配的内存池的指针。错误时返回 NULL并设置rte_errno。
错误码:
错误码 | 含义 |
---|---|
E_RTE_NO_CONFIG | 函数无法获取指向rte_config结构的指针 |
E_RTE_SECONDARY | 函数从辅助流程实例调用 |
EINVAL | 提供的缓存大小太大,或者priv_size未对齐。 |
ENOSPC | memzone的最大数量已经分配 |
EEXIST | 已存在同名的memzone |
ENOMEM | 没有找到合适的内存区域来创建memzone |
3.11、rte_pktmbuf_pool_create()
创建mbuf池。创建并初始化packet mbuf池;它是rte_mempool的封装器。
函数原型:
#include <rte_mbuf.h>
struct rte_mempool* rte_pktmbuf_pool_create (
const char *name,
unsigned n,
unsigned cache_size,
uint16_t priv_size,
uint16_t data_room_size,
int socket_id
);
成功时,返回指向新分配的内存池的指针。错误时返回 NULL,并设置rte_errno值。
参数:
参数 | 含义 |
---|---|
name | mbuf 池的名称。 |
n | mbuf 池中的元素数。内存池的最佳大小(就内存使用而言):n = (2^q - 1)。 |
cache_size | 每核对象缓存的大小。有关详细信息,请参见 rte_mempool_create()。 |
priv_size | 应用程序私有的大小,介于rte_mbuf结构和数据缓冲区之间。此值必须与RTE_MBUF_PRIV_ALIGN对齐。 |
data_room_size | 每个 mbuf 中数据缓冲区的大小,包括RTE_PKTMBUF_HEADROOM。 |
socket_id | 应在其中分配内存的套接字标识符。如果保留区域没有 NUMA 约束,则可以SOCKET_ID_ANY该值。 |
错误码:
错误码 | 含义 |
---|---|
E_RTE_NO_CONFIG | 函数无法获取指向rte_config结构的指针 |
E_RTE_SECONDARY | 函数从辅助流程实例调用 |
EINVAL | 提供的缓存大小太大,或者priv_size未对齐。 |
ENOSPC | memzone的最大数量已经分配 |
EEXIST | 已存在同名的memzone |
ENOMEM | 没有找到合适的内存区域来创建memzone |
3.12、rte_socket_id()
函数原型:
#include <rte_Icore.h>
unsigned int rte_socket_id(void);
返回正在运行的逻辑核心的物理套接字的ID。
返回当前lcoreid的物理套接字的ID。
3.13、rte_eth_dev_count_avail()
获取应用程序可用的端口数量。
这些设备必须使用宏RTE_ETH_FOREACH_DEV或RTE_ETH_FOREACH_DEV_OWNED_BY来处理非连续范围的设备。
函数原型:
#include <rte_ethdev.h>
uint16_t rte_eth_dev_count_avail(void );
返回可用的以太网设备的数量。
3.14、rte_eth_dev_info_get()
检索以太网设备的上下文信息。
函数原型:
#include <rte_ethdev.h>
int rte_eth_dev_info_get(uint16_t port_id, struct rte_eth_dev_info * dev_info);
参数 | 含义 |
---|---|
port_id | 以太网设备的端口标识符。 |
dev_info | 指向rte_eth_dev_info类型的结构的指针,以填充以太网设备的上下文信息。 |
返回值 | 含义 |
---|---|
0 | 成功。 |
ENOTSUP | 设备不支持 dev_infos_get()。 |
ENODEV | port_id无效。 |
EINVAL | 参数错误。 |
3.15、rte_eth_dev_configure()
配置以太网设备。
必须先调用此函数,然后再调用以太网 API 中的任何其他函数。当设备处于停止状态时,也可以重新调用此函数。
函数原型:
#include <rte_ethdev.h>
int rte_eth_dev_configure(
uint16_t port_id,
uint16_t nb_rx_queue,
uint16_t nb_tx_queue,
const struct rte_eth_conf * eth_conf
);
参数说明:
参数 | 含义 |
---|---|
port_id | 要配置的以太网设备的端口标识符。 |
nb_rx_queue | 要为以太网设备设置的接收队列数。 |
nb_tx_queue | 要为以太网设备设置的传输队列数。 |
eth_conf | 指向要用于以太网设备的配置数据的指针。将所有配置信息嵌入到单个数据结构中是更灵活的方法,允许在不更改 API 语法的情况下添加新功能。 |
返回值:
返回 | 含义 |
---|---|
0 | 成功,设备已配置。 |
<0 | 驱动程序配置函数返回的错误代码。 |
3.16、rte_eth_dev_socket_id()
函数原型:
返回以太网设备连接到的NUMA套接字。
#include <rte_ethdev.h>
int rte_eth_dev_socket_id ( uint16_t port_id )
- port_id:以太网设备的端口标识符。
- 返回:以太网设备连接到的NUMA套接字ID,如果无法确定套接字,则默认为零。当port_id值超出范围时返回-1。
3.17、rte_eth_rx_queue_setup()
分配并设置以太网设备的接收队列。
该函数为来自socket_id关联的内存区域的nb_rx_desc接收描述符分配一个连续的内存块,并使用从内存池mb_pool分配的网络缓冲区初始化每个接收描述符。
函数原型:
#include <rte_ethdev.h>
int rte_eth_rx_queue_setup (
uint16_t port_id,
uint16_t rx_queue_id,
uint16_t nb_rx_desc,
unsigned int socket_id,
const struct rte_eth_rxconf * rx_conf,
struct rte_mempool * mb_pool
);
参数 | 含义 |
---|---|
port_id | 以太网设备的端口标识符。 |
rx_queue_id | 要设置的接收队列的索引。该值必须在之前提供给 rte_eth_dev_configure() 的范围内 [0, nb_rx_queue - 1]。 |
nb_rx_desc | 要为接收环分配的接收描述符数。 |
socket_id | socket_id参数是 NUMA 情况下的套接字标识符。如果为环的接收描述符分配的 DMA 内存没有 NUMA 约束,则可以SOCKET_ID_ANY该值。 |
rx_conf | 指向要用于接收队列的配置数据的指针。允许空值,在这种情况下将使用默认的 Rx 配置。rx_conf结构包含一个rx_thresh结构,其中包含接收环的预取、主机和回写阈值寄存器的值。 |
mb_pool | 指向要从中分配rte_mbuf网络内存缓冲区以填充接收环的每个描述符的内存池的指针。有两个选项可用于提供 Rx 缓冲区配置:(1)mb_pool不为 NULL,rx_conf.rx_nseg 为 0(单个池)。(2)mb_pool为 NULL,rx_conf.rx_seg 不为 NULL,rx_conf.rx_nseg 不是 0(多段描述)。仅当在卸载中设置了标志RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT时才采取。 |
返回值 | 含义 |
---|---|
0 | 成功,接收队列设置正确。 |
EIO | 设备被移除。 |
ENODEV | port_id无效。 |
EINVAL | 内存池指针为 null,或者可从此内存池分配的网络缓冲区的大小不适合设备控制器允许的各种缓冲区大小。 |
ENOMEM | 初始化接收描述符时,无法分配接收环描述符或从内存池中分配网络内存缓冲区。 |
3.18、rte_eth_tx_queue_setup()
分配并设置以太网设备的传输队列。
函数原型:
#include <rte_ethdev.h>
int rte_eth_tx_queue_setup (
uint16_t port_id,
uint16_t tx_queue_id,
uint16_t nb_tx_desc,
unsigned int socket_id,
const struct rte_eth_txconf * tx_conf
);
成功,返回0,发送队列正确建立。
失败,返回-ENOMEM,无法分配传输环描述符。
参数 | 含义 |
---|---|
port_id | 以太网设备的端口标识符。 |
tx_queue_id | 要设置的传输队列的索引。该值必须在之前提供给 rte_eth_dev_configure() 的范围内 [0, nb_tx_queue - 1]。 |
nb_tx_desc | 要为传输环分配的传输描述符的数量。 |
socket_id | socket_id参数是 NUMA 情况下的套接字标识符。如果为环的传输描述符分配的 DMA 内存没有 NUMA 约束,则可以SOCKET_ID_ANY其值。 |
tx_conf | 指向要用于传输队列的配置数据的指针。允许使用 NULL 值,在这种情况下,将使用默认的 Tx 配置。 |
3.19、rte_eth_dev_start()
启动以太网设备。
设备启动步骤是最后一个步骤,包括设置配置的卸载功能以及启动设备的发送和接收单元。
设备RTE_ETH_DEV_NOLIVE_MAC_ADDR标志会导致在调用 PMD 端口启动回调函数之前设置 MAC 地址。
成功后,可以调用以太网 API 导出的所有基本功能(链路状态、接收/传输等)。
函数原型:
#include <rte_ethdev.h>
int rte_eth_dev_start(uint16_t port_id);
- 成功返回0;
- 失败返回负数。
3.20、rte_eth_macaddr_get()
获取以太网设备MAC地址。
#include <rte_ethdev.h>
int rte_eth_macaddr_get (uint16_t port_id, struct rte_ether_addr *mac_addr);
- port_id:以太网设备的端口标识符。
- mac_addr:存放mac地址的地址指针。
返回值 | 含义 |
---|---|
0 | 成功 |
ENODEV | port_id无效。 |
EINVAL | 无效参数 |
3.21、rte_eth_rx_burst()
返回实际检索到的数据包数,即有效提供给rx_pkts数组的rte_mbuf数据结构数。返回值等于 nb_pkts 表示 Rx 队列至少包含 rx_pkts 数据包,这可能表示其他接收的数据包仍保留在输入队列中。
函数不提供任何错误通知,以避免相应的开销。作为提示,一旦系统地返回给定尝试次数的 0 值,上层应用程序可能会检查设备链接的状态。
函数原型:
static uint16_t rte_eth_rx_burst (
uint16_t port_id,
uint16_t queue_id,
struct rte_mbuf ** rx_pkts,
const uint16_t nb_pkts
);
参数 | 含义 |
---|---|
port_id | 以太网设备的端口标识符。 |
queue_id | 要从中检索输入数据包的接收队列的索引。该值必须在之前提供给 rte_eth_dev_configure() 的范围内 [0, nb_rx_queue - 1]。 |
rx_pkts | 指向rte_mbuf结构的指针数组的地址,这些结构必须足够大才能在其中存储nb_pkts指针。 |
nb_pkts | 要检索的最大数据包数。该值必须可被 8 整除才能与任何驱动程序一起使用。 |
返回实际检索到的数据包数,即指向有效提供给rx_pkts阵列rte_mbuf结构的指针数。
3.22、rte_pktmbuf_mtod_offset()
指向mbuf中数据偏移量的宏。
函数原型:
#include <rte_mbuf_core.h>
#define rte_pktmbuf_mtod_offset (m, t, o ) ((t)(void *)((char *)(m)->buf_addr + (m)->data_off + (o)))
返回的指针被强制转换为t类型。在使用这个函数之前,用户必须确保第一个段足够大以容纳它的数据。
参数:
- m:mbuf数据结构包。
- t:要强制转换的类型。
- o:偏移位置。
3.23、rte_pktmbuf_mtod()
指向mbuf中数据开始的宏。
函数原型:
#include <rte_mbuf_core.h>
#define rte_pktmbuf_mtod (m,t ) rte_pktmbuf_mtod_offset(m, t, 0)
- m:mbuf数据结构包。
- t:要强制转换的类型。
返回的指针被强制转换为t类型。在使用这个函数之前,用户必须确保第一个段足够大以容纳它的数据。
3.24、rte_cpu_to_be_16()
将一个16位的值从CPU顺序转换为大端序。
函数原型:
#include <rte_byteorder.h>
static rte_be16_t rte_cpu_to_be_16(uint16_t x);
3.25、rte_memcpy()
将字节从一个位置复制到另一个位置,位置不能重叠。
返回指向目标数据的指针。
函数原型:
#include <rte_memcpy.h>
static void* rte_memcpy (
void * dst,
const void * src,
size_t n
);
注意:
(1)这是作为宏实现的,因此不应获取其地址,并且需要小心,因为参数表达式可能会被多次计算。
(2)对于要启用 AVX-512 memcpy 实现的 x86 平台,请在 CFLAGS 中设置 -DRTE_MEMCPY_AVX512 宏,或者在包含rte_memcpy头文件之前在源文件中显式定义RTE_MEMCPY_AVX512宏。
参数 | 含义 |
---|---|
dst | 指向数据目标的指针。 |
src | 指向源数据的指针。 |
n | 要复制的字节数。 |
3.26、rte_pktmbuf_alloc()
从内存池分配一个新的mbuf。
这个新的mbuf包含一个长度为0的段。指向数据的指针被初始化为在缓冲区中有一些字节的净空空间(如果缓冲区大小允许)。
#include <rte_mbuf.h>
static struct rte_mbuf* rte_pktmbuf_alloc(struct rte_mempool * mp);
mp:分配mbuf的内存池。
成功返回新的mbuf指针。
失败返回NULL。
3.27、rte_eth_tx_burst()
在以太网设备的传输队列上发送突发输出数据包。
#include <rte_ethdev.h>
static uint16_t rte_eth_tx_burst(
uint16_t port_id,
uint16_t queue_id,
struct rte_mbuf ** tx_pkts,
uint16_t nb_pkts
);```
参数 | 含义 |
---|---|
port_id | 以太网设备的端口标识符。 |
queue_id | 必须通过其发送输出数据包的传输队列的索引。该值必须在之前提供给 rte_eth_dev_configure() 的范围内 [0, nb_tx_queue - 1]。 |
tx_pkts | 指向包含输出数据包的rte_mbuf结构的nb_pkts指针数组的地址。 |
nb_pkts | 要传输的最大数据包数。 |
返回实际存储在传输环的传输描述符中的输出数据包数。当传输环已满或已满时,返回值可以小于tx_pkts参数的值。
3.28、rte_pktmbuf_free()
将数据包 mbuf 释放回其原始内存池。
释放 mbuf 及其所有段,以防出现链接缓冲区。每个段都会添加回其原始内存池中。
函数原型:
#include <rte_mbuf.h>
static void rte_pktmbuf_free ( struct rte_mbuf * m )
m:要释放的数据包 mbuf。如果为 NULL,则该函数不执行任何操作。
3.29、DPDK定义的协议头
3.29.1、rte_ether_hdr
以太网报头:包含目的地址、源地址和帧类型。
#include <rte_ether.h>
#define RTE_ETHER_ADDR_LEN 6
typedef uint16_t rte_be16_t
struct rte_ether_addr{
uint8_t addr_bytes [RTE_ETHER_ADDR_LEN];
};
struct rte_ether_hdr{
struct rte_ether_addr dst_addr;
struct rte_ether_addr src_addr;
rte_be16_t ether_type;
};
3.29.2、rte_ipv4_hdr
IPv4 Header
#include <rte_ip.h>
struct rte_ipv4_hdr {
__extension__
union {
uint8_t version_ihl;
struct {
#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
uint8_t ihl:4;
uint8_t version:4;
#elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN
uint8_t version:4;
uint8_t ihl:4;
#endif
};
};
uint8_t type_of_service;
rte_be16_t total_length;
rte_be16_t packet_id;
rte_be16_t fragment_offset;
uint8_t time_to_live;
uint8_t next_proto_id;
rte_be16_t hdr_checksum;
rte_be32_t src_addr;
rte_be32_t dst_addr;
} __rte_packed;
3.29.3、rte_udp_hdr
UDP Header
#include <rte_udp.h>
struct rte_udp_hdr {
rte_be16_t src_port;
rte_be16_t dst_port;
rte_be16_t dgram_len;
rte_be16_t dgram_cksum;
} __rte_packed;
3.29.4、rte_tcp_hdr
TCP Header
#include <rte_tcp.h>
struct rte_tcp_hdr {
rte_be16_t src_port;
rte_be16_t dst_port;
rte_be32_t sent_seq;
rte_be32_t recv_ack;
uint8_t data_off;
uint8_t tcp_flags;
rte_be16_t rx_win;
rte_be16_t cksum;
rte_be16_t tcp_urp;
} __rte_packed;
四、代码实践:DPDK实现协议栈
以TCP/UDP为例,实现一个简单示例。
主要有如下实现步骤:
(1)初始化EA。
(2)创建mbuf内存池
(3)配置以太网设备端口。
(4)设置以太网收发队列
(4)开启以太网设备
(5)收取网络数据包;协议解析。
(6)发送网络数据包;协议打包。
实现代码如下:
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <stdio.h>
#include <arpa/inet.h>
#define ENABLE_SEND 1
#define ENABLE_ARP 1
#define ENABLE_TCP 1
#define TCP_MAX_SEQ 4294967295
#define NUM_MBUFS (4096-1)
#define BURST_SIZE 32
#if ENABLE_SEND
static uint32_t gSrcIp; //
static uint32_t gDstIp;
static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN];
static uint8_t gDstMac[RTE_ETHER_ADDR_LEN];
static uint16_t gSrcPort;
static uint16_t gDstPort;
#endif
#if ENABLE_TCP
uint32_t seqnum;
uint32_t acknum = 0;
uint8_t flag;
#endif
int gDpdkPortId = 0;
static const struct rte_eth_conf port_conf_default = {
.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};
static void ng_init_port(struct rte_mempool *mbuf_pool) {
// 获取可用的以太网设备数量
uint16_t nb_sys_ports= rte_eth_dev_count_avail(); //
if (nb_sys_ports == 0) {
rte_exit(EXIT_FAILURE, "No Supported eth found\n");
}
// 检索以太网设备的上下文信息
struct rte_eth_dev_info dev_info;
rte_eth_dev_info_get(gDpdkPortId, &dev_info); //
// 配置以太网设备
const int num_rx_queues = 1;
const int num_tx_queues = 1;
struct rte_eth_conf port_conf = port_conf_default;
rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf);
//分配并设置以太网设备的接收队列。
if (rte_eth_rx_queue_setup(gDpdkPortId, 0 , 1024,
rte_eth_dev_socket_id(gDpdkPortId),NULL, mbuf_pool) < 0) {
rte_exit(EXIT_FAILURE, "Could not setup RX queue\n");
}
#if ENABLE_SEND
// 分配并设置以太网设备的传输队列。
struct rte_eth_txconf txq_conf = dev_info.default_txconf;
txq_conf.offloads = port_conf.rxmode.offloads;
if (rte_eth_tx_queue_setup(gDpdkPortId, 0 , 1024,
rte_eth_dev_socket_id(gDpdkPortId), &txq_conf) < 0) {
rte_exit(EXIT_FAILURE, "Could not setup TX queue\n");
}
#endif
//启动以太网设备
if (rte_eth_dev_start(gDpdkPortId) < 0 ) {
rte_exit(EXIT_FAILURE, "Could not start\n");
}
}
static int ng_encode_udp_pkt(uint8_t *msg, unsigned char *data, uint16_t total_len) {
// encode
// 1 ethhdr
struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;
rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);
eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);
// 2 iphdr
struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(msg + sizeof(struct rte_ether_hdr));
ip->version_ihl = 0x45;
ip->type_of_service = 0;
ip->total_length = htons(total_len - sizeof(struct rte_ether_hdr));
ip->packet_id = 0;
ip->fragment_offset = 0;
ip->time_to_live = 64; // ttl = 64
ip->next_proto_id = IPPROTO_UDP;
ip->src_addr = gSrcIp;
ip->dst_addr = gDstIp;
ip->hdr_checksum = 0;
ip->hdr_checksum = rte_ipv4_cksum(ip);
// 3 udphdr
struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(msg + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr));
udp->src_port = gSrcPort;
udp->dst_port = gDstPort;
uint16_t udplen = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
udp->dgram_len = htons(udplen);
rte_memcpy((uint8_t*)(udp+1), data, udplen);
udp->dgram_cksum = 0;
udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);
struct in_addr addr;
addr.s_addr = gSrcIp;
printf(" --> src: %s:%d, ", inet_ntoa(addr), ntohs(gSrcPort));
addr.s_addr = gDstIp;
printf("dst: %s:%d\n", inet_ntoa(addr), ntohs(gDstPort));
return 0;
}
static struct rte_mbuf * ng_send(struct rte_mempool *mbuf_pool, uint8_t *data, uint16_t length) {
// mempool --> mbuf
const unsigned total_len = length + 42;
// 从内存池分配一个新的mbuf。
struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
if (!mbuf) {
rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
}
mbuf->pkt_len = total_len;
mbuf->data_len = total_len;
// 从mbuf中提取出pktdata
uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);
ng_encode_udp_pkt(pktdata, data, total_len);
return mbuf;
}
static int ng_encode_tcp_pkt(uint8_t *msg) {
// encode
// 1 ethhdr
struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;
rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);
eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);
// 2 iphdr
struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(msg + sizeof(struct rte_ether_hdr));
ip->version_ihl = 0x45;
ip->type_of_service = 0;
ip->total_length = htons(sizeof(struct rte_ipv4_hdr)+sizeof(struct rte_tcp_hdr));
ip->packet_id = 0;
ip->fragment_offset = 0;
ip->time_to_live = 64; // ttl = 64
ip->next_proto_id = IPPROTO_UDP;
ip->src_addr = gSrcIp;
ip->dst_addr = gDstIp;
ip->hdr_checksum = 0;
ip->hdr_checksum = rte_ipv4_cksum(ip);
// 3 tcphdr
struct rte_tcp_hdr *tcp = (struct rte_tcp_hdr *)(msg + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr));
tcp->src_port = gSrcPort;
tcp->dst_port = gDstPort;
//seqnum:代表对方发送数据的索引号
//acknum : 代表对方已收到我方数据的索引号。
uint32_t seed = time(NULL);
tcp->sent_seq = rand_r(&seed);//acknum表示我方应答字节序
tcp->recv_ack = seqnum;//seqnum表示应答对方的字节序
tcp->data_off = 0x50;
tcp->rx_win = 1024;
tcp->tcp_urp = 0;
tcp->tcp_flags = RTE_TCP_SYN_FLAG | RTE_TCP_ACK_FLAG;
tcp->cksum = 0;
tcp->cksum = rte_ipv4_udptcp_cksum(ip, tcp);
return 0;
}
static struct rte_mbuf * ng_send_tcp(struct rte_mempool *mbuf_pool) {
// mempool --> mbuf
const unsigned total_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr)+ sizeof(struct rte_tcp_hdr);
// 从内存池分配一个新的mbuf。
struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
if (!mbuf) {
rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
}
mbuf->pkt_len = total_len;
mbuf->data_len = total_len;
// 从mbuf中提取出pktdata
uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);
ng_encode_tcp_pkt(pktdata);
return mbuf;
}
int main(int argc, char *argv[]) {
//EAL初始化
if (rte_eal_init(argc, argv) < 0) {
rte_exit(EXIT_FAILURE, "Error with EAL init\n");
}
//创建并初始化packet mbuf池
struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_MBUFS,
0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
if (mbuf_pool == NULL) {
rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
}
ng_init_port(mbuf_pool);
//获取以太网设备的mac地址
rte_eth_macaddr_get(gDpdkPortId, (struct rte_ether_addr *)gSrcMac);
while (1) {
//从以太网设备的接收队列中检索突发的输入数据包。检索到的数据包存储在rte_mbuf结构中
struct rte_mbuf *mbufs[BURST_SIZE];
unsigned num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_SIZE);
if (num_recvd > BURST_SIZE) {
rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
}
unsigned i = 0;
for (i = 0;i < num_recvd;i ++) {
// 将mbufs数据包中的以太网头提取出来
struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*);
//将一个16位的值从CPU顺序转换为大端序
if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
continue;
}
// 将mbufs数据包中的ipv4头提取出来
struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *,
sizeof(struct rte_ether_hdr));
if (iphdr->next_proto_id == IPPROTO_UDP) {
struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);
#if ENABLE_SEND //
rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));
rte_memcpy(&gSrcPort, &udphdr->dst_port, sizeof(uint16_t));
rte_memcpy(&gDstPort, &udphdr->src_port, sizeof(uint16_t));
#endif
uint16_t length = ntohs(udphdr->dgram_len);
*((char*)udphdr + length) = '\0';
struct in_addr addr;
addr.s_addr = iphdr->src_addr;
printf("src: %s:%d, ", inet_ntoa(addr), ntohs(udphdr->src_port));
addr.s_addr = iphdr->dst_addr;
printf("dst: %s:%d, %s\n", inet_ntoa(addr), ntohs(udphdr->dst_port),
(char *)(udphdr+1));
#if ENABLE_SEND
struct rte_mbuf *txbuf = ng_send(mbuf_pool, (uint8_t *)(udphdr+1), length);
rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);
rte_pktmbuf_free(txbuf);
#endif
rte_pktmbuf_free(mbufs[i]);
}
else if (iphdr->next_proto_id == IPPROTO_TCP)
{
struct rte_tcp_hdr *tcphdr = (struct rte_tcp_hdr *)(iphdr + 1);
#if ENABLE_SEND //
rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));
rte_memcpy(&gSrcPort, &tcphdr->dst_port, sizeof(uint16_t));
rte_memcpy(&gDstPort, &tcphdr->src_port, sizeof(uint16_t));
#endif
if (tcphdr->tcp_flags & RTE_TCP_SYN_FLAG)
{
//第一次握手
seqnum = ntohl(tcphdr->sent_seq)+1;
struct rte_mbuf *txbuf = ng_send_tcp(mbuf_pool);
rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);
rte_pktmbuf_free(txbuf);
rte_pktmbuf_free(mbufs[i]);
}else if(tcphdr->tcp_flags & RTE_TCP_ACK_FLAG)
{
// 处理第三次
}
struct in_addr addr;
addr.s_addr = iphdr->src_addr;
printf("tcp src: %s:%d, ", inet_ntoa(addr), ntohs(tcphdr->src_port));
addr.s_addr = iphdr->dst_addr;
printf("tcp dst: %s:%d,seqnum: %u, flag: %x\n", inet_ntoa(addr), ntohs(tcphdr->dst_port),
seqnum, tcphdr->tcp_flags);
}
}
}
}
总结
dpdk可以实现旁路获取网络数据包,获取到的原始数据可以用户实现协议栈,以太网协议的解析 --> IP协议解析–>TCP/UDP解析等等。
在进行协议栈调试时,可以使用wareshare工具抓包分析。
DPDK实现协议栈的框图如下:
- 点赞
- 收藏
- 关注作者
评论(0)