LWIP学习笔记-ARP协议


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(&ethhdr->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, &ethbroadcast,
                    (struct eth_addr *)netif->hwaddr, &netif->ip_addr, &ethzero,
                    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(&ethhdr->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数据包输入输出整体流程图如下:
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 *)&ethbroadcast;
  /* 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;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火山宝 && 王林宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值