相关代码:DPDK_Learning/ICMP_echo_reply at main · mfdycs/DPDK_Learning (github.com)

ICMP报文格式如图所示,每一个ICMP消息都将包含引发这条ICMP消息的数据包的完全IP包头,ICMP报文则作为IP数据包的数据部分封装在IP数据包内部

  • Type字段表示ICMP消息的类型;
  • Code字段表示ICMP消息类型细分的子类型;
  • Checksum字段表示ICMP报文的校验和。

对于ICMP响应报文,各字段的具体内容如下:

500
500

添加标识

#define ENABLE_ICMP 1

主函数判断

主函数中判断是否为 8 0 echo请求报文,是的话返回响应

#if ENABLE_ICMP  
            if (iphdr->next_proto_id == IPPROTO_ICMP) {   
                struct rte_icmp_hdr *icmphdr = (struct rte_icmp_hdr *)(iphdr + 1);  

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

                // 如果是ICMP请求: 8 0  
                if (icmphdr->icmp_type == RTE_IP_ICMP_ECHO_REQUEST) {  

                    addr.s_addr = iphdr->dst_addr;  
                    printf("icmp ---> local: %s , type : %d\n", inet_ntoa(addr), icmphdr->icmp_type);  

                    struct rte_mbuf *txbuf = ng_send_icmp(mbuf_pool, ethr->s_addr.addr_bytes, iphdr->dst_addr,  
                            iphdr->src_addr, icmphdr->icmp_ident, icmphdr->icmp_seq_nb);  

                    rte_eth_tx_burst(DPDK_PortID, 0, &txbuf, 1);  
                    rte_pktmbuf_free(txbuf);  
                    rte_pktmbuf_free(mbufs[i]);  
                }  
            }  
#endif

发送函数

回发的时候需要发送IP, 目的IP, ICMP是网络层协议, 没有端口的概念
此外还需要ICMP协议首部相关值,根据ICMP官方文档,响应时identifier字段为发送时的值

static struct rte_mbuf *ng_send_icmp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac,  
                                     uint32_t sip, uint32_t dip, uint16_t id, uint16_t seqnb)  
{   
    const unsigned total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr);  

    struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);  
    if (!mbuf) {  
        rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");  
    }  

    mbuf->pkt_len = total_length;  
    mbuf->data_len = total_length;  

    uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);  
    ng_encode_icmp_pkt(pkt_data, dst_mac, sip, dip, id, seqnb);  

    return mbuf;  
}

封装ICMP数据

static int ng_encode_icmp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t dip, uint16_t id, uint16_t seqnb)  
{  
    // 1. ethhdr  
    struct rte_ether_hdr* eth = (struct rte_ether_hdr*)msg;  
    rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);  
    rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);  
    eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); 

    // 2. iphdr  
    struct rte_ipv4_hdr* ip = (struct rte_ipv4_hdr*)(msg + sizeof(struct rte_ether_hdr));  
    ip->version_ihl = 0x54;
    ip->type_of_service = 0;  
    ip->total_length = htons(sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr));  
    ip->packet_id = 0; 
    ip->fragment_offset = 0; 
    ip->time_to_live = 64;  
    ip->next_proto_id = IPPROTO_ICMP;  
    ip->src_addr = gSrcIp;  
    ip->dst_addr = gDstIp;  

    ip->hdr_checksum = 0;
    ip->hdr_checksum = rte_ipv4_cksum(ip);  

    // 3. icmphdr  
    struct rte_icmp_hdr* icmp = (struct rte_icmp_hdr*)(msg + sizeof(struct rte_ether_hdr) + sizeof(struct rte_icmp_hdr));  
    icmp->icmp_type = RTE_IP_ICMP_ECHO_REPLY;  
    icmp->icmp_code = 0;

    icmp->icmp_ident = id;  
    icmp->icmp_seq_nb = seqnb;  

    // icmp->icmp_cksum = rte_ipv4_cksum(ip, icmp); // 错误
    icmp->icmp_cksum = icmp_checksum((uint16_t*)icmp, sizeof(struct rte_icmp_hdr));  
}

值得注意的是,ICMP首部中的Checksum字段不能使用dpdk中ipv4校验和的计算方法

// 错误计算
icmp->icmp_cksum = rte_ipv4_cksum(ip, icmp); 

所以需要参考官方文档中的计算方法:RFC 1071: Computing the Internet checksum (rfc-editor.org)

static uint16_t icmp_checksum(uint16_t *addr, int count) {  

    register long sum = 0;  

    while (count > 1) {  
        sum += *(unsigned short*)addr++;  
        count -= 2;  
    }  

    if (count > 0) {  
        sum += *(unsigned char *)addr;  
    }  

    while (sum >> 16) {  
        sum = (sum & 0xffff) + (sum >> 16);  
    }  

    return ~sum;  
}

对于各种协议,代码的处理逻辑都是类似的,那么是否可以抽象成一个方法出来
最终提供一个接口,供外部访问使用即可