在TCP/IP网络中,ARP协议始终是相当重要的一个环节,它主要是负责将32位的IP地址(IPv6则是128位)映射为48位的MAC地址(规定每一种网络通信设备具备唯一的MAC地址),从而可以在底层数据链路层中标记不同的网络通信设备。
ARP协议的本质
由于IP地址和MAC地址没有固定的映射规则,所以为了描述这种映射关系,在实现上LWIP采用ARP缓存表这种结构来存储这种映射关系。简单的说就是需要将一条条的(IP地址,MAC地址)存储起来,当发送IP数据包时可以根据该表进行查询相应主机的MAC地址。
在windows中的控制台中输入arp -a会得到这台主机的ARP缓存表:
可以看到局域网中的(IP地址,MAC地址)之间一一对应的关系。
LWIP中ARP缓存表数据结构
如果接触过虚拟存储器中的页表,那么ARP缓存表也一定很快就能理解。ARP缓存表由一个个缓存表项(entry)组成,这些表项的核心当然是刚刚提过的IP地址和MAC地址的映射关系。那么是不是光记录这个信息就足够了呢?我们先来看看LWIP中的ARP缓存表项的实现。
1、ARP缓存表的实现:
static struct etharp_entry arp_table[ARP_TABLE_SIZE];
即用了简单的数组来实现ARP缓存表。
2、etharp_entry(缓存表项)数据结构
struct etharp_entry {
#if ARP_QUEUEING
/** Pointer to queue of pending outgoing packets on this ARP entry. */
struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
/** Pointer to a single pending outgoing packet on this ARP entry. */
struct pbuf *q;
#endif /* ARP_QUEUEING */
ip_addr_t ipaddr;
struct netif *netif;
struct eth_addr ethaddr;
u8_t state;
u8_t ctime;
};
可以看到上述的结构中不仅有ipaddr(IP地址)和ethaddr(MAC地址),还有其他成员。
netif : 描述网络接口,主要是考虑到有些网络设备(如路由器)连接不同的网络,会有不通的(IP,MAC)地址映射对。
p:当上层应用需要发送IP数据包时,而现在ARP缓存表尚未建立该目的IP地址的表项,需要将待发送的IP地址组织起来,待建立好目的IP地址的表项再发送出去。
state:描述该表项的状态。主要是由于目前网络拓扑结构不固定,随时可能有设备加入或者离开本网络。
ctime:记录该表项的时间信息,主要是为更新state而记录时间。
3、etharp_q_entry(放置带发送数据包的链表)的数据结构如下:
struct etharp_q_entry {
struct etharp_q_entry *next;
struct pbuf *p;
};
即简单的将带发送的数据包用链表形式串起来,等建立起相应的ARP表项再发送出去。
4、etharp_state(表项的状态)的数据结构如下:
enum etharp_state {
ETHARP_STATE_EMPTY = 0,
ETHARP_STATE_PENDING,
ETHARP_STATE_STABLE,
ETHARP_STATE_STABLE_REREQUESTING
#if ETHARP_SUPPORT_STATIC_ENTRIES
,ETHARP_STATE_STATIC
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};
在ARP初始化的时候,每个缓存表项的state均为empty状态。所以设备在刚开始的时候会进行广播,获得本局域网中的设备地址对。在等待其他设备应答的时候,LWIP就会将表项状态设置为pending状态。当LWIP收到来自其他主机回应的地址对时,就会将这些地址对写入到各表项,同时更新这些表项的state状态为stable。
由于设备可以随时加入和离开网络,所以需要每隔一段时间进行一个update操作,目的就是维持ARP表项为目前最新的映射关系。以下为update的时间:
1、pending持续最长的时间为10s
2、stable持续的最长时间为20min
源码的操作如下:
#define ARP_MAXAGE 240 //240*5s = 20min
#define ARP_MAXPENDING 2 //2*5s = 10s
etharp_tmr(void)
{
u8_t i;
/* 遍历ARP表项 */
for (i = 0; i < ARP_TABLE_SIZE; ++i) {
u8_t state = arp_table[i].state;
if (state != ETHARP_STATE_EMPTY
#if ETHARP_SUPPORT_STATIC_ENTRIES
&& (state != ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
) {
arp_table[i].ctime++;
if ((arp_table[i].ctime >= ARP_MAXAGE) ||
((arp_table[i].state == ETHARP_STATE_PENDING) &&
(arp_table[i].ctime >= ARP_MAXPENDING))) {
/* 表项过期了,则将该表项进行删除*/
etharp_free_entry(i);
}
else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING) {
/* 这里我也不是很明白。。。 */
arp_table[i].state = ETHARP_STATE_STABLE;
}
}
}
}
其实上述代码原理并不难,就是依据每个表项中的ctime信息来对state状态进行更新。
接收数据包
LWIP中的处理流程如下:
由流程图可以知道,无论接收到的是IP数据包或者ARP数据包,均有可能对ARP缓存表进行更新。下面简单讲述一下ARP协议核心的几个函数:
/*arp数据包处理函数*/
etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
{
struct etharp_hdr *hdr;
struct eth_hdr *ethhdr;
ip_addr_t sipaddr, dipaddr;
u8_t for_us;
...
/* 判断接收到的ARP包是否是给我们的 */
if (ip_addr_isany(&netif->ip_addr)) { /* 若IP地址为NULL或者为全0,则不是给我们的,将for_us设为0 */
for_us = 0;
} else {
/*否则将目的IP和网卡IP进行匹配,相同时将for_us置1 */
for_us = (u8_t)ip_addr_cmp(&dipaddr, &(netif->ip_addr));
}
/*更新arp缓存表,若arp包给我们的则硬性更换ARP表项,否则采用尝试更换*/
etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);
/* 通过ARP请求类型做出相应的处理 */
switch (hdr->opcode) {
/* 是否是ARP请求? */
case PP_HTONS(ARP_REQUEST):
/* 判断是否是给我们的ARP报文 */
if (for_us) {/* 若是ARP请求,则需要我们主机应答 */
hdr->opcode = htons(ARP_REPLY);//将ARP报文类型改成ARP响应类型
/*构造ARP应答报文,将主机IP以及MAC地址写入到应答报文*/
IPADDR2_COPY(&hdr->dipaddr, &hdr->sipaddr);
IPADDR2_COPY(&hdr->sipaddr, &netif->ip_addr);
ETHADDR16_COPY(&hdr->dhwaddr, &hdr->shwaddr);
ETHADDR16_COPY(ðhdr->dest, &hdr->shwaddr);
ETHADDR16_COPY(&hdr->shwaddr, ethaddr);
ETHADDR16_COPY(ðhdr->src, ethaddr);
/* 通过low_level_input函数发送ARP应答报文 */
netif->linkoutput(netif, p);
} else if (ip_addr_isany(&netif->ip_addr)) {
/*若arp请求报文不是给我们的,且我们的IP地址尚未配置,则打印对应的调试信息*/
} else {
/*若arp请求报文不是给我们的,且我们的IP地址已经配置,则也打印对应的调试信息*/
}
break;
case PP_HTONS(ARP_REPLY):
/* 若是ARP应答报文,则不用再做什么工作了,因为之前就应经更新了ARP表*/
break;
default:
break;
}
/* 释放数据包 */
pbuf_free(p);
}
以上源代码均经过笔者修剪过,有兴趣的朋友可以直接下载源码进行研究,LWIP源码下载地址
今天先写到这里吧,等后面接着写ARP协议2