0
开始配置dpdk
环境的虚拟机外部配置
配置网卡
初始网卡选择桥接模式作为dpdk
运行的网卡,添加第二张网卡NAT
模式用于ssh
连接
内存与CPU
核数选择
8g
以上,核数选择8
核以上修改源文件让网卡支持多队列
vmx
文件,修改ethernet0.virtualDev = "e1000"
的值并添加一行ethernet0.virtualDev = "vmxnet3"
ethernet0.wakeOnPcktRcv = "TRUE"
内部配置
配置网卡
在安装``ubunt_userver时,存在多个网卡时需要选择默认网关网卡,这里选择用于
ssh连接网卡
ens33`
ens160
是多队列网卡,配置其静态ip
地址,在/etc/network/interface
追加下面行
auto ens160
iface ens160 inet static
address 10.17.43.34
netmask 255.255.0.0
ip
需要根据主机的ip
地址的网段和掩码一致,因为这是桥接模式,第二章nat
网卡直接使用dhcp
自动获取ip
网关dns
服务器即可
使用route -n
可以查看默认网关
修改系统启动参数
修改etc/default/grub
文件
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""
>>>>修改后
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX="find_preseed=/preseed.cfg noprompt net.ifnames=0 biosdevname=0 default_hugepages=1G hugepagesz=2M hugepages=1024 isolCPUs=0-2"
修改后update-grub
后reboot
会发现网卡名字变为了eth*
,此时修改/etc/network/interfaces
文件
eth0
对应第一张也就是桥接模式的网卡,就替换ens160
eth1
替换ens33
dpdk
的编译usertool/dpdk-setup.sh
下载dpdk-19.08.2
,在dpdk-stable-19.08.2
文件夹下执行./usertools/dpdk-setup.sh
选择不带app
的x86_64-native-linux-gcc[39]
,因为它可以改源码
设置运行时环境变量
export RTE_SDK=/home/ljq/share/module/dpdk-stable-19.08.2/
设置运行时环境的目标
export RTE_TARGET=x86_64-native-linux-gcc
./usertools/dpdk-setup.sh
选择[43] or [44]
插入IGB_UIO
模块 or
插入V
选择[45]
插入KNI
模块
选择[46] or [47]
巨页的配置
46
代表采用no numa
方式管理多内存编号,47
代表采用numa
方式管理多内存编号
numa
表示不是同一的编码方式,每个内存块都从索引0
开始,no-numa
表示同一编码方式,内存块之间的索引是连着的
写512
来设置1g
的巨页
选择[49] or [50]
把对应网卡的绑定设备绑定在哪个模块上[49]->UIO
[50]->VFIO
两者选一个 我选49
接着输入PCI
地址来绑定IGB UIO
找到eth0
对应的行,输入其前面的数字字符串 我的是0000:03:00.0
回车后会警告:
Warning: routing table indicates that interface 0000:03:00.0 is active. Not modifying OK
,意思是还在工作中 没有修改成功
我们要给他down
掉,就是不让对应的NIC
接收数据,让dpdk
去绑定这个网卡去截获,网卡的数据就不会往内核里面走了
ifconfig eth0 down
再次选择49
,输入0000:03:00.0
后绑定成功
此时ifconfig
是不存在eth0
,也就是说明此时eth0
对应的NIC
不会再去接管他了,之后所有数据就是走到dpdk
里面了
编译前需要安装的
sudo apt-get install libnuma-dev libpcap-dev make
dpdk
接管网卡的两种方式
- 通用型
IO
- 虚拟
IO
注意有可能45出会出现错误,此时使用
grep CONFIG_RTE_KNI /boot/config-$(uname -r)
查看CONFIG_RTE_KNI
是否被设置为了m
或者y
,如果没有则需要手动添加进去
dpdk
需要什么配置来支持dpdk
要指定用哪一个队列来接收数据
vmware
的网络适配器就是一个网卡,是vmware
模拟的)NIC
NIC
)
NIC
什么数据结构存?ifconfig
就是查看NIC
的数量以及相关信息NIC
都会将它组织成sk_buff
(其中有很多指针,指向数据包中的各个层的头部),发送给协议栈,协议栈就解析其各个头NIC
把数据抛给内核协议栈,协议栈解析其sk_buff
中指向的各个头,注意这个协议栈是所有网卡共用的
NIC
驱动的数据交互是怎样的
NIC
都会将它组织成sk_buff
(其中有很多指针,指向数据包中的各个层的头部),发送给协议栈,协议栈就解析其各个头Posix API
应用层就接收到了…
网卡数据拷贝到NIC
,组织出sk_buff
APP
调用recv..
将数据从内核态拷贝到用户态
上述操作需要CPU
的参与
DPDK
介绍dpdk
做的事把网卡通过一种映射的方式,把网卡接收数据的存储空间映射到用户态的内存空间中,
这其中没有拷贝,是通过DMA
的方式,直接网络适配器与硬盘驱动器与内存交互
dpdk
主要做的事:把网卡的数据快速转移到用户态内存里
dpdk
如何组织映射的数据Huge Page
4k
,假设一秒钟来了1
个G
的数据,那么4k
的页划分的包就为256x1024
,太多了,于是dpdk
采用巨页dpdk
优化了什么对thread
做CPU
的亲缘性
CPU
上运行KNI
机制用dpdk
只想处理一种协议,其他协议还是通过内核协议栈来处理
dpdk
提供了一种方式,将不需要的数据写入到内核协议栈,内核网络接口(Kernel Network Interface
)DPDK
的技术边界dpdk
处理数据时是跨过了NIC
这一层,在NIC
接收数据前就截获了数据,与NIC
平级,改变了数据流程
DPDK
能做什么SDN
网络定制开发ddos
攻击
VPN
vxline
或加上隧道技术再从网卡中发送出去DPDK
该怎么学环境搭建完后该做的事
coding
,代码可控
eth,ip,arp,icmp,tcp,udp
)posix api, epoll
的实现vpp
ovs
dpvs
pktgen
1-2
款上线的产品。。。coding
rte_eal_init(argc, argv)
初始化dpdk
应用rte_pktmbuf_pool_create
创建rte_pktmbuf_pool_create(
const char * name,
unsigned int n, // 初始化mbufpoll池能放多少个mbuf
unsigned int cache_size, // 0
uint16_t priv_size, // 0
unit16_t data_room_size,
int socket_id // 设置为一个全局的一个变量per_lcore_socket_id
);
// 返回一个rte_mempoll结构体
mbuf
就是内核中的sk_buf
name
:Packet Buffer
池的名称,可以是任何字符串,建议使用有意义的名称。n
:Packet Buffer
池中 Packet Buffer
的数量,建议设置为大于等于 1024
。cache_size
:Packet Buffer
池中的本地缓存大小,可以根据使用场景进行调整,建议设置为 Packet Buffer
数量的1/8
左右。priv_size
:Packet Buffer
中每个 Packet Buffer
的私有数据大小,如果不需要私有数据,则可以设置为 0
。data_room_size
:Packet Buffer
中数据区域的大小,即可以存储数据包的空间大小,建议设置为 2048
或更大。socket_id
:用于分配内存的 NUMA
节点编号,建议设置为套接字绑定的CPU
所在的NUMA
节点编号。在TCP/IP
网络中,当一个网络设备接收到来自其他设备的数据包时,就会触发RX
操作,将数据包解析并交给上层协议或应用程序
tx与rx是什么分别用在什么地方
txqueue
主要用于管理应用程序待发送的数据包,当应用程序需要向网络中发送数据时,首先将其封装成一个数据包,并添加到txqueue
中。
rxqueue
主要用于管理已经从网络中接收到的数据包,当网络中有数据包到达时,DPDK
会将其添加到rxqueue
中,应用程序可以从rxqueue
中取出数据包并进行进一步的处理。
// setup
uint16_t nb_rx_queues = 1;
uint16_t nb_tx_queues = 0;
const struct rte_eth_conf port_conf_default = {
.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};
rte_eth_dev_configure(gDpdkPortId,
nb_rx_queues,
nb_tx_queues,
&port_conf_default);
// 设置第gDpdkPortId个绑定网口的信息,使用了索引为0对应的rx队列,设置了rx队列的大小为128,使用mbuf_pool来存储接收队列缓存
rte_eth_rx_queue_setup(gDpdkPortId, // 设置第gDpdkPortId个绑定网口的信息
0, // 使用了索引为0对应的rx队列
128, // 设置了rx队列的大小为128
rte_eth_dev_socket_id(gDpdkPortId),
NULL,
mbuf_pool); // 使用mbuf_pool来存储接收队列缓存
//
rte_eth_dev_start(gDpdkPortId);
// disable
//rte_eth_promiscuous_enable(gDpdkPortId); //
rte_eth_dev_configure
API介绍
ret_eth_dev_configure(
unit16_t port_id, //网口 nic 的id是什么
unit16_t nb_rx_q, // rx队列 ,对应索引 0表示使用第一个
unit16_t nb_tx_q,
const struct rte_eth_conf* dev_conf // 配置信息 每个包的长度
);
port_id
:以太网设备的端口 ID。nb_rx_queue
:用于接收数据包的 RX 队列数量。nb_tx_queue
:用于发送数据包的 TX 队列数量。eth_conf
:一个结构体,包含了以太网设备的配置信息,如 CRC
校验、硬件 RSS
散列、速率控制,以及杂项配置等。rte_eth_rx_queue_setup
API
介绍
int rte_eth_rx_queue_setup(
uint16_t port_id, // 要绑定的网口索引号 0代表使用第一个网口
uint16_t rx_queue_id, // 要绑定的队列的索引,0代表绑定第一个rx队列
uint16_t nb_rx_desc, // 指定队列可以装多少mbuf
unsigned int socket_id,
const struct rte_eth_rxconf *rx_conf,
struct rte_mempool *mb_pool
);
port_id
:以太网设备的端口 ID。rx_queue_id
:将要创建的接收队列的队列号。nb_rx_desc
:用于网卡接收队列的缓存区描述符数量,可以简单理解为接收队列的缓存容量。(是网卡上面的rx_queue_id
对应id
的接收队列的大小,前面mbuf_pool
内存池的大小就是用来接收这个队列中的节点,所以这个内存池的大小肯定要比rx
队列大小大)socket_id
:用于分配和管理内存资源的 NUMA
节点 ID
,一般使用 rte_socket_id()
函数获取。rx_conf
:用于指定接收队列的配置信息,如 RSS
散列、哈希过滤器和 Ptype
解析等特性。如果不需要使用这些特性,可以将该参数设置为 NULL
。mb_pool
:用于存储接收队列缓存的内存池指针。 while(1){
//..后面的代码都在这个里面
}
API
接受数据 struct rte_mbuf *mbufs[MBUF_SIZE]; // 32
unsigned num_recvd = rte_eth_rx_burst(
gDpdkPortId, // 绑定的网口
0, // rx队列索引
mbufs,
MBUF_SIZE // 设置的32
);
if (num_recvd > MBUF_SIZE) {
rte_exit(EXIT_FAILURE, "rte_eth_rx_burst Error\n");
}
rte_eth_rx_burst
uint16_t rte_eth_rx_burst(uint16_t port_id,
uint16_t queue_id,
struct rte_mbuf **rx_pkts, // 传出参数
const uint16_t nb_pkts
);
// 不需要考虑释放 在内存池中直接将对应id取出来直接用
port_id
:以太网设备的端口 ID
。queue_id
:用于接收数据包的 RX
队列 ID
。rx_pkts
:指向 rte_mbuf
结构体指针的数组,用于存储接收到的数据包。nb_pkts
:要读取的最大数据包数量。unsigned i = 0;
for (i = 0;i < num_recvd;i ++) {
// 宏函数
struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(
mbufs[i],
struct rte_ether_hdr *
);
if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
continue;
}
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); // 加上IP头得到udp头
uint16_t length = udphdr->dgram_len;
*((char*) udphdr + length - 1) = '\0';
printf("udp: %s\n", (char*)(udphdr+1));
}
}
rte_pktmbuf_mtod
是一个宏,而不是一个函数。在 DPDK
的 rte_mbuf.h
文件中,可以看到 rte_pktmbuf_mtod
的定义如下:
#define rte_pktmbuf_mtod(m, t) ((t)((char *)((m)->buf_addr) + (m)->data_off))
这个宏的实现使用了强制类型转换,其目的是将缓冲区中数据的地址转换为用户指定的数据类型 t
的指针,从而方便用户访问和处理接收到的数据包。需要注意的是,在使用 rte_pktmbuf_mtod
宏时,应当确保传入的参数合法,并且传入的数据类型和数据包的格式相符,否则可能会导致程序错误。
设置
arp
静态映射用
arp
静态映射 就可以用网络调试工具调试了注意设置的ip地址是要与本机网卡ip地址是同一个网段
arp -s 10.17.31.80 00-0C-29-AF-77-5C
arp -d 10.17.31.80
# The ARP entry addition failed: Access is denied
netsh i i show in # 查看网络接口的索引号
netsh -c i i add neighbors 18 10.17.31.80 00-0C-29-AF-77-5C
netsh -c "i i" delete neighbors 1
dpdk
无法接收来自主机的数据解决/etc/network/interfaces
文件,设置eth
的静态ip
,需要与主机ip
为同一网段即可