ARP协议
关于ARP的介绍摘录自ARP协议 | 菜鸟教程
ARP(Address Resolution Protocol,地址解析协议)是一种用于将IP地址解析为如MAC地址的协议。ARP在局域网中广泛使用,帮助设备在通信时确定目标设备的物理地址。ARP的核心功能是通过广播请求和单播响应,将 IP 地址解析为 MAC 地址。核心过程包括:
- ARP请求:当设备需要与目标设备通信时,如果不知道目标设备的MAC地址,它会发送一个ARP请求广播包,询问谁拥有这个IP地址;
- ARP应答:如果某个设备发现自己的IP地址与ARP请求中的目标IP地址匹配,它会发送一个ARP应答包,告知自己的MAC地址,该响应包是单播的,只发送给请求方;
- ARP缓存:发送方在收到ARP响应后,会将IP地址和MAC地址的映射关系存储在本地ARP缓存中,以便后续通信时直接使用。
程序框架设计
整体架构被划分为主线程和子线程两个主要部分,主线程负责数据包的接收与发送,子线程负责数据包的处理:
- 主线程:主线程从网卡中接收数据包,并放入环形队列recv_ring中,供子线程后续处理。主线程还负责从环形队列send_ring中取出数据包,并执行数据包的发送;
- 子线程:子线程从环形队列recv_ring中取出数据包,执行相应的数据处理逻辑(如协议解析、转发决策等),处理完成后,再将待发送的数据包放入环形队列send_recv中,由主线程统一负责发送。
1
2
3
4
5
6
7
8
9
|
+-------------+ +------------+
| Main Thread | RECV_RING | Worker |
| RX | ------------> | Thread |
+-------------+ +------------+
+-------------+ +------------+
| Main Thread | SEND_RING | Worker |
| TX | <----------- | Thread |
+-------------+ +------------+
|
初始化等操作
环境、内存池、端口初始化等操作均与“DPDK单核数据包接收案例”基本一致,可参考:DPDK单核数据包接收案例 。
在DPDK的多线程框架中,主线程和子线程通过环形队列通信,因此唯一需要补充的操作是创建环形队列send_ring和recv_ring。DPDK提供的环形队列是一种逻辑上首尾相连、固定大小的循环缓冲区,其核心优势在于采用无锁设计,专用于在线程之间高性能传递指针。在多核高并发环境中,传统的加锁机制容易成为性能瓶颈,而无锁环形队列则显著降低了同步开销,提升了系统的吞吐能力与实时性。环形队列使用rte_ring_create()函数创建,其参数依次为:
- name:队列名称,需全局唯一;
- count:队列元素数量,即最大容量;
- socket_id:指定在哪个NUMA节点上分配内存;
- flags:队列模式,如单生产者、多消费者。
队列的最大容量通常取为2的整数次幂(如1024、2048、4096),具体取值取决于数据包处理的速率与延迟容忍度,过小的队列容量会导致严重的丢包,过大的队列容量会浪费内存并可能影响缓存命中,一般初始设置为1024或4096,再根据实际吞吐进行调整与测试。
队列模式决定了生产者(入队)和消费者(出队)线程的数量限制,环形队列进而启用不同的并发控制机制,避免不必要的原子操作和内存屏障,以提高性能。生产者包括单生产者SP和多生产者MP模式,消费者包括单消费者SC和多消费者MC模式,两两组合共包含4种队列模式:
- SP/SC:性能最高,典型一对一场景,flag填写RING_F_SP_ENQ | RING_F_SC_DEQ;
- SP/MC:一个线程写、多个线程读,flag填写RING_F_SP_ENQ;
- MP/SC:多个线程写、一个线程读,flag填写RING_F_SC_DEQ;
- MP/MC:完全通用,默认模式,线程数不固定,flag填写0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#define RING_SIZE 2048
// Create ring buffer for RX
struct rte_ring *recv_ring = rte_ring_create("RECV_RING", RING_SIZE, rte_socket_id(), RING_F_SP_ENQ | RING_F_SC_DEQ);
if (recv_ring == NULL)
{
rte_exit(EXIT_FAILURE, "Cannot create recv ring\n");
}
// Create ring buffer for TX
struct rte_ring *send_ring = rte_ring_create("SEND_RING", RING_SIZE, rte_socket_id(), RING_F_SP_ENQ | RING_F_SC_DEQ);
if (send_ring == NULL)
{
rte_exit(EXIT_FAILURE, "Cannot create send ring\n");
}
|
在全部初始化工作完成后,即可依次启动处理数据包的子线程、数据包收发的主线程。启动子线程时,需要先使用rte_get_next_lcore()函数查找下一个可用的逻辑核心,参数如下:
- 第一个参数-1表示从编号为-1的逻辑核开始查找,即从头查找;
- 第二个参数1表示跳过主逻辑核、0表示包括主逻辑核;
- 第三个参数0表示禁止回绕查找,即当遍历到逻辑核末尾仍未找到可用核心时,不再从头开始。启用回绕表示在末尾找不到之后从头重新开始查找。无论是否启用回绕查找,函数最多遍历一圈核心列表,因此不会陷入无限循环。
需要注意的是,rte_get_next_lcore()函数不会判断核心是否正忙,仅依据EAL启动时是否启用该核心进行判断。若找到可用核心,则返回其逻辑核编号。若未找到,则返回RTE_MAX_LCORE。
在获取子线程运行的逻辑核编号后,即可调用rte_eal_remote_launch()函数中该核心上启动一个函数:
- lcore_pkt_process_main是在目标逻辑核心上运行的函数指针,即为数据包处理函数;
- 第二个参数为传递给函数的参数指针,此处NULL表示该函数不需要外部参数;
- lcore_id为目标逻辑核心的编号,指定函数运行的核心。
在子线程启动后,即可中主线程上执行数据包的接收与发送任务lcore_send_recv_main()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Call lcore_main on the core
unsigned lcore_id = rte_get_next_lcore(-1, 1, 0);
if (lcore_id == RTE_MAX_LCORE)
{
rte_exit(EXIT_FAILURE, "No available lcore for thread\n");
}
rte_eal_remote_launch(lcore_pkt_process_main, NULL, lcore_id);
// Call lcore_main on the main core only. Called on single lcore
uint16_t portid = 0;
lcore_send_recv_main(portid);
// Clean up the EAL
rte_eal_cleanup();
printf("Bye...\n");
|
关于rte_eal_remote_launch()传递参数的补充:通用的方式是定义参数结构体,然后传递该结构体的指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// Define the parameter structure
struct lcore_args {
uint16_t port_id;
uint16_t queue_id;
};
// Defining sub-threaded execution functions
int lcore_pkt_process_main(void *arg) {
struct lcore_args *args = (struct lcore_args *)arg;
printf("Port: %u, Queue: %u\n", args->port_id, args->queue_id);
return 0;
}
// Create parameters and pass
struct lcore_args *args = malloc(sizeof(struct lcore_args));
args->port_id = 0;
args->queue_id = 1;
unsigned lcore_id = rte_get_next_lcore(-1, 1, 0);
rte_eal_remote_launch(lcore_pkt_process_main, (void *)args, lcore_id);
|
数据包收发主线程
主线程和子线程之间的数据交换依赖环形队列,rte_ring_lookup()函数可根据名称查找、获得之前已经创建的环形队列send_ring和recv_ring。
1
2
3
4
5
6
7
|
// Get RX and TX ring
struct rte_ring *recv_ring = rte_ring_lookup("RECV_RING");
struct rte_ring *send_ring = rte_ring_lookup("SEND_RING");
if (recv_ring == NULL || send_ring == NULL)
{
rte_exit(EXIT_FAILURE, "[SEND_RECV] Cannot find ring: RECV_RING or SEND_RING\n");
}
|
主线程上的核心是持续运行的主循环,只有当force_quit为true时才会终止。在每一轮循环中,程序首先通过rte_eth_rx_burst()函数批量接收最多BURST_SIZE个数据包,并遍历接收到的每一个数据包,将其放入环形队列recv_ring中。随后从环形队列send_ring取出待发送的数据包,并通过rte_eth_tx_burst()函数执行发送。
rte_ring_enqueue()函数用于将一个元素入队到环形队列中,返回值为0表示操作成功、负值表示入队失败(例如-ENOBUFS表示队列已满)。若一次性将多个元素入队到环形队列中可使用rte_ring_enqueue_bulk()函数。
rte_ring_dequeue()函数用于从环形队列中取出一个元素,返回值为0表示操作成功、负值表示入队失败。其中传递的是tx_buf指针的指针,因为需要修改指针变量为出队元素的指针。若一次性将多个元素出队可使用rte_ring_dequeue_bulk()函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
extern volatile bool force_quit;
// Main work of loop
while (!force_quit)
{
// Recv packets
struct rte_mbuf *rx_bufs[BURST_SIZE];
const uint16_t nb_rx = rte_eth_rx_burst(portid, 0, rx_bufs, BURST_SIZE);
for (uint16_t i = 0; i < nb_rx; i++)
{
struct rte_mbuf *mbuf = rx_bufs[i];
if (rte_ring_enqueue(recv_ring, mbuf) < 0)
{
rte_pktmbuf_free(mbuf);
}
}
// Send packets
struct rte_mbuf *tx_buf;
while (rte_ring_dequeue(send_ring, (void **)&tx_buf) == 0)
{
const uint16_t nb_tx = rte_eth_tx_burst(portid, 0, &tx_buf, 1);
if (nb_tx == 0)
{
rte_pktmbuf_free(tx_buf);
}
}
}
|
数据包处理子线程
子线程框架
子线程的初始化阶段包括内存、地址、ARP 表和定时器的设置,随后同样是不断执行的循环,循环持续从环形队列recv_ring中取出接收到的数据包,并进行逐个处理。
子线程需承担以下任务:响应其他节点发送的 ARP 请求报文、周期性向其他节点发送 ARP 请求以维护地址映射表,以及解析并处理来自其他节点的 ARP 响应报文。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
extern volatile bool force_quit;
// Basic packet processing application lcore
int lcore_pkt_process_main(void *arg)
{
// Initialize
init_memory();
init_address();
init_arp_table();
init_timer();
// Main work of loop
while (!force_quit)
{
// Process a packet
struct rte_mbuf *mbuf;
if (rte_ring_dequeue(recv_ring, (void **)&mbuf) == 0)
{
process_packet(mbuf);
rte_pktmbuf_free(mbuf);
}
// Call timer management to check and run timer callbacks
rte_timer_manage();
}
return 0;
}
|
内存、地址的初始化
通过rte_mempool_lookup()函数、rte_ring_lookup()函数分别获取已经创建的内存池与环形队列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
struct rte_mempool *mempool;
struct rte_ring *send_ring;
struct rte_ring *recv_ring;
void init_memory()
{
// Get mempool
mempool = rte_mempool_lookup("mbuf_pool");
if (mempool == NULL)
{
rte_exit(EXIT_FAILURE, "[PKT_PROCESS] Mempool is not found!\n");
}
// Get RX and TX ring
recv_ring = rte_ring_lookup("RECV_RING");
send_ring = rte_ring_lookup("SEND_RING");
if (recv_ring == NULL || send_ring == NULL)
{
rte_exit(EXIT_FAILURE, "[PKT_PROCESS] Cannot find ring: RECV_RING or SEND_RING\n");
}
}
|
在端口信息设置过程中,本示例程序仅使用端口0。首先通过rte_eth_macaddr_get()函数获取端口0的MAC地址,并将其存储中local_mac结构体中。随后为该端口手动设置IP地址,inet_pton()函数将IP地址字符串转换为in_addr结构体中的网络字节序整数,再通过rte_be_to_cpu_32()函数将其转换为主机字节序,存入local_ip变量中,便于后续处理。
在编程中需特别注意 IP 地址等变量在主机字节序与网络字节序之间的转换,确保数据处理的正确性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
uint16_t portid;
struct rte_ether_addr *local_mac;
uint32_t local_ip;
void init_address()
{
// Set port identify
portid = 0;
// Get MAC address for port 0
local_mac = malloc(sizeof(struct rte_ether_addr));
if (rte_eth_macaddr_get(portid, local_mac))
{
rte_exit(EXIT_FAILURE, "[ARP] Failed to get MAC address for port %u\n", portid);
}
// Parse local IP address
const char *local_ip_str = "192.168.226.100";
struct in_addr addr;
inet_pton(AF_INET, local_ip_str, &addr);
local_ip = rte_be_to_cpu_32(addr.s_addr);
}
|
ARP表的初始化
ARP表是IP地址与MAC地址的映射,可采用哈希表进行高效的存储,哈希表的配置采用rte_hash_parameters结构体进行描述,包括以下配置参数:
- name:哈希表名称;
- entries:最大支持的键值对数量;
- key_len:键的长度,此处设置uint32_t表示以IP地址作为键;
- hash_func:设置使用的哈希函数,此处设置为rte_jhash,该哈希函数兼容性好、性能适中、适合通用键类型;
- hash_func_init_val:哈希函数的种子值,若需要随机性可采用rte_rdtsc()函数读取CPU的时间戳计时器、作为种子值;
- socket_id:NUMA节点编号,确保内存分配在当前CPU核心所在的NUMA节点上,以提高性能。
在完成哈希表配置的填充后,rte_hash_create()函数即可根据上述参数创建哈希表,并将创建的哈希表返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#define ARP_TABLE_NAME "arp_table"
#define MAX_ARP_ENTRIES 1024
struct rte_hash *arp_hash;
// Initialize table for ARP information
void init_arp_table(void)
{
struct rte_hash_parameters hash_params = {
.name = ARP_TABLE_NAME,
.entries = MAX_ARP_ENTRIES,
.key_len = sizeof(uint32_t), // IP address
.hash_func = rte_jhash,
.hash_func_init_val = 0,
.socket_id = rte_socket_id(),
};
arp_hash = rte_hash_create(&hash_params);
if (arp_hash == NULL)
{
rte_exit(EXIT_FAILURE, "[PKT_PROCESS] Failed to create ARP hash table\n");
}
}
|
定时器的初始化
本程序采用周期性定时发送ARP应答包的策略,需通过DPDK的定时器rte_timer来触发定时任务。定时任务通过回调函数触发,回调函数中定时器超时触发时被调用,回调函数的参数tim指向触发该回调的rte_timer对象、参数arg是在设置定时器时传入的自定义参数指针,由于tim和arg中函数体内部未被使用,因此按照DPDK的规范标记为__rte_unused。
1
2
3
4
5
|
// Callback function for the periodic ARP timer
static void arp_timer_cb(__rte_unused struct rte_timer *tim, __rte_unused void *arg)
{
handle_arp_timer_cb();
}
|
在首次使用定时器前,需要调用rte_timer_subsystem_init()函数,以完成 DPDK 定时器子系统的初始化。随后声明并初始化一个定时器对象arp_timer,用于后续的注册与启动。DPDK的定时器以CPU时钟周期(cycles)为单位进行计时,因此若希望设置定时器每 10 秒触发一次,需要将当前平台每秒对应的时钟周期数(通过rte_get_timer_hz()函数获得)乘以10得到。最后通过rte_timer_reset()函数进行定时器的注册与启动,包括以下参数:
- &arp_timer:要配置、注册的定时器对象;
- hz:超时时长,其单位为CPU周期数;
- PERIODICAL:定时器类型,PERIODICAL表示周期性触发(每过一个固定时间周期,定时器自动重新激活,持续触发回调函数),SINGLE则为一次性定时器(只在超时时间到达时触发一次回调,之后定时器自动停止);’
- rte_lcore_id():当前逻辑核心编号,指定在哪个核心上触发回调;
- arp_timer_cb:定时器触发时调用的回调函数;
- NULL:传递给回调函数的参数,本例为空。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void init_timer()
{
// Initialize timer subsystem
rte_timer_subsystem_init();
// Initialize ARP timer
struct rte_timer arp_timer;
rte_timer_init(&arp_timer);
// Set a periodic timer to trigger every 10 seconds
uint64_t hz = 10 * rte_get_timer_hz(); // Number of cycles per 10 seconds
rte_timer_reset(&arp_timer, hz, PERIODICAL, rte_lcore_id(), arp_timer_cb, NULL);
}
|
DPDK的定时器依赖轮询机制,需要在主循环中定期调用rte_timer_manage()来处理超时事件,该函数的主要功能包括:
- 遍历当前逻辑核心上的定时器队列(定时器是每个逻辑核私有的,即绑定在某个核心上执行);
- 比较定时器的超时时间和当前时间;
- 如果定时器已超时,就调用其对应的回调函数;
- 根据定时器类型决定是否重新安排(周期性)或移除(一次性);
- 处理定时器的启动、停止、重置等状态变更(用户发起定时器的状态变更,不会立即生效,而是先把操作记录下来,实际的状态变更是在下一次执行rte_timer_manage()函数时生效)。
1
2
3
4
5
6
7
8
9
10
11
12
|
int lcore_pkt_process_main(void *arg)
{
// ......
init_timer();
while (!force_quit)
{
// ......
// Call timer management to check and run timer callbacks
rte_timer_manage();
}
return 0;
}
|
定时器是每个逻辑核私有的,即绑定在某个核心上执行。例如:线程A运行在lcore 0,设置了定时器A;线程B运行在lcore 1,设置了定时器B。则:必须在lcore 0上的循环里调用rte_timer_manage()函数以触发定时器A;也必须在lcore 1上的循环里调用rte_timer_manage()函数以触发定时器B。
定时器运行在哪一个逻辑核心上执行是由rte_timer_reset()的第四个参数lcore_id决定的,因此可以在线程A上将定时器绑定到线程B所在的逻辑核心上,此时这个定时器只能由线程B调用rte_timer_manage()函数来触发回调。
rte_timer_subsystem_init()函数中整个程序中只需要调用一次,它是整个DPDK定时器模块的全局初始化函数,在任何线程开始使用定时器之前调用即可。
数据包分发
在主循环中,每次从环形队列rx_ring取出数据包后,调用process_packet()函数将数据包通过不同的协议处理函数进行处理。具体而言,该函数从数据包中提取以太网头部,读取ether_type字段判断数据包是否为ARP包,如果是就调用handle_arp()函数进行进一步的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void process_packet(struct rte_mbuf *mbuf)
{
// Get the Ethernet header from mbuf
struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
// Check if it's an ARP packet (EtherType == 0x0806)
if (rte_be_to_cpu_16(eth_hdr->ether_type) == RTE_ETHER_TYPE_ARP)
{
// Get ARP header
struct rte_arp_hdr *arp_hdr = (struct rte_arp_hdr *)(eth_hdr + 1);
handle_arp(arp_hdr, eth_hdr);
return;
}
}
|
handle_arp()函数提取ARP报文中的操作类型字段opcode,常见的ARP操作码有:RTE_ARP_OP_REQUEST表示ARP请求(值为1)、RTE_ARP_OP_REPLY表示ARP应答(值为2),根据ARP操作码调用相应的函数分别处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Main handler of ARP packet
void handle_arp(struct rte_arp_hdr *arp_hdr, struct rte_ether_hdr *eth_hdr)
{
uint16_t opcode = rte_be_to_cpu_16(arp_hdr->arp_opcode);
if (opcode == RTE_ARP_OP_REQUEST)
{
// ARP Request received
handle_arp_request(arp_hdr, eth_hdr);
return;
}
if (opcode == RTE_ARP_OP_REPLY)
{
// ARP Reply received
handle_arp_reply(arp_hdr);
}
return;
}
|
ARP请求
取出ARP请求数据中的目标IP地址字段arp_tip,判断目标IP地址是否是本地机器的IP。如果不是本机的IP,说明这条ARP请求不是发给本机的,本机无需应答,直接返回退出。
1
2
3
4
5
6
|
// Check if the target IP matches our local IP
uint32_t target_ip = rte_be_to_cpu_32(arp_hdr->arp_data.arp_tip);
if (target_ip != local_ip)
{
return;
}
|
为即将发送的ARP响应包分配并初始化一个新的mbuf缓冲区,以准备构造和发送数据包。随后中该缓冲区中申请pkt_len字节的空间,用于存放以太网头部和ARP报文。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Allocate a new mbuf for the ARP reply
struct rte_mbuf *reply_mbuf = rte_pktmbuf_alloc(mempool);
if (!reply_mbuf)
{
printf("[ARP] Failed to allocate mbuf for ARP reply\n");
return;
}
// Append space for Ethernet + ARP header
uint16_t pkt_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr);
void *data_ptr = rte_pktmbuf_append(reply_mbuf, pkt_len);
if (!data_ptr)
{
printf("[ARP] Failed to append data to mbuf\n");
rte_pktmbuf_free(reply_mbuf);
return;
}
|
填充以太网头部各字段:将原始ARP请求包中的源MAC 地址(即对方的地址)复制为响应包的目标地址;将本地网卡的MAC地址作为源地址写入;将以太网类型字段ether_type设置为ARP类型。
1
2
3
4
5
|
// Fill Ethernet header
struct rte_ether_hdr *reply_eth_hdr = rte_pktmbuf_mtod(reply_mbuf, struct rte_ether_hdr *);
rte_ether_addr_copy(ð_hdr->src_addr, &reply_eth_hdr->dst_addr);
rte_ether_addr_copy(local_mac, &reply_eth_hdr->src_addr);
reply_eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP);
|
填写ARP响应报文的各字段:将硬件类型设为以太网RTE_ARP_HRD_ETHER、协议类型设为IPv4、硬件地址长度设置为MAC地址的长度RTE_ETHER_ADDR_LEN、协议地址长度设置为IPv4地址长度(即4字节)、操作码设为ARP响应RTE_ARP_OP_REPLY,随后设置发送方的MAC地址字段arp_sha为本节点的MAC地址、发送方的IP地址字段arp_sip为本节点的IP地址,并设置目标方的MAC地址字段arp_tha与IP地址字段arp_tip(从源ARP请求包的arp_sha、arp_sip字段提取填入)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// Fill ARP header
struct rte_arp_hdr *reply_arp = (struct rte_arp_hdr *)(reply_eth_hdr + 1);
reply_arp->arp_hardware = rte_cpu_to_be_16(RTE_ARP_HRD_ETHER);
reply_arp->arp_protocol = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
reply_arp->arp_hlen = RTE_ETHER_ADDR_LEN;
reply_arp->arp_plen = sizeof(uint32_t);
reply_arp->arp_opcode = rte_cpu_to_be_16(RTE_ARP_OP_REPLY);
rte_ether_addr_copy(local_mac, &reply_arp->arp_data.arp_sha);
reply_arp->arp_data.arp_sip = rte_cpu_to_be_32(local_ip);
rte_ether_addr_copy(&arp_hdr->arp_data.arp_sha, &reply_arp->arp_data.arp_tha);
reply_arp->arp_data.arp_tip = arp_hdr->arp_data.arp_sip;
|
将构造好的ARP响应包放入发送队列tx_ring中,后续由主线程发送。
1
2
3
4
5
6
7
8
9
10
11
12
|
// Enqueue to TX ring for later sending
if (rte_ring_enqueue(send_ring, reply_mbuf) < 0)
{
printf("[ARP] Failed to enqueue ARP reply to tx_ring\n");
rte_pktmbuf_free(reply_mbuf);
}
else
{
struct in_addr ip_addr;
ip_addr.s_addr = rte_cpu_to_be_32(target_ip);
printf("[ARP] ARP reply enqueued for %s\n", inet_ntoa(ip_addr));
}
|
ARP应答
从接收到的ARP报文中提取发送端的IP和MAC地址,为后续更新本地ARP表等操作做准备。
1
2
3
|
// Parse for IP and MAC
uint32_t ip = rte_be_to_cpu_32(arp_hdr->arp_data.arp_sip);
struct rte_ether_addr *mac = &arp_hdr->arp_data.arp_sha;
|
在添加新的MAC-IP地址映射前,需要预先检测哈希表中是否已经存在该IP的ARP条目。若不进行检查而直接插入新键值对,如果键已存在,插入操作则将错误,且原有数据不会被覆盖。
rte_hash_lookup_data()函数可以在哈希表arp_hash中根据指定的键ip查找对应的值existing_data。函数返回非负整数表示查找成功,其值为键的哈希位置索引(通常不需要关注)。若函数返回负值,则表示键不存在。
若哈希表arp_hash中已经存在键ip,则使用rte_hash_del_key()函数删除该键值对,以便后续添加新的键值对。当然也可以采用rte_hash_set_key_data()函数直接更新其值。
1
2
3
4
5
6
7
8
9
|
// Check if an ARP entry for this IP already exists in the hash table
void *existing_data = NULL;
int ret = rte_hash_lookup_data(arp_hash, &ip, &existing_data);
if (ret >= 0)
{
// Entry exists, free old value
rte_free(existing_data);
rte_hash_del_key(arp_hash, &ip);
}
|
通过rte_hash_add_key_data()函数新的IP-MAC映射条目加入到哈希表中,非负数的函数返回值表示键值对插入成功(返回值为该键在哈希表中的位置索引,通常不需要使用),负数的函数返回值表示插入失败。由于函数需要传入的是一个持久有效的指针,而代码中的mac指针位于栈中,后续该地址会被修改或释放,因此需要将数据包中获得的mac变量拷贝一份出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// Copy the MAC address from the ARP reply into the new entry
struct arp_entry *entry = rte_malloc(NULL, sizeof(struct arp_entry), 0);
if (!entry)
{
printf("[ARP] Memory allocation failed for ARP entry\n");
return;
}
rte_ether_addr_copy(mac, &entry->mac);
// Add the new IP-to-MAC mapping into the hash table
ret = rte_hash_add_key_data(arp_hash, &ip, entry);
if (ret < 0)
{
printf("[ARP] Failed to insert ARP entry for IP: %08x\n", ip);
rte_free(entry);
}
else
{
struct in_addr ip_addr;
ip_addr.s_addr = rte_cpu_to_be_32(ip);
char mac_str[RTE_ETHER_ADDR_FMT_SIZE];
rte_ether_format_addr(mac_str, RTE_ETHER_ADDR_FMT_SIZE, mac);
printf("[ARP] ARP entry added: %s → %s\n", inet_ntoa(ip_addr), mac_str);
}
|
周期性发送ARP请求
定时器的回调函数将进一步调用handle_arp_timer_cb()函数执行ARP请求的发送,该函数周期性的扫描本地子网内的主机,并向尚未在ARP表中的主机发送ARP请求,以主动发现其MAC地址。具体过程如下:
- 定义子网掩码为255.255.255.0,即/24网络,用于提取子网地址;
- 计算当前主机所在子网的起始IP,即网段基地址;
- 遍历该子网内的IP地址,目标IP的地址基于子网基地址加偏移进行构造,其中排除网络地址.0和广播地址.255;
- 调用send_arp_request()函数使向target_ip发送ARP请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// Callback function for the periodic ARP timer
void handle_arp_timer_cb()
{
uint32_t subnet_mask = 0xFFFFFF00; // 255.255.255.0
uint32_t subnet_base_ip = local_ip & subnet_mask;
for (uint32_t offset = 1; offset < 255; offset++)
{
uint32_t target_ip = subnet_base_ip + offset;
if (target_ip == local_ip)
{
continue;
}
void *existing_data = NULL;
if (rte_hash_lookup_data(arp_hash, &target_ip, &existing_data) >= 0)
{
continue;
}
send_arp_request(target_ip);
}
}
|
创建ARP请求包并发送,具体流程包括:
- 申请一个新的数据包缓冲区mbuf,并设置数据包长度;
- 填写以太网头部:目标MAC设置为广播地址,源MAC设置为本机地址,太网类型设置为ARP;
- 填写ARP报文:硬件类型arp_hardware为以太网设备(RTE_ARP_HRD_ETHER),协议类型arp_protocol为IPv4(RTE_ETHER_TYPE_IPV4),硬件地址长度arp_hlen为6字节(RTE_ETHER_ADDR_LEN),协议地址长度arp_plen为4字节(即IPv4地址的长度),操作码arp_opcode为ARP请求(RTE_ARP_OP_REQUEST),源MAC地址arp_sha为本节点MAC地址,源IP地址arp_sip为local_ip,目标MAC地址arp_tha为0(表示未知),目标IP地址arp_tip为target_ip;
- 将构造好的数据包加入发送队列send_ring。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
void send_arp_request(uint32_t target_ip)
{
// Allocate a new mbuf for the ARP request
struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mempool);
if (mbuf == NULL)
{
printf("[ARP] Failed to allocate mbuf for ARP request\n");
return;
}
// Reserve space for Ethernet + ARP headers
char *pkt_data = rte_pktmbuf_append(mbuf, sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr));
if (pkt_data == NULL)
{
printf("[ARP] Failed to append space for ARP request\n");
rte_pktmbuf_free(mbuf);
return;
}
struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)pkt_data;
struct rte_arp_hdr *arp_hdr = (struct rte_arp_hdr *)(eth_hdr + 1);
// Ethernet header
struct rte_ether_addr broadcast_mac = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
rte_ether_addr_copy(&broadcast_mac, ð_hdr->dst_addr);
rte_ether_addr_copy(local_mac, ð_hdr->src_addr);
eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP);
// ARP header
arp_hdr->arp_hardware = rte_cpu_to_be_16(RTE_ARP_HRD_ETHER);
arp_hdr->arp_protocol = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
arp_hdr->arp_hlen = RTE_ETHER_ADDR_LEN;
arp_hdr->arp_plen = sizeof(uint32_t);
arp_hdr->arp_opcode = rte_cpu_to_be_16(RTE_ARP_OP_REQUEST);
// ARP data
rte_ether_addr_copy(local_mac, &arp_hdr->arp_data.arp_sha);
arp_hdr->arp_data.arp_sip = rte_cpu_to_be_32(local_ip);
struct rte_ether_addr zero_mac = {{0, 0, 0, 0, 0, 0}};
rte_ether_addr_copy(&zero_mac, &arp_hdr->arp_data.arp_tha);
arp_hdr->arp_data.arp_tip = rte_cpu_to_be_32(target_ip);
// Enqueue the packet to tx_ring
if (rte_ring_enqueue(send_ring, mbuf) < 0)
{
printf("[ARP] Failed to enqueue ARP request for IP %s\n", inet_ntoa(*(struct in_addr *)&arp_hdr->arp_data.arp_tip));
rte_pktmbuf_free(mbuf);
}
}
|
程序测试
主机A的ens37网卡的MAC地址为00:0C:29:F4:89:34、IP地址为192.168.226.128,主机B的ens37网卡的MAC地址为00:0C:29:8B:E2:F8、IP地址为192.168.226.100,同时此时主机A队对主机B对应的MAC地址未知。

主机B编译运行该程序。
1
2
3
4
|
sudo make clean
sudo make
cd build
sudo ./arp_icmp
|
此时主机A已经学习到了主机B的IP-MAC映射关系为192.168.226.100 - 00:0C:29:8B:E2:F8。

运行DPDK的主机B也学习到其它设备的IP-MAC映射关系。

源码
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
// Include and define
#include <rte_eal.h>
#include <signal.h>
#include <rte_lcore.h>
#include "device_init.h"
#include "send_recv.h"
#include "packet_process.h"
// Signal handler for exiting
volatile bool force_quit = false;
static void signal_handler(int signum)
{
if (signum == SIGINT || signum == SIGTERM)
{
printf("\n\nSignal %d received, preparing to exit...\n", signum);
force_quit = true;
}
}
// The main function, which does initialization and calls the per-lcore functions
int main(int argc, char *argv[])
{
// Initialize the Environment Abstraction Layer (EAL)
int ret = rte_eal_init(argc, argv);
if (ret < 0)
{
rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
}
argc -= ret;
argv += ret;
// Register signal handlers
force_quit = false;
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Initialize device and ports
device_init();
printf("**************************************************************************************\n");
// Call lcore_main on the core
unsigned lcore_id = rte_get_next_lcore(-1, 1, 0);
if (lcore_id == RTE_MAX_LCORE)
{
rte_exit(EXIT_FAILURE, "No available lcore for thread\n");
}
rte_eal_remote_launch(lcore_pkt_process_main, NULL, lcore_id);
// Call lcore_main on the main core only. Called on single lcore
uint16_t portid = 0;
lcore_send_recv_main(portid);
// Clean up the EAL
rte_eal_cleanup();
printf("Bye...\n");
// Return 0 to indicate successful execution
return 0;
}
|
send_recv.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#include "send_recv.h"
#define BURST_SIZE 32
extern volatile bool force_quit;
// Basic forwarding application lcore
void lcore_send_recv_main()
{
uint16_t portid = 0;
// Check that the port is on the same NUMA node as the polling thread for best performance
if (rte_eth_dev_socket_id(portid) >= 0 && rte_eth_dev_socket_id(portid) != (int)rte_socket_id())
{
printf("[SEND_RECV] Port %u is on remote NUMA node to polling thread.\n", portid);
printf("Performance will not be optimal.\n");
}
printf("Core %u receiving packets. [Ctrl+C to quit]\n", rte_lcore_id());
// Get RX and TX ring
struct rte_ring *recv_ring = rte_ring_lookup("RECV_RING");
struct rte_ring *send_ring = rte_ring_lookup("SEND_RING");
if (recv_ring == NULL || send_ring == NULL)
{
rte_exit(EXIT_FAILURE, "[SEND_RECV] Cannot find ring: RECV_RING or SEND_RING\n");
}
// Main work of loop
while (!force_quit)
{
// Recv packets
struct rte_mbuf *rx_bufs[BURST_SIZE];
const uint16_t nb_rx = rte_eth_rx_burst(portid, 0, rx_bufs, BURST_SIZE);
for (uint16_t i = 0; i < nb_rx; i++)
{
struct rte_mbuf *mbuf = rx_bufs[i];
if (rte_ring_enqueue(recv_ring, mbuf) < 0)
{
rte_pktmbuf_free(mbuf);
}
}
// Send packets
struct rte_mbuf *tx_buf;
while (rte_ring_dequeue(send_ring, (void **)&tx_buf) == 0)
{
const uint16_t nb_tx = rte_eth_tx_burst(portid, 0, &tx_buf, 1);
if (nb_tx == 0)
{
rte_pktmbuf_free(tx_buf);
}
}
}
}
|
packet_process.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
#include "packet_process.h"
#include "arp.h"
#include "icmp.h"
extern volatile bool force_quit;
struct rte_mempool *mempool;
struct rte_ring *send_ring;
struct rte_ring *recv_ring;
uint16_t portid;
struct rte_ether_addr *local_mac;
uint32_t local_ip;
void process_packet(struct rte_mbuf *mbuf)
{
// Get the Ethernet header from mbuf
struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
// Check if it's an ARP packet (EtherType == 0x0806)
if (rte_be_to_cpu_16(eth_hdr->ether_type) == RTE_ETHER_TYPE_ARP)
{
// Get ARP header
struct rte_arp_hdr *arp_hdr = (struct rte_arp_hdr *)(eth_hdr + 1);
handle_arp(arp_hdr, eth_hdr);
return;
}
// Check if it's an IP packet (EtherType == 0x0800)
if (rte_be_to_cpu_16(eth_hdr->ether_type) == RTE_ETHER_TYPE_IPV4)
{
struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
// Check if it is an ICMP packet
if (ip_hdr->next_proto_id == IPPROTO_ICMP)
{
// Get ICMP header
uint8_t ihl = (ip_hdr->version_ihl & 0x0f) * 4;
struct rte_icmp_hdr *icmp_hdr = (struct rte_icmp_hdr *)((uint8_t *)ip_hdr + ihl);
handle_ping(icmp_hdr, ip_hdr, eth_hdr);
return;
}
}
}
// Callback function for the periodic ARP timer
static void arp_timer_cb(__rte_unused struct rte_timer *tim, __rte_unused void *arg)
{
handle_arp_timer_cb();
}
void init_memory()
{
// Get mempool
mempool = rte_mempool_lookup("mbuf_pool");
if (mempool == NULL)
{
rte_exit(EXIT_FAILURE, "[PKT_PROCESS] Mempool is not found!\n");
}
// Get RX and TX ring
recv_ring = rte_ring_lookup("RECV_RING");
send_ring = rte_ring_lookup("SEND_RING");
if (recv_ring == NULL || send_ring == NULL)
{
rte_exit(EXIT_FAILURE, "[PKT_PROCESS] Cannot find ring: RECV_RING or SEND_RING\n");
}
}
void init_address()
{
// Set port identify
portid = 0;
// Get MAC address for port 0
local_mac = malloc(sizeof(struct rte_ether_addr));
if (rte_eth_macaddr_get(portid, local_mac))
{
rte_exit(EXIT_FAILURE, "[ARP] Failed to get MAC address for port %u\n", portid);
}
// Parse local IP address
const char *local_ip_str = "192.168.226.100";
struct in_addr addr;
inet_pton(AF_INET, local_ip_str, &addr);
local_ip = rte_be_to_cpu_32(addr.s_addr);
}
void init_timer()
{
// Initialize timer subsystem
rte_timer_subsystem_init();
// Initialize ARP timer
struct rte_timer arp_timer;
rte_timer_init(&arp_timer);
// Set a periodic timer to trigger every 10 seconds
uint64_t hz = 10 * rte_get_timer_hz(); // Number of cycles per 10 seconds
rte_timer_reset(&arp_timer, hz, PERIODICAL, rte_lcore_id(), arp_timer_cb, NULL);
}
// Basic packet processing application lcore
int lcore_pkt_process_main(void *arg)
{
// Initialize
init_memory();
init_address();
init_arp_table();
init_timer();
// Main work of loop
while (!force_quit)
{
// Process a packet
struct rte_mbuf *mbuf;
if (rte_ring_dequeue(recv_ring, (void **)&mbuf) == 0)
{
process_packet(mbuf);
rte_pktmbuf_free(mbuf);
}
// Call timer management to check and run timer callbacks
rte_timer_manage();
}
return 0;
}
|
arp.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
#include "arp.h"
#define ARP_TABLE_NAME "arp_table"
#define MAX_ARP_ENTRIES 1024
extern struct rte_mempool *mempool;
extern struct rte_ring *send_ring;
extern struct rte_ring *recv_ring;
struct rte_hash *arp_hash;
extern struct rte_ether_addr *local_mac;
extern uint32_t local_ip;
void handle_arp_reply(struct rte_arp_hdr *arp_hdr)
{
// Parse for IP and MAC
uint32_t ip = rte_be_to_cpu_32(arp_hdr->arp_data.arp_sip);
struct rte_ether_addr *mac = &arp_hdr->arp_data.arp_sha;
// Check if an ARP entry for this IP already exists in the hash table
void *existing_data = NULL;
int ret = rte_hash_lookup_data(arp_hash, &ip, &existing_data);
if (ret >= 0)
{
// Entry exists, free old value
rte_free(existing_data);
rte_hash_del_key(arp_hash, &ip);
}
// Copy the MAC address from the ARP reply into the new entry
struct arp_entry *entry = rte_malloc(NULL, sizeof(struct arp_entry), 0);
if (!entry)
{
printf("[ARP] Memory allocation failed for ARP entry\n");
return;
}
rte_ether_addr_copy(mac, &entry->mac);
// Add the new IP-to-MAC mapping into the hash table
ret = rte_hash_add_key_data(arp_hash, &ip, entry);
if (ret < 0)
{
printf("[ARP] Failed to insert ARP entry for IP: %08x\n", ip);
rte_free(entry);
}
else
{
struct in_addr ip_addr;
ip_addr.s_addr = rte_cpu_to_be_32(ip);
char mac_str[RTE_ETHER_ADDR_FMT_SIZE];
rte_ether_format_addr(mac_str, RTE_ETHER_ADDR_FMT_SIZE, mac);
printf("[ARP] ARP entry added: %s → %s\n", inet_ntoa(ip_addr), mac_str);
}
}
void handle_arp_request(struct rte_arp_hdr *arp_hdr, struct rte_ether_hdr *eth_hdr)
{
// Check if the target IP matches our local IP
uint32_t target_ip = rte_be_to_cpu_32(arp_hdr->arp_data.arp_tip);
if (target_ip != local_ip)
{
return;
}
// Allocate a new mbuf for the ARP reply
struct rte_mbuf *reply_mbuf = rte_pktmbuf_alloc(mempool);
if (!reply_mbuf)
{
printf("[ARP] Failed to allocate mbuf for ARP reply\n");
return;
}
// Append space for Ethernet + ARP header
uint16_t pkt_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr);
void *data_ptr = rte_pktmbuf_append(reply_mbuf, pkt_len);
if (!data_ptr)
{
printf("[ARP] Failed to append data to mbuf\n");
rte_pktmbuf_free(reply_mbuf);
return;
}
// Fill Ethernet header
struct rte_ether_hdr *reply_eth_hdr = rte_pktmbuf_mtod(reply_mbuf, struct rte_ether_hdr *);
rte_ether_addr_copy(ð_hdr->src_addr, &reply_eth_hdr->dst_addr);
rte_ether_addr_copy(local_mac, &reply_eth_hdr->src_addr);
reply_eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP);
// Fill ARP header
struct rte_arp_hdr *reply_arp = (struct rte_arp_hdr *)(reply_eth_hdr + 1);
reply_arp->arp_hardware = rte_cpu_to_be_16(RTE_ARP_HRD_ETHER);
reply_arp->arp_protocol = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
reply_arp->arp_hlen = RTE_ETHER_ADDR_LEN;
reply_arp->arp_plen = sizeof(uint32_t);
reply_arp->arp_opcode = rte_cpu_to_be_16(RTE_ARP_OP_REPLY);
rte_ether_addr_copy(local_mac, &reply_arp->arp_data.arp_sha);
reply_arp->arp_data.arp_sip = rte_cpu_to_be_32(local_ip);
rte_ether_addr_copy(&arp_hdr->arp_data.arp_sha, &reply_arp->arp_data.arp_tha);
reply_arp->arp_data.arp_tip = arp_hdr->arp_data.arp_sip;
// Enqueue to TX ring for later sending
if (rte_ring_enqueue(send_ring, reply_mbuf) < 0)
{
printf("[ARP] Failed to enqueue ARP reply to tx_ring\n");
rte_pktmbuf_free(reply_mbuf);
}
else
{
struct in_addr ip_addr;
ip_addr.s_addr = rte_cpu_to_be_32(target_ip);
printf("[ARP] ARP reply enqueued for %s\n", inet_ntoa(ip_addr));
}
}
// Main handler of ARP packet
void handle_arp(struct rte_arp_hdr *arp_hdr, struct rte_ether_hdr *eth_hdr)
{
uint16_t opcode = rte_be_to_cpu_16(arp_hdr->arp_opcode);
if (opcode == RTE_ARP_OP_REQUEST)
{
// ARP Request received
handle_arp_request(arp_hdr, eth_hdr);
return;
}
if (opcode == RTE_ARP_OP_REPLY)
{
// ARP Reply received
handle_arp_reply(arp_hdr);
}
return;
}
void send_arp_request(uint32_t target_ip)
{
// Allocate a new mbuf for the ARP request
struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mempool);
if (mbuf == NULL)
{
printf("[ARP] Failed to allocate mbuf for ARP request\n");
return;
}
// Reserve space for Ethernet + ARP headers
char *pkt_data = rte_pktmbuf_append(mbuf, sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr));
if (pkt_data == NULL)
{
printf("[ARP] Failed to append space for ARP request\n");
rte_pktmbuf_free(mbuf);
return;
}
struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)pkt_data;
struct rte_arp_hdr *arp_hdr = (struct rte_arp_hdr *)(eth_hdr + 1);
// Ethernet header
struct rte_ether_addr broadcast_mac = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
rte_ether_addr_copy(&broadcast_mac, ð_hdr->dst_addr);
rte_ether_addr_copy(local_mac, ð_hdr->src_addr);
eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP);
// ARP header
arp_hdr->arp_hardware = rte_cpu_to_be_16(RTE_ARP_HRD_ETHER);
arp_hdr->arp_protocol = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
arp_hdr->arp_hlen = RTE_ETHER_ADDR_LEN;
arp_hdr->arp_plen = sizeof(uint32_t);
arp_hdr->arp_opcode = rte_cpu_to_be_16(RTE_ARP_OP_REQUEST);
// ARP data
rte_ether_addr_copy(local_mac, &arp_hdr->arp_data.arp_sha);
arp_hdr->arp_data.arp_sip = rte_cpu_to_be_32(local_ip);
struct rte_ether_addr zero_mac = {{0, 0, 0, 0, 0, 0}};
rte_ether_addr_copy(&zero_mac, &arp_hdr->arp_data.arp_tha);
arp_hdr->arp_data.arp_tip = rte_cpu_to_be_32(target_ip);
// Enqueue the packet to tx_ring
if (rte_ring_enqueue(send_ring, mbuf) < 0)
{
printf("[ARP] Failed to enqueue ARP request for IP %s\n", inet_ntoa(*(struct in_addr *)&arp_hdr->arp_data.arp_tip));
rte_pktmbuf_free(mbuf);
}
}
// Callback function for the periodic ARP timer
void handle_arp_timer_cb()
{
uint32_t subnet_mask = 0xFFFFFF00; // 255.255.255.0
uint32_t subnet_base_ip = local_ip & subnet_mask;
for (uint32_t offset = 1; offset < 255; offset++)
{
uint32_t target_ip = subnet_base_ip + offset;
if (target_ip == local_ip)
{
continue;
}
void *existing_data = NULL;
if (rte_hash_lookup_data(arp_hash, &target_ip, &existing_data) >= 0)
{
continue;
}
send_arp_request(target_ip);
}
}
// Initialize table for ARP information
void init_arp_table(void)
{
struct rte_hash_parameters hash_params = {
.name = ARP_TABLE_NAME,
.entries = MAX_ARP_ENTRIES,
.key_len = sizeof(uint32_t), // IP address
.hash_func = rte_jhash,
.hash_func_init_val = 0,
.socket_id = rte_socket_id(),
};
arp_hash = rte_hash_create(&hash_params);
if (arp_hash == NULL)
{
rte_exit(EXIT_FAILURE, "[PKT_PROCESS] Failed to create ARP hash table\n");
}
}
|
源码打包下载
源码打包下载:dpdk_arp_icmp.zip