用户态协议栈设计实现udp,arp与icmp协议

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 用户态协议栈设计实现udp,arp与icmp协议


前言

  内核里面已经有网络协议栈了,为什么还要实现一遍用户态协议栈呢,主要是站在一个设计者的角度,自己去尝试实现一个协议栈,那么对协议栈的理解会比较透彻,这不比背八股文强?本文实现简单的回发udp,arp与icmp协议。至于tcp,实在过于复杂,不如多看看===>Posix API 与 网络协议栈 详细介绍

  本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

获取原始数据

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

  1. 使用原始套接字raw socket , tcpdump和wireshark就是使用这个做的,raw socket主要用来抓包。
  2. dbdk,使用dbdk的话篇幅较长,这里就不展开了,有兴趣的可以看dpdk/网络协议栈/vpp/OvS/DDos/SDN/NFV/虚拟化/高性能专家之路
  3. netmap是用于用户层应用程序收发原始网络数据的高性能框架,本文使用netmap进行数据的收发。

netmap

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

零拷贝

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

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

  更多的零拷贝相关内容看一下这个老哥写的好了什么是零拷贝

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现在就是链路层的数据

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);

协议栈

协议栈的定义

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

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

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

链路层首部

以太网协议

以太网协议:两个地址皆为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协议

关于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协议

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下文再写

  为什么在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,所以我们按照这个顺序依次解析即可。

代码

//
// 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数据

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

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

2.实现arp协议

设计思路

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

代码

//
// 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请求

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

3.实现icmp协议

设计思路

  icmp协议是ip协议的一部分

  • icmp的类型:ping请求是8,ping回应是0,其他的就不介绍了
  • 代码:ping的代码为0
  • 校验和:它的计算方法与IP数据报中的首部校验和计算方式是一样IP首部校验和计算原理
      
      
      ICMP类型为ping时,具体协议格式如下:

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

代码

//
// 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都可以正常解析和发送

目录
相关文章
|
2月前
|
存储 网络协议 算法
UDP 协议和 TCP 协议
本文介绍了UDP和TCP协议的基本结构与特性。UDP协议具有简单的报文结构,包括报头和载荷,报头由源端口、目的端口、报文长度和校验和组成。UDP使用CRC校验和来检测传输错误。相比之下,TCP协议提供更可靠的传输服务,其结构复杂,包含序列号、确认序号和标志位等字段。TCP通过确认应答和超时重传来保证数据传输的可靠性,并采用三次握手建立连接,四次挥手断开连接,确保通信的稳定性和完整性。
88 1
UDP 协议和 TCP 协议
|
8天前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
19 4
|
17天前
|
网络协议 安全 NoSQL
网络空间安全之一个WH的超前沿全栈技术深入学习之路(8-2):scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练、就怕你学成黑客啦!
scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
网络空间安全之一个WH的超前沿全栈技术深入学习之路(8-2):scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练、就怕你学成黑客啦!
|
2月前
|
网络协议
UDP 协议
UDP 协议
119 58
|
1月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
28 1
|
1月前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
119 3
|
1月前
|
存储 网络协议 算法
更深层次理解传输层两协议【UDP | TCP】【UDP 缓冲区 | TCP 8种策略 | 三次握手四次挥手】
UDP和TCP各有所长,UDP以其低延迟、轻量级的特点适用于对实时性要求极高的应用,而TCP凭借其强大的错误检测、流量控制和拥塞控制机制,确保了数据的可靠传输,适用于文件传输、网页浏览等场景。理解它们的工作原理,特别是UDP的缓冲区管理和TCP的8种策略,对于优化网络应用的性能、确保数据的高效和可靠传输至关重要。开发者在选择传输层协议时,应根据实际需求权衡利弊,合理利用这两项关键技术。
63 5
|
1月前
|
JavaScript 安全 Java
谈谈UDP、HTTP、SSL、TLS协议在java中的实际应用
下面我将详细介绍UDP、HTTP、SSL、TLS协议及其工作原理,并提供Java代码示例(由于Deno是一个基于Node.js的运行时,Java代码无法直接在Deno中运行,但可以通过理解Java示例来类比Deno中的实现)。
68 1
|
2月前
|
监控 网络协议 网络性能优化
如何办理支持UDP协议的网络
在当今网络环境中,UDP(用户数据报协议)因传输速度快、延迟低而广泛应用于在线游戏、视频流媒体、VoIP等实时服务。本文详细介绍了办理支持UDP协议网络的方法,包括了解UDP应用场景、选择合适的ISP及网络套餐、购买支持UDP的设备并进行优化设置,以及解决常见问题的策略,帮助用户确保网络稳定性和速度满足实际需求。
|
2月前
|
网络协议 Unix 网络架构
网际控制报文协议ICMP
网际控制报文协议(ICMP)是TCP/IP体系结构中网际层的关键组件,用于提高IP数据报的成功传输率。ICMP主要处理两类报文:差错报告报文与询问报文。前者包括终点不可达、源点抑制、时间超过、参数问题及重定向等五类;后者则涵盖回送请求/回答及时间戳请求/回答。ICMP广泛应用于检测网络连通性的PING工具和追踪数据包路径的traceroute工具中。两者分别利用ICMP的回送请求报文及差错报告报文实现功能。
79 10