DPDK用户态协议栈-KNI

简介: DPDK用户态协议栈-KNI

kni

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

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

  1. 网络设备绑定:用户空间应用程序通过创建虚拟网卡(vEth设备),将其与物理网卡绑定起来。这样,在用户空间中就可以对虚拟网卡进行操作。
  2. 数据传输:当网络数据包到达物理网卡时,内核会将数据包复制到用户空间缓冲区中,并触发一个中断通知用户空间。
  3. 用户空间处理:用户空间应用程序收到中断通知后,可以立即开始处理网络数据包。通过KNI接口,应用程序可以直接读取、修改或丢弃数据包。
  4. 内核回写:如果需要将修改后的数据包发送出去,应用程序可以通过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启动其实是没有问题的。

需要的协议

相关文章
|
11天前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
16 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
11天前
初步认识栈和队列
初步认识栈和队列
35 10
|
5天前
数据结构(栈与列队)
数据结构(栈与列队)
11 1
|
9天前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
37 1
|
6天前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
9 0
|
10天前
探索顺序结构:栈的实现方式
探索顺序结构:栈的实现方式
|
11天前
|
存储 C语言
栈和队列题目练习
栈和队列题目练习
12 0
|
18天前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
84 64
|
27天前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
|
11天前
|
算法
数据结构与算法二:栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式
这篇文章讲解了栈的基本概念及其应用,并详细介绍了中缀表达式转换为后缀表达式的算法和实现步骤。
28 3