用户态协议栈

前言

用户态协议栈是指把网络协议栈accept()、listen()等原本在操作系统中的接口,与应用程序放在一起,把网络协议的解析放做进程中的一部分。用户态协议栈的主要作用,是减少网络数据从网卡到应用程序拷贝过程中的系统调用次数,从而减少CPU上下文切换的次数,达到提高性能的目的。

有了用户态协议栈,对于网卡的想象空间也会增大,网卡的可操作性会更强。比如,可以通过控制网卡,将PC机做成交换机或路由器,可以过滤数据。另外,用户态协议栈可以用在网关上,因为当连接数增多,网关会成为性能的瓶颈,使用用户态协议栈可以提高网关的性能。

协议栈

是操作系统内核中对网卡数据进行解析的那部分。通过结构体sk_buff将网卡中的数据拷贝到协议栈中。

        每次数据到达网卡时,需要经过2次copy,一次是将数据从网卡拷贝到协议栈中,另一次是将数据从内核中的协议栈拷贝到内存中,然后应用程序再对内存进行操作。

        如果我们要实现用户态的协议栈就要想办法在网卡到内核协议栈中间将其拦截,直接将网卡中的数据映射到内存中,对内存进行操作,达到零copy,就可极大提高效率。

获取原始数据

        如何从网卡中取到一帧完整的网络数据呢。有三个方法,一是利用原生的socket如raw socket,二是利用开源框架如netmap,三是利用商业框架如dpdk。

        本文使用netmap来获取数据。netmap是应用程序用来收发原始网络数据的高性能框架,采用mmap方式,直接将网卡的数据映射到内存中,应用程序可以直接哦通过mmap操作相应内存中的数据,实现零copy.

        零copy并不是没有拷贝数据,而是减少用户态、内核态的切换次数及CPU拷贝次数。主要还是依靠DMA技术,DMA本质上是一块主板上独立的芯片,允许外设设备和内存存储器之间直接进行IO数据传输,其过程不需要CPU的参与。零拷贝讲解博客

netmap的安装使用

参考手把手教你ubuntu18.04安装netmap

netmap API

nm_open

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_open()会对传递的 ifname 指针里面的字符串进行分析,提取出网络接口名;
nm_open() 会申请struct nm_desc结构体大小的内存空间并返回首地址(这里用nmr接收);
通过 nmr->fd =open(NETMAP_DEVICE_NAME, O_RDWR);打开一个特殊的设备/dev/netmap 来创建文件描述符 nmr->fd

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

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

nm_nextpkt

static u_char *nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr);
作用:用来接收网卡上到来的数据包,每次只能取一个数据包。

例子:
unsigned* stream = nm_nextpkt(nmr, &nmhead);
stream为数据在缓冲区中的首地址;struct nm_pkthdr为返回的数据包头部信息;如果不需要管头部的话直接从stream去取数据就行了。stream现在就是链路层的数据,包含了一些头部协议数据。

nm_inject

static int nm_inject(struct nm_desc *d, const void *buf, size_t size);
作用:用来往共享内存中写入待发送的数据包数据的。数据包经共享内存拷贝到网卡,然后发送出去。所以 nm_inject()是用来发包的。每次只能发一个包。

例子:nm_inject(nmr,&arp_rt,sizeof(arp_rt));

nm_close

static int nm_close(struct nm_desc *d);
作用:回收动态内存,回收共享内存,关闭文件描述符。

例子:
nm_close(nmr);

UDP数据帧内容

 以太网协议头(数据链路层)

目的地址:目的MAC地址,6字节。

源地址:源MAC地址,6字节。

类型:网络层所用的协议类型,一般是IP协议,2字节。

数据段的长度为46-1500字节。

以太网数据帧并没有表示长度的字段。主机确定以太网数据帧接收完毕依靠的是主机接收器感受不到电压变换。当接收器感受不到电压的变化时,表明这一帧数据已经接收完成。

IP协议头(网络层)

版本:记录数据报属于哪一个版本的协议,如IPv4或IPv6

首部长度:指明IP头部长度,单位是字,也就是两个字节。该域的值最小为5,就是标准的头部长度;最大为15,表明有扩展部分。

服务类型:用来区分不同服务的需要

总长度:整个IP报的长度,单位为字节。16位范围为65535,最大发64k。MTU=1500是最大传输单元,发送需要分片。

16位标识:分片后的所有的包有同样的标识。比如15k数据分割成如干个1500字节的包有同样的标识。

标记:共三个bit,第一个未使用;第二个DF(Don’t Fragment),设置成1表示这个数据包不能被分割,这个是针对路由器的一条指令;第三个MF(MoreFragment),如果一个数据包被分割了,那么除了最后一个分段以外的所有分段都必须设置为1,用来表示后面还有更多的分段没有到达,最后一个设置为0,用来表示分割的段全部到达。

段偏移量:这个域有13bit,也就是每一个数据报最多有8192个分段。每一个分段的长度必须是8字节的倍数,也就是说8字节是分段的基本单位,当然分组的最后一个段不做限制。这样最大的数据报长度为8*8192=65536字节,比目前限制的最大数据报长度还多1,能够满足对网络中所有数据报传送的需求。

生存时间(TTL)这是一个生存期计数器,最大为255s,但是实际上使用的时候用作跳数计数器,当值为0时数据报被丢弃,用来避免一个数据报过久的逗留在网络中。

8位协议:这里和链路层的类型作用相同,用来表示更高层的协议,这个数据报里是UDP

16位首部校验和:IP头部的校验和

源IP地址:数据报来源主机的IP地址

目的IP地址:数据报目的主机的IP地址

UDP协议头(传输层)

(1)源端口(Source Port):16位的源端口域包含初始化通信的端口号。源端口和IP地址的作用是标识报文的返回地址。

(2)目的端口(Destination Port):16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。

(3)封包长度(Length):UDP头和数据的总长度。

(4)校验和(Check Sum):和TCP和校验和一样,不仅对头数据进行校验,还对包的内容进行校验。

封装各个协议头

协议头定义

#define ETHER_ADDR_LEN 6
 
//以太网首部
struct ether_hdr{
	unsigned char dst_mac[ETHER_ADDR_LEN]; 	//目地MAC地址
	unsigned char src_mac[ETHER_ADDR_LEN];	//源MAC地址
	unsigned shor protocol;					//类型
};
 
//IP数据头
struct ip_hdr{
	unsigned char version:4,	//版本
				  hdrlen:4;		//首部长度
	unsigned char tos;			//服务类型
	unsigned short totlen;		//总长度
 
	unsigned short id;			//标识
	unsigned short flag:3,		//标志 缺省
				   offset:13;	//片偏移
 
	unsigned char ttl;			//生存时间(TTL)
	unsigned char protocol;		//协议
	unsigned short check;		//首部检验和
 
	unsigned int sip;			//源IP地址
	unsigned int dip;			//目的IP地址
};
 
//UDP协议头
struct udp_hdr{
	unsigned short sport;		//源端口
	unsigned short dport;		//目的端口
	
	unsigned short length;		//封包长度
	unsigned short check;		//校验和
};

//整个数据包
struct udp_pkt {
	struct ether_hdr eh;
	struct ip_hdr ip;
	struct udp_hdr udp;
	unsigned char payload[0]; 	//柔性数组
};

udp的最后一个成员是柔性数组,是用来定义用户数据包的起始地址而不占用实际的结构体空间,用来指向数据的首地址。

柔性数组

长度为0的数组的主要用途是为了满足需要变长度的结构体。

1、用法 : 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的。
2、对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量。
下面两个情况下是可以使用柔性数组的:

1:内存是已经分配好的。

2:这个柔性数组的长度不确定但是我们是可以通过其他方法计算出来的。


简单的测试例子

代码

//test.cpp

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
 
#include <sys/poll.h>
#include <arpa/inet.h>
 
 
#define NETMAP_WITH_LIBS	//开启netmap
 
#include <net/netmap_user.h> 
 
 
 
 
#pragma pack(1) 	//1字节对齐
 
//#define NETMAP_WITH_LIBS 
 
#define ETHER_ADDR_LEN 6
#define PROTOT_IP 	0x0800	//IP协议
#define PROTOT_UDP 	0x11		//UDP协议
 
//以太网首部
struct ether_hdr{
	unsigned char dst_mac[ETHER_ADDR_LEN]; 	//目地MAC地址
	unsigned char src_mac[ETHER_ADDR_LEN];	//源MAC地址
	unsigned short protocol;					//类型
};
 
//IP数据头
struct ip_hdr{
	unsigned char version:4,	//版本
				  hdrlen:4;		//首部长度
	unsigned char tos;			//服务类型
	unsigned short totlen;		//总长度
 
	unsigned short id;			//标识
	unsigned short flag:3,		//标志 缺省
				   offset:13;	//片偏移
 
	unsigned char ttl;			//生存时间(TTL)
	unsigned char protocol;		//协议
	unsigned short check;		//首部检验和
 
	unsigned int sip;			//源IP地址
	unsigned int dip;			//目的IP地址
};
 
//UDP协议头
struct udp_hdr{
	unsigned short sport;		//源端口
	unsigned short dport;		//目的端口
	
	unsigned short length;		//封包长度
	unsigned short check;		//校验和
};
 
struct udp_pkt {
	struct ether_hdr eh;
	struct ip_hdr ip;
	struct udp_hdr udp;
	unsigned char payload[0]; 	//柔性数组
};
 
 
// netmap -- > personal
 
int main()
{
	struct nm_pkthdr h;
	struct nm_desc *nmr = nm_open("netmap:ens38", NULL, 0, NULL);
	if(nmr == NULL)return -1;
 
	//加入poll监控IO
	struct pollfd pfd = {0};
	pfd.fd = nmr->fd;
	pfd.events = POLLIN;
 
	while(1){
		int ret = poll(&pfd, 1, -1);
		if(ret < 0)continue;
 
		if(pfd.revents & POLLIN){
			unsigned char* stream = nm_nextpkt(nmr, &h); //接收网卡上到来的数据包
 
			struct ether_hdr* eh = (struct ether_hdr*)stream;
 
			if(ntohs(eh->protocol) == PROTOT_IP){ //是IP数据
				struct udp_pkt* pkt = (struct udp_pkt*)stream;
				if(pkt->ip.protocol == PROTOT_UDP){ //UDP协议
					int length = ntohs(pkt->udp.length);
 
					pkt->payload[length - 8] = '\0';
					printf("pkt: %s\n", pkt->payload);
				}
			}
		}
	}
}
 
#先启动netmap
insmod netmap.ko
#编译
g++ test.cpp -o test -I /home/user/Downloads/netmap-master/sys/
#运行
./test

测试结果

        可以发现,当客户端(windows)向服务端(windows下的linux虚拟机)发送信息的时候,发现发送了一段时间后就没法发送了。

        刚开始能正常解析数据是因为,虚拟机刚开机的时候,我ping过虚拟机,所以window主机发送过arp请求,而虚拟机的内核协议栈此时还没被netmap接管,回应了arp请求,主机就将虚拟机的信息暂时的添加到arp表中。(apr -a

         当动态arp记录失效,udp包不知道发给谁,就会先发arp请求(如果源主机没有收到ARP响应数据包则表示查询失败,也就不会发udp包了)。一般这个记录会缓存个二十分钟左右。如果没法理解可以看看这篇数据链路层:ARP协议详解(绝对经典)
  解决这个问题的办法很简单,要么我们手动在宿主机上添加一条静态的arp记录,要么我们实现arp协议,下面我们来实现arp协议。

ARP协议实现

arp属于网络层协议,主要功能是将IP地址解析称MAC地址,其协议头如下:

硬件类型:表示硬件地址的类型,值为1表示以太网地址
协议类型:表示要映射的协议地址类型。它的值为0x0800表示IP地址类型
硬件地址长度和协议地址长度以字节为单位,对于以太网上的IP地址的ARP请求或应答来说,他们的值分别为6和4;
操作类型(op):1表示ARP请求,2表示ARP应答
发送端MAC地址:发送方设备的硬件地址;
发送端IP地址:发送方设备的IP地址;
目标MAC地址:接收方设备的硬件地址。
目标IP地址:接收方设备的IP地址。

 因此,我们要先解析以太网首部,在解析arp包首部。

实现代码

arp+udp协议的具体实现代码如下:

//insmod netmap.ko
//gcc -o arp_netmap arp_netmap.c
#include <stdio.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define NETMAP_WITH_LIBS	//开启netmap
#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_UDP 17

struct ethhdr {
	unsigned char h_dst[ETH_ADDR_LENGTH];//目地MAC地址
	unsigned char h_src[ETH_ADDR_LENGTH];//源MAC地址
	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;			//生存时间(TTL)
	unsigned char type;			//协议
	unsigned short check;		//首部检验和
	unsigned int sip;			//源IP地址
	unsigned int dip;			//目的IP地址
};

struct udphdr {
	unsigned short sport;		//源端口
	unsigned short dport;		//目的端口
	unsigned short length;		//封包长度
	unsigned short check;		//校验和
};

struct ippkt {
	struct ethhdr eh; //14
	struct iphdr ip; //20
};

struct arphdr {
	unsigned short h_type;		//硬件类型
	unsigned short h_proto;		//协议类型

	unsigned char h_addrlen;	//硬件长度
	unsigned char h_protolen;	//协议长度

	unsigned short oper;		//操作码 ARP请求(1),ARP响应(2)

	unsigned char smac[ETH_ADDR_LENGTH];	//源硬件地址
	unsigned int sip;						//源逻辑地址
	unsigned char dmac[ETH_ADDR_LENGTH];	//目的硬件地址
	unsigned int dip;						//目的逻辑地址
};

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

struct arppkt {
	struct ethhdr eh;
	struct arphdr arp;
};

//将字符串解析称MAC地址
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;
	printf("str=%s,  mac=%s\n", str, mac);
	return 0;
}

//封装待发送的udp包
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;
}

//封装待发送的arp包
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:ens38", 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) {	//1.如果收到的包的网络层是IP协议
				struct ippkt *iph = (struct ippkt *)stream;
				if (iph->ip.type == PROTP_UDP) {	//1.1 如果收到的包的IP协议的上层是UDP协议
					//解析收到的UDP包
					struct udppkt *udp = (struct udppkt *) stream;
					int udplength = ntohs(udp->udp.length);
					udp->data[udplength - 8] = '\0';
					printf("udp ---> %s\n", udp->data);
					//回复UDP包给发送端
					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) {	//2.如果收到的包的网络层是ARP协议
				struct arppkt *arp = (struct arppkt *) stream;
				struct arppkt arp_rt;
				if (arp->arp.dip == inet_addr("192.168.240.130")) {	//将自己的IP地址和MAC地址放到回复包ARP里
					echo_arp_pkt(arp, &arp_rt, "00:0c:29:7b:e4:67");
					nm_inject(nmr, &arp_rt, sizeof(arp_rt));
					printf("arp ret\n");
				}
			}
		}
	}
	nm_close(nmr);
}

测试结果

当客户端发送arp请求时,服务端这边响应了arp请求。

        当我们在这个程序运行时,接着在客户端ping 192.168.240.130时,发现ping不通,这是因为调用 nm_open 函数,网卡的数据就不从内核协议栈走了,而我们的程序没有支持ICMP协议(ping是icmp协议),所以不能ping通。接下来,我们实现ICMP协议。

ICMP协议实现

ICMP是一个差错报告机制,在IP主机和路由器之间传递控制消息,用于报告主机是否可达、路由是否可用等。通常被IP层或更高层协议(TCP或UDP)使用,属于网络层协议,其报文格式:

 代码实现

//insmod netmap.ko
//gcc -o icmp_netmap icmp_netmap.c
#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_UDP 17
#define PROTO_ICMP    1
 
struct ethhdr {
    unsigned char h_dst[ETH_ADDR_LENGTH];//目地MAC地址
    unsigned char h_src[ETH_ADDR_LENGTH];//源MAC地址
    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;			//生存时间(TTL)
    unsigned char type;			//协议
    unsigned short check;		//首部检验和
    unsigned int sip;			//源IP地址
    unsigned int dip;			//目的IP地址
};
 
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;		//操作码 ARP请求(1),ARP响应(2)
 
    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_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;
    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;	//类型 ping请求是8,ping回应是0
    unsigned char code;	//代码(Code):4位,标明报文的类型。ping的代码为0
    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:ens38", 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_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));
                }
				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.240.130")) {
                    echo_arp_pkt(arp, &arp_rt, "00:0c:29:7b:e4:67");
                    nm_inject(nmr, &arp_rt, sizeof(arp_rt));
                    printf("arp ret\n");
                }
            }
			
        }
    }
    nm_close(nmr);
}
 

测试结果

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值