dpdk-完整的arp功能

目标

arp的相关功能分为:

  1. 定时发送arp请求(广播)
  2. 收到对方应答
  3. 记录在arp表中,等待查表时使用
  4. 应答其他人发来的arp请求

在前面实现了“4.应答其他人发来的arp请求”,本节来实现“2”和“3”.

思路与实现

维护arp表(“待办2”和“待办3”)

arp表使用双向链表实现,在头文件arp_table.h中,实现:

  1. 结构体:arp表(链表)、arp表项(链表节点)
  2. 宏:链表的插入、删除
  3. arp表的单例
#ifndef __NG_ARP_H__
#define __NG_ARP_H__

#include <rte_ether.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 *next;
    struct arp_entry *prev;

};

struct arp_table {

    struct arp_entry *entries;
    int count;

};



static struct  arp_table *arpt = NULL;

static struct  arp_table *arp_table_instance(void) {

    if (arpt == NULL) {

        arpt = rte_malloc("arp table", sizeof(struct  arp_table), 0);
        if (arpt == NULL) {
            rte_exit(EXIT_FAILURE, "rte_malloc arp表失败\n");
        }
        memset(arpt, 0, sizeof(struct  arp_table));
    }

    return arpt;

}


static uint8_t* jr_get_dst_macaddr(uint32_t dip) {

    struct arp_entry *iter;
    struct arp_table *table = arp_table_instance();

    for (iter = table->entries;iter != NULL;iter = iter->next) {
        if (dip == iter->ip) {
            return iter->hwaddr;
        }
    }

    return NULL;
}


#endif

在main函数中,实现:

用结构体rte_arp_hdr中的arp_opcode字段判断是请求还是应答报文(注意使用rte_cpu_to_be_16,功能是将一个16位的整数从主机字节序(CPU本地字节序)转换为大端字节序(Big-Endian))。

对于别人发的请求报文,前面已经实现了应答。对于别人的应答报文,要做的是解析并放入arp表

#ifdef ENABLE_ARP

            if (ehdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {

                // 解析这个arp报文并回复
                struct rte_arp_hdr *ahdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_arp_hdr *, sizeof(struct rte_ether_hdr));

                // 调试
                struct in_addr addr_source, addr_target;
                addr_source.s_addr = ahdr->arp_data.arp_sip;
                printf("[recv][arp协议] src: %s ", inet_ntoa(addr_source));
                addr_target.s_addr = ahdr->arp_data.arp_tip;
                printf("target:%s\n",inet_ntoa(addr_target));

                if (ahdr->arp_data.arp_tip == gLocalIp) { // 如果arp问的就是自己的p地址

                    if (ahdr->arp_opcode == rte_cpu_to_be_16(RTE_ARP_OP_REQUEST)) {
                        printf("\t[是向本机的请求报文]\n");
                        struct rte_mbuf * arpbuf = jr_arp_send(mbuf_pool, RTE_ARP_OP_REPLY, ahdr->arp_data.arp_sha.addr_bytes, ahdr->arp_data.arp_tip, ahdr->arp_data.arp_sip);
                        rte_eth_tx_burst(gDpdkPortId, 0, &arpbuf, 1);// 真正的发送操作,第四个参数:发一个包,第二个参数:使用0号队列发送
                        rte_pktmbuf_free(arpbuf);
                        rte_pktmbuf_free(mbufs[i]);
                    }
                    else if (ahdr->arp_opcode == rte_cpu_to_be_16(RTE_ARP_OP_REPLY)) {
                        printf("\t[是给本机的应答报文]\n");
                        struct arp_table *table = arp_table_instance();
                        uint8_t *hardware_addr = jr_get_dst_macaddr(ahdr->arp_data.arp_sip);
                        if (hardware_addr == NULL){
                            // 解析mac地址,如果没存过,则存到arp表中

                            struct arp_entry *entry = rte_malloc("arp entry", sizeof(struct arp_entry), 0);

                            if (entry) { //注意此处若失败就失败了,不用终止程序
                                // 在entry中填上内容并加到链表中
                                entry->ip = ahdr->arp_data.arp_sip;
                                rte_memcpy(entry->hwaddr, ahdr->arp_data.arp_sha.addr_bytes, RTE_ETHER_ADDR_LEN);
                                entry->type = ARP_ENTRY_STATUS_DYNAMIC;

                                LL_ADD(entry, table->entries);
                                table->count++;
                            }
                        }

#if ENABLE_DEBUG
                        // 打印arp表
                        struct arp_entry *iter;
                        for (iter = table->entries;iter != NULL;iter = iter->next) {
                            // mac
                            jr_print_ether_addr("", (const struct rte_ether_addr *)iter->hwaddr);
                            // ip
                            struct in_addr addr;
                            addr.s_addr = iter->ip;
                            printf("\t\t%s", inet_ntoa(addr));                          
                            // type
                            printf("\t\t%s\n", iter->type == ARP_ENTRY_STATUS_DYNAMIC ? "dynamic" : "static");
                        }
#endif
                    }
                }
                continue;
            }
#endif

定时发送(“待办1”)

定时器的初始化与使用

example/timer.c是我们的例子。

首先把该文件中回调函数的声明复制过来(static void timer0_cb(__attribute__((unused)) struct rte_timer *tim, __attribute__((unused)) void *arg))稍后进行填充内容。

然后复制example/timer.c中的初始化部分,到我们的arp.c的main函数中。

#if ENABLE_TIMER

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

#endif

在main函数中while(1)的末尾,添加:(也是从example/timer.c中复制来的)

#if ENABLE_TIMER

        static uint64_t prev_tsc = 0, cur_tsc;
        uint64_t diff_tsc;

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

#endif
填充定时器的功能

在这个函数中,要做的是组织发送的包。请注意,以太网头有个mac地址, arp头也有个mac地址。实验发现,如果不知对方的mac,那么ethdr中都是F,而arp中都是0(千万不要反了,不然只能收到物理机的,而收不到其他网卡的)

#if ENABLE_TIMER
static void arp_request_timer_cb(__attribute__((unused)) struct rte_timer *tim, void *arg) {
        struct rte_mempool *mbuf_pool = (struct rte_mempool *)arg;
        for (int i=0; i<=254; i++) {
            uint32_t dstip = (gLocalIp & 0x00FFFFFF) | (0xFF000000 & (i << 24));
            struct in_addr addr;
            addr.s_addr = dstip;
            printf("arp ---> src: %s \n", inet_ntoa(addr));

            struct rte_mbuf *arpbuf = NULL;
            uint8_t *dstmac = jr_get_dst_macaddr(dstip);
            if (dstmac == NULL) {
                arpbuf = jr_arp_send(mbuf_pool, RTE_ARP_OP_REQUEST, gDefaultArpMac, gLocalIp, dstip);
            } else {
                arpbuf = jr_arp_send(mbuf_pool, RTE_ARP_OP_REQUEST, dstmac, gLocalIp, dstip);
            }
            rte_eth_tx_burst(gDpdkPortId, 0, &arpbuf, 1);
            rte_pktmbuf_free(arpbuf);
        }
}

#endif

注意事项

  1. rte_cpu_to_be_16的使用
  2. 以太网头有个mac地址, arp头也有个mac地址。实验发现,如果不知对方的mac,那么ethdr中都是F,而arp中都是0

结果

arp ---> src: 192.168.43.0
arp ---> src: 192.168.43.1
……省略很多条……
arp ---> src: 192.168.43.253
arp ---> src: 192.168.43.254
[recv][arp协议] src: 192.168.43.130 target:192.168.43.110
    [是给本机的应答报文]
00:0C:29:14:56:E3       192.168.43.130      dynamic
[recv][arp协议] src: 192.168.43.140 target:192.168.43.110
    [是给本机的应答报文]
00:0C:29:14:56:E3       192.168.43.140      dynamic
00:0C:29:14:56:E3       192.168.43.130      dynamic
[recv][arp协议] src: 192.168.43.188 target:192.168.43.110
    [是给本机的应答报文]
3C:21:9C:3B:6A:1E       192.168.43.188      dynamic
00:0C:29:14:56:E3       192.168.43.140      dynamic
00:0C:29:14:56:E3       192.168.43.130      dynamic

完整代码

太长了,暂略。