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启动其实是没有问题的。