1、arp报文格式
《TCP-IP详解卷 1:协议》P40中的ARP请求/应答报文格式如下所示:
局域网链路层通信使用MAC地址,以交换机为例,连在一个交换机上的主机可以作为一个局域网,该网内的主机通过MAC地址通信;主机发数据到交换机时,交换机会记录该主机的MAC地址所在交换机的端口,其他主机发送报文到该主机时,交换机根据记录的MAC地址与端口的映射,找到对应MAC地址所在端口,然后从该端口把报文转发出去(转发到目的主机)。
以太网目的地址、以太网源地址即为目的主机、源主机的MAC地址;帧类型表示该报文的类型(ARP报文: 0x0806,IP报文: 0x0800);op表示操作类型(对于ARP报文,ARP请求: 1, ARP应答: 2)。
2、arp报文输入
2.1、lwip以太网报文头部数据结构
以太网报文头部数据结构eth_hdr如下(包含以太网目的地址dest、以太网源地址src、帧类型type):
struct eth_hdr {
#if ETH_PAD_SIZE
PACK_STRUCT_FIELD(u8_t padding[ETH_PAD_SIZE]);
#endif
PACK_STRUCT_FIELD(struct eth_addr dest); // 以太网目的地址
PACK_STRUCT_FIELD(struct eth_addr src); // 以太网源地址
PACK_STRUCT_FIELD(u16_t type); // 帧类型
} PACK_STRUCT_STRUCT;
2.2、帧类型定义
#define ETHTYPE_ARP 0x0806 // ARP帧类型
#define ETHTYPE_IP 0x0800
#define ETHTYPE_PPPOEDISC 0x8863 /* PPP Over Ethernet Discovery Stage */
#define ETHTYPE_PPPOE 0x8864 /* PPP Over Ethernet Session Stage */
ARP帧类型为0x0806。
2.3、报文输入
lwip ethernetif_input线程通过WinPcap从网卡读取报文。
获取报文头部(ethhdr指向报文起始地址)。
/* points to packet payload, which starts with an Ethernet header */
ethhdr = p->payload;
根据帧类型字段(ethhdr->type)判断帧类型。
switch (htons(ethhdr->type)) {
/* IP or ARP packet? */
case ETHTYPE_IP:
/* update ARP table */
etharp_ip_input(netif, p);
/* skip Ethernet header */
//pbuf_header(p, -(14+ETH_PAD_SIZE));
/* pass to network layer */
netif->input(p, netif);
break;
case ETHTYPE_ARP: // ARP报文
etharp_arp_input(netif, ethernetif->ethaddr, p);
break;
ETHTYPE_ARP(0x0806)即为ARP帧;调用etharp_arp_input处理ARP报文。
3、arp报文解析
3.1、arp帧数据结构
etharp_hdr即为ARP请求/应答报文(与第1章的图一致)。
struct etharp_hdr {
PACK_STRUCT_FIELD(struct eth_hdr ethhdr);
PACK_STRUCT_FIELD(u16_t hwtype);
PACK_STRUCT_FIELD(u16_t proto);
PACK_STRUCT_FIELD(u16_t _hwlen_protolen);
PACK_STRUCT_FIELD(u16_t opcode);
PACK_STRUCT_FIELD(struct eth_addr shwaddr);
PACK_STRUCT_FIELD(struct ip_addr2 sipaddr);
PACK_STRUCT_FIELD(struct eth_addr dhwaddr);
PACK_STRUCT_FIELD(struct ip_addr2 dipaddr);
} PACK_STRUCT_STRUCT;
3.2、arp报文输入处理(etharp_arp_input)
3.2.1、arp报文检查
获取ARP请求/应答报文(etharp_hdr指向报文起始地址)。
hdr = p->payload;
报文检查(报文硬件类型、硬件地址长、协议类型是否正确),如果检查错误,则丢弃报文返回。
/* RFC 826 "Packet Reception": */
if ((hdr->hwtype != htons(HWTYPE_ETHERNET)) ||
(hdr->_hwlen_protolen != htons((ETHARP_HWADDR_LEN << 8) | sizeof(struct ip_addr))) ||
(hdr->proto != htons(ETHTYPE_IP)) ||
(hdr->ethhdr.type != htons(ETHTYPE_ARP))) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | 1,
("etharp_arp_input: packet dropped, wrong hw type, hwlen, proto, protolen or ethernet type (%"U16_F"/%"U16_F"/%"U16_F"/%"U16_F"/%"U16_F")\n",
hdr->hwtype, ARPH_HWLEN(hdr), hdr->proto, ARPH_PROTOLEN(hdr), hdr->ethhdr.type));
ETHARP_STATS_INC(etharp.proterr); // 错误协议帧计数器加1
ETHARP_STATS_INC(etharp.drop); // 丢弃报文计数器加1
pbuf_free(p); // 释放报文内存空间
return; // 直接返回
}
收到的ARP报文计数器加1。
ETHARP_STATS_INC(etharp.recv);
检查目的ip地址是否是本机(检查本机是否配置ip地址以及报文目的ip地址是否为本机ip地址),如果是发给本机的设置for_us为1,否则设置for_us为0。
/* this interface is not configured? */
if (netif->ip_addr.addr == 0) { // 网卡没有配置ip地址,不接收报文
for_us = 0; // for_us设置为0,标记不是发给自己的
} else {
/* ARP packet directed to us? */
for_us = ip_addr_cmp(&dipaddr, &(netif->ip_addr)); // 比较目的ip地址是否为本机地址(相等返回1,不相等返回0)
}
3.3、arp缓存更新
发送给自己的ARP报文,调用update_arp_entry更新arp缓存表,缓存/更新对端主机的mac、ip地址。(ETHARP_TRY_HARD标记硬性更新,报文是发送给自己的,报文比较可靠,如果不存在缓存,则插入到arp缓存(没有可用条目时,需要淘汰旧的arp条目))
if (for_us) {
/* add IP address in ARP cache; assume requester wants to talk to us.
* can result in directly sending the queued packets for this host. */
update_arp_entry(netif, &sipaddr, &(hdr->shwaddr), ETHARP_TRY_HARD);
/* ARP message not directed to us? */
} else {
不是发给自己的报文,调用update_arp_entry更新arp缓存表。(如果arp缓存表中存在对端ip地址的缓存项,那么就更新对于的arp条目,报文不是发给自己的,报文不太可靠,如果不存在缓存并且没有空的缓存条目可用,则不插入arp缓存)
} else {
/* update the source IP address in the cache, if present */
update_arp_entry(netif, &sipaddr, &(hdr->shwaddr), 0);
}
3.4、arp请求/应答处理
根据arp op字段hdr->opcode判断ARP报文的类型(请求/应答)。
3.4.1、处理arp请求
"if (for_us)"仅处理发送给自己的arp请求,非发给自己的arp请求不处理;构建ARP应答报文,发送给对端主机(请求ARP的主机),arp报文中已有对端目的主机的以太网地址及ip地址,arp应答报文与arp请求报文格式一致,仅op不同而已。
case ARP_REQUEST:
/* ARP request. If it asked for our address, we send out a
* reply. In any case, we time-stamp any existing ARP entry,
* and possiby send out an IP packet that was queued on it. */
LWIP_DEBUGF (ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: incoming ARP request\n"));
/* ARP request for our address? */
if (for_us) { // 发送给自己的arp请求(构造应答报文,发送ARP应答给远端主机(发送ARP请求的主机))
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: replying to ARP request for our IP address\n"));
/* Re-use pbuf to send ARP reply.
Since we are re-using an existing pbuf, we can't call etharp_raw since
that would allocate a new pbuf. */
hdr->opcode = htons(ARP_REPLY); // 在原报文基础上设置op为ARP应答ARP_REPLY
hdr->dipaddr = hdr->sipaddr; // 目的地址替换为原报文的源地址
SMEMCPY(&hdr->sipaddr, &netif->ip_addr, sizeof(hdr->sipaddr)); // 使用本机ip地址填充报文的源ip地址
LWIP_ASSERT("netif->hwaddr_len must be the same as ETHARP_HWADDR_LEN for etharp!",
(netif->hwaddr_len == ETHARP_HWADDR_LEN));
i = ETHARP_HWADDR_LEN;
#if LWIP_AUTOIP
/* If we are using Link-Local, ARP packets must be broadcast on the
* link layer. (See RFC3927 Section 2.5) */
ethdst_hwaddr = ((netif->autoip != NULL) && (netif->autoip->state != AUTOIP_STATE_OFF)) ? (u8_t*)(ethbroadcast.addr) : hdr->shwaddr.addr;
#endif /* LWIP_AUTOIP */
while(i > 0) { // 使用报文中的以太网源地址填充应答报文的以太网目的地址,是本机的MAC地址填充应答报文的以太网源地址
i--;
hdr->dhwaddr.addr[i] = hdr->shwaddr.addr[i];
#if LWIP_AUTOIP
hdr->ethhdr.dest.addr[i] = ethdst_hwaddr[i];
#else /* LWIP_AUTOIP */
hdr->ethhdr.dest.addr[i] = hdr->shwaddr.addr[i];
#endif /* LWIP_AUTOIP */
hdr->shwaddr.addr[i] = ethaddr->addr[i];
hdr->ethhdr.src.addr[i] = ethaddr->addr[i];
}
/* hwtype, hwaddr_len, proto, protolen and the type in the ethernet header
are already correct, we tested that before */
/* return ARP reply */
netif->linkoutput(netif, p); // 链路层报文输出(通过网卡将应答报文发送出去)
/* we are not configured? */
} else if (netif->ip_addr.addr == 0) {
/* { for_us == 0 and netif->ip_addr.addr == 0 } */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: we are unconfigured, ARP request ignored.\n"));
/* request was not directed to us */
} else {
/* { for_us == 0 and netif->ip_addr.addr != 0 } */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: ARP request was not for us.\n"));
}
break;
3.4.2、处理arp应答
arp应答报文仅用于获取MAC地址,实际在缓存/更新arp缓存时已经处理。因此如下代码对arp应答没有做任何操作。
case ARP_REPLY: // arp应答报文处理
/* ARP reply. We already updated the ARP cache earlier. */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: incoming ARP reply\n"));
#if (LWIP_DHCP && DHCP_DOES_ARP_CHECK)
/* DHCP wants to know about ARP replies from any host with an
* IP address also offered to us by the DHCP server. We do not
* want to take a duplicate IP address on a single network.
* @todo How should we handle redundant (fail-over) interfaces? */
dhcp_arp_reply(netif, &sipaddr);
#endif
break;
4、arp缓存更新(find_entry)
4.1、arp缓存表条目结构体
arp缓存表每一个条目为一个ip地址的缓存,etharp_entry结构体如下:
struct etharp_entry {
#if ARP_QUEUEING
/**
* Pointer to queue of pending outgoing packets on this ARP entry.
*/
struct etharp_q_entry *q;
#endif
struct ip_addr ipaddr;
struct eth_addr ethaddr;
enum etharp_state state;
u8_t ctime;
struct netif *netif;
};
ipaddr为ip地址,ethaddr为以太网地址(mac地址)、state为该条目的状态(ETHARP_STATE_EMPTY/ETHARP_STATE_PENDING/ETHARP_STATE_STABLE,标记该条目是否有效,ETHARP_STATE_STABLE表示可用,ETHARP_STATE_PENDING表示请求过程中)。
lwip的缓存表arp_table为一数组,每个元素为etharp_entry。
4.2、arp表的查找(find_entry)
遍历arp_table,遍历过程记录最久没更新的各状态的arp条目(索引及更新时间),如果找到了ip地址对应的条目则返回索引i,find_entry函数返回。
for (i = 0; i < ARP_TABLE_SIZE; ++i) { // ARP_TABLE_SIZE arp表的大小,逐个遍历arp表项
/* no empty entry found yet and now we do find one? */
if ((empty == ARP_TABLE_SIZE) && (arp_table[i].state == ETHARP_STATE_EMPTY)) { // arp条目为空
LWIP_DEBUGF(ETHARP_DEBUG, ("find_entry: found empty entry %"U16_F"\n", (u16_t)i));
/* remember first empty entry */
empty = i; // empty记录第一个空的arp条目(可用于插入新的arp缓存)
}
/* pending entry? */
else if (arp_table[i].state == ETHARP_STATE_PENDING) { // arp条目为PENDING状态,正在获取该ip地址对应的mac地址
/* if given, does IP address match IP address in ARP entry? */
if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) { // ip地址为该条目的ip地址
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: found matching pending entry %"U16_F"\n", (u16_t)i));
/* found exact IP address match, simply bail out */
#if LWIP_NETIF_HWADDRHINT
NETIF_SET_HINT(netif, i);
#else /* #if LWIP_NETIF_HWADDRHINT */
etharp_cached_entry = i;
#endif /* #if LWIP_NETIF_HWADDRHINT */
return i; // 返回i(ip地址在arp表中的索引)
#if ARP_QUEUEING
/* pending with queued packets? */
} else if (arp_table[i].q != NULL) { // ip地址不是arp条目的ip地址,但是该条目是PENDING状态,且q不为空(该条目正在被使用,有数据等待获取mac地址后发送)
if (arp_table[i].ctime >= age_queue) { // age_queue记录最久没有更新的条目,如果当前arp条目的更新时间大于等于age_queue,那么需要更新age_queue、old_queue
old_queue = i; // 更新old_queue为最久没有更新且有数据待发送的PENDING状态的arp条目
age_queue = arp_table[i].ctime; // 更新最久没有更新时间age_queue为ctime
}
#endif
/* pending without queued packets? */
} else { // PENDING状态的条目没有数据待发送(没有在使用)
if (arp_table[i].ctime >= age_pending) {
old_pending = i; // 记录没有使用的最久没有更新的PENDING状态的arp条目
age_pending = arp_table[i].ctime; // 记录没有使用最久没有更新的时间
}
}
}
/* stable entry? */
else if (arp_table[i].state == ETHARP_STATE_STABLE) { // STABLE状态的arp条目
/* if given, does IP address match IP address in ARP entry? */
if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) { // ip地址为该条目的ip地址
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: found matching stable entry %"U16_F"\n", (u16_t)i));
/* found exact IP address match, simply bail out */
#if LWIP_NETIF_HWADDRHINT
NETIF_SET_HINT(netif, i);
#else /* #if LWIP_NETIF_HWADDRHINT */
etharp_cached_entry = i;
#endif /* #if LWIP_NETIF_HWADDRHINT */
return i; // 返回找到的arp条目
/* remember entry with oldest stable entry in oldest, its age in maxtime */
} else if (arp_table[i].ctime >= age_stable) { // 记录STABLE状态最久没有更新的时间
old_stable = i;
age_stable = arp_table[i].ctime;
}
}
}
4.3、判断是否插入缓存(find_entry)
find_entry除了查找arp entry外,也包含了插入arp缓存的作用,如果非硬性插入缓存(不可靠的arp报文,没有设置ETHARP_TRY_HARD标志)且没有空闲的arp条目可用,那么返回内存错误(ERR_MEM),不执行插入操作。
/* no empty entry found and not allowed to recycle? */
if (((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_TRY_HARD) == 0))
/* or don't create new entry, only search? */
|| ((flags & ETHARP_FIND_ONLY) != 0)) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: no empty entry found and not allowed to recycle\n"));
return (s8_t)ERR_MEM;
}
4.4、找出可用的arp条目(find_entry)
如果遍历过程找到了可用的空闲arp条目(empty < ARP_TABLE_SIZE),那么使用arp_table[empty]缓存新的arp条目,否则依次使用最久没更新的stable、pending、pending(queue);
对于stable状态的arp条目,如果当前有数据需要发送(不包括tcp等待发送的数据,tcp可能因为没有获取到ACK以及拥塞控制等原因,部分数据缓存在协议栈里面,并不能马上发送),目的主机的mac已经有了,那么早就发送出去了,因此stable状态的arp条目可以优先淘汰给新的ip地址缓存,其次是pending状态queue为空的arp条目,pending状态表示arp请求正在进行中,可能将要被使用,queue为空表示没有数据要发送,因此也可以淘汰,再次是pending状态queue不为空的arp条目,实在没有可用arp条目,现在又要硬性插入新的arp条目,那么只能淘汰有数据待发送但是更新时间最久的arp条目(很久没有更新了,可能对端已经挂掉了),释放queue里面待发送的数据。
/* 1) empty entry available? */
if (empty < ARP_TABLE_SIZE) {
i = empty;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting empty entry %"U16_F"\n", (u16_t)i));
}
/* 2) found recyclable stable entry? */
else if (old_stable < ARP_TABLE_SIZE) {
/* recycle oldest stable*/
i = old_stable;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting oldest stable entry %"U16_F"\n", (u16_t)i));
#if ARP_QUEUEING
/* no queued packets should exist on stable entries */
LWIP_ASSERT("arp_table[i].q == NULL", arp_table[i].q == NULL);
#endif
/* 3) found recyclable pending entry without queued packets? */
} else if (old_pending < ARP_TABLE_SIZE) {
/* recycle oldest pending */
i = old_pending;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting oldest pending entry %"U16_F" (without queue)\n", (u16_t)i));
#if ARP_QUEUEING
/* 4) found recyclable pending entry with queued packets? */
} else if (old_queue < ARP_TABLE_SIZE) {
/* recycle oldest pending */
i = old_queue;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting oldest pending entry %"U16_F", freeing packet queue %p\n", (u16_t)i, (void *)(arp_table[i].q)));
free_etharp_q(arp_table[i].q);
arp_table[i].q = NULL;
#endif
/* no empty or recyclable entries found */
} else {
return (s8_t)ERR_MEM;
}
4.5、插入arp表(find_entry)
i指向空闲arp条目或者被淘汰的arp标目(此处只更新了ip地址和更新时间,状态仍为EMPTY,状态在update_arp_entry函数中更新为STABLE)。
/* recycle entry (no-op for an already empty entry) */
arp_table[i].state = ETHARP_STATE_EMPTY;
/* IP address given? */
if (ipaddr != NULL) {
/* set IP address */
ip_addr_set(&arp_table[i].ipaddr, ipaddr);
}
arp_table[i].ctime = 0;
5、arp缓存更新(update_arp_entry)
find_entry返回arp条目的索引,状态等在update_arp_entry中更新。
5.1、arp条目查找
调用find_entry找到ip地址对应的arp条目在arp表中的索引。
i = find_entry(ipaddr, flags); // 返回负数表示没有找到
5.2、更新arp条目状态、发送网卡及更新时间
更新arp条目状态为STABLE及网卡信息(目的ip地址的报文是从netif进入的,那么该网卡和目的主机应该是连通的,设置netif为收到报文的网卡,发送数据时从该网卡发送即可)。
/* mark it stable */
arp_table[i].state = ETHARP_STATE_STABLE;
/* record network interface */
arp_table[i].netif = netif;
更新arp条目的mac地址及更新时间。
/* update address */
k = ETHARP_HWADDR_LEN;
while (k > 0) {
k--;
arp_table[i].ethaddr.addr[k] = ethaddr->addr[k];
}
/* reset time stamp */
arp_table[i].ctime = 0;
5.3、发送待发送的数据
如果该arp条目有待发送的数据,则发送数据(数据发送前没有获取到目的主机的以太网地址,要发送的数据保存在arp条目的queue里面,获取到目的主机的以太网地址后,直接发送数据即可)。
/* this is where we will send out queued packets! */
while (arp_table[i].q != NULL) {
struct pbuf *p;
/* remember remainder of queue */
struct etharp_q_entry *q = arp_table[i].q;
/* pop first item off the queue */
arp_table[i].q = q->next;
/* get the packet pointer */
p = q->p;
/* now queue entry can be freed */
memp_free(MEMP_ARP_QUEUE, q);
/* send the queued IP packet */
etharp_send_ip(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr);
/* free the queued IP packet */
pbuf_free(p);
}
6、arp查询及请求(etharp_query)
网络层发送报文时,调用etharp_query获取目的主机的mac地址。
6.1、arp缓存查询
调用find_entry查询arp条目(如果没有,如果有空闲条目或者有可淘汰的条目,则插入一条新的缓存)。
i = find_entry(ipaddr, ETHARP_TRY_HARD);
6.2、缓存状态更新
对于新插入的条目,其状态为EMPTY,更新其状态为PENDING(稍后将发送arp请求,此处提前设置为PENDING状态)。
/* mark a fresh entry as pending (we just sent a request) */
if (arp_table[i].state == ETHARP_STATE_EMPTY) {
arp_table[i].state = ETHARP_STATE_PENDING;
}
6.3、发送arp请求
如果arp条目为PENDING状态或者此次需要发送的数据q为空,则发送arp请求(etharp_query仅为了获得目的主机的mac地址而已,没有数据需要发送;非PENDING状态不需要发送arp请求)。
/* do we have a pending entry? or an implicit query request? */
if ((arp_table[i].state == ETHARP_STATE_PENDING) || (q == NULL)) {
/* try to resolve it; send out ARP request */
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. */
}
}
6.4、数据发送
判断是否有数据需要发送。
if (q != NULL) {
如果arp条目的状态为STABLE(目的主机mac地址已经获取),调用etharp_send_ip发送报文即可。
if (arp_table[i].state == ETHARP_STATE_STABLE) {
/* we have a valid IP->Ethernet address mapping */
/* send the packet */
result = etharp_send_ip(netif, q, srcaddr, &(arp_table[i].ethaddr));
/* pending entry? (either just created or already pending */
} else if (arp_table[i].state == ETHARP_STATE_PENDING) {
如果arp条目的状态为PENDING(前面已经发送过arp请求了),拷贝待发送数据到一个新的内存空间,追加待发送数据到arp条目待发送队列q里面。
} else if (arp_table[i].state == ETHARP_STATE_PENDING) {
#if ARP_QUEUEING /* queue the given q packet */
struct pbuf *p;
int copy_needed = 0;
/* IF q includes a PBUF_REF, PBUF_POOL or PBUF_RAM, we have no choice but
* to copy the whole queue into a new PBUF_RAM (see bug #11400)
* PBUF_ROMs can be left as they are, since ROM must not get changed. */
p = q;
while (p) { // 检查数据是否需要拷贝
LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == 0));
if(p->type != PBUF_ROM) { // 非ROM只读数据需要拷贝
copy_needed = 1; // 拷贝标志
break;
}
p = p->next;
}
if(copy_needed) {
/* copy the whole packet into new pbufs */
p = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
if(p != NULL) {
if (pbuf_copy(p, q) != ERR_OK) { // 待发送数据q拷贝到p里面
pbuf_free(p);
p = NULL;
}
}
} else {
/* referencing the old pbuf is enough */
p = q; // 只读数据,p直接指向q
pbuf_ref(p); // 引用计数器加1
}
/* packet could be taken over? */
if (p != NULL) { // 有待发送数据
/* queue packet ... */
struct etharp_q_entry *new_entry;
/* allocate a new arp queue entry */
new_entry = memp_malloc(MEMP_ARP_QUEUE);
if (new_entry != NULL) {
new_entry->next = 0;
new_entry->p = p; // etharp_q_entry数据指向p
if(arp_table[i].q != NULL) { // arp条目之前就有待发送数据,etharp_q_entry追加的待发送q的末尾即可
/* queue was already existent, append the new entry to the end */
struct etharp_q_entry *r;
r = arp_table[i].q;
while (r->next != NULL) {
r = r->next;
}
r->next = new_entry;
} else {
/* queue did not exist, first item in queue */
arp_table[i].q = new_entry; // 之前没待发送数据,将q指向当前待发送数据即可
}
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"S16_F"\n", (void *)q, (s16_t)i));
result = ERR_OK;
} else {
/* the pool MEMP_ARP_QUEUE is empty */ // 申请不到MEMP_ARP_QUEUE,没办法缓存待发送数据,直接丢弃
pbuf_free(p); // 释放待发送的数据(由上层协议负责重发)
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not queue a copy of PBUF_REF packet %p (out of memory)\n", (void *)q));
/* { result == ERR_MEM } through initialization */
}
} else {
ETHARP_STATS_INC(etharp.memerr);
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not queue a copy of PBUF_REF packet %p (out of memory)\n", (void *)q));
/* { result == ERR_MEM } through initialization */
}
#else /* ARP_QUEUEING == 0 */
/* q && state == PENDING && ARP_QUEUEING == 0 => result = ERR_MEM */
/* { result == ERR_MEM } through initialization */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: Ethernet destination address unknown, queueing disabled, packet %p dropped\n", (void *)q));
#endif
}
7、arp条目状态转移(entry)
arp_table数目有限,不能存储太多的arp条目(entry),lwip由定时器etharp_tmr对arp_table表中的各entry进行计时,释放长时间没有更新的entry(pending状态长时间没有更新,则可能目的主机不可达,获取不到目的主机的mac地址;stable状态长时间没更新,则目的主机与本机之间长时间没有通信,如果有收到目的主机的arp/ip报文,那么该条目的更新时间ctime会被更新,没有数据来往才可能导致ctime超时)。状态转移如下:
empty状态(初始化状态),插入一条新的条目并发送arp请求时,转换为pending状态(正在等待获取目的主机的以太网地址(等待arp应答或者其他报文中有目的主机的以太网地址));
pending状态,收到arp应答或者其他报文中有目的主机的以太网地址,调用update_arp_entry更新缓存的状态及更新时间ctime;pending状态超时或者被回收,转换为empty状态;
stable状态,长时间没有更新,转换为empty状态;有报文输入,则更新ctime。
ctime更新主要是在接收到报文时会更新为0,etharp_tmr则对ctime进行计时并判断是否超时。