目录

1. 网络模式

2. 获取原始数据

3. UDP数据帧格式

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

IP数据头(网络层) -- 20byte

UDP协议的数据格式(传输层) -- 8byte

4. 封装协议头

协议头代码

柔性数组

5.Netmap和API介绍

5.1 netmap原理介绍

5.2 nm_open 函数

5.3 nm_nextpkt函数

5.4 nm_close 函数

6. 测试用户态协议栈

完整代码

结果显示

7. arp协议实现

报文格式

完整代码

结果显示

8. icmp协议实现

完整代码

结果显示


1. 网络模式

在物理层上,双绞线传的是电信号,光纤传的是光信号。

网卡即不在物理层,也不在数据链路层,是在这两层之间做转换。

数据传输的流程

网卡将物理层的光电信号转换为数字信号(0101010)。给到网卡驱动,然后把这个数据(通过sk_buff(搬运工)) 拷贝迁移到协议栈。 然后协议栈解析完数据之后将数据拷贝放入recv buffer,然后应用程序通过系统调用就能得到这个数据。

2. 获取原始数据

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

不经过网络协议栈解析,拿到原始数据sk_buff;

  1. 使用原始套接字raw socket , tcpdump和wireshark就是使用这个做的,raw socket主要用来抓包。
  2. dbdk
  3. netmap是用于用户层应用程序收发原始网络数据的高性能框架,本文使用netmap进行数据的收发。

3. UDP数据帧格式

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

目地MAC地址(6字节)
源MAC地址(6字节)
类型(2字节):IP协议=0x0800

数据段的长度为46到1500字节

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

IP数据头(网络层) -- 20byte

字节和数字的存储顺序是从右到左,依次是从低位到高位,而网络存储顺序是从左到右,依次从低位到高位。

  1. 版本:占第一个字节的高四位。IPV4 = 0100, IPV4 = 0110。
  2. 首部长度:占第一个字节的低四位。4位能最表示15字节的长度。
  3. 服务类型:前3位为优先字段权,现在已经被忽略。接着4位用来表示最小延迟、最大吞吐量、最高可靠性和最小费用。
  4. 总长度:整个IP报的长度,单位为字节。16位范围为65535,最大发64k。MTU=1500是最大传输单元,发送需要分片。
  5. 16位标识:分片后的所有的包有同样的标识。比如15k数据分割成如干个1500字节的包有同样的标识。
  6. 3位标志:缺省。
  7. 13位片偏移:分片后的所有的包基于原来的整包的偏移。
  8. 生存时间(TTL):就是封包的生存时间。通常用通过的路由器的个数来衡量,比如初始值设置为32,则每通过一个路由器处理就会被减一,当这个值为0的时候就会丢掉这个包,并用ICMP消息通知源主机。
  9. 协议:定义了数据的协议,分别为:TCP、UDP、ICMP和IGMP。定义为:

#define PROTOCOL_TCP    0x06

#define PROTOCOL_UDP    0x11

#define PROTOCOL_ICMP   0x06

#define PROTOCOL_IGMP   0x06

  1. 首部检验和:校验的首先将该字段设置为0,然后将IP头的每16位进行二进制取反求和,将结果保存在校验和字段。
  2. 源IP地址:将IP地址看作是32位数值则需要将网络字节序转化位主机字节序。转化的方法是:将每4个字节首尾互换,将2、3字节互换。
  3. 目的IP地址:转换方法和来源IP地址一样。

UDP协议的数据格式(传输层) -- 8byte

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

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

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

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

4. 封装协议头

协议头代码

#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];     //柔性数组
};

柔性数组

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

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

两个情况下是可以使用柔性数组的:

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

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

5.Netmap和API介绍

5.1 netmap原理介绍

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

DMA方式,将网卡映射到内存中去(mmap)。 应用程序是可以在内存中间直接读取这块映射过来的数据的。(DMA的方式不需要通过CPU去执行指令,直接将数据放入内存)这就叫零拷贝。

5.2 nm_open 函数

1.调用 nm_open 函数时,如:nmr = nm_open("netmap:eth0", NULL, 0, NULL); nm_open()会
对传递的 ifname 指针里面的字符串进行分析,提取出网络接口名。
2.nm_open() 会 对 struct nm_desc *d 申 请 内 存 空 间 , 并 通 过 d->fd =
open(NETMAP_DEVICE_NAME, O_RDWR);打开一个特殊的设备/dev/netmap 来创建文件描述
符 d->fd。
3.通过 ioctl(d->fd, NIOCREGIF, &d->req)语句,将 d->fd 绑定到一个特殊的接口,并对 d->req
结构体里面的成员做初始化,包括 a.在共享内存区域中 nifp 的偏移,b.共享区域的大小
nr_memsize,c.tx/rx 环的大小 nr_tx_slots/nr_rx_slots(大小为 256),d.tx/rx 环的数量 nr_tx_rings、nr_rx_rings(视硬件性能而定)等。
4.接着在 if ((!(new_flags & NM_OPEN_NO_MMAP) || parent) && nm_mmap(d, parent))语句
中调用 nm_mmap 函数,继续给d指针指向的内存赋值。

5.3 nm_nextpkt函数

1. nm_nextpkt()是用来接收网卡上到来的数据包的函数。
2. nm_nextpkt()会将所有 rx 环都检查一遍,当发现有一个 rx 环有需要接收的数据包时,
得到这个数据包的地址,并返回。所以 nm_nextpkt()每次只能取一个数据包。
nm_nextpkt()源代码:

static u_char *nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr)
{
int ri = d->cur_rx_ring; //当前的接收环的编号
do
{
/* compute current ring to use */
struct netmap_ring *ring = NETMAP_RXRING(d->nifp, ri); //得
到当前 rx 环的地址
if (!nm_ring_empty(ring)) //判断环里是否有新到的包
{
u_int i = ring->cur; //当前该访问哪个槽(buffer)了
u_int idx = ring->slot[i].buf_idx; //得到第 i 个 buffer 的
下标
//printf("%d\n", idx);
u_char *buf = (u_char *) NETMAP_BUF(ring, idx); //得到存
有到来数据包的地址
// __builtin_prefetch(buf);
hdr->ts = ring->ts;
hdr->len = hdr->caplen = ring->slot[i].len;
ring->cur = nm_ring_next(ring, i); //ring->cur 向后移动
一位
/* we could postpone advancing head if we want
* to hold the buffer. This can be supported in
* the future.
*/
ring->head = ring->cur;
d->cur_rx_ring = ri; //将当前环(d->cur_rx_ring)指向第 ri
个(因为可能有多个环)。
return buf; //将数据包地址返回
}
ri++;
if (ri > d->last_rx_ring) //如果 ri 超过了 rx 环的数量,则再从
第一个 rx 环开始检测是否有包到来。
ri = d->first_rx_ring;
} while (ri != d->cur_rx_ring);
return NULL; /* 什么也没发现 */
}

5.4 nm_close 函数

源代码:

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

static int nm_close(struct nm_desc *d)
{
/*
* ugly trick to avoid unused warnings
*/
static void *__xxzt[] __attribute__ ((unused)) =
{ (void *) nm_open, (void *) nm_inject, (void *) nm_dispatch,
(void *) nm_nextpkt };
if (d == NULL || d->self != d)
return EINVAL;
if (d->done_mmap && d->mem)
munmap(d->mem, d->memsize); //释放申请的共享内存
if (d->fd != -1)
{
close(d->fd); //关闭文件描述符
}
bzero(d, sizeof(*d)); //将 d 指向的空间全部置 0
free(d); //释放指针 d 指向的空间
return 0;
} 

6. 测试用户态协议栈

需要insmod netmap.ko ,然后我们查看ls /dev/netmap -l

完整代码

#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 -- > personalint main()
{struct nm_pkthdr h;struct nm_desc *nmr = nm_open("netmap:ens38", NULL, 0, NULL);if(nmr == NULL)return -1;//加入poll监控IOstruct 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);}}}}
}

结果显示

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

在windows下cmd里面输入arp -a
可以看到里面有arp表,里面有我测试的服务端的地址192.168.240.130,因此此时可以发送udp数据成功

但是由于是动态arp,可能此arp项会消失,因此会导致udp数据发送失败。
主要是因为客户端这边的arp表内没有 服务器的arp信息。

当客户端发送数据包的时候,如果arp表内没有相应的信息,就会发送arp请求,因此服务端还要具有相应arp请求的功能。

7. arp协议实现

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

报文格式

  1. 硬件类型:16位字段,用来定义运行ARP的网络类型。每个局域网基于其类型被指派一个整数。例如:以太网的类型为1。ARP可用在任何物理网络上。
  2. 协议类型:16位字段,用来定义使用的协议。例如:对IPv4协议这个字段是0800。ARP可用于任何高层协议
  3. 硬件长度:8位字段,用来定义物理地址的长度,以字节为单位。例如:对于以太网的值为6。
  4. 协议长度:8位字段,用来定义逻辑地址的长度,以字节为单位。例如:对于IPv4协议的值为4。
  5. 操作码:16位字段,用来定义报文的类型。已定义的分组类型有两种:ARP请求(1),ARP响应(2)。
  6. 源硬件地址:这是一个可变长度字段,用来定义发送方的物理地址。例如:对于以太网这个字段的长度是6字节。
  7. 源逻辑地址:这是一个可变长度字段,用来定义发送方的逻辑(IP)地址。例如:对于IP协议这个字段的长度是4字节。
  8. 目的硬件地址:这是一个可变长度字段,用来定义目标的物理地址,例如,对以太网来说这个字段位6字节。对于ARP请求报文,这个字段为全0,因为发送方并不知道目标的硬件地址。

9. 目的逻辑地址:这是一个可变长度字段,用来定义目标的逻辑(IP)地址,对于IPv4协议这个字段的长度为4个字节。

完整代码

udp+arp测试代码

//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 17struct 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; //14struct iphdr ip; //20
};struct udphdr {unsigned short sport;      //源端口unsigned short dport;      //目的端口unsigned short length;        //封包长度unsigned short check;     //校验和
};struct udppkt {   struct ethhdr eh; //14struct iphdr ip; //20struct udphdr udp;//8unsigned 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);//以太网首部填入目的 macstr2mac(arp_rt->eh.h_src, mac);//以太网首部填入源macarp_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报文填入发送端 ipmemcpy(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) {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 (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);
}

结果显示

响应了arp请求

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

8. icmp协议实现

ICMP协议是IP的一个组成部分,负责传递 。

类型(Type):4位,标明ICMP报文的作用及格式。ping请求是8,ping回应是0

代码(Code):4位,标明报文的类型。ping的代码为0

校验和:8位,检验报文是否有误。

完整代码

//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    1struct 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; //14struct iphdr ip; //20
};struct udphdr {unsigned short sport;      //源端口unsigned short dport;      //目的端口unsigned short length;        //封包长度unsigned short check;     //校验和
};struct udppkt {   struct ethhdr eh; //14struct iphdr ip; //20struct udphdr udp;//8unsigned 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);//以太网首部填入目的 macstr2mac(arp_rt->eh.h_src, mac);//以太网首部填入源macarp_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报文填入发送端 ipmemcpy(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回应是0unsigned char code;    //代码(Code):4位,标明报文的类型。ping的代码为0unsigned 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都可以正常解析和发送

推荐一个不错的学习网站 C/C++后台高级服务器https://ke.qq.com/course/417774?flowToken=1010783。

用户态协议栈设计实现相关推荐

  1. Linux网络设计之用户态协议栈与dpdk

    用户态协议栈设计与dpdk dpdk环境开启 Windowe下配置静态IP表 DPDK API介绍 struct rte_memzone结构体 struct rte_mempool结构体 struct ...

  2. 用户态协议栈之tcp/ip设计

    1 解决问题 对于服务器而言,正常的接受一帧Data的过程,客户端先通过网络发送一帧数据到网卡,再经过协议栈,最后通过系统调用叨叨应用程序.具体的流程图如下: 针对上面的两个流程,涉及到两次拷贝(网卡 ...

  3. 用户态协议栈tcp/ip设计

    1 解决问题 对于服务器而言,正常的接受一帧Data的过程,客户端先通过网络发送一帧数据到网卡,再经过协议栈,最后通过系统调用叨叨应用程序.具体的流程图如下: 针对上面的两个流程,涉及到两次拷贝(网卡 ...

  4. 一文彻底掌握用户态协议栈,一看就懂的

    用户态协议栈 那我们先跟大家解释这个协议栈这个东西啊协议栈这个东西呢或多或少啊各个朋友应该都听过,我们站在一个设计者的角度,站在一个设计者的角度,站在tcpip的个人的角度,我们怎么去设计这个协议的? ...

  5. 架构决定可扩展性--聊聊用户态协议栈的意义

    在进入这个话题之前先说说通用和专业之间的区别.   举个很好的例子,好比我们个人,绝大部分的人都是"通用"的,而只有极少部分的人是"专业"的.通用的人主要目标是 ...

  6. FD.io VSAP(VPP Stack Acceleration Project),通过FD.io VSAP构建用户态协议栈

    目录 VSAP Scope-使用范围 Releases List of all subpages (used or unused) 通过FD.io VSAP构建用户态协议栈 VSAP https:// ...

  7. 深入浅出用户态协议栈

    一.前言 在讲网络协议栈前,先理解一个数据包在网络传输是一个怎么样的流程,如下图所示. 正常的流程是网卡接收到数据后,把数据copy到协议栈(sk_buff),协议栈把sk_buff数据解析完后再把数 ...

  8. 【高阶知识】用户态协议栈之Epoll实现原理

    Epoll 是 Linux IO 多路复用的管理机制.作为现在 Linux 平台高性能网络 IO 必要的组件.内核的实现可以参照:fs/eventpoll.c . 为什么需要自己实现 epoll 呢? ...

  9. 100行源代码搞定用户态协议栈丨udp,icmp,arp协议的现实丨网络协议栈丨Linux服务器开发丨C++后端开发丨Linux后台开发

    100行源代码搞定用户态协议栈 视频讲解如下,点击观看: 100行源代码搞定用户态协议栈丨udp,icmp,arp协议的现实丨网络协议栈丨Linux服务器开发丨C++后端开发丨Linux后台开发丨网络 ...

最新文章

  1. 澳门大学燕茹教授课题组招聘/招生启事
  2. Python编程基础:第九节 逻辑运算Logical Operators
  3. Java在使用时需要注意那些问题_java使用String.split方法时要注意的问题
  4. 《系统集成项目管理工程师》必背100个知识点-58沟通方式
  5. python函数拟合不规则曲线_python曲线拟合
  6. 存储过程从入门到熟练(多个存储过程完整实例及调用方法)_AX
  7. Android 启动模拟器是出现 Failed to allocate memory 8 错误提示的原因及解决办法
  8. Eclipse常用插件下载
  9. python图像文字识别算法_Python图像处理之图片文字识别功能(OCR)
  10. win10电脑网速慢怎么解决
  11. python小游戏开题报告范文_课题开题报告范文
  12. 在软件测试面试过程中如何进行自我介绍?
  13. 基于线性函数近似的安全强化学习 Safe RL with Linear Function Approximation 翻译 1
  14. Win32应用程序开发:完整的开发流程
  15. 测试之颠,必先利其器
  16. Dev-C++如何更改字体大小
  17. Ubuntu 之 Audacity踩坑之旅
  18. bitnami redmine 4 windows一键式版本安装企业微信插件方法
  19. python--socket(套接字/插口)
  20. Octapharma Group公布强劲的2018年业绩

热门文章

  1. Mapabc地图----展示地图上的所有点
  2. 2010年6月大学英语四级考试全真预测(三)
  3. 过敏性鼻炎的发病原因有哪些啊?
  4. 求sum=d+dd+ddd+……+dd...d
  5. Unity3D游戏开发——塔防小游戏
  6. 下载 apache-apollo-1.7.1-windows
  7. 卷积码matlab图,【卷积码的MATLAB仿真设计】 卷积码仿真
  8. 家用燃气灶的自动保护问题
  9. 自智网络——网络的数智化转型
  10. GIT+ Coding使用方法