目标
arp的相关功能分为:
- 定时发送arp请求(广播)
- 收到对方应答
- 记录在arp表中,等待查表时使用
- 应答其他人发来的arp请求
在前面实现了“4.应答其他人发来的arp请求”,本节来实现“2”和“3”.
思路与实现
维护arp表(“待办2”和“待办3”)
arp表使用双向链表实现,在头文件arp_table.h中,实现:
- 结构体:arp表(链表)、arp表项(链表节点)
- 宏:链表的插入、删除
- 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
注意事项
- rte_cpu_to_be_16的使用
- 以太网头有个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
完整代码
太长了,暂略。