查询ARP缓存
Lwip中查询ARP缓存的函数etharp_query声明如下:
err_t etharp_query(struct netif *netif,const ip4_addr_t *ipaddr,struct pbuf *q) |
参数ipaddr是需要进行ARP解析的IP地址,此函数的主要功能就是给发送一个关于此IP地址的ARP请求广播包,或一并发送数据包。下面结合函数前面的注释看看对于ipaddr不同情况的处理:
* If the IP address was not yet in the cache, a pending ARP cache entry * is added and an ARP request is sent for the given address. The packet * is queued on this entry. |
如果IP地址不在ARP缓存表中,一个新的ARP表项会被创建,并设置为pending状态,然后一个请求解析此IP地址对应MAC地址的ARP请求包会被广播出去。需要发送的数据包挂载在此ARP表项的q队列指针上。
* If the IP address was already pending in the cache, a new ARP request * is sent for the given address. The packet is queued on this entry. |
如果IP地址在ARP缓存表中,并且为pending状态,那么也会发送一个ARP请求广播包出去,并且挂载要发送的数据到缓存项的q队列指针上。
* If the IP address was already stable in the cache, and a packet is * given, it is directly sent and no ARP request is sent out. * * If the IP address was already stable in the cache, and no packet is * given, an ARP request is sent out. |
如果IP地址在ARP缓存表中,并且为stable状态,如果有数据包需要发送,则直接发送数据包,不需要发送ARP请求包。如果没有数据包要发送,则发送ARP请求包。
分析完此函数的参数ipaddr,下面来看下函数的主要实现代码:
/* non-unicast address? */ if (ip4_addr_isbroadcast(ipaddr,netif)|| ip4_addr_ismulticast(ipaddr)|| ip4_addr_isany(ipaddr)) { return ERR_ARG; } |
上述代码是对参数ipaddr进行合法性判断,如果ipaddr为广播地址或者多播地址,或者空的IP地址,那么直接返回ERR_ARG。
i =etharp_find_entry(ipaddr,ETHARP_FLAG_TRY_HARD,netif); if (i< 0) { if (q) { ETHARP_STATS_INC(etharp.memerr); } return (err_t)i; } /*找到一个匹配的表项或者新建一个表项*/ if (arp_table[i].state == ETHARP_STATE_EMPTY) { is_new_entry =1; arp_table[i].state = ETHARP_STATE_PENDING; arp_table[i].netif = netif; } |
首先调用etharp_find_entry函数查找ARP缓存表中是否有ipaddr对应的表项,注意参数flags设置为ETHARP_FLAG_TRY_HARD,意味着即使没有找到空闲的ARP表项,也会回收一个表项。返回值i表示ARP表项的索引值,如果i小于0,在当前这种情况下,应该只有一种可能就是ARP缓存表里面全是静态ARP表项。在没有找到可用的表项情况下,如果还有未发送的数据挂载在q队列上,增加etharp.memerr统计。
如果找到的ARP表项状态为ETHARP_STATE_EMPTY,说明是一个新建的ARP表项,变量is_new_entry置为1,ARP表项状态设置为ETHARP_STATE_PENDING。
if (is_new_entry|| (q ==NULL)) { result =etharp_request(netif,ipaddr); if (result!= ERR_OK) { /* ARP request couldn't be sent */ /* We don't re-send arp request in etharp_tmr, but we still queue packets, since this failure could be temporary, and the next packet calling etharp_query again could lead to sending the queued packets. */ } if (q ==NULL) { return result; } } |
上面这段代码是调用etharp_request函数发送一个ARP请求包。变量is_new_entry为1代表着是一个新建的ARP缓存表,当然需要发送一个关于ipaddr的ARP请求广播包出去。指针q等于NULL,意味着没有数据需要发送,因此前面发送了ARP请求之后就可以直接返回。
在etharp_request返回值不ok的情况下,不需要再发送ARP请求包,将要发送的数据挂载到q队列上面或者直接发送出去,具体看后面代码如何处理的。
下面的代码片段是处理在q不为NULL,即有数据需要发送的情况下的处理:
if (arp_table[i].state >= ETHARP_STATE_STABLE) { result =etharp_send_ip(netif,q,srcaddr,&(arp_table[i].ethaddr)); } else if (arp_table[i].state == ETHARP_STATE_PENDING) { p =q; while (p) { if (p->type !=PBUF_ROM) { copy_needed = 1; break; } p =p->next; }
if (copy_needed) { p =pbuf_alloc(PBUF_RAW_TX,p->tot_len, PBUF_RAM); if (p!= NULL) { if (pbuf_copy(p,q)!= ERR_OK) { pbuf_free(p); p =NULL; } } } else { p =q; pbuf_ref(p); } |
如果找到的ARP表项状态为stable,则直接调用etharp_send_ip函数将数据包发送出去。
对于状态为pending的表项需要将缓存数据发送到q队列上。Pbuf有四种类型,对于PBUF_REF、PBUF_POOL和PBUF_RAM这三种类型的pbuf数据包不能直接挂载在队列q上面,需要将数据拷贝到新申请的PBUF_RAM类型的pbuf中,然后将此新的pbuf挂载到q上。
while循环就是查找待发送数据pbuf是否有上述的三种类型中任意一个,找到的话就设置copy_needed为1,并break退出。对于一个数据包它可能使用pbuf任意四种类型,并将它们串在一起。
如果copy_needed为1,则调用pbuf_alloc函数申请一个类型为PBUF_RAM的pbuf p,并将q队列上的数据都拷贝到p上;否则只需要将p的引用计数加1即可。
if (p!= NULL) { /*有数据包需要挂载*/ #if ARP_QUEUEING struct etharp_q_entry*new_entry;
new_entry =(struct etharp_q_entry*)memp_malloc(MEMP_ARP_QUEUE); if (new_entry!= NULL) { new_entry->next = 0; new_entry->p = p; if (arp_table[i].q != NULL) { struct etharp_q_entry*r; r =arp_table[i].q; qlen++; while (r->next !=NULL) { r =r->next; qlen++; } r->next = new_entry; } else { arp_table[i].q = new_entry; } #if ARP_QUEUE_LEN if (qlen>= ARP_QUEUE_LEN) { struct etharp_q_entry*old; old =arp_table[i].q; arp_table[i].q = arp_table[i].q->next; pbuf_free(old->p); memp_free(MEMP_ARP_QUEUE,old); } #endif |
指针p不为NULL,说明有数据需要挂载到此ARP表项的队列q上。首先从内存池中申请一个MEMP_ARP_QUEUE类型的一片内存给new_entry,并将要发送的数据包p挂载到new_entry上。
如果此ARP表项上之前已挂载有未发送的数据,那么将此新的new_entry链入在链表的最后面;如果此ARP表项上之前没有挂载未发送的数据,那么直接将队列q指针指向new_entry即可。
如果挂载了新的new_entry之后,整个队列项的长度qlen超过ARP_QUEUE_LEN,则选择将q链表头的队列项移除,也就是丢掉最老的未发送的数据包。
ARP工作流程
接收到数据包时ARP的工作流程:
如果接收到的数据包是IP包,则首先会调用etharp_update_arp_entry函数更新ARP缓存表,然后将数据传送到IP层。
如果接收到的数据包是ARP包,则首先更新ARP缓存表,然后判断ARP包的类型。如果是ARP应答包,则不作任何处理,因为前面已经更新ARP表了;如果是ARP请求包,则判断是不是给自己的,如果是发给自己的ARP请求包,则回一个相应的ARP应答包,如果不是给自己的,则忽略就行。