目录

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

网卡即不在物理层,也不在数据链路层,是在这两层之间做转换。
数据传输的流程
网卡将物理层的光电信号转换为数字信号(0101010)。给到网卡驱动,然后把这个数据(通过sk_buff(搬运工)) 拷贝迁移到协议栈。 然后协议栈解析完数据之后将数据拷贝放入recv buffer,然后应用程序通过系统调用就能得到这个数据。
获取原始数据的三种方法介绍
不经过网络协议栈解析,拿到原始数据sk_buff;

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


目地MAC地址(6字节)
源MAC地址(6字节)
类型(2字节):IP协议=0x0800
数据段的长度为46到1500字节
以太网数据帧并没有表示长度的字段。主机确定以太网数据帧接收完毕依靠的是主机接收器感受不到电压变换。当接收器感受不到电压的变化时,表明这一帧数据已经接收完成。

字节和数字的存储顺序是从右到左,依次是从低位到高位,而网络存储顺序是从左到右,依次从低位到高位。
- 版本:占第一个字节的高四位。IPV4 = 0100, IPV4 = 0110。
- 首部长度:占第一个字节的低四位。4位能最表示15字节的长度。
- 服务类型:前3位为优先字段权,现在已经被忽略。接着4位用来表示最小延迟、最大吞吐量、最高可靠性和最小费用。
- 总长度:整个IP报的长度,单位为字节。16位范围为65535,最大发64k。MTU=1500是最大传输单元,发送需要分片。
- 16位标识:分片后的所有的包有同样的标识。比如15k数据分割成如干个1500字节的包有同样的标识。
- 3位标志:缺省。
- 13位片偏移:分片后的所有的包基于原来的整包的偏移。
- 生存时间(TTL):就是封包的生存时间。通常用通过的路由器的个数来衡量,比如初始值设置为32,则每通过一个路由器处理就会被减一,当这个值为0的时候就会丢掉这个包,并用ICMP消息通知源主机。
- 协议:定义了数据的协议,分别为:TCP、UDP、ICMP和IGMP。定义为:
#define PROTOCOL_TCP 0x06
#define PROTOCOL_UDP 0x11
#define PROTOCOL_ICMP 0x06
#define PROTOCOL_IGMP 0x06
- 首部检验和:校验的首先将该字段设置为0,然后将IP头的每16位进行二进制取反求和,将结果保存在校验和字段。
- 源IP地址:将IP地址看作是32位数值则需要将网络字节序转化位主机字节序。转化的方法是:将每4个字节首尾互换,将2、3字节互换。
- 目的IP地址:转换方法和来源IP地址一样。

(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]; //柔性数组
- };
长度为0的数组的主要用途是为了满足需要变长度的结构体
两个情况下是可以使用柔性数组的:
1:内存是已经分配好的。
2:这个柔性数组的长度不确定但是我们是可以通过其他方法计算出来的。
内核协议栈的数据到应用层的数据会经历两次拷贝,而netmap采用mmap的方式,直接将网卡的数据映射到一块内存中,应用程序可以直接通过mmap操作相应内存的数据。



DMA方式,将网卡映射到内存中去(mmap)。 应用程序是可以在内存中间直接读取这块映射过来的数据的。(DMA的方式不需要通过CPU去执行指令,直接将数据放入内存)这就叫零拷贝。
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指针指向的内存赋值。
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; /* 什么也没发现 */
- }
源代码:
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;
- }
需要insmod netmap.ko ,然后我们查看ls /dev/netmap -l
- #include
- #include
- #include
- #include
-
- #include
- #include
-
-
- #define NETMAP_WITH_LIBS //开启netmap
-
- #include
-
-
-
-
- #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);
- }
- }
- }
- }
- }
-
当客户端(windows)向服务端(windows下的linux虚拟机)发送信息的时候,发现发送了一段时间后就没法发送了

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

但是由于是动态arp,可能此arp项会消失,因此会导致udp数据发送失败。
主要是因为客户端这边的arp表内没有 服务器的arp信息。
当客户端发送数据包的时候,如果arp表内没有相应的信息,就会发送arp请求,因此服务端还要具有相应arp请求的功能。

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


- 硬件类型:16位字段,用来定义运行ARP的网络类型。每个局域网基于其类型被指派一个整数。例如:以太网的类型为1。ARP可用在任何物理网络上。
- 协议类型:16位字段,用来定义使用的协议。例如:对IPv4协议这个字段是0800。ARP可用于任何高层协议
- 硬件长度:8位字段,用来定义物理地址的长度,以字节为单位。例如:对于以太网的值为6。
- 协议长度:8位字段,用来定义逻辑地址的长度,以字节为单位。例如:对于IPv4协议的值为4。
- 操作码:16位字段,用来定义报文的类型。已定义的分组类型有两种:ARP请求(1),ARP响应(2)。
- 源硬件地址:这是一个可变长度字段,用来定义发送方的物理地址。例如:对于以太网这个字段的长度是6字节。
- 源逻辑地址:这是一个可变长度字段,用来定义发送方的逻辑(IP)地址。例如:对于IP协议这个字段的长度是4字节。
- 目的硬件地址:这是一个可变长度字段,用来定义目标的物理地址,例如,对以太网来说这个字段位6字节。对于ARP请求报文,这个字段为全0,因为发送方并不知道目标的硬件地址。
9. 目的逻辑地址:这是一个可变长度字段,用来定义目标的逻辑(IP)地址,对于IPv4协议这个字段的长度为4个字节。
udp+arp测试代码
- //insmod netmap.ko
- //gcc -o arp_netmap arp_netmap.c
- #include
- #include
- #include
- #include
- #define NETMAP_WITH_LIBS //开启netmap
- #include
- #include
-
- #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 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
- }
-
-
- 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协议

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
- #include
- #include
- #include
-
- #define NETMAP_WITH_LIBS
-
- #include
- #include
-
- #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都可以正常解析和发送

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