DKDP用户态协议栈-kni

kni

KNI(Kernel Network Interface)是一种在Linux内核中实现的网络编程接口,它提供了一种高效的方式来处理网络数据包。KNI的原理是将用户空间和内核空间之间的数据传输最小化,以降低网络处理的延迟和开销。

KNI允许用户空间应用程序直接访问内核网络协议栈,从而可以更灵活地控制和处理网络数据包。其主要原理如下:

  1. 网络设备绑定:用户空间应用程序通过创建虚拟网卡(vEth设备),将其与物理网卡绑定起来。这样,在用户空间中就可以对虚拟网卡进行操作。

  2. 数据传输:当网络数据包到达物理网卡时,内核会将数据包复制到用户空间缓冲区中,并触发一个中断通知用户空间。

  3. 用户空间处理:用户空间应用程序收到中断通知后,可以立即开始处理网络数据包。通过KNI接口,应用程序可以直接读取、修改或丢弃数据包。

  4. 内核回写:如果需要将修改后的数据包发送出去,应用程序可以通过KNI接口将数据包写回内核网络协议栈,并选择相应的物理网卡进行发送。

通过使用KNI,用户空间应用程序能够获得更高的灵活性和性能。它适用于需要对网络数据包进行深度处理的场景,如网络加速、防火墙、负载均衡等应用。

简单来说,kni就是vfs中的一个设备,在/dev/目录下。有点像我上次提到的进程间通信组件,我们将数据写入kni而kni负责将数据写入内核协议栈。

kni工作的位置

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240816154428.png

kni的启动

全局KNI变量
struct rte_kni *global_kni = NULL;
gport配置
static int ng_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;
}

这段代码是一个静态函数 ng_config_network_if,它用于配置网络接口的状态。它接受两个参数:port_id 是指要配置的网络接口的端口号,if_up 是一个布尔值,表示是否启用该网络接口。

首先,它会检查给定的 port_id 是否有效,如果无效,则返回错误码 -EINVAL

然后,在 if_up 为真时(即启用网络接口),它会先停止当前的网络设备(调用 rte_eth_dev_stop(port_id)),然后尝试重新启动该设备(调用 rte_eth_dev_start(port_id))。如果启动失败,将返回相应的错误码。

if_up 为假时(即禁用网络接口),它只会停止当前的网络设备(调用 rte_eth_dev_stop(port_id))。

最后,无论成功与否,都会返回 0 表示操作完成。

pkt_process使用kni传入内核(重构协议分发流程)
static int pkt_process(void *arg) {

	struct rte_mempool *mbuf_pool = (struct rte_mempool *)arg;
	struct inout_ring *ring = ringInstance();

	while (1) {

		struct rte_mbuf *mbufs[BURST_SIZE];
		unsigned num_recvd = rte_ring_mc_dequeue_burst(ring->in, (void**)mbufs, BURST_SIZE, NULL);
		
		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 0
#if ENABLE_ARP

			if (ehdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {

				struct rte_arp_hdr *ahdr = rte_pktmbuf_mtod_offset(mbufs[i], 
					struct rte_arp_hdr *, sizeof(struct rte_ether_hdr));

				/*
				struct in_addr addr;
				addr.s_addr = ahdr->arp_data.arp_tip;
				printf("arp ---> src: %s ", inet_ntoa(addr));

				addr.s_addr = gLocalIp;
				printf(" local: %s \n", inet_ntoa(addr));
				*/
				
				if (ahdr->arp_data.arp_tip == gLocalIp) {

					if (ahdr->arp_opcode == rte_cpu_to_be_16(RTE_ARP_OP_REQUEST)) {

						//printf("arp --> request\n");

						struct rte_mbuf *arpbuf = ln_send_arp(mbuf_pool, RTE_ARP_OP_REPLY, ahdr->arp_data.arp_sha.addr_bytes, 
							ahdr->arp_data.arp_tip, ahdr->arp_data.arp_sip);

						//rte_eth_tx_burst(gDpdkPortId, 0, &arpbuf, 1);
						//rte_pktmbuf_free(arpbuf);

						rte_ring_mp_enqueue_burst(ring->out, (void**)&arpbuf, 1, NULL);

					} else if (ahdr->arp_opcode == rte_cpu_to_be_16(RTE_ARP_OP_REPLY)) {

						//printf("arp --> reply\n");

						struct arp_table *table = arp_table_instance();

						uint8_t *hwaddr = ln_get_dst_macaddr(ahdr->arp_data.arp_sip);
						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 = ahdr->arp_data.arp_sip;
								rte_memcpy(entry->hwaddr, ahdr->arp_data.arp_sha.addr_bytes, RTE_ETHER_ADDR_LEN);
								entry->type = 0;
								
								LL_ADD(entry, table->entries);
								table->count ++;
							}

						}
#if 0 //ENABLE_DEBUG
						struct arp_entry *iter;
						for (iter = table->entries; iter != NULL; iter = iter->next) {
					
							struct in_addr addr;
							addr.s_addr = iter->ip;

							print_ethaddr("arp table --> mac: ", (struct rte_ether_addr *)iter->hwaddr);
								
							printf(" ip: %s \n", inet_ntoa(addr));
					
						}
#endif
						rte_pktmbuf_free(mbufs[i]);
					}
				
					continue;
				} 
			}
#endif
#endif

			if (ehdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_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) {

					udp_process(mbufs[i]);
					
				}
				else if (iphdr->next_proto_id == IPPROTO_TCP) {

					printf("ln_tcp_process\n");
					ln_tcp_process(mbufs[i]);
					
				}
				else {

					rte_kni_tx_burst(global_kni, mbufs, num_recvd);
					printf("tcp/udp --> rte_kni_handle_request\n");
				}

			}
			else {

				rte_kni_tx_burst(global_kni, mbufs, num_recvd);
				printf("ip --> rte_kni_handle_request\n");
			}

#if 0
#if ENABLE_ICMP

			if (iphdr->next_proto_id == IPPROTO_ICMP) {

				struct rte_icmp_hdr *icmphdr = (struct rte_icmp_hdr *)(iphdr + 1);

				
				struct in_addr addr;
				addr.s_addr = iphdr->src_addr;
				printf("icmp ---> src: %s ", inet_ntoa(addr));

				
				if (icmphdr->icmp_type == RTE_IP_ICMP_ECHO_REQUEST) {

					addr.s_addr = iphdr->dst_addr;
					printf(" local: %s , type : %d\n", inet_ntoa(addr), icmphdr->icmp_type);
				

					struct rte_mbuf *txbuf = ln_send_icmp(mbuf_pool, ehdr->s_addr.addr_bytes,
						iphdr->dst_addr, iphdr->src_addr, icmphdr->icmp_ident, icmphdr->icmp_seq_nb);

					//rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);
					//rte_pktmbuf_free(txbuf);
					rte_ring_mp_enqueue_burst(ring->out, (void**)&txbuf, 1, NULL);

					rte_pktmbuf_free(mbufs[i]);
				}
				

			}


#endif
#endif
			
		}

		rte_kni_handle_request(global_kni);

#if ENABLE_UDP_APP

		udp_out(mbuf_pool);

#endif


#if ENABLE_TCP_APP

		ln_tcp_out(mbuf_pool);

#endif

	}

	return 0;
}
主函数启动KNI
#if ENABLE_KNI

	if (-1 == rte_kni_init(gDpdkPortId)) {

		rte_exit(EXIT_FAILURE, "kni init failed");
	}

	ln_init_port(mbuf_pool);
#else
	global_kni = ln_alloc_kni(mbuf_pool);

#endif

arptable加锁实现线程安全

struct arp_table {

	struct arp_entry *entries;
	int count;

	pthread_spinlock_t spinlock;
};
static int ln_arp_entry_insert(uint32_t ip, uint8_t* mac) {

	struct arp_table* table = arp_table_instance();

	uint8_t* hwaddr = ln_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;
}

在我们使用多线程对同一张arp表进行读取和写入的时候,不可避免的带来了线程安全的问题。为了防止死锁带来的问题,这里我们在插入arp的时候设置锁,解决线程安全的问题。

效果

KNI的设备文件

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240817164343.png

KNI设备文件创建的虚拟网卡

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240817164451.png

不需要的协议

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240817175958.png

这里可以看到主机是无法被ping通的,这个问题是因为KNI相关还没有完全实现,后面会得到解决;同时,我们通过tcpdump抓包可以看到,在我们开启了混杂模式之后,内核是可以接收到icmp的数据包的,所以证明我们的kni启动其实是没有问题的。

需要的协议

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240817170158.png

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240817171221.png

项目地址

项目地址

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值