netmap实现用户态协议栈

本文介绍网卡的基本作用及其在用户态协议栈中的应用,并通过netmap技术实现数据的高效处理。内容涵盖netmap的工作原理、主要结构体定义及如何添加ARP和ICMP协议支持。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、网卡的作用

将物理层的光纤双绞线传输过来的模拟信号转换成数字信号,然后交给数据链路层处理,因此工作在在七层协议模型中的物理层和数据链路层之间。
实现网络协议栈需要获取最原始的以太网的数据的方式。用户态协议栈的优点:减少系统调用和CPU上下文切换的次数,提高网络工作性能,比如处理C10M问题时。

二、网卡原始数据的获取

1.raw sock()
2.旁路:netmap、dpdk的方式
3.hook:bpf/ebpf、netfilter上

三、 netmap的功能

1.将网卡旁路出来,将网卡数据mmap到内存,直接操作内存数据即可。
2.使用:编译之后进行 insmod netmap.ko 就会多出个字符设备/dev/netmap,可以通过dev/netmap 字符设备 操作网卡。

采用旁路的方式实现网络协议栈(数据链路层、网络层、传输层)
在这里插入图片描述

四、主要结构体定义

以 实现 UDP 协议栈为例:用客户端发报文,测试收到UDP报文 实现简易的用户态协议栈。
如果实现TCP版本,需要实现 三次握手来建立连接,发送数据到客户端,还有滑动窗口定时器等等。。。。
以太网协议结构体:
在这里插入图片描述

struct ethhdr {
	unsigned char h_dest[ETH_ALEN];
	unsigned char h_source[ETH_ALEN];
	unsigned short h_proto;
};

ipheader: 一个UDP包长度小于64K,而16位总长度如果超过了MTU 就会进行IP分片,16位标识用以标记被分片的UDP包,13位片偏移表示具体的位置。

在这里插入图片描述

struct iphdr {
	unsigned char version;
	unsigned char tos;
	unsigned short tot_len;
	unsigned short id;
	unsigned short flag_off;
	unsigned char ttl;
	unsigned char protocol;
	unsigned short check;
	unsigned int saddr;
	unsigned int daddr;
};

每一层都包含上一层协议的类型。

udpheader:

struct udphdr {
	unsigned short source;
	unsigned short dest;
	unsigned short len;
	unsigned short check;
};


struct udppkt {
	struct ethhdr eh;
	struct iphdr ip;
	struct udphdr udp;
	unsigned char body[0];//柔性数组:数组长度可扩展。

};

五、添加ARP和ICMP协议

如果只有以上三个协议,会出现两个问题
1.之后不能接收数据,使用 ARP -a 命令在ARP表中查不到 IP 地址了,刚开始能正常解析数据是因为:虚拟机刚开机的时候,用xshell 连虚拟机,宿主机发送过 arp 请求,而虚拟机的内核协议栈此时还没被 netmap 接管,回应了 arp 请求,宿主机就将虚拟机的信息暂时的添加到arp表中,当动态arp记录失效,udp 包不知道发给谁,就会先发arp请求。

解决方法:在宿主机 arp 表中添加静态的 arp 记录或实现 ARP 协议把 client 的 MAC 地址放到以太网头里面发送出去。

2.同理之后也不能 ping 通,需要实现 ICMP 协议。

具体实现:
server.c

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

	struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);  //将eth0网卡映射到内存
	if (nmr == NULL) {
		return -1;
	}

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

	while (1) {
		int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;
		
		//netmap帮我们封装成了IO的形式,若网卡有数据来了,IO可读,所以可用poll管理
		if (pfd.revents & POLLIN) {  
			stream = nm_nextpkt(nmr, &h);    //读取包     
			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);
					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);
					struct udppkt udp_rt;
					echo_udp_pkt(udp, &udp_rt);
					nm_inject(nmr, &udp_rt, sizeof(struct udppkt));  //发送包
					
				} else if (udp->ip.protocol == PROTO_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) {

				struct arppkt *arp = (struct arppkt *)stream;
				struct arppkt arp_rt;

				if (arp->arp.dip == inet_addr("192.168.0.26")) {
					echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");
					nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
				}
			}
		} 
	}
}
    






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值