TCP/IP链路层ARP协议实现(lwip)

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进行计时并判断是否超时。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值