【dpdk】9.KNI学习笔记

10 篇文章 3 订阅

基于之前实现的UDP/TCP Server代码,直接添加KNI部分


前言

DPDK提供了一系列对网卡的操作,包括rte_ether_hdrrte_ipv4_hdrrte_udp_hdr等,需要自己进行协议栈的解析处理。但是在应用开发的过程中,有很多部分是不需要DPDK做这种处理的,比如:DNS仅处理所有协议中UDP部分的数据,而其他类型的数据DPDK不做处理,还是写回内核,交给内核协议栈去做。对于需要写回内核的数据,DPDK提供了一组接口——Kernel Network Interface,简称KNI

DPDK开启了一个线程,不断地取数据,通过KNI往内核里面送,内核协议栈处理完后,再交给应用程序。这个过程中,KNI会在内核中生成一个net device,例如:veth0。与eth0、eth1一样,DPDK通过这个虚拟的veth0送数据,对应内核协议栈接管这个数据。DPDK能够通过修改生成的映射文件dev/kni对它进行操作。

一、KNI启动

在运行DPDK时,需要选择“45”选项,插入KNI模块。

1.先初始化KNI

在初始化端口时,初始化KNI并分配内存。

struct rte_kni *global_kni = NULL;
if (-1 == rte_kni_init(gDpdkPortId)) {
	rte_exit(EXIT_FAILURE, "kni init failed\n");
}
init_port(mbuf_pool);
// kni_alloc
global_kni = alloc_kni(mbuf_pool);

2.分配一个KNI

创建kni函数定义:

static struct rte_kni *alloc_kni(struct rte_mempool *mbuf_pool);

所需的参数:

  • mbuf_pool,dpdk为kni提供了一个线程,线程分配了一块内存,接收数据就需要内存池;
  • conf:配置kni的一些参数,包括配置名字、绑定的ID、包的大小等;
  • ops:表示操作,比如 :更改IP地址,更改MAC地址,down掉网卡等。

1)初始化conf

struct rte_kni_conf conf;
memset(&conf, 0, sizeof(conf));

snprintf(conf.name, RTE_KNI_NAMESIZE, "vEth%u", gDpdkPortId);
conf.group_id = gDpdkPortId;
conf.mbuf_size = MAX_PACKET_SIZE;
rte_eth_macaddr_get(gDpdkPortId, (struct rte_ether_addr *)conf.mac_addr);
rte_eth_dev_get_mtu(gDpdkPortId, &conf.mtu);

print_ethaddr("alloc_kni: ", (struct rte_ether_addr *)conf.mac_addr);

2)初始化ops

struct rte_kni_ops ops;
memset(&ops, 0, sizeof(ops));

ops.port_id = gDpdkPortId;
ops.config_network_if = config_network_if;

这里ops.config_network_if设置的处理函数如下:

// config_network_if
// rte_kni_handle_request
static int config_network_if(uint16_t port_id, uint8_t if_up) {

	if (!rte_eth_dev_is_valid_port(port_id)) {
		return -EINVAL;
	}

	int ret = 0;
	if (if_up) {
		rte_eth_dev_stop(port_id);
		ret = rte_eth_dev_start(port_id);
	} else {
		rte_eth_dev_stop(port_id);
	}

	if (ret < 0) {
		printf("Failed to start port : %d\n", port_id);
	}

	return 0;
}

3)初始化ops

创建kni_hanlder 后,作为函数返回值

kni_hanlder = rte_kni_alloc(mbuf_pool, &conf, &ops);	
if (!kni_hanlder) {
	rte_exit(EXIT_FAILURE, "Failed to create kni for port : %d\n", gDpdkPortId);
}

return kni_hanlder;

3.开启混杂模式

init_port函数中开启混杂模式

rte_eth_promiscuous_enable(gDpdkPortId);

二、重构网络协议分发流程

将UDP/TCP以外的所有协议,交给KNI处理。其中,保留一部分ARP功能,在协议栈层处理UDP/TCP报文时,判断IP与MAC信息是否在ARP表中,若没有则添加进去。

1.插入ARP信息

插入ARP信息实现函数如下:

static int arp_entry_insert(uint32_t ip, uint8_t *mac) {
	struct arp_table *table = arp_table_instance();
	uint8_t *hwaddr = get_dst_macaddr(ip);
	if (hwaddr == NULL) {
		struct arp_entry *entry = rte_malloc("arp_entry",sizeof(struct arp_entry), 0);
		if (entry) {
			memset(entry, 0, sizeof(struct arp_entry));

			entry->ip = ip;
			rte_memcpy(entry->hwaddr, mac, RTE_ETHER_ADDR_LEN);
			entry->type = 0;

			pthread_spin_lock(&table->spinlock);
			LL_ADD(entry, table->entries);
			table->count ++;
			pthread_spin_unlock(&table->spinlock);		
		}
		return 1; //
	}
	return 0;
}

2.流程重构

将其他报文交给KNI,在pkt_process函数中,添加以下代码:
在这里插入图片描述
rte_kni_tx_burstrte_kni_handle_request函数是配套使用的,如果不执行rte_kni_handle_request函数,一旦执行ifconfig vEth0 192.168.0.119 up,是不会有反应的,因为config_network_if没有回调。

3.报错

执行ifconfig vEth0 192.168.0.119 up出现:

SIOCSIFFLAGS: Timer expired

原因是UDP Server在处理时,定时器会发送ARP请求,ARP send时进入了死循环。

  • ARP Timer超时
  • arptable没有加速

这里对ARP表优化,进行加速:

struct arp_table {

	struct arp_entry *entries;
	int count;

	pthread_spinlock_t spinlock;
};

在每次添加ARP结点时加锁:

pthread_spin_lock(&table->spinlock);
LL_ADD(entry, table->entries);
table->count ++;
pthread_spin_unlock(&table->spinlock);

三、tcpdump抓包调试KNI

对虚拟网卡vEth0抓包:

tcpdump -i vEth0

tcpdump 是一个运行在命令行下的抓包工具。
例:

  • 在ens33网卡上监听100个目标端口是443端口的数据包,并保存到/root/1.cap,可再用wireshark打开并分析数据包
tcpdump -i ens33 -c 100 ‘dst port 443’ -w /root/1.cap
  • 在ens33网卡上监听5个目标主机是192.168.146.131的不是tcp协议的数据包:
tcpdump -i ens33 -c 5 dst host 192.168.146.131 and ‘not tcp’

四、总结

KNI主要是DPDK用于对内核的补充,不需要处理的信息,写入到KNI,让内核协议栈处理。可以让内核协议栈与DPDK用户态协议栈同时存在运行。
另外,代码运行时可能会出现内存泄漏的现象,就需要在使用完或者未生成时释放TCP/UDP数据 对应的内存。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
运行 DPDK 的 Kernel Network Interface (KNI) 需要以下步骤: 1. 编译 DPDK 库和示例程序。 2. 加载 DPDK 驱动程序。 3. 配置 KNI 接口。 4. 运行示例程序。 下面给出详细说明和示例: 1. 编译 DPDK 库和示例程序。 下载 DPDK 19.11.4 版本,并解压缩: ``` tar -xvf dpdk-19.11.4.tar.xz cd dpdk-19.11.4 ``` 设置环境变量: ``` export RTE_SDK=`pwd` export RTE_TARGET=x86_64-native-linuxapp-gcc ``` 编译 DPDK 库和示例程序: ``` make config T=x86_64-native-linuxapp-gcc make ``` 2. 加载 DPDK 驱动程序。 使用 DPDKKNI 接口需要加载 DPDK 驱动程序,可以使用以下命令加载: ``` sudo modprobe uio sudo insmod $RTE_SDK/$RTE_TARGET/kmod/igb_uio.ko ``` 3. 配置 KNI 接口。 为了使用 KNI 接口,需要在 DPDK 的 EAL 中设置 KNI 接口的名称和数量: ``` sudo $RTE_SDK/usertools/dpdk-devbind.py -b igb_uio 0000:01:00.0 sudo $RTE_SDK/usertools/dpdk-devbind.py -b igb_uio 0000:01:00.1 sudo $RTE_SDK/usertools/dpdk-devbind.py --status sudo $RTE_SDK/usertools/dpdk-setup.sh ``` 其中,第一行和第二行将网络设备绑定到 DPDK 的驱动程序上。第三行是检查绑定结果。第四行是配置 KNI 接口的名称和数量。 4. 运行示例程序。 DPDK 示例程序提供了 kni 示例,可以使用以下命令运行: ``` sudo ./examples/kni/build/kni -c 0x1 -n 4 -- -P -p 0x3 --config="(0,0,1),(1,0,2)" ``` 其中,-c 0x1 表示使用核心 1 运行程序,-n 4 表示使用 4 个内存通道,-P 表示启用了包括 KNI 在内的所有端口,-p 0x3 表示使用了两个物理端口,--config="(0,0,1),(1,0,2)" 表示将这两个物理端口连接起来。 这样,就可以使用 kni 示例程序进行 KNI 接口的测试了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值