Linux用户态协议栈与DPDK构建高性能应用

简介: 本文深入探讨了如何利用Linux用户态协议栈与DPDK构建高性能应用的方法和技巧。在现代网络编程中,性能是至关重要的,特别是在需要处理大量并发连接的场景下。传统的内核态网络栈在面对高负载时可能会遇到性能瓶颈,而本文介绍了如何借助用户态协议栈和DPDK来突破这些限制。从零开始搭建一个基于用户态协议栈和DPDK的网络应用并不是一件容易的事情,但本文将通过实际示例和步骤指导读者完成这一过程。最后,本文总结了使用用户态协议栈和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实现协议栈的框图如下:
image.png
image.png

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
2月前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
4天前
|
Prometheus 运维 监控
Prometheus+Grafana+NodeExporter:构建出色的Linux监控解决方案,让你的运维更轻松
本文介绍如何使用 Prometheus + Grafana + Node Exporter 搭建 Linux 主机监控系统。Prometheus 负责收集和存储指标数据,Grafana 用于可视化展示,Node Exporter 则采集主机的性能数据。通过 Docker 容器化部署,简化安装配置过程。完成安装后,配置 Prometheus 抓取节点数据,并在 Grafana 中添加数据源及导入仪表盘模板,实现对 Linux 主机的全面监控。整个过程简单易行,帮助运维人员轻松掌握系统状态。
42 3
|
1月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
46 5
|
1月前
|
存储 安全 关系型数据库
Linux系统在服务器领域的应用与优势###
本文深入探讨了Linux操作系统在服务器领域的广泛应用及其显著优势。通过分析其开源性、安全性、稳定性和高效性,揭示了为何Linux成为众多企业和开发者的首选服务器操作系统。文章还列举了Linux在服务器管理、性能优化和社区支持等方面的具体优势,为读者提供了全面而深入的理解。 ###
|
4月前
|
编解码 Linux 程序员
深度探索Linux操作系统 —— 构建根文件系统2
深度探索Linux操作系统 —— 构建根文件系统
53 12
|
4月前
|
前端开发 Linux
深度探索Linux操作系统 —— 构建桌面环境3
深度探索Linux操作系统 —— 构建桌面环境
67 12
|
4月前
|
存储 搜索推荐 Linux
深度探索Linux操作系统 —— 构建桌面环境1
深度探索Linux操作系统 —— 构建桌面环境
112 8
|
4月前
|
Linux 编译器 C语言
深度探索Linux操作系统 —— 构建桌面环境2
深度探索Linux操作系统 —— 构建桌面环境
47 6
|
4月前
|
Linux Shell 网络安全
深度探索Linux操作系统 —— 构建根文件系统1
深度探索Linux操作系统 —— 构建根文件系统
62 6
|
4月前
|
存储 缓存 IDE
深度探索Linux操作系统 —— 构建initramfs
深度探索Linux操作系统 —— 构建initramfs
147 5

热门文章

最新文章