相关代码: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响应报文,各字段的具体内容如下:
添加标识
#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;
}
对于各种协议,代码的处理逻辑都是类似的,那么是否可以抽象成一个方法出来
最终提供一个接口,供外部访问使用即可