ARP协议全称是Address Resolution Protocol,译作地址解析协议。它与底层的网络接口密切相关,为什么这么说?网络层来看,主机与主机之间通过IP地址来唯一标识,而底层硬件链路层看,是有自己的一套寻址机制,比如以太网中使用48位MAC地址标识不同的网络通信设备。那么当主机发送数据包时只知道IP地址,底层发送数据包时需要将IP地址转换为MAC地址才能发送,转换过程关键作用就是ARP协议。
ARP协议
为实现网络接口物理地址与IP地址的转换,ARP协议中引入ARP缓存表。ARP缓存表记录了一条条的<IP,MAC>对,他们时主机最近运行获取的关于周围其他主机的IP到物理地址的绑定。当需要发送IP数据包时,ARP层根据目的IP来查找ARP缓存表,将匹配的MAC地址装入以太网帧首部,最后发送以太网数据。
ARP功能实现和流程图如下:
从上面图看出,ARP协议的核心时对ARP缓存表的操作。实质时对缓存表的建立、更新、查询等操作。
数据结构
ARP缓存表
缓存表结构如下:
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 eth_addr ethaddr;
#if LWIP_SNMP
struct netif *netif;
#endif /* LWIP_SNMP */
u8_t state;
u8_t ctime;
#if ETHARP_SUPPORT_STATIC_ENTRIES
u8_t static_entry;
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};
etharp_q_entry是缓存表的数据包缓冲队列。
struct etharp_q_entry {
struct etharp_q_entry *next;
struct pbuf *p;
};
该结构用于挂载缓存表数据队列中数据包,数据结构如图所示:
当IP层发送一个数据包时,先在ARP表中查找与目的IP地址对应的表项,若找不到,则ARP会针对这个目的IP创建一个新的表项,然后发送一个ARP请求数据包,然后发送一个ARP请求数据包,请求具有IP地址对应的MAC地址。在得到目的主机的返回信息之前,是不能把数据包发送出去的,因为不知道目的主机的MAC地址。因此这里先将数据包暂时放在缓冲队列指针上,当接收到目的主机的ARP应答后,才将数据包发送出去。
state描述是缓存表项的状态,可能有如下状态:
enum etharp_state {
ETHARP_STATE_EMPTY = 0,
ETHARP_STATE_PENDING,
ETHARP_STATE_STABLE
};
LWIP内部通过数组方式创建缓存表,如下所示:
static struct etharp_entry arp_table[ARP_TABLE_SIZE];
初始化时,ARP处于empty状态,这种状态下可以被ARP模块使用,在需要添加新地址的情况下,ARP模块会顺序查找表,使用第一个处于empty状态的ARP表项。pending状态表示处于不稳定状态,此时该表只记录了IP地址,而没有记录MAC地址。当收到ARP应答后,提取MAC并更新ARP表,此时就处于stable状态。
为保证缓存表种各个表项的有效性,ARP模块设置一定的超时检查机制。在实际的物理链路中,然而连接不是一直保持有效的,即处于稳定状态下ARP表项不一定一直有效。例如:以太网环境中有两台计算机在同一局域网,当A发送请求,B做出了应答,那么A的缓存表中有B地址的稳定记录。但是当B故障或关机,A没有收到任何通知,当A再向B发送数据时,这些数据包都无法送达B的。
为解决这一问题,ARP协议会给每一个stable状态的表项设置一个定时器,当计时超时后,对应表项就会删除,通常设置为20分钟。
同样当pending状态下等待ARP应答时,也可能出现关机或故障,我们也不可能无休止等待下去,设置一个定时器,通常为10秒。
ctime表示每个表项的计数器。协议栈会周期性调用一个etharp_tmr函数,每5秒调用一次,函数中ctime会自增1,当ctime大于系统规定的某值时,系统会删除对应的表项。
void etharp_tmr(void)
{
u8_t i;
/* remove expired entries from the ARP table */
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
&& (arp_table[i].static_entry == 0)
#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))) {
/* pending or stable entry has become old! */
/* clean up entries that have just been expired */
free_entry(i);
}
}
}
}
etharp_tmr函数实现了定时功能,保证了ARP缓存大小有限的情况ixa,尽量提高其使用率,及时删除旧的不用的配对信息。
数据包
要获取目标主机的MAC地址,源主机与目的主机的交互是必要的。此时ARP报文就应运而生了。ARP报文组成结构如下:
报文中帧类型占两个字节,对ARP数据包,0x0806;对IP数据包,0x0800;PPPoE数据包,0x8864。这里需要注意的是大小端转换问题,网络中数据发送采用大端方式,开发板处理器有可能是小端存储模式。
硬件类型表示当前硬件接口类型。协议类型是当前映射的协议地址类型。硬件地址长度和协议地址长度分别指出了相应的地址长度,单位是字节。OP是ARP数据包的类型,1为ARP请求,2为ARP应答,3为RARP请求,4为RARP应答,3,4这里不讨论。后面4个字段分别是发送端以太网MAC地址、发送端IP地址、目的端以太网MAC地址、目的端IP地址。
PACK_STRUCT_BEGIN
struct eth_addr {
PACK_STRUCT_FIELD(u8_t addr[ETHARP_HWADDR_LEN]);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
PACK_STRUCT_BEGIN
/** Ethernet header */
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;
PACK_STRUCT_END
#define SIZEOF_ETHARP_HDR 28
#define SIZEOF_ETHARP_PACKET (SIZEOF_ETH_HDR + SIZEOF_ETHARP_HDR)
/** 5 seconds period */
#define ARP_TMR_INTERVAL 5000
#define ETHTYPE_ARP 0x0806U
#define ETHTYPE_IP 0x0800U
#define ETHTYPE_VLAN 0x8100U
#define ETHTYPE_PPPOEDISC 0x8863U /* PPP Over Ethernet Discovery Stage */
#define ETHTYPE_PPPOE 0x8864U /* PPP Over Ethernet Session Stage */
上面的数据结构中用PACK_STRUCT_FIELD来禁止编译器字对齐,因为我们需要用结构体中字段去直接操作数据包中的数据,而数据包中的数据都是以字节的方式对齐。
发送ARP请求包是etharp_request。通过调用etharp_raw函数实现,etharp_raw这里只负责ARP组包,包括请求和响应。相关源码如下:
err_t etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
const struct eth_addr *ethdst_addr,
const struct eth_addr *hwsrc_addr, const ip_addr_t *ipsrc_addr,
const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr,
const u16_t opcode)
{
struct pbuf *p;
err_t result = ERR_OK;
struct eth_hdr *ethhdr;
struct etharp_hdr *hdr;
/* allocate a pbuf for the outgoing ARP request packet */
p = pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM);
/* could allocate a pbuf for an ARP request? */
if (p == NULL) {
ETHARP_STATS_INC(etharp.memerr);
return ERR_MEM;
}
ethhdr = (struct eth_hdr *)p->payload;
hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);
hdr->opcode = htons(opcode);
/* Write the ARP MAC-Addresses */
ETHADDR16_COPY(&hdr->shwaddr, hwsrc_addr);
ETHADDR16_COPY(&hdr->dhwaddr, hwdst_addr);
/* Write the Ethernet MAC-Addresses */
ETHADDR16_COPY(ðhdr->src, ethsrc_addr);
/* Copy struct ip_addr2 to aligned ip_addr, to support compilers without
* structure packing. */
IPADDR2_COPY(&hdr->sipaddr, ipsrc_addr);
IPADDR2_COPY(&hdr->dipaddr, ipdst_addr);
hdr->hwtype = PP_HTONS(HWTYPE_ETHERNET);
hdr->proto = PP_HTONS(ETHTYPE_IP);
/* set hwlen and protolen */
hdr->hwlen = ETHARP_HWADDR_LEN;
hdr->protolen = sizeof(ip_addr_t);
ethhdr->type = PP_HTONS(ETHTYPE_ARP);
/* send ARP query */
result = netif->linkoutput(netif, p);
ETHARP_STATS_INC(etharp.xmit);
/* free ARP query packet */
pbuf_free(p);
p = NULL;
/* could not allocate pbuf for ARP request */
return result;
}
/**
* Send an ARP request packet asking for ipaddr.
*
* @param netif the lwip network interface on which to send the request
* @param ipaddr the IP address for which to ask
* @return ERR_OK if the request has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
err_t etharp_request(struct netif *netif, ip_addr_t *ipaddr)
{
return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, ðbroadcast,
(struct eth_addr *)netif->hwaddr, &netif->ip_addr, ðzero,
ipaddr, ARP_REQUEST);
}
#endif /* LWIP_ARP */
数据包输入
ARP和IP都是网络层协议,准确讲ARP应该是网络层中更底层的,因为ARP提供映射功能,IP数据包才能在以太网上发送接收。
数据包接收函数ethernetif_input,实现了以太网数据包接收和递交,其中调用了底层数据包接收函数low_level_input读取网卡中的数据包,然后再交给上层处理。下面是源码实现:
static void ethernetif_input(struct netif *netif)
{
struct ethernetif *ethernetif;
struct eth_hdr *ethhdr;
struct pbuf *p;
ethernetif = netif->state;
* move received packet into a new pbuf */
p = low_level_input(netif);
/* no packet could be read, silently ignore this */
if (p == NULL) return;
/* points to packet payload, which starts with an Ethernet header */
ethhdr = p->payload;
switch (htons(ethhdr->type)) {
/* IP or ARP packet? */
case ETHTYPE_IP:
case ETHTYPE_ARP:
#if PPPOE_SUPPORT
/* PPPoE packet? */
case ETHTYPE_PPPOEDISC:
case ETHTYPE_PPPOE:
#endif /* PPPOE_SUPPORT */
/* full packet send to tcpip_thread to process */
if (netif->input(p, netif)!=ERR_OK)
{
pbuf_free(p);
p = NULL;
}
break;
default:
pbuf_free(p);
p = NULL;
break;
}
}
这个函数只是判断了以太网的帧类型,调用响应的注册函数netif->input处理IP和ARP数据包。此时input是ethernet_input,代码如下:
err_t ethernet_input(struct pbuf *p, struct netif *netif)
{
struct eth_hdr* ethhdr;
u16_t type;
s16_t ip_hdr_offset = SIZEOF_ETH_HDR;
if (p->len <= SIZEOF_ETH_HDR) {
/* a packet with only an ethernet header (or less) is not valid for us */
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
goto free_and_return;
}
/* points to packet payload, which starts with an Ethernet header */
ethhdr = (struct eth_hdr *)p->payload;
type = ethhdr->type;
#if LWIP_ARP_FILTER_NETIF
netif = LWIP_ARP_FILTER_NETIF_FN(p, netif, htons(type));
#endif /* LWIP_ARP_FILTER_NETIF*/
switch (type) {
#if LWIP_ARP
/* IP packet? */
case PP_HTONS(ETHTYPE_IP):
if (!(netif->flags & NETIF_FLAG_ETHARP)) {
goto free_and_return;
}
#if ETHARP_TRUST_IP_MAC
/* update ARP table */
etharp_ip_input(netif, p);
#endif /* ETHARP_TRUST_IP_MAC */
/* skip Ethernet header */
if(pbuf_header(p, -ip_hdr_offset)) {
LWIP_ASSERT("Can't move over header in packet", 0);
goto free_and_return;
} else {
/* pass to IP layer */
ip_input(p, netif);
}
break;
case PP_HTONS(ETHTYPE_ARP):
if (!(netif->flags & NETIF_FLAG_ETHARP)) {
goto free_and_return;
}
/* pass p to ARP module */
etharp_arp_input(netif, (struct eth_addr*)(netif->hwaddr), p);
break;
#endif /* LWIP_ARP */
#if PPPOE_SUPPORT
case PP_HTONS(ETHTYPE_PPPOEDISC): /* PPP Over Ethernet Discovery Stage */
pppoe_disc_input(netif, p);
break;
case PP_HTONS(ETHTYPE_PPPOE): /* PPP Over Ethernet Session Stage */
pppoe_data_input(netif, p);
break;
#endif /* PPPOE_SUPPORT */
default:
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
goto free_and_return;
}
/* This means the pbuf is freed or consumed,
so the caller doesn't have to free it again */
return ERR_OK;
free_and_return:
pbuf_free(p);
return ERR_OK;
}
实际上上面两段代码都没有具体实现什么功能,只是做了个判断框架,重点落在etharp_arp_input、ip_input函数里。
etharp_arp_input源码如下:
static void etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
{
struct etharp_hdr *hdr;
struct eth_hdr *ethhdr;
/* these are aligned properly, whereas the ARP header fields might not be */
ip_addr_t sipaddr, dipaddr;
u8_t for_us;
/* drop short ARP packets: we have to check for p->len instead of p->tot_len here
since a struct etharp_hdr is pointed to p->payload, so it musn't be chained! */
if (p->len < SIZEOF_ETHARP_PACKET) {
ETHARP_STATS_INC(etharp.lenerr);
ETHARP_STATS_INC(etharp.drop);
pbuf_free(p);
return;
}
ethhdr = (struct eth_hdr *)p->payload;
hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);
/* RFC 826 "Packet Reception": */
if ((hdr->hwtype != PP_HTONS(HWTYPE_ETHERNET)) ||
(hdr->hwlen != ETHARP_HWADDR_LEN) ||
(hdr->protolen != sizeof(ip_addr_t)) ||
(hdr->proto != PP_HTONS(ETHTYPE_IP))) {
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
pbuf_free(p);
return;
}
ETHARP_STATS_INC(etharp.recv);
/* Copy struct ip_addr2 to aligned ip_addr, to support compilers without
* structure packing (not using structure copy which breaks strict-aliasing rules). */
IPADDR2_COPY(&sipaddr, &hdr->sipaddr);
IPADDR2_COPY(&dipaddr, &hdr->dipaddr);
/* this interface is not configured? */
if (ip_addr_isany(&netif->ip_addr)) {
for_us = 0;
} else {
/* ARP packet directed to us? */
for_us = (u8_t)ip_addr_cmp(&dipaddr, &(netif->ip_addr));
}
/* ARP message directed to 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.
ARP message not directed to us?
-> update the source IP address in the cache, if present */
update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);
/* now act on the message itself */
switch (hdr->opcode) {
/* ARP request? */
case PP_HTONS(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. */
/* ARP request for our address? */
if (for_us) {
/* 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);
IPADDR2_COPY(&hdr->dipaddr, &hdr->sipaddr);
IPADDR2_COPY(&hdr->sipaddr, &netif->ip_addr);
ETHADDR16_COPY(&hdr->dhwaddr, &hdr->shwaddr);
ETHADDR16_COPY(&hdr->shwaddr, ethaddr);
ETHADDR16_COPY(ðhdr->src, ethaddr);
/* 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 (ip_addr_isany(&netif->ip_addr)) {
/* { for_us == 0 and netif->ip_addr.addr == 0 } */
/* request was not directed to us */
} else {
/* { for_us == 0 and netif->ip_addr.addr != 0 } */
}
break;
case PP_HTONS(ARP_REPLY):
/* ARP reply. We already updated the ARP cache earlier. */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: incoming ARP reply\n"));
break;
default:
ETHARP_STATS_INC(etharp.err);
break;
}
/* free ARP packet */
pbuf_free(p);
}
etharp_arp_input代码逻辑框图如下:
update_arp_entry和find_entry实现ARP缓存表更新。当ARP数据包处理函数得到一个新的IP和MAC地址时。都会调用update_arp_entry来更新ARP表。而find_entry找出和IP地址匹配的表项。
在update_arp_entry中有个标志ETHARP_TRY_HEAD,它标志着创建地址对的方式。当设置这个标志时,函数创建时如果所有ARP表项都用完了,此时会去回收哪些比较旧的表项,将它们删除,然后建立新的表项;当不设置的时候,当ARP表项用完的时候,就不做任何操作,相当于关门不做事了。
find_entry函数输入时IP地址,返回是IP地址对应的ARP缓存表项索引。这个函数只查找,并不改变表项的状态字段。主要功能是寻找一个与ipaddr匹配的ARP表项或者创建一个新的表项,并返回表项在 缓存表中索引号。find_entry的实现逻辑流程如下图:
清楚find_entry的逻辑过程后,update_arp_entry就不难了。它有两个重要参数分别是匹配的IP和MAC。函数用这两个地址更新或插入一个ARP表项,它的逻辑流程如下:
数据包输出
ARP数据包输入输出整体流程图如下:
etharp_output被IP层数据包发送函数ip_output调用。由于发送IP数据包,所以一开始需要更改数据包pbuf的payload指针,让以太网帧头出现在数据包中。检查目的IP地址,分为广播包、多播包、单播包三种情况讨论。
1.广播包:ip_addr_isbroadcast函数判断IP地址是否是广播地址,若是广播包,则目的MAC地址是ff-ff-ff-ff-ff-ff,此时不用查询arp表;
2.多播包:判断目的IP地址是否是D类IP地址,若是,MAC地址可以直接算出,01-00-5e-00-00-00的低23位设置为IP地址的低23位;
3.单播包:要比较目的IP和本地IP地址,看是否是局域网内,若不是局域网内,则目的IP地址设位默认网关的地址,然后统一调用etharp_query查找MAC地址,最后将数据包发送出去。
etharp_output源码如下:
err_t etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr)
{
struct eth_addr *dest, mcastaddr;
/* make room for Ethernet header - should not fail */
if (pbuf_header(q, sizeof(struct eth_hdr)) != 0) {
/* bail out */
LINK_STATS_INC(link.lenerr);
return ERR_BUF;
}
/* assume unresolved Ethernet address */
dest = NULL;
/* Determine on destination hardware address. Broadcasts and multicasts
* are special, other IP addresses are looked up in the ARP table. */
/* broadcast destination IP address? */
if (ip_addr_isbroadcast(ipaddr, netif)) {
/* broadcast on Ethernet also */
dest = (struct eth_addr *)ðbroadcast;
/* multicast destination IP address? */
} else if (ip_addr_ismulticast(ipaddr)) {
/* Hash IP multicast address to MAC address.*/
mcastaddr.addr[0] = 0x01;
mcastaddr.addr[1] = 0x00;
mcastaddr.addr[2] = 0x5e;
mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
mcastaddr.addr[4] = ip4_addr3(ipaddr);
mcastaddr.addr[5] = ip4_addr4(ipaddr);
/* destination Ethernet address is multicast */
dest = &mcastaddr;
/* unicast destination IP address? */
} else {
/* outside local network? */
if (!ip_addr_netcmp(ipaddr, &(netif->ip_addr), &(netif->netmask)) &&
!ip_addr_islinklocal(ipaddr)) {
{
/* interface has default gateway? */
if (!ip_addr_isany(&netif->gw)) {
/* send to hardware address of default gateway IP address */
ipaddr = &(netif->gw);
/* no default gateway available */
} else {
/* no route to destination error (default gateway missing) */
return ERR_RTE;
}
}
}
if ((arp_table[etharp_cached_entry].state == ETHARP_STATE_STABLE) &&
(ip_addr_cmp(ipaddr, &arp_table[etharp_cached_entry].ipaddr))) {
/* the per-pcb-cached entry is stable and the right one! */
ETHARP_STATS_INC(etharp.cachehit);
return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr),
&arp_table[etharp_cached_entry].ethaddr);
}
/* queue on destination Ethernet address belonging to ipaddr */
return etharp_query(netif, ipaddr, q);
}
/* continuation for multicast/broadcast destinations */
/* obtain source Ethernet address of the given interface */
/* send packet directly on the link */
return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr), dest);
}
广播包和多播包通过调用etharp_send_ip将数据包发送出去,源码逻辑很简单,将传入参数添加到以太网帧头的三个字段,调用底层数据包发送函数发送,源码就不放这了。
单播包就不简单了,难点在于数据包挂载时需要考虑的情况很多。具体步骤如下:
源码如下:
err_t etharp_query(struct netif *netif, ip_addr_t *ipaddr, struct pbuf *q)
{
struct eth_addr * srcaddr = (struct eth_addr *)netif->hwaddr;
err_t result = ERR_MEM;
s8_t i; /* ARP entry index */
/* non-unicast address? */
if (ip_addr_isbroadcast(ipaddr, netif) ||
ip_addr_ismulticast(ipaddr) ||
ip_addr_isany(ipaddr)) {
return ERR_ARG;
}
/* find entry in ARP cache, ask to create entry if queueing packet */
i = find_entry(ipaddr, ETHARP_FLAG_TRY_HARD);
/* could not find or create entry? */
if (i < 0) {
if (q) {
ETHARP_STATS_INC(etharp.memerr);
}
return (err_t)i;
}
/* 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;
}
/* { i is either a STABLE or (new or existing) PENDING entry } */
/* 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. */
}
if (q == NULL) {
return result;
}
}
/* stable entry? */
if (arp_table[i].state == ETHARP_STATE_STABLE) {
/* we have a valid IP->Ethernet address mapping */
ETHARP_SET_HINT(netif, i);
/* 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) {
/* entry is still pending, queue the given packet 'q' */
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) {
if(p->type != PBUF_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) {
pbuf_free(p);
p = NULL;
}
}
} else {
/* referencing the old pbuf is enough */
p = q;
pbuf_ref(p);
}
/* packet could be taken over? */
if (p != NULL) {
/* queue packet ... */
#if ARP_QUEUEING
struct etharp_q_entry *new_entry;
/* allocate a new arp queue 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) {
/* 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;
}
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 */
pbuf_free(p);
result = ERR_MEM;
}
#else /* ARP_QUEUEING */
/* always queue one packet per ARP request only, freeing a previously queued packet */
if (arp_table[i].q != NULL) {
pbuf_free(arp_table[i].q);
}
arp_table[i].q = p;
result = ERR_OK;
#endif /* ARP_QUEUEING */
} else {
ETHARP_STATS_INC(etharp.memerr);
result = ERR_MEM;
}
}
return result;
}