基于netmap的用户态协议栈实现

基于netmap的用户态协议栈实现

一 引入

先提几个问题

为什么要做一个用户态的协议栈,好处在哪?

1.用户态实现协议栈可以避免用户态和内核态之间的频繁切换,从而提高网络处理性能。传统的内核态协议栈需要将网络数据包从用户态传递到内核态进行处理,然后再返回给用户态,这个过程涉及到多次上下文切换和数据拷贝,会引入较大的性能开销。而用户态协议栈直接在用户态中进行网络数据包的处理,避免了这些开销,可以更高效地处理网络流量。
2.灵活性和可定制性: 用户态协议栈可以根据具体需求进行定制和优化,灵活适应不同的应用场景。用户态协议栈的实现可以根据应用程序的特点和需求进行优化,例如针对特定的协议进行加速、减少不必要的功能模块、优化内存管理等。这种灵活性和可定制性使得用户态协议栈能够更好地满足特定应用的性能和功能需求。
3.可移植性和跨平台支持:用户态协议栈通常是以库的形式提供,可以在不同的操作系统和平台上使用。相比于依赖于特定操作系统的内核态协议栈,用户态协议栈可以更容易地移植到不同的平台上

那么如何实现用户态的协议栈呢?
这里拿下别人做的网图,讲解一下网络数据包的大致流程
在这里插入图片描述
正常的流程是网卡接收到数据后,把数据copy到协议栈(sk_buff),协议栈把sk_buff数据解析完后再把数据放到recv_buff,此时应用程序调用recv把数据从协议栈copy到应用程序;发送数据包,则与之相反,应用程序调用send把数据包copy到send_buff,协议栈从send_buff取数据放到sk_buff,交给网卡发送出去。这个过程有多次拷贝,为避免多次拷贝,使用dma的方式(零拷贝),把网卡的数据直接映射到内存,再由应用程序访问内存。

在这里我们需要做自己的协议栈的话,我们必须要能够拿到网卡的数据。

有这么几种方式
1.直接从网卡拿到原始数据,到用户端处理,这样还是会经过协议栈
2.数据旁路
在这里插入图片描述
Netmap安装后相当于多了一个网卡驱动
通过对网卡驱动的扩展,使得网卡可以直接将收到的数据包存储在用户态的内存缓冲区中,而不是传递给操作系统内核的协议栈。这样一来,用户态程序可以直接访问和处理这些数据包。

在启用Netmap后,操作系统的协议栈将不再直接收到网络数据包。Netmap的设计目标之一是绕过操作系统协议栈,将网络数据包直接传递给用户态程序进行处理,以提高性能和降低延迟。

当网卡进入Netmap模式后,数据包将通过Netmap框架直接传递给用户态程序,而不经过操作系统的协议栈。操作系统的协议栈将不会接收、处理或转发这些数据包。

  1. 利用hook

bpf或者epbf

二 netmap介绍和环境配置

介绍具体可以看下面的文章
netmap介绍

Netmap的原理可以概括为以下几点:

  • 网卡驱动的扩展:Netmap通过对网卡驱动的扩展,使得网卡可以直接将收到的数据包存储在用户态的内存缓冲区中,而不是传递给操作系统内核的协议栈。这样一来,用户态程序可以直接访问和处理这些数据包。
  • 共享内存环:Netmap引入了一个共享内存环(shared memory
    ring),用于在用户态程序和网卡之间传递数据包。网卡将收到的数据包写入共享内存环的发送队列,而用户态程序则从接收队列中读取数据包进行处理。这种基于共享内存的数据包传递方式避免了数据拷贝,提高了性能。
  • 零拷贝技术:Netmap利用共享内存环的特性,实现了零拷贝的数据包处理。用户态程序可以直接在共享内存中操作数据包,而无需将数据包从内核态复制到用户态。这样可以减少数据拷贝的开销,提高处理效率。
  • 轮询模式:Netmap使用轮询模式来实现高性能的数据包处理。用户态程序可以通过轮询共享内存环中的接收队列,及时处理收到的数据包,而不需要等待中断或轮询操作系统的网络缓冲区。这种主动轮询的方式可以降低处理延迟,并提高吞吐量。

环境配置

参考下面任一文章
netmap配置
netmap配置

这里讲我在插入netmap驱动时,需要修改本地网卡从ens33到ens0,如何修改

首先我的虚拟机系统是ubuntu20.04.6

  1. vim /etc/default/grub

  2. 重建grub配置文件
    执行命令:
    grub2-mkconfig -o /boot/grub2/grub.cfg
    可能会没有grub2-mkconfig命令 那就执行
    grub-mkconfig -o /boot/grub/grub.cfg

  3. 网上有些地方要修改/etc/network/interfaces文件,我这里没有修改。(里面只有一个lo地址)

  4. 重启系统

  5. ifconfig,发现已经更改完成

这时会发现虚拟机没网,因为没有IP了,原来ens33的IP是在/etc/network/interfaces里面配的

现在改为ens0后需要这样配(我配置的是静态IP,就是禁用了DHCP)

如果你想要为eth0网卡配置静态IP地址,可以按照以下步骤进行操作:

  1. 打开终端,以管理员权限运行以下命令来编辑网络接口的配置文件:
sudo nano /etc/netplan/00-installer-config.yaml

请注意,如果你的系统上的配置文件名称不是00-installer-config.yaml,请使用相应的文件名进行替换。

  1. 在打开的文件中,找到ethernets部分,如果不存在,请添加以下内容:
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: no
      addresses: [192.168.1.100/24]
      gateway4: 192.168.1.1
      nameservers:
        addresses: [8.8.8.8, 8.8.4.4]

在上述配置中,dhcp4: no指示禁用DHCP,并使用静态IP地址。将192.168.1.100/24替换为你想要为eth0网卡配置的静态IP地址和子网掩码。将192.168.1.1替换为你的网关地址,将8.8.8.88.8.4.4替换为你的首选和备用DNS服务器地址。

  1. 保存文件并退出编辑器。

  2. 运行以下命令来应用配置更改:

sudo netplan apply

配置更改将会生效,并为eth0网卡配置了指定的静态IP地址、子网掩码、网关和DNS服务器。请确保在修改配置文件之前备份该文件,以防出现意外情况。

在这里插入图片描述
最后配上了IP,如果你的虚拟机还是没有网络,可能是VMware的虚拟网络编辑器没有设置好,可以在网上查看如何给虚拟机配上静态IP,VMware这部分是一样的。

3 代码编写

这里只实现了简单的ARP协议,UDP协议,ICMP协议的收发,有需求大家可以自己完善,代码比较简单这里我写了详细的注释。可以自己查看。github自取。github代码

这里我讲下代码的核心逻辑,
利用nm_open函数打开一个网卡,这里是eth0,返回一个句柄
然后就可以用netmap提供的nm_nextpkt函数拿到数据包,然后分协议进行处理就行了,这里用到了linux提供的poll函数进行设计。

每次我们用echo_XXX_pkt 函数对数据包进行回显,再用nm_inject函数把数据包送入网卡。

这里实现ARP协议的目的:
如果只是实现一个UDP协议,会出现数据包发着发着就发不了的问题,原因在于每次主机发送数据包时可能会发送ARP请求虚拟机对应的MAC地址,而我们的虚拟机没有进行回复,这导致,主机的ARP表会失去虚拟机的映射关系,导致不能发送数据包。

ICMP协议,主要是进行Ping功能的测试。

int main() {
	
	struct ethhdr *eh;
	struct pollfd pfd = {0};
	struct nm_pkthdr h;
	unsigned char *stream = NULL;

	//打开netmap设备:使用nm_open函数打开netmap设备,这里是"netmap:eth0",表示打开名为eth0的网络接口。
	struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
	if (nmr == NULL) {
		return -1;
	}

	pfd.fd = nmr->fd;
	pfd.events = POLLIN;

	while (1) {
		//设置轮询事件:使用poll函数设置一个文件描述符的轮询事件,以等待数据包的到达。
		int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;
		
		if (pfd.revents & POLLIN) {
			//接收数据包:使用nm_nextpkt函数从netmap设备中接收下一个数据包,并将其存储在stream指针中。
			stream = nm_nextpkt(nmr, &h);
			// 解析以太网头部:将stream指针强制转换为以太网头部结构体ethhdr,以便进一步解析数据包。
			eh = (struct ethhdr*)stream;
			//通过检查以太网头部中的协议字段,判断数据包的协议类型。
			if (ntohs(eh->h_proto) == PROTO_IP) {

				struct udppkt *udp = (struct udppkt*)stream;
				if (udp->ip.protocol == PROTO_UDP) {

					struct in_addr addr;
					addr.s_addr = udp->ip.saddr;

					int udp_length = ntohs(udp->udp.len);
					//于将32位的IPv4地址转换为点分十进制字符串表示的函数
					printf("%s:%d:length:%d, ip_len:%d --> ", inet_ntoa(addr), udp->udp.source, 
						udp_length, ntohs(udp->ip.tot_len));

					udp->body[udp_length-8] = '\0';
					printf("udp --> %s\n", udp->body);
#if 1	
					struct udppkt udp_rt;
					echo_udp_pkt(udp, &udp_rt);
					nm_inject(nmr, &udp_rt, sizeof(struct udppkt));
#endif
				} else if (udp->ip.protocol == PROTO_ICMP) {  //发送ICMP包
					
					struct icmppkt *icmp = (struct icmppkt*)stream;

					printf("icmp ---------- --> %d, %x\n", icmp->icmp.type, icmp->icmp.check);
					if (icmp->icmp.type == 0x08) {
						struct icmppkt icmp_rt = {0};
						echo_icmp_pkt(icmp, &icmp_rt);

						//printf("icmp check %x\n", icmp_rt.icmp.check);
						nm_inject(nmr, &icmp_rt, sizeof(struct icmppkt));
					}
					
				} else if (udp->ip.protocol == PROTO_IGMP) {

				} else {
					printf("other ip packet");
				}
				
			}  else if (ntohs(eh->h_proto) == PROTO_ARP) { //如果是arp报文

				struct arppkt *arp = (struct arppkt *)stream;
				struct arppkt arp_rt;
				// 函数将 IPv4 地址字符串转换为 32 位无符号整数时,返回的整数值是以网络字节序(大端字节序)表示的。
				if (arp->arp.dip == inet_addr("192.168.82.168")) {
					echo_arp_pkt(arp, &arp_rt, "00:0c:29:fb:c6:6b");
					nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
				}
			}
		} 
	}
}

编译代码时可能会出现找不到头文件的情况

采用下面的编译命令,替换为自己的netmap安装路径

gcc -o server icmp_arp_udp_success.c -I /home/XXX/software/netmap-master/sys
  • 35
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
netmap是一个基于零拷贝思想的高速网络I/O架构,它通过在网卡运行在netmap模式下与主机协议栈断开连接,并创建一个netmap环来实现高效的数据包处理。\[2\]netmap的架构包括了网卡环、netmap环和用于与主机协议栈交互的环。网卡环是网卡直接将数据包存入的缓存,而netmap环是应用程序可以通过调用netmap API访问的缓存。这些缓存位于共享空间,应用程序可以直接访问数据包内容,实现网络数据包的零拷贝。\[2\] netmap的数据结构包括了netmap_if、nmreq、netmap_ring等。netmap_if是一个结构体,用于表示一个netmap接口,其中包含了与接口相关的信息。nmreq是一个结构体,用于向内核注册一个netmap接口。netmap_ring是一个结构体,用于表示一个netmap环,其中包含了环的相关信息,如可用的数据包数量、当前处理的数据包索引等。\[3\] 在使用netmap时,可以通过打开字符设备"/dev/netmap"来获取一个文件描述符,然后使用ioctl函数来注册网卡。接下来,可以使用mmap函数将共享内存映射到用户空间,从而可以访问netmap环中的数据包内容。最后,可以使用poll函数来等待数据包的到达,并通过遍历netmap环中的数据包来处理数据。\[3\] 需要注意的是,上述提供的代码示例是一个官方的例子,可能已经过时,不能直接使用。但是它可以大致说明netmap的使用过程。\[3\] #### 引用[.reference_title] - *1* *3* [netmap 介绍](https://blog.csdn.net/fengfengdiandia/article/details/52869290)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Netmap分析(一)](https://blog.csdn.net/superbfly/article/details/51224920)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值