udp协议发数据到界面的代码(用户态协议栈设计实现udp)

前言

内核里面已经有网络协议栈了,为什么还要实现一遍用户态协议栈呢,主要是站在一个设计者的角度,自己去尝试实现一个协议栈,那么对协议栈的理解会比较透彻,这不比背八股文强?

获取原始数据获取原始数据的三种方法介绍

1、使用原始套接字raw socket , tcpdump和wireshark就是使用这个做的,raw socket主要用来抓包。

2、dbdk,使用dbdk的话篇幅较长,这里就不展开了,有兴趣的可以看

Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

3、netmap是用于用户层应用程序收发原始网络数据的高性能框架,本文使用netmap进行数据的收发。

netmap

内核协议栈的数据到应用层的数据会经历两次拷贝,而netmap采用mmap的方式,直接将网卡的数据映射到一块内存中,应用程序可以直接通过mmap操作相应内存的数据。

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(1)

零拷贝

其实通过上面的图我们就能看到,采用传统的内核协议栈的方式,会发生两次拷贝,一是网卡数据拷贝到内核协议栈;二是内核再拷贝到内存中去。而netmap采用的则是零拷贝。

所谓零拷贝,指的是不由CPU操作,copy这个动作是由cpu发出指令move实现的,所以零拷贝就是不由CPU管理,由DMA管理。DMA允许外设与内存直接进行数据传输,这个过程不需要CPU的参与。

更多的零拷贝相关内容看一下这个彻底搞懂零拷贝(Zero-Copy)技术

netmap安装与常用api介绍安装netmap

单独写一篇安装教程,按照这个来即可手把手教你ubuntu18.04安装netmap

netmap的头文件#include<net/netmap_user.h>在 /netmap/sys/net/下

nm_open

调用 nm_open 函数时,如:nmr = nm_open("netmap:ens33", NULL, 0, NULL); nm_open()会对传递的 ifname 指针里面的字符串进行分析,提取出网络接口名。

nm_open() 会 对 struct nm_desc *d 申 请 内 存 空 间 , 并 通 过 d->fd =open(NETMAP_DEVICE_NAME, O_RDWR);打开一个特殊的设备/dev/netmap 来创建文件描述符 d->fd。

注意这个fd是/dev/netmap这个网卡设备,网卡只要来数据了,相应的这个fd就会有epollIN事件,这个fd是检测网卡有没有数据的,因为是mmap,只要网卡有数据了,那么内存就有数据的。

fd是指向网卡,操作数据是操作内存,内存和网卡数据的同步的,而我们cpu只能操作内存,不能操作外设。

简而言之,struct nm_desc里面包含一个fd,这个fd指向/dev/netmap,用于poll、epoll等系统调用。

一旦调用 nm_open 函数,网卡的数据就不从内核协议栈走了,这时候最好在虚拟机中建两个网卡,一个用于netmap,一个用于ssh等应用程序的正常工作。

struct nm_desc *nm_open(const char *ifname, const struct nmreq *req, uint64_t new_flags, const struct nm_desc *arg); struct nm_desc *nmr = nm_open("netmap:ens33", NULL, 0, NULL);

nm_nextpkt

nm_nextpkt是用来接收网卡上到来的数据包的函数。nm_nextpkt会将所有 rx 环都检查一遍,当发现有一个 rx 环有需要接收的数据包时,得到这个数据包的地址,并返回。所以 nm_nextpkt()每次只能取一个数据包。 因为接收到的数据包没有经过协议栈处理,因此需要在用户程序中自己解析。rx 环:想象成一个环形队列即可,每一项就是一个数据包。

stream即为数据在缓冲区中的首地址,struct nm_pkthdr为返回的数据包头部信息,不需要管头部的话直接从stream去取数据就行了。stream现在就是链路层的数据

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(2)

static u_char *nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr); unsigned* stream = nm_nextpkt(nmr, &nmhead);

nm_inject

nm_inject()是用来往共享内存中写入待发送的数据包数据的。数据包经共享内存拷贝到网卡,然后发送出去。所以 nm_inject()是用来发包的。

nm_inject()也会查找所有的发送环(tx 环),找到一个可以发送的槽,就将数据包写入并返回,所以每次函数调用也只能发送一个包。

static int nm_inject(struct nm_desc *d, const void *buf, size_t size); nm_inject(nmr,&arp_rt,sizeof(arp_rt));

nm_close

nm_close 函数就是回收动态内存,回收共享内存,关闭文件描述符什么的了。

static int nm_close(struct nm_desc *d) nm_close(nmr);

相关视频推荐

手写一个用户态协议栈以及零拷贝的实现

从netmap到dpdk,从硬件到协议栈,4个维度让网络体系构建起来

学习地址:C/C Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

需要C/C Linux服务器架构师学习资料加qun812855908获取(资料包括C/C ,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,tcp/IP,协程,DPDK,ffmpeg等),免费分享

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(3)

协议栈协议栈的定义

所谓协议栈,“栈”怎么理解,先进后出,正如下图udp协议所示。在应用层我们调用sendto发送数据时,我们需要在用户数据前面加上udp的协议头,然后进入网络层加入ip头,进入链路层加入以太网的头,最后由网卡进行数模转换变成光电信号发送给对端。对端网卡接收到光电信号后进行模数转换,再依次拆包,最终到达应用层就是我们最初发送的数据了。这个过程就像栈一样,先进后出。

协议栈在一定意义是又可以称为协议族,“族”怎么理解,我们看到传输层有udp,tcp等协议,网络层有ip,icmp协议等等,这些协议形成了一个家族。

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(4)

内核协议栈帮我们解析了传输层,网络层和数据链路层的协议,所以我们用户态协议栈正是去做这三层的协议。

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(5)

链路层首部以太网协议

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(6)

​以太网协议:两个地址皆为6字节的mac地址,后面2字节的类型区分是什么协议

#pragma pack(1) //一字节对齐 #define NETMAP_WITH_LIBS #define ETH_ADDR_LENGTH 6 #define PROTO_ip 0x0800 //ip协议 #define PROTO_ARP 0x0806 //arp请求协议 #define PROTO_RARP 0x0835 //rarp应答协议 #define PROTP_UPD 17 struct ethhdr { unsigned char h_dst[ETH_ADDR_LENGTH]; unsigned char h_src[ETH_ADDR_LENGTH]; unsigned short h_proto; };

网络层首部ip协议

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(7)

​关于ip协议里面第一个字节内的大小端转换不懂的请查看大端与小端概念、多字节之间与单字节多部分的大小端转换详解

struct iphdr { unsigned char hdrlen: 4, //一字节,手动大小端转换 version: 4; unsigned char tos; unsigned short totlen; unsigned short id; unsigned short flag_offset; unsigned char ttl; unsigned char type; unsigned short check; unsigned int sip; unsigned int dip; }; struct ippkt { struct ethhdr eh; //14 struct iphdr ip; //20 };

arp协议

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(8)

struct arphdr { unsigned short h_type; unsigned short h_proto; unsigned char h_addrlen; unsigned char h_protolen; unsigned short oper; unsigned char smac[ETH_ADDR_LENGTH]; unsigned int sip; unsigned char dmac[ETH_ADDR_LENGTH]; unsigned int dip; }; struct arppkt { struct ethhdr eh; struct arphdr arp; };

传输层首部udp协议

这里传输层在本文中先只介绍udp,tcp下文再写

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(9)

​为什么在udppkt里面,定义了一个unsigned char data[0];,这个叫柔性数组,或者叫零长数组,是用来定义用户数据包的起始地址而不占用实际的结构体空间。具体内容自行百度。

struct udphdr { unsigned short sport; unsigned short dport; unsigned short length; unsigned short check; }; struct udppkt { struct ethhdr eh; //14 struct iphdr ip; //20 struct udphdr udp;//8 unsigned char data[0]; };

用户态协议栈设计实现1. 实现udp协议设计思路

我们使用udp工具,给我们的服务器发送udp包,看是否能解析出来,然后按再回发回去

如果要解析udp,那么协议顺序为 ether -> ip ->udp,所以我们按照这个顺序依次解析即可。

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(10)

代码

// // Created by 68725 on 2022/7/19. // #include <stdio.h> #include <sys/poll.h> #include <netinet/in.h> #include <arpa/inet.h> #define NETMAP_WITH_LIBS #include <net/netmap_user.h> #include <string.h> #pragma pack(1) #define ETH_ADDR_LENGTH 6 #define PROTO_IP 0x0800 #define PROTO_ARP 0x0806 #define PROTP_UPD 17 struct ethhdr { unsigned char h_dst[ETH_ADDR_LENGTH]; unsigned char h_src[ETH_ADDR_LENGTH]; unsigned short h_proto; }; struct iphdr { unsigned char hdrlen: 4, version: 4; unsigned char tos; unsigned short totlen; unsigned short id; unsigned short flag_offset; unsigned char ttl; unsigned char type; unsigned short check; unsigned int sip; unsigned int dip; }; struct ippkt { struct ethhdr eh; //14 struct iphdr ip; //20 }; struct udphdr { unsigned short sport; unsigned short dport; unsigned short length; unsigned short check; }; struct udppkt { struct ethhdr eh; //14 struct iphdr ip; //20 struct udphdr udp;//8 unsigned char data[0]; }; struct arphdr { unsigned short h_type; unsigned short h_proto; unsigned char h_addrlen; unsigned char h_protolen; unsigned short oper; unsigned char smac[ETH_ADDR_LENGTH]; unsigned int sip; unsigned char dmac[ETH_ADDR_LENGTH]; unsigned int dip; }; struct arppkt { struct ethhdr eh; struct arphdr arp; }; void echo_udp_pkt(struct udppkt *udp, struct udppkt *udp_rt) { memcpy(udp_rt, udp, sizeof(struct udppkt)); memcpy(udp_rt->eh.h_dst, udp->eh.h_src, ETH_ADDR_LENGTH); memcpy(udp_rt->eh.h_src, udp->eh.h_dst, ETH_ADDR_LENGTH); udp_rt->ip.sip = udp->ip.dip; udp_rt->ip.dip = udp->ip.sip; udp_rt->udp.sport = udp->udp.dport; udp_rt->udp.dport = udp->udp.sport; } int main() { struct nm_pkthdr h; struct nm_desc *nmr = nm_open("netmap:ens33", NULL, 0, NULL); if (nmr == NULL) { return -1; } printf("open ens33 seccess\n"); struct pollfd pfd = {0}; pfd.fd = nmr->fd; pfd.events = POLLIN; while (1) { printf("new data coming!\n"); int ret = poll(&pfd, 1, -1); if (ret < 0) { continue; } if (pfd.revents & POLLIN) { unsigned char *stream = nm_nextpkt(nmr, &h); //ether struct ethhdr *eh = (struct ethhdr *) stream; if (ntohs(eh->h_proto) == PROTO_IP) { //ip struct ippkt *iph=(struct ippkt *)stream; if (iph->ip.type == PROTP_UPD) { //udp struct udppkt *udp = (struct udppkt *) stream; int udplength = ntohs(udp->udp.length); udp->data[udplength - 8] = '\0'; printf("udp ---> %s\n", udp->data); struct udppkt udp_rt; echo_udp_pkt(udp, &udp_rt); nm_inject(nmr, &udp_rt, sizeof(struct udppkt)); } } } } nm_close(nmr); }

测试能否解析udp包的数据
  • 开启netmap

每次重启使用前都需要insmod netmap.ko ,然后我们查看ls /dev/netmap -l,出现下面的设备就说明开启成功了。

root@wxf:/netmap/LINUX# insmod netmap.ko root@wxf:/netmap/LINUX# ls /dev/netmap -l crw------- 1 root root 10, 54 Jul 18 17:28 /dev/netmap

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(11)

  • 运行上面的udp测试代码

可以看到我们能够正常的接收udp数据

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(12)

​但是为什么过了一会再发数据,程序就接收不到了,而且还多了这么多非udp的数据包??因为宿主机不知道虚拟机的ip和mac地址了,我们查看arp表,发现没有192.168.109.100虚拟机的记录,此时宿主机会在局域网内广播arp请求,这也就是为什么new data coming但不是udp的原因。刚开始能正常解析数据是因为,虚拟机刚开机的时候,我用xshell连虚拟机,所以宿主机发送过arp请求,而虚拟机的内核协议栈此时还没被netmap接管,回应了arp请求,宿主机就将虚拟机的信息暂时的添加到arp表中,当动态arp记录失效,udp包不知道发给谁,就会先发arp请求。

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(13)

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(14)

​解决这个问题的办法很简单,要么我们手动在宿主机上添加一条静态的arp记录,要么我们实现arp协议,下面我们来实现arp协议。

2.实现arp协议设计思路

我们知道arp协议是在网络层的,所以我们先解析ether,再解析arp即可

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(15)

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(16)

代码

// // Created by 68725 on 2022/7/19. // #include <stdio.h> #include <sys/poll.h> #include <netinet/in.h> #include <arpa/inet.h> #define NETMAP_WITH_LIBS #include <net/netmap_user.h> #include <string.h> #pragma pack(1) #define ETH_ADDR_LENGTH 6 #define PROTO_IP 0x0800 #define PROTO_ARP 0x0806 #define PROTO_RARP 0x0835 #define PROTP_UPD 17 struct ethhdr { unsigned char h_dst[ETH_ADDR_LENGTH]; unsigned char h_src[ETH_ADDR_LENGTH]; unsigned short h_proto; }; struct iphdr { unsigned char hdrlen: 4, version: 4; unsigned char tos; unsigned short totlen; unsigned short id; unsigned short flag_offset; unsigned char ttl; unsigned char type; unsigned short check; unsigned int sip; unsigned int dip; }; struct ippkt { struct ethhdr eh; //14 struct iphdr ip; //20 }; struct udphdr { unsigned short sport; unsigned short dport; unsigned short length; unsigned short check; }; struct udppkt { struct ethhdr eh; //14 struct iphdr ip; //20 struct udphdr udp;//8 unsigned char data[0]; }; struct arphdr { unsigned short h_type; unsigned short h_proto; unsigned char h_addrlen; unsigned char h_protolen; unsigned short oper; unsigned char smac[ETH_ADDR_LENGTH]; unsigned int sip; unsigned char dmac[ETH_ADDR_LENGTH]; unsigned int dip; }; struct arppkt { struct ethhdr eh; struct arphdr arp; }; int str2mac(char *mac, char *str) { char *p = str; unsigned char value = 0x0; int i = 0; while (p != '\0') { if (*p == ':') { mac[i ] = value; value = 0x0; } else { unsigned char temp = *p; if (temp <= '9' && temp >= '0') { temp -= '0'; } else if (temp <= 'f' && temp >= 'a') { temp -= 'a'; temp = 10; } else if (temp <= 'F' && temp >= 'A') { temp -= 'A'; temp = 10; } else { break; } value <<= 4; value |= temp; } p ; } mac[i] = value; return 0; } void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *mac) { memcpy(arp_rt, arp, sizeof(struct arppkt)); memcpy(arp_rt->eh.h_dst, arp->eh.h_src, ETH_ADDR_LENGTH);//以太网首部填入目的 mac str2mac(arp_rt->eh.h_src, mac);//以太网首部填入源mac arp_rt->eh.h_proto = arp->eh.h_proto;//以太网协议还是arp协议 arp_rt->arp.h_addrlen = 6; arp_rt->arp.h_protolen = 4; arp_rt->arp.oper = htons(2); // ARP响应 str2mac(arp_rt->arp.smac, mac);//arp报文填入源mac arp_rt->arp.sip = arp->arp.dip; // arp报文填入发送端 ip memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ADDR_LENGTH);//arp报文填入目的 mac arp_rt->arp.dip = arp->arp.sip; // arp报文填入目的 ip } int main() { struct nm_pkthdr h; struct nm_desc *nmr = nm_open("netmap:ens33", NULL, 0, NULL); if (nmr == NULL) { return -1; } printf("open ens33 seccess\n"); struct pollfd pfd = {0}; pfd.fd = nmr->fd; pfd.events = POLLIN; while (1) { printf("new data coming!\n"); int ret = poll(&pfd, 1, -1); if (ret < 0) { continue; } if (pfd.revents & POLLIN) { unsigned char *stream = nm_nextpkt(nmr, &h); struct ethhdr *eh = (struct ethhdr *) stream; if (ntohs(eh->h_proto) == PROTO_IP) { struct ippkt *iph=(struct ippkt *)stream; if (iph->ip.type == PROTP_UPD) { struct udppkt *udp = (struct udppkt *) stream; int udplength = ntohs(udp->udp.length); udp->data[udplength - 8] = '\0'; printf("udp ---> %s\n", udp->data); struct udppkt udp_rt; echo_udp_pkt(udp, &udp_rt); nm_inject(nmr, &udp_rt, sizeof(struct udppkt)); } } 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.109.100")) { echo_arp_pkt(arp, &arp_rt, "00:0c:29:1b:18:20"); nm_inject(nmr, &arp_rt, sizeof(arp_rt)); printf("arp ret\n"); } } } } nm_close(nmr); } //gcc -o main main.c -I /netmap/sys/ //insmod netmap.ko

测试能否回应arp请求
  • 开启netmap

每次重启使用前都需要insmod netmap.ko ,然后我们查看ls /dev/netmap -l,出现下面的设备就说明开启成功了。

root@wxf:/netmap/LINUX# insmod netmap.ko root@wxf:/netmap/LINUX# ls /dev/netmap -l crw------- 1 root root 10, 54 Jul 18 17:28 /dev/netmap

  • 运行上面的udp arp测试代码

我们发现成功响应了arp请求

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(17)

​前面也说过了,一旦调用nm_open函数,网卡的数据就不从内核协议栈走了,所以我们ping一下看看能不能ping通,ping是icmp协议。发现ping不同,但是我们是接收到数据包了的,下面就来实现icmp协议

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(18)

3.实现icmp协议设计思路

icmp协议是ip协议的一部分

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(19)

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(20)

​icmp的类型:ping请求是8,ping回应是0,其他的就不介绍了

  • 代码:ping的代码为0
  • 校验和:它的计算方法与IP数据报中的首部校验和计算方式是一样IP首部校验和计算原理

ICMP类型为ping时,具体协议格式如下:

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(21)

​其中增加的标识符和序号字段按照请求端的数据返回即可,同时对选项数据也必须进行回显,客户端可以会验证这些数据。

代码

// // Created by 68725 on 2022/7/19. // #include <stdio.h> #include <sys/poll.h> #include <netinet/in.h> #include <arpa/inet.h> #define NETMAP_WITH_LIBS #include <net/netmap_user.h> #include <string.h> #pragma pack(1) #define ETH_ADDR_LENGTH 6 #define PROTO_IP 0x0800 #define PROTO_ARP 0x0806 #define PROTO_RARP 0x0835 #define PROTP_UPD 17 #define PROTO_ICMP 1 struct ethhdr { unsigned char h_dst[ETH_ADDR_LENGTH]; unsigned char h_src[ETH_ADDR_LENGTH]; unsigned short h_proto; }; struct iphdr { unsigned char hdrlen: 4, version: 4; unsigned char tos; unsigned short totlen; unsigned short id; unsigned short flag_offset; unsigned char ttl; unsigned char type; unsigned short check; unsigned int sip; unsigned int dip; }; struct ippkt { struct ethhdr eh; //14 struct iphdr ip; //20 }; struct udphdr { unsigned short sport; unsigned short dport; unsigned short length; unsigned short check; }; struct udppkt { struct ethhdr eh; //14 struct iphdr ip; //20 struct udphdr udp;//8 unsigned char data[0]; }; struct arphdr { unsigned short h_type; unsigned short h_proto; unsigned char h_addrlen; unsigned char h_protolen; unsigned short oper; unsigned char smac[ETH_ADDR_LENGTH]; unsigned int sip; unsigned char dmac[ETH_ADDR_LENGTH]; unsigned int dip; }; struct arppkt { struct ethhdr eh; struct arphdr arp; }; int str2mac(char *mac, char *str) { char *p = str; unsigned char value = 0x0; int i = 0; while (p != '\0') { if (*p == ':') { mac[i ] = value; value = 0x0; } else { unsigned char temp = *p; if (temp <= '9' && temp >= '0') { temp -= '0'; } else if (temp <= 'f' && temp >= 'a') { temp -= 'a'; temp = 10; } else if (temp <= 'F' && temp >= 'A') { temp -= 'A'; temp = 10; } else { break; } value <<= 4; value |= temp; } p ; } mac[i] = value;//a return 0; } void echo_udp_pkt(struct udppkt *udp, struct udppkt *udp_rt) { memcpy(udp_rt, udp, sizeof(struct udppkt)); memcpy(udp_rt->eh.h_dst, udp->eh.h_src, ETH_ADDR_LENGTH); memcpy(udp_rt->eh.h_src, udp->eh.h_dst, ETH_ADDR_LENGTH); udp_rt->ip.sip = udp->ip.dip; udp_rt->ip.dip = udp->ip.sip; udp_rt->udp.sport = udp->udp.dport; udp_rt->udp.dport = udp->udp.sport; } void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *mac) { memcpy(arp_rt, arp, sizeof(struct arppkt)); memcpy(arp_rt->eh.h_dst, arp->eh.h_src, ETH_ADDR_LENGTH);//以太网首部填入目的 mac str2mac(arp_rt->eh.h_src, mac);//以太网首部填入源mac arp_rt->eh.h_proto = arp->eh.h_proto;//以太网协议还是arp协议 arp_rt->arp.h_addrlen = 6; arp_rt->arp.h_protolen = 4;//aa arp_rt->arp.oper = htons(2); // ARP响应 str2mac(arp_rt->arp.smac, mac);//arp报文填入源mac arp_rt->arp.sip = arp->arp.dip; // arp报文填入发送端 ip memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ADDR_LENGTH);//arp报文填入目的 mac arp_rt->arp.dip = arp->arp.sip; // arp报文填入目的 ip } struct icmphdr { unsigned char type; unsigned char code; unsigned short check; unsigned short identifier; unsigned short seq; unsigned char data[32]; }; struct icmppkt { struct ethhdr eh; struct iphdr ip; struct icmphdr icmp; }; unsigned short in_cksum(unsigned short *addr, int len) { register int nleft = len; register unsigned short *w = addr; register int sum = 0; unsigned short answer = 0; while (nleft > 1) { sum = *w ; nleft -= 2; } if (nleft == 1) { *(u_char *) (&answer) = *(u_char *) w; sum = answer; } sum = (sum >> 16) (sum & 0xffff); sum = (sum >> 16); answer = ~sum; return (answer); } void echo_icmp_pkt(struct icmppkt *icmp, struct icmppkt *icmp_rt) { memcpy(icmp_rt, icmp, sizeof(struct icmppkt)); icmp_rt->icmp.type = 0x0; // icmp_rt->icmp.code = 0x0; // icmp_rt->icmp.check = 0x0; icmp_rt->ip.sip = icmp->ip.dip; icmp_rt->ip.dip = icmp->ip.sip; memcpy(icmp_rt->eh.h_dst, icmp->eh.h_src, ETH_ADDR_LENGTH); memcpy(icmp_rt->eh.h_src, icmp->eh.h_dst, ETH_ADDR_LENGTH); icmp_rt->icmp.check = in_cksum((unsigned short *) &icmp_rt->icmp, sizeof(struct icmphdr)); } int main() { struct nm_pkthdr h; struct nm_desc *nmr = nm_open("netmap:ens33", NULL, 0, NULL); if (nmr == NULL) { return -1; } printf("open ens33 seccess\n"); struct pollfd pfd = {0}; pfd.fd = nmr->fd; pfd.events = POLLIN; while (1) { printf("new data coming!\n"); int ret = poll(&pfd, 1, -1); if (ret < 0) { continue; } if (pfd.revents & POLLIN) { unsigned char *stream = nm_nextpkt(nmr, &h); struct ethhdr *eh = (struct ethhdr *) stream; if (ntohs(eh->h_proto) == PROTO_IP) { struct ippkt *iph = (struct ippkt *) stream; if (iph->ip.type == PROTP_UPD) { struct udppkt *udp = (struct udppkt *) stream; int udplength = ntohs(udp->udp.length); udp->data[udplength - 8] = '\0'; printf("udp ---> %s\n", udp->data); struct udppkt udp_rt; echo_udp_pkt(udp, &udp_rt); nm_inject(nmr, &udp_rt, sizeof(struct udppkt)); } else if (iph->ip.type == 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); nm_inject(nmr, &icmp_rt, sizeof(struct icmppkt)); } } } 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.109.100")) { echo_arp_pkt(arp, &arp_rt, "00:0c:29:1b:18:20"); nm_inject(nmr, &arp_rt, sizeof(arp_rt)); printf("arp ret\n"); } } } } nm_close(nmr); } //gcc -o main main.c -I /netmap/sys/ //insmod netmap.ko

测试能否回应ping请求
  • 开启netmap

每次重启使用前都需要insmod netmap.ko ,然后我们查看ls /dev/netmap -l,出现下面的设备就说明开启成功了。

root@wxf:/netmap/LINUX# insmod netmap.ko root@wxf:/netmap/LINUX# ls /dev/netmap -l crw------- 1 root root 10, 54 Jul 18 17:28 /dev/netmap

运行上面的代码,发现udp,arp,icmp都可以正常解析和发送

udp协议发数据到界面的代码(用户态协议栈设计实现udp)(22)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页