kni
KNI(Kernel Network Interface)是一种在Linux内核中实现的网络编程接口,它提供了一种高效的方式来处理网络数据包。KNI的原理是将用户空间和内核空间之间的数据传输最小化,以降低网络处理的延迟和开销。
KNI允许用户空间应用程序直接访问内核网络协议栈,从而可以更灵活地控制和处理网络数据包。其主要原理如下:
- 网络设备绑定:用户空间应用程序通过创建虚拟网卡(vEth设备),将其与物理网卡绑定起来。这样,在用户空间中就可以对虚拟网卡进行操作。
- 数据传输:当网络数据包到达物理网卡时,内核会将数据包复制到用户空间缓冲区中,并触发一个中断通知用户空间。
- 用户空间处理:用户空间应用程序收到中断通知后,可以立即开始处理网络数据包。通过KNI接口,应用程序可以直接读取、修改或丢弃数据包。
- 内核回写:如果需要将修改后的数据包发送出去,应用程序可以通过KNI接口将数据包写回内核网络协议栈,并选择相应的物理网卡进行发送。
通过使用KNI,用户空间应用程序能够获得更高的灵活性和性能。它适用于需要对网络数据包进行深度处理的场景,如网络加速、防火墙、负载均衡等应用。
简单来说,kni就是vfs中的一个设备,在/dev/目录下。有点像我上次提到的进程间通信组件,我们将数据写入kni而kni负责将数据写入内核协议栈。
kni工作的位置
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的设备文件
KNI设备文件创建的虚拟网卡
不需要的协议
这里可以看到主机是无法被ping通的,这个问题是因为KNI相关还没有完全实现,后面会得到解决;同时,我们通过tcpdump抓包可以看到,在我们开启了混杂模式之后,内核是可以接收到icmp的数据包的,所以证明我们的kni启动其实是没有问题的。
需要的协议