用户态协议栈设计与dpdk

  • dpdk环境开启
  • Windowe下配置静态IP表
  • DPDK API介绍
    • struct rte_memzone结构体
    • struct rte_mempool结构体
    • struct rte_eth_dev_info结构体
    • struct rte_eth_conf结构体
    • struct rte_eth_rxconf结构体
    • struct rte_eth_txconf结构体
    • rte_eal_init()
    • rte_exit()
    • rte_memzone_reserve()
    • rte_mempool_create()
    • rte_pktmbuf_pool_create()
    • rte_socket_id()
    • rte_eth_dev_count_avail()
    • rte_eth_dev_info_get()
    • rte_eth_dev_configure()
    • rte_eth_dev_socket_id()
    • rte_eth_rx_queue_setup()
    • rte_eth_tx_queue_setup()
    • rte_eth_dev_start()
    • rte_eth_macaddr_get()
    • rte_eth_rx_burst()
    • rte_pktmbuf_mtod_offset()
    • rte_pktmbuf_mtod()
    • rte_cpu_to_be_16()
    • rte_memcpy()
    • rte_pktmbuf_alloc()
    • rte_eth_tx_burst()
    • rte_pktmbuf_free()
    • DPDK定义的协议头
      • rte_ether_hdr
      • rte_ipv4_hdr
      • rte_udp_hdr
      • rte_tcp_hdr
  • 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 ScriptOption: 43Unloading any existing DPDK UIO module
Loading uio module
Loading DPDK UIO modulePress enter to continue .........Option: 44Unloading any existing VFIO module
Loading VFIO module
chmod /dev/vfio
OKPress enter to continue .........Option: 45Unloading any existing DPDK KNI module
Loading DPDK KNI modulePress enter to continue ...
......Option: 46Removing currently reserved hugepages
Unmounting /mnt/huge and removing directoryInput the number of 1048576kB hugepagesExample: 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 hugetlbfsPress enter to continue .........Option: 47Removing currently reserved hugepages
Unmounting /mnt/huge and removing directoryInput the number of 1048576kB hugepages for each nodeExample: 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 hugetlbfsPress enter to continue ...
......Option: 49Network 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
OKPress enter to continue .........Option: 60

Windowe下配置静态IP表

需要管理员权限
(1)查看要加入的静态表接口

arp -a

示例结果如下,可以看到0x13这个位置,后面步骤用到。

接口: 192.168.2.130 --- 0x13Internet 地址         物理地址              类型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 119          35        1500  connected     以太网 25          35        1500  connected     VMware Network Adapter VMnet115          35        1500  connected     VMware Network Adapter VMnet839          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。

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的标识

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 内存块列表

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];
};

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;
};

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 队列 IDuint64_t              offloads;//卸载,每队列 Rx 卸载要使用 RTE_ETH_RX_OFFLOAD_* 标志进行设置。union rte_eth_rxseg *     rx_seg;//指向整个数据包的分段描述数组。数组元素是连续 Rx 段的属性。uint64_t                reserved_64s [2];//保留void *                     reserved_ptrs [2];//保留
};

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];//保留
};

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 表示服务核心未能成功启动。

rte_exit()

立即终止 应用程序,打印错误消息并将exit_code返回到 shell。此函数不会有返回。
函数原型:

#include <rte_common.h>__rte_noreturn void rte_exit(int    exit_code,const char *  format,... );
参数 含义
exit_code 应用程序要返回的退出代码
format 用于打印消息的格式字符串。这可以包括 printf 格式字符,这些字符将使用函数的任何其他参数进行扩展。

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

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

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

rte_socket_id()

函数原型:

#include <rte_Icore.h>unsigned int rte_socket_id(void);

返回正在运行的逻辑核心的物理套接字的ID。
返回当前lcoreid的物理套接字的ID。

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 );

返回可用的以太网设备的数量。

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 参数错误。

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 驱动程序配置函数返回的错误代码。

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。

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 初始化接收描述符时,无法分配接收环描述符或从内存池中分配网络内存缓冲区。

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 配置。

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;
失败返回负数。

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 无效参数

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结构的指针数。

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类型。在使用这个函数之前,用户必须确保第一个段足够大以容纳它的数据。

Parameters
m:mbuf数据结构包。
t:要强制转换的类型。
o:偏移位置。

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类型。在使用这个函数之前,用户必须确保第一个段足够大以容纳它的数据。

rte_cpu_to_be_16()

将一个16位的值从CPU顺序转换为大端序。
函数原型:

#include <rte_byteorder.h>static rte_be16_t rte_cpu_to_be_16(uint16_t  x);

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 要复制的字节数。

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。

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参数的值。

rte_pktmbuf_free()

将数据包 mbuf 释放回其原始内存池。
释放 mbuf 及其所有段,以防出现链接缓冲区。每个段都会添加回其原始内存池中。
函数原型:

#include <rte_mbuf.h>static void rte_pktmbuf_free  (   struct rte_mbuf *   m   )

m:要释放的数据包 mbuf。如果为 NULL,则该函数不执行任何操作。

DPDK定义的协议头

rte_ether_hdr

以太网报头:包含目的地址、源地址和帧类型。

#include <rte_ether.h>#define RTE_ETHER_ADDR_LEN   6typedef uint16_t rte_be16_tstruct 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;
};

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_ENDIANuint8_t ihl:4;     uint8_t version:4; #elif RTE_BYTE_ORDER == RTE_BIG_ENDIANuint8_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;

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;

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_SENDstatic 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_TCPuint32_t seqnum;
uint32_t acknum = 0;
uint8_t  flag;#endifint 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 ethhdrstruct 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 = 64ip->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 --> mbufconst 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中提取出pktdatauint8_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 ethhdrstruct 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 = 64ip->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 --> mbufconst 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中提取出pktdatauint8_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));#endifuint16_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_SENDstruct rte_mbuf *txbuf = ng_send(mbuf_pool, (uint8_t *)(udphdr+1), length);rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);rte_pktmbuf_free(txbuf);#endifrte_pktmbuf_free(mbufs[i]);}else if (iphdr->next_proto_id == IPPROTO_UDP){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));#endifif (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实现协议栈的框图如下:

#mermaid-svg-9zJkCVDTiGnVBGtt {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt .error-icon{fill:#552222;}#mermaid-svg-9zJkCVDTiGnVBGtt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9zJkCVDTiGnVBGtt .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-9zJkCVDTiGnVBGtt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9zJkCVDTiGnVBGtt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9zJkCVDTiGnVBGtt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9zJkCVDTiGnVBGtt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9zJkCVDTiGnVBGtt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9zJkCVDTiGnVBGtt .marker.cross{stroke:#333333;}#mermaid-svg-9zJkCVDTiGnVBGtt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9zJkCVDTiGnVBGtt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt .cluster-label text{fill:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt .cluster-label span{color:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt .label text,#mermaid-svg-9zJkCVDTiGnVBGtt span{fill:#333;color:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt .node rect,#mermaid-svg-9zJkCVDTiGnVBGtt .node circle,#mermaid-svg-9zJkCVDTiGnVBGtt .node ellipse,#mermaid-svg-9zJkCVDTiGnVBGtt .node polygon,#mermaid-svg-9zJkCVDTiGnVBGtt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9zJkCVDTiGnVBGtt .node .label{text-align:center;}#mermaid-svg-9zJkCVDTiGnVBGtt .node.clickable{cursor:pointer;}#mermaid-svg-9zJkCVDTiGnVBGtt .arrowheadPath{fill:#333333;}#mermaid-svg-9zJkCVDTiGnVBGtt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9zJkCVDTiGnVBGtt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9zJkCVDTiGnVBGtt .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-9zJkCVDTiGnVBGtt .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-9zJkCVDTiGnVBGtt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9zJkCVDTiGnVBGtt .cluster text{fill:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt .cluster span{color:#333;}#mermaid-svg-9zJkCVDTiGnVBGtt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9zJkCVDTiGnVBGtt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

实现
init port
检索以太网设备上下文-->
rte_eth_dev_info_get()
获取以太网设备可用数量-->
rte_eth_dev_count_avail()
配置以太网设备-->
rte_eth_dev_configure()
设置以太网设备接收队列-->
rte_eth_rx_queue_setup()
设置以太网设备传输队列-->
rte_eth_tx_queue_setup()
启动以太网设备-->
rte_eth_dev_start()
读写循环--while
解析数据包-->
rte_pktmbuf_mtod()
收取网络数据包-->
rte_eth_rx_burst()
解析数据包-->
rte_pktmbuf_mtod_offset()
协议分析
申请mbuff-->
rte_pktmbuf_alloc()
协议打包
发送网络数据包-->
rte_eth_tx_burst
释放内存-->
rte_pktmbuf_free
初始化EAL -->
rte_eal_init()
创建mbuf池 -->
rte_pktmbuf_pool_create()
init port

后言

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux系统提升感兴趣的读者,可以点击链接,详细查看详细的服务:C/C++服务器课程

Linux网络设计之用户态协议栈与dpdk相关推荐

  1. 100行源代码搞定用户态协议栈丨udp,icmp,arp协议的现实丨网络协议栈丨Linux服务器开发丨C++后端开发丨Linux后台开发

    100行源代码搞定用户态协议栈 视频讲解如下,点击观看: 100行源代码搞定用户态协议栈丨udp,icmp,arp协议的现实丨网络协议栈丨Linux服务器开发丨C++后端开发丨Linux后台开发丨网络 ...

  2. 【Linux服务器开发系列】手写用户态协议栈,udpipeth数据包的封装,零拷贝的实现,柔性数组

    视频教你手写网络协议栈,保证大家能学会,耐心看 1. 用户态协议栈 2. udp/ip/eth数据包的封装 3. 零拷贝的实现 4. 零长数组(柔性数组) [Linux服务器开发系列]手写用户态协议栈 ...

  3. 用户态协议栈设计实现

    目录 1. 网络模式 2. 获取原始数据 3. UDP数据帧格式 以太网协议头(数据链路层) -- 14byte IP数据头(网络层) -- 20byte UDP协议的数据格式(传输层) -- 8by ...

  4. 用户态协议栈之tcp/ip设计

    1 解决问题 对于服务器而言,正常的接受一帧Data的过程,客户端先通过网络发送一帧数据到网卡,再经过协议栈,最后通过系统调用叨叨应用程序.具体的流程图如下: 针对上面的两个流程,涉及到两次拷贝(网卡 ...

  5. 用户态协议栈tcp/ip设计

    1 解决问题 对于服务器而言,正常的接受一帧Data的过程,客户端先通过网络发送一帧数据到网卡,再经过协议栈,最后通过系统调用叨叨应用程序.具体的流程图如下: 针对上面的两个流程,涉及到两次拷贝(网卡 ...

  6. 一文彻底掌握用户态协议栈,一看就懂的

    用户态协议栈 那我们先跟大家解释这个协议栈这个东西啊协议栈这个东西呢或多或少啊各个朋友应该都听过,我们站在一个设计者的角度,站在一个设计者的角度,站在tcpip的个人的角度,我们怎么去设计这个协议的? ...

  7. 架构决定可扩展性--聊聊用户态协议栈的意义

    在进入这个话题之前先说说通用和专业之间的区别.   举个很好的例子,好比我们个人,绝大部分的人都是"通用"的,而只有极少部分的人是"专业"的.通用的人主要目标是 ...

  8. 深入浅出用户态协议栈

    一.前言 在讲网络协议栈前,先理解一个数据包在网络传输是一个怎么样的流程,如下图所示. 正常的流程是网卡接收到数据后,把数据copy到协议栈(sk_buff),协议栈把sk_buff数据解析完后再把数 ...

  9. 【高阶知识】用户态协议栈之Epoll实现原理

    Epoll 是 Linux IO 多路复用的管理机制.作为现在 Linux 平台高性能网络 IO 必要的组件.内核的实现可以参照:fs/eventpoll.c . 为什么需要自己实现 epoll 呢? ...

最新文章

  1. Redis最佳实践:7个维度+43条使用规范,带你彻底玩转Redis | 附实践清单
  2. 全球第一家只接收BCH的慈善组织
  3. mysql二进制日志文件差不多_mysql数据同步-基于二进制日志文件和position复制点的方式...
  4. PHP获取当前时间戳,当前时间、及解决时区问题
  5. STL的Vector, List and Deque
  6. opencv图像清晰度计算_收藏|分析君带你认识Python中的十大图像处理工具
  7. php实现无限级树型菜单(函数递归算法)
  8. Android学习系列(41)--Android Studio简单使用
  9. ansible 循环与条件判断when
  10. 我的Mac中毒了,病毒居然叫做MacPerformance
  11. 判断闰年简单逻辑运算符
  12. 什么是CAD定位功能?CAD定位功能如何使用?
  13. 大学计算机课程ppt,以计算思维为导向的大学计算机基础课
  14. 数析三剑客 numpy pandas matplotlib 基础操作
  15. SwiftUI 语音合成与语言识别教程之 03 实现录音文件转文字(含完整项目源码)SFSpeechURLRecognitionRequest
  16. 每天读一点好玩心理学--酒吧
  17. 最小树形图——朱刘算法学习小记
  18. 广告联盟反作弊一些常识
  19. 浙江大学郑强教授的演讲(国民必看)
  20. Framework学习(五)应用程序启动过程

热门文章

  1. 对Java平台的理解
  2. 脱水缩合(字符串处理)
  3. JESD204接口调试总结——Xilinx JESD204C IP工程应用
  4. 考研二战日记-第36天小结
  5. EndNote与word的兼容性
  6. 元AI ChatGPT系统4.0.0独立开源版 + H5端+小程序安装配置教程
  7. SpringCloud 之 聚合项目 IDEA 创建(准备篇)
  8. SQL案例学习-微信好友关系
  9. 海外CDN加速的好处
  10. Qt实现Excel表格的读写操作(office,WPS)