DPDK开发技能树:KNI模块的代码实现,超越内核壁垒!

DPDK的KNI模块允许用户空间程序与内核进行数据通信,提高网络数据传输效率。文章介绍了KNI的背景、实现原理,包括ifreq结构体在设置网络接口信息中的作用,以及KNI的代码实现。通过KNI,可以绕过内核网络栈,实现高性能数据包传输,用于网络加速。
部署运行你感兴趣的模型镜像

一、背景

在当前互联网快速发展的时代,网络性能和速度已成为网络应用和服务的关键考量因素。传统的内核网络栈在处理网络数据包时存在一定的性能瓶颈,限制了整体网络性能的提升。为了突破这一限制,需要一种高性能的数据平面开发框架,能够在绕过内核网络协议栈的情况下直接操作网络硬件,并且提高数据包的处理速度。

动态网路接口(Kernel Network Interface,KNI)模块是DPDK的一个核心组件,它允许用户空间程序和内核进行数据通信,基于DPDK的KNI模块可以提供更高效的网络数据传输服务。通过使用KNI模块,可以实现用户空间应用程序与内核之间的高性能数据包传输,无需经过内核网络协议栈,避免了内核网络协议栈的性能瓶颈。

在DPDK开发的时候,如果有些协议不想处理,只处理关注的协议,可以把其他协议写回内核,让内核处理。此时的DPDK就起到分发的作用,类似一个过滤器。

DPDK开发技能树示意图:

DPDK基础知识
使用DPDK的环境搭建
DPDK的核心概念与架构
掌握DPDK的数据平面模型
了解DPDK的管理平面与控制平面
掌握DPDK的Packet I/O模型
熟悉DPDK的内存管理与优化
深入理解DPDK的调度与多核优化
KNI模块的原理与实现
理解KNI模块的源码结构
掌握KNI模块的初始化与配置方法
学习如何使用KNI模块实现高性能数据包传输
超越内核壁垒,实现网络加速
掌握DPDK与KNI模块的网络调试与优化技巧

DPDK开发技能树的主要内容和学习路径涵盖了DPDK的基础知识、环境搭建、核心概念、数据平面模型、管理平面与控制平面、Packet I/O模型、内存管理与优化、调度与多核优化等方面的内容。其中,KNI模块的原理与实现是技能树中的一个重要分支,通过学习KNI模块的源码结构、初始化与配置方法,以及如何使用KNI模块实现高性能数据包传输,可以实现超越内核壁垒,实现网络加速的目标。

二、KNI实现原理 – ifreq

2.1、ifreq简介

ifreq是一个用于获取或设置网络接口信息的结构体,是Linux内核中的一个数据结构。ifreq的全称是interface request,即接口请求。ifreq结构体通常用于与网络接口相关的系统调用中,例如获取接口地址、MAC地址、MTU等信息。

ifreq结构体的定义如下:

struct ifreq {
    char ifr_name[IFNAMSIZ]; /* Interface name */
    union {
        struct sockaddr ifr_addr;
        struct sockaddr ifr_dstaddr;
        struct sockaddr ifr_broadaddr;
        struct sockaddr ifr_netmask;
        struct sockaddr ifr_hwaddr;
        short           ifr_flags;
        int             ifr_ifindex;
        int             ifr_metric;
        int             ifr_mtu;
        struct ifmap    ifr_map;
        char            ifr_slave[IFNAMSIZ];
        char            ifr_newname[IFNAMSIZ];
        char           *ifr_data;
    };
};

ifreq结构体包含了接口名、接口地址、接口掩码、MAC地址、MTU等接口信息。其中ifreq_name成员表示接口名,ifreq_addr成员表示接口地址,ifreq_netmask成员表示接口掩码等。

ifreq结构体通常与ioctl系统调用一起使用,可以通过ioctl调用来获取或设置接口信息。例如,以下代码可以用于获取指定接口的IP地址:

struct ifreq ifr;
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
strcpy(ifr.ifr_name, "eth0");
ioctl(sockfd, SIOCGIFADDR, &ifr);
struct sockaddr_in *addr = (struct sockaddr_in*)&ifr.ifr_addr;
printf("IP Address: %s\n", inet_ntoa(addr->sin_addr));

其中,SIOCGIFADDR表示获取接口地址,inet_ntoa函数可以将网络地址转换为字符串形式。

ifreq是一个用于获取或设置网络接口信息的结构体,通常与ioctl系统调用一起使用,可以通过ioctl调用来获取或设置接口信息。ifreq结构体包含了接口名、接口地址、接口掩码、MAC地址、MTU等接口信息。

2.2、ifreq的使用

主要利用内核的/dev/net/tun。做VPN时也会用到这个设备文件。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <errno.h>
#include <sys/ioctl.h>

int tun_alloc(char *dev)
{
    struct ifreq ifr;
    memset(&ifr,0,sizeof(ifr));
    int fd=open("/dev/net/tun",O_RDWR);
    if(fd<0)
        return -1;
    // IFF_TAP针对的是以太网协议,需要传入MAC;TUN主要针对IP层协议
    ifr.ifr_flags=IFF_TAP|IFF_NO_PI;
    memcpy(ifr.ifr_name,dev,strlen(dev));
    int err;
    printf("fd = %d, dev = %s, len = %ld\n",fd,dev,strlen(dev));
    // 设置进去
    if((err=ioctl(fd,TUNSETIFF,(char *)&ifr))<0)
    {
        printf("ioctl fail(%d): %s\n",err,strerror(errno));
        close(fd);
        return err;
    }

    return fd;
}

int main()
{
    int code = tun_alloc("MyDev");
    printf("return code %d\n",code);
    getchar();
    return 0;
}

特别注意,ifr.ifr_name不能有空格。
编译:

gcc -o ifr ifr.c

执行后,使用如下命名查询:

ifconfig -a

可以看到多了MyDev。

MyDev     Link encap:以太网  硬件地址 c2:44:70:f2:79:f9  
          BROADCAST MULTICAST  MTU:1500  跃点数:1
          接收数据包:0 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:0 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:0 (0.0 B)  发送字节:0 (0.0 B)

ens33     Link encap:以太网  硬件地址 00:0c:29:79:9b:f7  
          inet 地址:192.168.0.106  广播:192.168.0.255  掩码:255.255.255.0
          inet6 地址: fe80::b608:7cba:aa19:e2d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  跃点数:1
          接收数据包:7543 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:4518 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:3014986 (3.0 MB)  发送字节:657222 (657.2 KB)

lo        Link encap:本地环回  
          inet 地址:127.0.0.1  掩码:255.0.0.0
          inet6 地址: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  跃点数:1
          接收数据包:304 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:304 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:25426 (25.4 KB)  发送字节:25426 (25.4 KB)

这就是kni的实现原理,由两部分组成:
(1)对外提供了一个字符设备,通过ioctl()操作。
(2)底层是一个网口。

三、代码实现

  1. 定义全局的KNI变量:struct rte_kni *。
  2. KNI初始化:rte_kni_init(…)。
  3. 完善struct rte_kni_conf,用于写入内核中。
  4. 完善struct rte_kni_ops。
  5. 实现一个config_network_if类型的函数,用于网络的up、down操作。
  6. 分配KNI,保存到全局变量中:rte_kni_alloc(…)。
  7. 把包发送到内核中:rte_kni_tx_brust(…)。
  8. 特别注意,要打开混杂模式:rte_eth_promiscuous_enable(…)。
  9. 这里演示了把包发送到内核,并没有从内核中抓取返回的包发送出去。

(dpdk_udp.c)

#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <rte_kni.h>

#include <stdio.h>
#include <arpa/inet.h>


#define ENABLE_SEND	1

#define ENABLE_KNI	1


#define NUM_MBUFS (4096-1)

#define BURST_SIZE	32

int gDpdkPortId = 0; //

static const struct rte_eth_conf port_conf_default = {
	.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};

#if ENABLE_KNI

struct rte_kni *global_kni = NULL;

#endif


#if ENABLE_SEND

// sender 
static uint32_t gSrcIp;
static uint32_t gDstIp;

static uint16_t gSrcPort;
static uint32_t gDstPort;

static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN];
static uint8_t gDstMac[RTE_ETHER_ADDR_LEN];


#endif

//
static void ng_init_port(struct rte_mempool *mbuf_pool) {

	//1 count avail
	uint16_t nb_sys_ports= rte_eth_dev_count_avail(); //
	if (nb_sys_ports == 0) {
		rte_exit(EXIT_FAILURE, "No Supported eth found\n");
	}

	//1 
	struct rte_eth_dev_info dev_info;
	rte_eth_dev_info_get(gDpdkPortId, &dev_info); //

	//1 
	const int num_rx_queues = 1;
	const int num_tx_queues = 1;
	struct rte_eth_conf port_conf = port_conf_default;
	rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf);

	//1 rx queue setup
	if (rte_eth_rx_queue_setup(gDpdkPortId, 0 , 1024, 
		rte_eth_dev_socket_id(gDpdkPortId),NULL, mbuf_pool) < 0) {

		rte_exit(EXIT_FAILURE, "Could not setup RX queue\n");

	}

#if ENABLE_SEND
	struct rte_eth_txconf txq_conf = dev_info.default_txconf;
	txq_conf.offloads = port_conf.rxmode.offloads;
	if (rte_eth_tx_queue_setup(gDpdkPortId, 0 , 1024, 
		rte_eth_dev_socket_id(gDpdkPortId), &txq_conf) < 0) {

		rte_exit(EXIT_FAILURE, "Could not setup TX queue\n");

	}

#endif

	//1 start
	if (rte_eth_dev_start(gDpdkPortId) < 0 ) {
		rte_exit(EXIT_FAILURE, "Could not start\n");
	}

	rte_eth_promiscuous_enable( gDpdkPortId);

}


#if ENABLE_SEND



static int ng_encode_udp_pkt(uint8_t *msg, unsigned char *data, uint16_t 
total_len) {

	// encode 

	// 1 ethhdr
	struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;
	rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
	rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);
	eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);
	

	// 2 iphdr 
	struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(msg + sizeof(struct 
rte_ether_hdr));
	ip->version_ihl = 0x45;
	ip->type_of_service = 0;
	ip->total_length = htons(total_len - sizeof(struct rte_ether_hdr));
	ip->packet_id = 0;
	ip->fragment_offset = 0;
	ip->time_to_live = 64; // ttl = 64
	ip->next_proto_id = IPPROTO_UDP;
	ip->src_addr = gSrcIp;
	ip->dst_addr = gDstIp;
	
	ip->hdr_checksum = 0;
	ip->hdr_checksum = rte_ipv4_cksum(ip);

	// 3 udphdr 

	struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(msg + sizeof(struct 
rte_ether_hdr) + sizeof(struct rte_ipv4_hdr));
	udp->src_port = gSrcPort;
	udp->dst_port = gDstPort;
	uint16_t udplen = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct 
rte_ipv4_hdr);
	udp->dgram_len = htons(udplen);

	rte_memcpy((uint8_t*)(udp+1), data, udplen);

	udp->dgram_cksum = 0;
	udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);

	struct in_addr addr;
	addr.s_addr = gSrcIp;
	printf(" --> src: %s:%d, ", inet_ntoa(addr), ntohs(gSrcPort));

	addr.s_addr = gDstIp;
	printf("dst: %s:%d\n", inet_ntoa(addr), ntohs(gDstPort));

	return 0;
}


static struct rte_mbuf * ng_send(struct rte_mempool *mbuf_pool, uint8_t *data
, uint16_t length) {

	// mempool --> mbuf

	const unsigned total_len = length + 42;

	struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
	if (!mbuf) {
		rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
	}
	mbuf->pkt_len = total_len;
	mbuf->data_len = total_len;

	uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);

	ng_encode_udp_pkt(pktdata, data, total_len);

	return mbuf;

}



#endif

#if ENABLE_KNI

static int gconfig_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;
}

#endif


int main(int argc, char *argv[]) {

	if (rte_eal_init(argc, argv) < 0) {
		rte_exit(EXIT_FAILURE, "Error with EAL init\n");
		
	}

	struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", 
NUM_MBUFS,
		0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
	if (mbuf_pool == NULL) {
		rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
	}

#if ENABLE_KNI 
	rte_kni_init(gDpdkPortId);
#endif

	ng_init_port(mbuf_pool);
	
#if ENABLE_KNI

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

	snprintf(conf.name, RTE_KNI_NAMESIZE, "vEth%d", gDpdkPortId);
	conf.group_id = gDpdkPortId;
	conf.mbuf_size = RTE_MBUF_DEFAULT_BUF_SIZE;
	//conf.

	rte_eth_macaddr_get(gDpdkPortId, (struct rte_ether_addr*)conf.mac_addr);
	rte_eth_dev_get_mtu(gDpdkPortId, &conf.mtu);


	struct rte_kni_ops ops;
	memset(&ops, 0, sizeof(ops));
	ops.port_id = gDpdkPortId;
	ops.config_network_if = gconfig_network_if;

	global_kni = rte_kni_alloc(mbuf_pool, &conf, &ops);
	
#endif

	while (1) {

		struct rte_mbuf *mbufs[BURST_SIZE];
		unsigned num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_SIZE);
		if (num_recvd > BURST_SIZE) {
			rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
		}

		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 (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
				continue;
			}

			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) {

				struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);

#if ENABLE_SEND		// echo

				// mac exchange
				rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				rte_memcpy(gSrcMac, ehdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				// ip exchange

				rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
				rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));
				// port exchange

				rte_memcpy(&gSrcPort, &udphdr->dst_port, sizeof(uint16_t));
				rte_memcpy(&gDstPort, &udphdr->src_port, sizeof(uint16_t));
				

#endif

				uint16_t length = ntohs(udphdr->dgram_len);
				*((char*)udphdr + length) = '\0';

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

				addr.s_addr = iphdr->dst_addr;
				printf("dst: %s:%d, %s\n", inet_ntoa(addr), udphdr->src_port, 
					(char *)(udphdr+1));

#if ENABLE_SEND

				struct rte_mbuf *txbuf = ng_send(mbuf_pool, (unsigned char*)(udphdr+1), 
length);
				rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);

#endif

				rte_pktmbuf_free(mbufs[i]);
			} else {


				rte_kni_tx_burst(global_kni, &mbufs[i], 1);


			}
			
		}

	}

}

Makefle:

# binary name
APP = dpdk_udp

# all source are stored in SRCS-y
SRCS-y := dpdk_udp.c

# Build using pkg-config variables if possible
ifeq ($(shell pkg-config --exists libdpdk && echo 0),0)

all: shared
.PHONY: shared static
shared: build/$(APP)-shared
        ln -sf $(APP)-shared build/$(APP)
static: build/$(APP)-static
        ln -sf $(APP)-static build/$(APP)

PKGCONF=pkg-config --define-prefix

PC_FILE := $(shell $(PKGCONF) --path libdpdk)
CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
LDFLAGS_STATIC = -Wl,-Bstatic $(shell $(PKGCONF) --static --libs libdpdk)

build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
        $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)

build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
        $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)

build:
        @mkdir -p $@

.PHONY: clean
clean:
        rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
        test -d build && rmdir -p build || true

else

ifeq ($(RTE_SDK),)
$(error "Please define RTE_SDK environment variable")
endif

# Default target, detect a build directory, by looking for a path with a .config
RTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config)))))

include $(RTE_SDK)/mk/rte.vars.mk

总结

DPDK是一个用于快速开发数据平面应用程序的开源工具包。其中,KNI(Kernel Network Interface)模块是DPDK中的一个重要组件,用于在用户空间和内核空间之间提供网络接口的交互。

开发DPDK的KNI模块需要注意以下几点:

  1. 配置DPDK环境。在开发KNI模块之前,需要先配置DPDK环境。可以使用DPDK提供的setup脚本进行快速配置。同时,还需要注意DPDK版本与Linux内核版本之间的兼容性。

  2. 编写KNI示例程序。DPDK提供了一个简单的KNI示例程序,位于examples/kni目录下。可以先尝试运行该示例程序,了解KNI的基本使用方法。

  3. 理解KNI工作原理。KNI模块的工作原理是通过在用户空间和内核空间之间共享一个虚拟接口,从而实现数据包的交换。需要理解KNI模块的接口配置、数据包处理、中断处理等相关知识。

  4. 编写KNI模块代码。在编写KNI模块代码时,需要注意以下几点:

    • 配置虚拟接口的MAC地址、IP地址等信息;
    • 实现虚拟接口的读写操作;
    • 处理接收到的数据包,包括协议解析、转发等;
    • 处理发送的数据包,包括构造数据包、填充协议头等;
    • 处理中断,例如接收中断、发送中断等。
  5. 调试KNI模块代码。在调试KNI模块代码时,可以使用DPDK提供的调试工具,例如pktgen、testpmd等。同时,还可以通过打印日志、增加调试信息等方式进行调试。

开发DPDK的KNI模块需要先配置DPDK环境,编写KNI示例程序,理解KNI工作原理,编写KNI模块代码,以及调试KNI模块代码。需要注意KNI模块的接口配置、数据包处理、中断处理等相关知识,同时还可以使用DPDK提供的调试工具进行调试。

调试时,需要把/sys/devices/virtual/net/vEth0/carrier置为1。允许内核收发数据。

echo 1 > /sys/devices/virtual/net/vEth0/carrier

在这里插入图片描述

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion 莱恩呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值