dpdk 实现 ping (arp + icmp)

理论

当用一台主机A去 ping 另一台机器B的时候

首先假设机器A内部没有机器B的物理地址,则 A ping B 的时候需要先发 arp 请求,以获取机器 B 的 MAC 地址

1、获取 MAC 地址

(1)如果 A 和 B 在同一网段内

A 会直接广播询问 谁有 B的 MAC 地址请告知我,通过抓包来看
ARP请求包

B 会想 A发送一个 arp 应答,告知 A 我的MAC地址是xx:xx:xx:xx:xx:xx,形如
ARP应答包

(2)如果 A 和 B 不在同一网段内

该请求会经路由器进入主机 B所在的网段,然后进行 ARP 广播查找主机 B 的物理地址

至此主机 A 就获得了主机 B 的物理地址,并将其写入到了本地的 ARP 缓存表

2、发送 ICMP 报文

ping 实际上就是 A 向 B 发送ICMP请求, B 向 A 发送ICMP应答,形如
ICMP请求和应答包
红色的是请求,蓝色的是应答

3、代码流程

我们使用dpdk接管了一个网卡,当用主机去 ping 虚拟机中被 dpdk 接管的网卡的时候,我们写的程序,需要读取该网卡中的数据包,判断数据包中的目的地址是不是我的IP地址,然后判断它是ARP请求还是ICMP请求,然后相应的返回一个对应的数据包。

当我从该网卡读取了一个数据包,发现目的地址是我的IP地址后:

(1)如果是ARP请求

我需要封装一个ARP响应包,把我的MAC地址写进去并发送出去。封装ARP协议之后,还需要封装成以太网帧,即还需要添加首部。然后通过 dpdk 的 API 将数据包发送给目的主机。

(2)如果是ICMP请求

我需要封装一个ICMP应答包来响应该ICMP请求并发送出去。封装协议的过程,其实就是在协议的首部填充内容,例如填充你的MAC地址、源IP地址、目的IP地址、报文类型、首部大小、校验和等。

(注)

计算机网络中我们了解到,对数据的封装是从上至下的,即先封装应用层,然后分别是传输层、网络层、网络接口层,但其实都只是在数据包前面不停的加首部。例如现在我需要发送一个ICMP包,需要在数据前面添加ICMP首部、IP首部、以太网帧首部。因为每次加的都是首部,因此实际使用过程中往往是自下而上的,即先加以太网帧首部,然后指针向后偏移一个以太网帧首部的大小,再加IP首部,然后指针再次从IP首部的起始位置偏移一个IP首部的大小,再封装ICMP首部。这样计算偏移量比较方便容易。

最初,我的主机上是没有虚拟机中被dpdk接管的网卡的MAC地址的,如图所示
没有目的主机的MAC地址
当我写完ARP后,如果我用主机ping我的虚拟机的网卡,显示超市(或者其他,反正就是没有ping通),然后通过

arp -a

发现我有了目的主机的MAC地址,恭喜你,你的ARP应答已经完成了。如下图所示。
没有ping通
成功获取到了MAC地址

下一步就是完成ICMP应答,当完成ICMP应答后,就可ping通了,如下图所示。字节为0,是因为ICMP报文只有头部,没有数据部分(新创建的包嘛)。
完成ICMP应答

理论存在,实践开始。

代码实现

ARP报文格式
在这里插入图片描述
IP报文格式
在这里插入图片描述
在这里插入图片描述

核心代码

#ifdef enable_arp
/*
 * @param msg       数据,后面需要在其前面加一层一层的首部
 * @param dst_mac   目的MAC地址
 * @param sip       源IP
 * @param tip       目的IP
 * @param port_id   网卡设备
 */
 // 创建一个arp数据包
static void encode_arp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t tip, uint16_t port_id) {
    struct rte_ether_hdr *ether_hdr = (struct rte_ether_hdr *)msg;

    // 将源MAC和目的MAC地址拷贝到结构体中
    rte_memcpy(ether_hdr->dst_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);
    rte_memcpy(ether_hdr->src_addr.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
    ether_hdr->ether_type = htons(RTE_ETHER_TYPE_ARP);

    // 填充ARP首部
    struct rte_arp_hdr *arp_hdr = (struct rte_arp_hdr *)(ether_hdr + 1);
    arp_hdr->arp_protocol = htons(RTE_ETHER_TYPE_IPV4);
    arp_hdr->arp_plen = sizeof(uint32_t);
    arp_hdr->arp_opcode = htons(2);
    arp_hdr->arp_hardware = htons(1);
    arp_hdr->arp_hlen = RTE_ETHER_ADDR_LEN;

    rte_memcpy(arp_hdr->arp_data.arp_sha.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
    rte_memcpy(arp_hdr->arp_data.arp_tha.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);

    arp_hdr->arp_data.arp_tip = tip;
    arp_hdr->arp_data.arp_sip = sip;
}

/**
 * mbuf_pool 内存池  需要从内存池中获取mbuf
 */
static struct rte_mbuf *send_arp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac, uint32_t sip, uint32_t dip,uint16_t port_id) {
    const uint32_t total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr);

    struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
    if (mbuf) {
        mbuf->pkt_len = total_length;
        mbuf->data_len = total_length;
        uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
        encode_arp_pkt(pkt_data, dst_mac, sip, dip, port_id);
    }
    return mbuf;
}
#endif

#ifdef enable_icmp
// icmp校验和
static uint16_t checksum(uint16_t *addr, int count) {
    long sum = 0;
    while (count > 1) {
        sum += *(ushort *)addr++;
        count -= 2;
    }
    if (count > 0) {
        sum += *(u_char *)addr;
    }
    while (sum >> 16) {
        sum = (sum & 0xffff) + (sum >> 16);
    }
    return ~sum;
}

// 创建一个icmp数据包
static void encode_icmp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t tip, uint16_t port_id, uint16_t id, uint16_t seq) {

    // 填充和以太网帧首部
    struct rte_ether_hdr *hdr = (struct rte_ether_hdr *)msg;
    rte_memcpy(hdr->dst_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);
    rte_memcpy(hdr->src_addr.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
    hdr->ether_type = htons(RTE_ETHER_TYPE_IPV4);

    // 填充IPV4首部
    struct rte_ipv4_hdr *ipv4_hdr = (struct rte_ipv4_hdr *)(hdr + 1);
    ipv4_hdr->src_addr = sip;
    ipv4_hdr->dst_addr = tip;
    ipv4_hdr->next_proto_id = IPPROTO_ICMP;
    ipv4_hdr->time_to_live = 64;
    ipv4_hdr->type_of_service = 0;
    ipv4_hdr->fragment_offset = 0;

    // 0x45  8位  前四位表示版本号,后四位表示首部长度(以4字节为1个单位,5表示首部长度为5各单位,即20字节)
    ipv4_hdr->version_ihl = 0x45;
    ipv4_hdr->total_length = htons(sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr));
    ipv4_hdr->packet_id = 0;

    // 求校验和之前,必须把校验和先置为0,否则求出的校验和是不正确的
    ipv4_hdr->hdr_checksum = 0;
    ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);

    // 填充ICMP首部
    struct rte_icmp_hdr *icmp_hdr = (struct rte_icmp_hdr *)(ipv4_hdr + 1);
    icmp_hdr->icmp_type = RTE_IP_ICMP_ECHO_REPLY;
    icmp_hdr->icmp_code = 0;
    icmp_hdr->icmp_seq_nb = seq;
    icmp_hdr->icmp_ident = id;

    // 求校验和之前,必须把校验和先置为0,否则求出的校验和是不正确的
    icmp_hdr->icmp_cksum = 0;
    icmp_hdr->icmp_cksum = checksum((uint16_t *)icmp_hdr, sizeof(struct rte_icmp_hdr));

}

static struct rte_mbuf *send_icmp(struct rte_mempool *mempool, uint8_t *dst_mac, uint32_t sip, uint32_t tip, uint16_t port_id, uint16_t id, uint16_t seq) {
    struct rte_mbuf *mbuf = NULL;
    const uint32_t total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr);
    mbuf = rte_pktmbuf_alloc(mempool);
    if (mbuf) {
        mbuf->pkt_len = total_length;
        mbuf->data_len = total_length;
        uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
        encode_icmp_pkt(pkt_data, dst_mac, sip, tip, port_id, id, seq);
    }
    return mbuf;
}
#endif

在 main 函数里调用他们来完成数据包的发送

system("clear");

    uint16_t sz;
    while (!quit) {
        struct rte_mbuf *mbuf[MAX_PKT_BURST];
        struct rte_ether_hdr *hdr;
        uint j;

        RTE_ETH_FOREACH_DEV(port_id) {
            sz = rte_eth_rx_burst(port_id, 0, mbuf, MAX_PKT_BURST);
            if (sz > MAX_PKT_BURST) RTE_EXIT("rte_eth_rx_burst");
            for (j = 0; j < sz; j++) {
                // 拿到这个数据包
                hdr = rte_pktmbuf_mtod(mbuf[j], struct rte_ether_hdr *);
                // 判断协议类型是否为arp
                if (hdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {
#ifdef enable_arp
                    // 指针偏移 获取arp首部
                    struct rte_arp_hdr *arp_hdr = rte_pktmbuf_mtod_offset(mbuf[j], struct rte_arp_hdr *, sizeof(struct rte_ether_hdr));
                    struct in_addr *addr = (struct in_addr *) malloc(sizeof(struct in_addr));
                    char *ip;
                    addr->s_addr = arp_hdr->arp_data.arp_tip;

                    ip = inet_ntoa(*addr);
                    printf("\n=================================\n");
                    printf("arp ---> src : %s ", ip);

                    printf("local : %s\n\n", inet_ntoa(local));
                    if (LOCAL_IP == addr->s_addr) {

                        struct rte_mbuf *arp_buf;
                        // 创建一个arp数据包
                        arp_buf = send_arp(mem_pool, arp_hdr->arp_data.arp_sha.addr_bytes, arp_hdr->arp_data.arp_tip, arp_hdr->arp_data.arp_sip, port_id);
                        if (!arp_buf) RTE_EXIT("send_arp");

                        // 发送该arp包
                        rte_eth_tx_burst(port_id, 0, &arp_buf, 1);
                        printf("rte_eth_tx_burst has been launch successfully\n");

                        // 归还资源
                        rte_pktmbuf_free(arp_buf);
                        rte_pktmbuf_free(mbuf[j]);

                    }

                    printf("=================================\n");
#endif
                }
                // 判断协议类型是否是IPV4
                else if (hdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
#ifdef enable_icmp
                    // 指针偏移 获取IPV4首部
                    struct rte_ipv4_hdr *ipv4_hdr;
                    ipv4_hdr = rte_pktmbuf_mtod_offset(mbuf[j], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
                    // 不是我的数据包不予处理
                    if (LOCAL_IP == ipv4_hdr->dst_addr) continue;
                    // 判断是否是ICMP
                    if (ipv4_hdr->next_proto_id == IPPROTO_ICMP) {

                        struct in_addr addr;
                        addr.s_addr = ipv4_hdr->src_addr;
                        printf("\n=================================\n");
                        printf("icmp --> src : %s ", inet_ntoa(addr));

                        // 指针偏移  获取ICMP首部
                        struct rte_icmp_hdr *icmp_hdr;
                        icmp_hdr = (struct rte_icmp_hdr *)(ipv4_hdr + 1);
                        if (icmp_hdr->icmp_type == RTE_IP_ICMP_ECHO_REQUEST) {

                            addr.s_addr = ipv4_hdr->dst_addr;
                            printf("local : %s\n\n", inet_ntoa(addr));
                            struct rte_mbuf *icmp_buf;

                            // 创建一个ICMP报文
                            icmp_buf = send_icmp(mem_pool, hdr->src_addr.addr_bytes,
                                      LOCAL_IP, ipv4_hdr->src_addr,
                                      port_id, icmp_hdr->icmp_ident, icmp_hdr->icmp_seq_nb);
                            // 发送该ICMP报文
                            rte_eth_tx_burst(port_id, 0, &icmp_buf, 1);
                            printf("rte_eth_tx_burst has been finished successfully\n");

                            rte_pktmbuf_free(icmp_buf);
                            rte_pktmbuf_free(mbuf[j]);
                        }

                        printf("=================================\n");
                    }
#endif
                }
            }
        }
    }

main函数调用也可以这样写(将收到的ICMP包的内容魔改一下,地址互换一下重新发送出去)结果如下图。
在这里插入图片描述

else if (hdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
#ifdef enable_icmp
                    struct rte_ipv4_hdr *ipv4_hdr;
                    ipv4_hdr = rte_pktmbuf_mtod_offset(mbuf[j], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
                    // 不是我的数据包不予处理
                    if (LOCAL_IP == ipv4_hdr->dst_addr) continue;
                    if (ipv4_hdr->next_proto_id == IPPROTO_ICMP) {

                        struct in_addr addr;
                        addr.s_addr = ipv4_hdr->src_addr;
                        printf("\n=================================\n");
                        printf("icmp --> src : %s ", inet_ntoa(addr));

                        struct rte_icmp_hdr *icmp_hdr;
                        icmp_hdr = (struct rte_icmp_hdr *)(ipv4_hdr + 1);
                        if (icmp_hdr->icmp_type == RTE_IP_ICMP_ECHO_REQUEST) {

                            addr.s_addr = ipv4_hdr->dst_addr;
                            printf("local : %s\n\n", inet_ntoa(addr));
                            struct rte_mbuf *icmp_buf;

                            rte_memcpy(hdr->dst_addr.addr_bytes, hdr->src_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
                            rte_memcpy(hdr->src_addr.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);

                            ipv4_hdr->dst_addr = ipv4_hdr->src_addr;
                            ipv4_hdr->src_addr = LOCAL_IP;
                            ipv4_hdr->time_to_live = 64;
                            ipv4_hdr->hdr_checksum = 0;
                            ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);

                            icmp_hdr->icmp_type = RTE_IP_ICMP_ECHO_REPLY;
                            icmp_hdr->icmp_code = 0;
                            icmp_hdr->icmp_cksum = 0;
                            // icmp_hdr->icmp_cksum = checksum((uint16_t *)icmp_hdr, sizeof(struct rte_icmp_hdr) + 32);
                            
                            // 32是windows发出的icmp包中的数据部分的大小
                            // TODO
                            // 推荐改为  获得的数据包的大小-IPV4首部-以太网帧首部  即
                            icmp_hdr->icmp_cksum = checksum((uint16_t *)icmp_hdr, sizeof(*mbuf[j]) - sizeof(struct rte_ipv4_hdr) - sizeof(struct rte_ether_hdr));
                            // 因为linux发出的数据包的数据部分大小和windows不同  如果采用第一种方式每次需要修改源码

//                            icmp_buf = send_icmp(mem_pool, hdr->src_addr.addr_bytes,
//                                                 LOCAL_IP, ipv4_hdr->src_addr,
//                                                 port_id, icmp_hdr->icmp_ident, icmp_hdr->icmp_seq_nb);

                            rte_eth_tx_burst(port_id, 0, &mbuf[j], 1);
                            printf("rte_eth_tx_burst has been finished successfully\n");

                            rte_pktmbuf_free(icmp_buf);
                            rte_pktmbuf_free(mbuf[j]);
                        }

                        printf("=================================\n");
                    }
#endif
                }

其余代码完成就是一些配置和初始化的工作

完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <signal.h>
#include <stdbool.h>

#include <rte_common.h>
#include <rte_memcpy.h>
#include <rte_eal.h>
#include <rte_lcore.h>
#include <rte_interrupts.h>
#include <rte_ether.h>
#include <rte_ethdev.h>
#include <rte_mempool.h>
#include <rte_mbuf.h>

#define RTE_EXIT(x) rte_exit(EXIT_FAILURE, "error at %s\n", x)
#define MEM_CACHE_SIZE 256
#define MAX_PKT_BURST 32
#define MAX_PORTS 32


const char *local_ip_str = "192.168.13.136";
static struct in_addr local;
// 本机IP的字节码
#define LOCAL_IP inet_addr(local_ip_str)

// 启用混杂模式
#define promiscuous_on

#define enable_arp
#define enable_icmp

static bool quit = false;



static uint16_t nb_rxd = 1024;
static uint16_t nb_txd = 1024;

static struct rte_eth_conf port_conf = {
        .rxmode = {
                .split_hdr_size = 0,
        },
        .txmode = {
                .mq_mode = RTE_ETH_MQ_TX_NONE,
        },
};

// 每个设备对应的MAC地址
static struct rte_ether_addr ether_address[MAX_PORTS];

// 注册了一些终止信号
static void signal_handler(int num) {
    if (num == SIGINT || num == SIGTERM) {
        printf("\n\nSignal %d received, preparing to exit...\n", num);
        quit = true;
    }
}

#ifdef enable_arp
/*
 * @param msg       数据,后面需要在其前面加一层一层的首部
 * @param dst_mac   目的MAC地址
 * @param sip       源IP
 * @param tip       目的IP
 * @param port_id   网卡设备
 */
 // 创建一个arp数据包
static void encode_arp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t tip, uint16_t port_id) {
    struct rte_ether_hdr *ether_hdr = (struct rte_ether_hdr *)msg;

    // 将源MAC和目的MAC地址拷贝到结构体中
    rte_memcpy(ether_hdr->dst_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);
    rte_memcpy(ether_hdr->src_addr.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
    ether_hdr->ether_type = htons(RTE_ETHER_TYPE_ARP);

    // 填充ARP首部
    struct rte_arp_hdr *arp_hdr = (struct rte_arp_hdr *)(ether_hdr + 1);
    arp_hdr->arp_protocol = htons(RTE_ETHER_TYPE_IPV4);
    arp_hdr->arp_plen = sizeof(uint32_t);
    arp_hdr->arp_opcode = htons(2);
    arp_hdr->arp_hardware = htons(1);
    arp_hdr->arp_hlen = RTE_ETHER_ADDR_LEN;

    rte_memcpy(arp_hdr->arp_data.arp_sha.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
    rte_memcpy(arp_hdr->arp_data.arp_tha.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);

    arp_hdr->arp_data.arp_tip = tip;
    arp_hdr->arp_data.arp_sip = sip;
}

/**
 * mbuf_pool 内存池  需要从内存池中获取mbuf
 */
static struct rte_mbuf *send_arp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac, uint32_t sip, uint32_t dip,uint16_t port_id) {
    const uint32_t total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr);

    struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
    if (mbuf) {
        mbuf->pkt_len = total_length;
        mbuf->data_len = total_length;
        uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
        encode_arp_pkt(pkt_data, dst_mac, sip, dip, port_id);
    }
    return mbuf;
}
#endif

#ifdef enable_icmp
// icmp校验和
static uint16_t checksum(uint16_t *addr, int count) {
    long sum = 0;
    while (count > 1) {
        sum += *(ushort *)addr++;
        count -= 2;
    }
    if (count > 0) {
        sum += *(u_char *)addr;
    }
    while (sum >> 16) {
        sum = (sum & 0xffff) + (sum >> 16);
    }
    return ~sum;
}

// 创建一个icmp数据包
static void encode_icmp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t tip, uint16_t port_id, uint16_t id, uint16_t seq) {

    // 填充和以太网帧首部
    struct rte_ether_hdr *hdr = (struct rte_ether_hdr *)msg;
    rte_memcpy(hdr->dst_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);
    rte_memcpy(hdr->src_addr.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
    hdr->ether_type = htons(RTE_ETHER_TYPE_IPV4);

    // 填充IPV4首部
    struct rte_ipv4_hdr *ipv4_hdr = (struct rte_ipv4_hdr *)(hdr + 1);
    ipv4_hdr->src_addr = sip;
    ipv4_hdr->dst_addr = tip;
    ipv4_hdr->next_proto_id = IPPROTO_ICMP;
    ipv4_hdr->time_to_live = 64;
    ipv4_hdr->type_of_service = 0;
    ipv4_hdr->fragment_offset = 0;

    // 0x45  8位  前四位表示版本号,后四位表示首部长度(以4字节为1个单位,5表示首部长度为5各单位,即20字节)
    ipv4_hdr->version_ihl = 0x45;
    ipv4_hdr->total_length = htons(sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr));
    ipv4_hdr->packet_id = 0;

    // 求校验和之前,必须把校验和先置为0,否则求出的校验和是不正确的
    ipv4_hdr->hdr_checksum = 0;
    ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);

    // 填充ICMP首部
    struct rte_icmp_hdr *icmp_hdr = (struct rte_icmp_hdr *)(ipv4_hdr + 1);
    icmp_hdr->icmp_type = RTE_IP_ICMP_ECHO_REPLY;
    icmp_hdr->icmp_code = 0;
    icmp_hdr->icmp_seq_nb = seq;
    icmp_hdr->icmp_ident = id;

    // 求校验和之前,必须把校验和先置为0,否则求出的校验和是不正确的
    icmp_hdr->icmp_cksum = 0;
    icmp_hdr->icmp_cksum = checksum((uint16_t *)icmp_hdr, sizeof(struct rte_icmp_hdr));

}

static struct rte_mbuf *send_icmp(struct rte_mempool *mempool, uint8_t *dst_mac, uint32_t sip, uint32_t tip, uint16_t port_id, uint16_t id, uint16_t seq) {
    struct rte_mbuf *mbuf = NULL;
    const uint32_t total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr);
    mbuf = rte_pktmbuf_alloc(mempool);
    if (mbuf) {
        mbuf->pkt_len = total_length;
        mbuf->data_len = total_length;
        uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
        encode_icmp_pkt(pkt_data, dst_mac, sip, tip, port_id, id, seq);
    }
    return mbuf;
}
#endif

int main(int argc, char **argv) {
    setbuf(stdout, NULL);
    system("clear");
    int ret;
    local.s_addr = LOCAL_IP;
    uint16_t nb_ports_avail, port_id;

    struct rte_mempool *mem_pool;

    ret = rte_eal_init(argc, argv);
    if (ret < 0) RTE_EXIT("rte_eal_init()");

    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

    nb_ports_avail = rte_eth_dev_count_avail();

    printf("nb_ports_avail = %u\n", nb_ports_avail);
    if (nb_ports_avail <= 0) RTE_EXIT("rte_eth_dev_count_avail");

    uint nb_mbuf = RTE_MAX(1 * (nb_rxd + nb_txd + MAX_PKT_BURST + 1 * MEM_CACHE_SIZE),
                      8192U);
    printf("nb_mbuf = %u\n", nb_mbuf);

    mem_pool = NULL;
    mem_pool = rte_pktmbuf_pool_create("mbuf", nb_mbuf, MEM_CACHE_SIZE, 0,
                            RTE_MBUF_DEFAULT_BUF_SIZE, (int)rte_socket_id());
    if (mem_pool == NULL) {
        RTE_EXIT("rte_pktmbuf_pool_create");
    }

    RTE_ETH_FOREACH_DEV(port_id) {
        struct rte_eth_dev_info dev_info;
        struct rte_eth_conf local_conf = port_conf;
        struct rte_eth_rxconf rxconf;
        struct rte_eth_txconf txconf;
        struct rte_ether_addr *addr;

        ret = rte_eth_macaddr_get(port_id, &ether_address[port_id]);
        if (ret) RTE_EXIT("rte_eth_macaddr_get");

        addr = (struct rte_ether_addr *)malloc(sizeof(struct rte_ether_addr));

        ret = rte_eth_dev_info_get(port_id, &dev_info);
        if (ret < 0) RTE_EXIT("rte_eth_dev_info_get");

        if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE)
            local_conf.rxmode.offloads |= RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;
        ret = rte_eth_dev_configure(port_id, 1, 1, &local_conf);
        if (ret) RTE_EXIT("rte_eth_dev_configure");

        ret = rte_eth_dev_adjust_nb_rx_tx_desc(port_id, &nb_rxd, &nb_txd);
        if (ret) RTE_EXIT("rte_eth_dev_adjust_nb_rx_tx_desc");

        ret = rte_eth_macaddr_get(port_id, addr);
        if (ret) RTE_EXIT("rte_eth_macaddr_get");

        fflush(stdout);
        rxconf = dev_info.default_rxconf;
        rxconf.offloads = local_conf.rxmode.offloads;

        ret = rte_eth_rx_queue_setup(port_id,
                                     0,
                                     nb_rxd,
                                     rte_eth_dev_socket_id(port_id),
                                     &rxconf,
                                     mem_pool);
        if (ret) RTE_EXIT("rte_eth_rx_queue_setup");

        fflush(stdout);
        txconf = dev_info.default_txconf;
        txconf.offloads = local_conf.txmode.offloads;

        ret = rte_eth_tx_queue_setup(port_id, 0, nb_txd, rte_eth_dev_socket_id(port_id),&txconf);
        if (ret) RTE_EXIT("rte_eth_tx_queue_setup");

#ifdef promiscuous_on
        rte_eth_promiscuous_enable(port_id);
#endif

        ret = rte_eth_dev_start(port_id);
        if (ret) RTE_EXIT("rte_eth_dev_start");

    }

    system("clear");

    uint16_t sz;
    while (!quit) {
        struct rte_mbuf *mbuf[MAX_PKT_BURST];
        struct rte_ether_hdr *hdr;
        uint j;

        RTE_ETH_FOREACH_DEV(port_id) {
            sz = rte_eth_rx_burst(port_id, 0, mbuf, MAX_PKT_BURST);
            if (sz > MAX_PKT_BURST) RTE_EXIT("rte_eth_rx_burst");
            for (j = 0; j < sz; j++) {
                // 拿到这个数据包
                hdr = rte_pktmbuf_mtod(mbuf[j], struct rte_ether_hdr *);
                // 判断协议类型是否为arp
                if (hdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {
#ifdef enable_arp
                    // 指针偏移 获取arp首部
                    struct rte_arp_hdr *arp_hdr = rte_pktmbuf_mtod_offset(mbuf[j], struct rte_arp_hdr *, sizeof(struct rte_ether_hdr));
                    struct in_addr *addr = (struct in_addr *) malloc(sizeof(struct in_addr));
                    char *ip;
                    addr->s_addr = arp_hdr->arp_data.arp_tip;

                    ip = inet_ntoa(*addr);
                    printf("\n=================================\n");
                    printf("arp ---> src : %s ", ip);

                    printf("local : %s\n\n", inet_ntoa(local));
                    if (LOCAL_IP == addr->s_addr) {

                        struct rte_mbuf *arp_buf;
                        // 创建一个arp数据包
                        arp_buf = send_arp(mem_pool, arp_hdr->arp_data.arp_sha.addr_bytes, arp_hdr->arp_data.arp_tip, arp_hdr->arp_data.arp_sip, port_id);
                        if (!arp_buf) RTE_EXIT("send_arp");

                        // 发送该arp包
                        rte_eth_tx_burst(port_id, 0, &arp_buf, 1);
                        printf("rte_eth_tx_burst has been launch successfully\n");

                        // 归还资源
                        rte_pktmbuf_free(arp_buf);
                        rte_pktmbuf_free(mbuf[j]);

                    }

                    printf("=================================\n");
#endif
                }
                // 判断协议类型是否是IPV4
                else if (hdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
#ifdef enable_icmp
					// 指针偏移  获取ipv4首部
                    struct rte_ipv4_hdr *ipv4_hdr;
                    ipv4_hdr = rte_pktmbuf_mtod_offset(mbuf[j], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
					
					// 不是我的数据包不予处理
                    if (LOCAL_IP == ipv4_hdr->dst_addr) continue;					
					
					// 判断是否是 ICMP
                    if (ipv4_hdr->next_proto_id == IPPROTO_ICMP) {

                        struct in_addr addr;
                        addr.s_addr = ipv4_hdr->src_addr;
                        printf("\n=================================\n");
                        printf("icmp --> src : %s\n", inet_ntoa(addr));

                        struct rte_icmp_hdr *icmp_hdr;
                        // 指针偏移 获取icmp首部
                        icmp_hdr = (struct rte_icmp_hdr *)(ipv4_hdr + 1);
                        // 如果收到的是一个icmp请求包  需要给予应答
                        if (icmp_hdr->icmp_type == RTE_IP_ICMP_ECHO_REQUEST) {

                            addr.s_addr = ipv4_hdr->dst_addr;
                            struct rte_mbuf *icmp_buf;
							
							// 把收到的包魔改一下   MAC地址互换
                            rte_memcpy(hdr->dst_addr.addr_bytes, hdr->src_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
                            rte_memcpy(hdr->src_addr.addr_bytes, ether_address[port_id].addr_bytes, RTE_ETHER_ADDR_LEN);
							
							// IP地址 互换  重新计算校验和
                            ipv4_hdr->dst_addr = ipv4_hdr->src_addr;
                            ipv4_hdr->src_addr = LOCAL_IP;
                            ipv4_hdr->time_to_live = 64;
                            ipv4_hdr->hdr_checksum = 0;
                            ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);

                            // 将ICMP首部的类型 改为请求  code=0  (上面的图片里有)  重新计算校验和
                            icmp_hdr->icmp_type = RTE_IP_ICMP_ECHO_REPLY;
                            icmp_hdr->icmp_code = 0;
                            icmp_hdr->icmp_cksum = 0;
                            // ICMP校验和是(ICMP首部和数据部分)都要计算的
                            icmp_hdr->icmp_cksum = checksum((uint16_t *)icmp_hdr, sizeof(*mbuf[j]) - sizeof(struct rte_ipv4_hdr) - sizeof(struct rte_ether_hdr));

                            rte_eth_tx_burst(port_id, 0, &mbuf[j], 1);
                            printf("rte_eth_tx_burst has been finished successfully\n");

                            rte_pktmbuf_free(icmp_buf);
                            rte_pktmbuf_free(mbuf[j]);
                        }

                        printf("=================================\n");
                    }
#endif
                }
            }
        }
    }


    RTE_ETH_FOREACH_DEV(port_id) {
        ret = rte_eth_dev_stop(port_id);
        if (ret) RTE_EXIT("rte_eth_dev_stop");

        ret = rte_eth_dev_close(port_id);
        if (ret) RTE_EXIT("rte_eth_dev_close");
    }

    ret = rte_eal_cleanup();
    if (ret) RTE_EXIT("rte_eal_cleanup");

    return ret;
}

问题

在发送ICMP包的时候,通过抓包工具看到校验和总是出错,但是ICMP包计算校验和的时候,的首地址和数据部分带下没有出错。通过抓包工具可以看到,数据部分就是48字节,我当时的那一版代码是写死了的,后来修改了代码,不再写死而是通过==数据包大小 − - ip首部大小 − - 以太网帧首部大小 获取,对比发现大小并非48字节,我随手改为64字节,校验和正确了。再次通过抓包工具看到,ICMP的首部并非一个标准的首部,而是加了一个8字节的时间戳可选项,所以代码里体现出来应该是 +54字节 为什么加64字节也正确了呢?因为后面的8字节默认填充0,对计算校验和无影响。

参考
【dpdk】3.dpdk实现arp
【dpdk】4.dpdk实现icmp

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
基于DPDK(Data Plane Development Kit)实现服务端和客户端的主要步骤如下: 1. 环境准备:首先需要确保DPDK已经正确安装并配置好了开发环境,包括正确安装可用的网卡驱动程序、配置正确的内存和共享内存等。 2. 服务端实现:在服务端的代码中,需要使用DPDK提供的API来初始化DPDK,包括初始化设备、设置设备属性,以及创建和配置接收数据包的队列等。然后,服务端需要创建一个socket并绑定到指定的IP地址和端口上。接下来,使用DPDK提供的API接收和处理客户端发送的数据包。 3. 客户端实现:在客户端的代码中,同样需要使用DPDK提供的API来初始化DPDK,并创建一个socket。然后,通过socket将数据包发送给服务端。客户端还可以通过DPDK提供的API来设置、修改发送数据包的属性,如目标IP地址和端口等。 4. 编译和运行:完成服务端和客户端的代码编写后,需要将代码编译成可执行文件。在编译时,需要使用DPDK提供的工具和指定相关的编译参数,如指定DPDK的路径、调整内核参数等。最后,将生成的可执行文件分别运行在服务端和客户端的主机上。 基于DPDK实现服务端和客户端可以提供高性能的网络连接和数据交互,减少了网络的延迟和负载。同时,DPDK还提供了丰富的API和功能,如多核支持、零拷贝等,可以进一步优化网络性能和提升系统吞吐量。因此,在需要处理大量网络数据流的场景下,基于DPDK实现服务端和客户端是一个很好的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值