在前面实现的arp协议中,我们只能被动回复对方发来的arp请求。在网络通讯中,一个设备应该有一张自己的arp表,同时可以广播自己的arp。所以现在需要实现一个arp表,同时可以广播自己的arp。

工作模式

PC 1 只知道PC3 的IP地址是10.1.1.3, 但是不知道PC3的MAC, 现在想获取PC3 的MAC地址:

  1. 发送者PC1:PC 1 会发送一个广播帧, 源IP和源MAC是PC1, 目的IP是PC3,目的MAC为FF-FF-FF-FF-FF-FF, 这个帧是广播发送的, 该网络内所有主机都会接收到,这个报文的载荷内容是一个ARP请求报文,意思是我的MAC地址是MAC1,我想给10.1.1.3发送数据, 谁是10.1.1.3, 请回复一下我。

  2. 非目的主机PC2:由于这个广播帧网络内主机都可以收到,PC2 收到这个帧, 提取IP地址, 发现, 它要找的IP地址是10.1.1.3, 我的IP地址是10.1.1.2, 这个数据包不是发给我的, 丢弃, 不回复。

  3. 目的主机PC3:PC 3 收到这个广播数据帧后,拆开数据帧, 提取IP地址, 发现和我本地IP地址是一样的, 这个数据包就是发给我的, 然后再拆, 提取报文后, 了解到对方要请求我自己的MAC地址,然后就打包一个ARP的应答报文,源IP和源MAC都是自己的,目的IP和目的MAC是对方的,单播的发送给接收者,这样PC1 就有了PC3的MAC地址。

数据结构

在Linux内核中有两种数据结构被频繁使用;一个是rb_tree​,还有一个是list​。也就是红黑树和链表,机器人里的arp不算多,使用链表就足够了。暂且使用一个头插list​作为arp表。

/*
 * 版权所有: Copyright (c) 2024-2025  XXX Company. All rights reserved.
 * 系统名称: Ubuntu20.04.6
 * 文件名称: arp.h
 * 内容摘要: 单向链表实现的arp表
 * 作     者: 黄彦杰 Lenn
 * 设计日期: 2024-07-22
*/

#ifndef __ARP_H__
#define __ARP_H__

#define ARP_ENTRY_STATUS_DYNAMIC 0
#define ARP_ENTRY_STATUS_STATIC 1

#define LL_ADD(item, list) do {                    \
    item->prev = NULL;                          \
    item->next = list;                          \
    if (list != NULL) list->prev = item;        \
    list = item;                                \
}while(0)

#define LL_REMOVE(item, list) do {                                    \
    if (item->prev != NULL) item->prev->next = item->next;          \
    if (item->next != NULL) item->next->prev = item->prev;          \
    if (list == item) list = item->next;                            \
    item->prev = item->next = NULL;                                 \
}while(0)

struct arp_entry {

    uint32_t ip;
    uint8_t hwaddr[RTE_ETHER_ADDR_LEN];

    uint8_t type;

    struct arp_entry* prev;
    struct arp_entry* next;
};

struct arp_table {

    struct arp_entry* entries;
    int count;
};

static struct arp_table* arpt = NULL;


/*
 * 函数名称: get_arp_instace
 * 作     者: 黄彦杰 Lenn
 * 设计日期: 2024-07-22
 * 功能描述: 单例模式,获取arp_table的单例
 * 返 回 值: arp_table句柄
*/
struct arp_table* get_arp_instace() {

    if (arpt == NULL) {

        arpt = (struct arp_table*)rte_malloc("arp table", sizeof(struct arp_table), 0);
        if (arpt == NULL) {

            rte_exit(EXIT_FAILURE, "Malloc arp table failed");
        }

        memset(arpt, 0, sizeof(struct arp_table));
    }

    return arpt;
}


/*
 * 函数名称: get_mac_from_arp
 * 作     者: 黄彦杰 Lenn
 * 设计日期: 2024-07-22

/*
 * 函数名称: 
 * 作     者: 黄彦杰 Lenn
 * 设计日期: 2024-07-22
 * 功能描述: 根据ip查找arp表
 * 返 回 值: ip对应的mac地址
*/
 * 功能描述: 
 * 返 回 值: 
*/
uint8_t* get_mac_from_arp(uint32_t ip) {

    struct arp_table* ins = get_arp_instace();
    struct arp_entry* ite = NULL;

    for(ite = ins->entries; ite != NULL; ite = ite->next) {

        if (ite->ip == ip)
            return ite->hwaddr;
    }

    return NULL;
}

#endif
  • arp表采用单例模式,使用的时候获取arp表头的句柄,再进行arp表的增删查改。

  • LL_ADD​和LL_REMOVE​使用宏实现头插的链表,这个链表后面还有用,可以复用。

新增函数

  • rte_timer_subsystem_init​:用于初始化定时器子系统。

  • rte_get_timer_hz​:获取系统时钟频率,即每秒的时钟周期数。

  • rte_lcore_id​:返回当前线程/核心的逻辑核 ID。

  • rte_timer_reset(struct rte_timer* tim, uint64_t ticks, enum rte_timer_type type, unsigned int tim_lcore, rte_timer_cb_t fct, void* arg)

  • tim:指向要重置的定时器结构体的指针。

  • ticks:定时器的触发时间,以系统时钟周期为单位。当系统经过指定数量的周期后,定时器将被触发。

  • type:定时器类型。可以是单次触发 (RTE_TIMER_SINGLE​) 或循环触发 (RTE_TIMER_PERIODICAL​)。

  • tim_lcore:定时器关联的逻辑核心 ID。在指定的逻辑核心上触发定时器回调函数。

  • fct:定时器回调函数指针。当定时器触发后,将会执行此回调函数。

  • arg:用户自定义参数,作为回调函数的参数传入。

  • rte_rdtsc​:用于获取当前处理器的时间戳计数器 (TSC) 值。

定时器和回调函数设计

/*
 * 函数名称: arp_request_timer_cb
 * 作     者: 黄彦杰 Lenn
 * 设计日期: 2024-07-22
 * 功能描述: 定时器回调函数,广播arp
 * 返 回 值: None
*/
static void arp_request_timer_cb(__attribute__((unused)) struct rte_timer* t, void* arg) {

    struct rte_mempool* mbuf_pool = (struct rte_mempool*)arg;

    int i = 0;
    for (i = 0; i < 255; i++) {

        uint32_t dstip = (nLocalIp & 0x00FFFFFF) | (0xFF000000 & (i << 24));

        struct in_addr addr;
        addr.s_addr = dstip;
        printf("<---- arp dst : %s\n", inet_ntoa(addr));

        struct rte_mbuf* arpbuf = NULL;
        uint8_t* dstmac = get_mac_from_arp(dstip);

        if (dstip == NULL) {

            arpbuf = ln_arp_send(mbuf_pool, RTE_ARP_OP_REQUEST, nDefaultArpMac, nLocalIp, dstip);
        }
        else {

            arpbuf = ln_arp_send(mbuf_pool, RTE_ARP_OP_REQUEST, dstmac, nLocalIp, dstip);
        }

        rte_eth_tx_burst(nDevPortId, 0, &arpbuf, 1);
        rte_pktmbuf_free(arpbuf);
    }
}



rte_timer_subsystem_init();

struct rte_timer arp_timer;
rte_timer_init(&arp_timer);

uint64_t hz = rte_get_timer_hz();
unsigned lcore_id = rte_lcore_id();
rte_timer_reset(&arp_timer, hz, PERIODICAL, lcore_id, arp_request_timer_cb, mbuf_pool);



static uint64_t prev_tsc = 0, cur_tsc;
uint64_t dirr_tsc;

cur_tsc = rte_rdtsc();
diff_tsc = cur_tsc - prev_tsc;
if(diff_tsc > TIMER_RESOLUTION_CYCLES) {

    rte_timer_manage();
    prev_tsc = cur_tsc;
}

arp相关修改

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240722153021.png

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240722153129.png

结果演示

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240722162315.png

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240722162328.png

项目地址

项目地址