dpdk 实现 ping (arp + icmp)
理论
当用一台主机A去 ping 另一台机器B的时候
首先假设机器A内部没有机器B的物理地址,则 A ping B 的时候需要先发 arp 请求,以获取机器 B 的 MAC 地址
1、获取 MAC 地址
(1)如果 A 和 B 在同一网段内
A 会直接广播询问 谁有 B的 MAC 地址请告知我,通过抓包来看
B 会想 A发送一个 arp 应答,告知 A 我的MAC地址是xx:xx:xx:xx:xx:xx,形如
(2)如果 A 和 B 不在同一网段内
该请求会经路由器进入主机 B所在的网段,然后进行 ARP 广播查找主机 B 的物理地址
至此主机 A 就获得了主机 B 的物理地址,并将其写入到了本地的 ARP 缓存表
2、发送 ICMP 报文
ping 实际上就是 A 向 B 发送ICMP请求, B 向 A 发送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地址的,如图所示
当我写完ARP后,如果我用主机ping我的虚拟机的网卡,显示超时(或者其他,反正就是没有ping通),然后通过
arp -a
发现我有了目的主机的MAC地址,恭喜你,你的ARP应答已经完成了。如下图所示。
下一步就是完成ICMP应答,当完成ICMP应答后,就可ping通了,如下图所示。字节为0,是因为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, ðer_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,对计算校验和无影响。
本文来自博客园,作者:correct,转载请注明原文链接:https://www.cnblogs.com/correct/p/16548380.html