《lwip学习6》-- ARP协议

初始ARP

地址解析协议(Address Resolution Protocol, ARP)是通过解析 IP 地址得到数据链路层地址的,是一个在网络协议包中极其重要的网络传输协议,它与网卡有着极其密切的关系,在 TCP/IP 分层结构中,把 ARP 划分为网络层, 为什么呢,因为在网络层看来,源主机与目标主机是通过 IP 地址进行识别的,而所有的数据传输又依赖网卡底层硬件,即链路层,那么就需要将这些 IP 地址转换为链路层可以识别的东西,在所有的链路中都有着自己的一套寻址机制,如在以太网中使用 MAC 地址进行寻址,标识不同的主机,那么就需要有一个协议将 IP 地址转换为 MAC 地址,由此就出现了 ARP 协议, ARP 协议在网络层被应用,它是网络层与链路层连接的重要枢纽。
在局域网中,网络中实际传输的是“帧”,帧里面是有目标主机的 MAC 地址的。在以太网中,一个主机要和另一个主机进行直接通信,必须要知道目标主机的 MAC 地址,那就需要 ARP 进行地址解析, 所谓“地址解析”就是主机在发送帧前将目标 IP 地址转换成目标 MAC 地址的过程。 ARP 协议的基本功能就是通过目标设备的 IP 地址,查询目标设备的 MAC 地址,以保证通信的顺利进行。

以太网帧结构

既然谈到 MAC 地址, 那就不得不说一下以太网帧结构了, 每个网卡都有唯一一个物理地址, 在硬件中进行数据帧传输的时候就必须有正确的目的物理地址, 例如以太网的 48位 MAC 地址就是存储在网卡内部存储器中。
在这里插入图片描述
以太网帧以一个 7 字节的前同步码(Preamble)字段开始。该前同步码的值都是10101010(0x55,大端模式) ; 而后紧接着一个字节的帧开始符, 其值是 10101011(0xD5,大端模式) 。前同步码字段的作用是实现物理层帧输入输出的同步, 而帧开始符表示着以太网帧的开始, 剩下的 5 个字段才是真正的以太网数据帧结构。
目标 MAC 地址(6 字节) : 这个字段包含目标网卡的 MAC 地址, 当一个网卡收到一个以太网数据帧,如果该数据帧的目标地址是网卡自身的 MAC 地址或者是 MAC 广播地址,它都将该帧的数据字段的内容传递给网络层;如果它收到了具有任何其他 MAC 地址的帧,则将该数据帧丢弃。
源 MAC 地址(6 字节) : 这个字段包含了传输该帧到局域网上的适配器的 MAC 地址。
类型字段(2 字节) : 类型字段允许以太网复用多种网络层协议。为了理解这点,我们需要记住主机能够使用除了 IP 以外的其他网络层协议。事实上,一台给定的主机可以支持多种网络层协议,以对不同的应用采用不同的协议。因此,当以太网帧到达网卡中, 网卡需要知道它应该将数据字段的内容传递给哪个网络层协议。 如 IP 协议、 ARP 协议等。
注意了: 当这个字段的值小于 1518 时,它表示后面数据字段的数据长度,当大于1518 的时候才表示递交给哪个协议。
数据字段(46~1500 字节) : 这个字段承载了 IP 数据报。以太网的最大传输单元(MTU)是 1500 字节。这意味着如果 IP 数据报超过了 1500 字节,则主机必须将该数据报分片(关于分片会在后续讲解) 。数据字段的最小长度是 46 字节, 这意味着如果 IP 数据报小于 46 字节,数据报必须被填充到 46 字节。当采用填充时,传递到网络层的数据包括IP 数据报和填充部分, 网络层使用 IP 数据报首部中的长度字段来去除填充部分。
CRC(4 字节) : CRC 字段包含了以太网的差错校验信息。

IP 地址映射为物理地址

TCP/IP 协议有自己的 IP 地址, IP 地址(IPv4) 是一个 32 位的 IP 地址,网络层发送数据包只需要知道目标主机 IP 地址即可,而以太网发送数据则必须知道对方的硬件 MAC 地址,同时 IP 地址的分配与硬件 MAC 地址是没有关系的,为了让网络层只需要知道 IP 地址就可以完成通信工作,那就需要有一个协议将 IP 地址映射成为对应的 MAC 地址。

ARP 缓存表

为了实现 IP 地址与网卡 MAC 地址的查询与转换, ARP 协议引入了 ARP 缓存表的概念, 每台主机或路由器在其内存中具有一个 ARP 缓存表(ARP table),这张表包含 IP 地址到 MAC 地址的映射关系,
表中记录了<IP 地址, MAC 地址>对,它们是主机最近运行时获得关于其他主机的 IP 地址到物理地址的映射,当需要发送 IP 数据的时候,主机就会根据目标 IP 地址到 ARP 缓存表中进行查找对应的 MAC 地址,然后通过网卡将数据发送出去。
ARP 表也包含一个寿命(TTL)值,它指示了从表中删除每个映射的时间。从一个表项放置到某 ARP 表中开始,一个表项通常的过期时间是 10 分钟。
我们电脑也是有自己的 ARP 缓存表的, 可以在控制台中通过“arp -a” 命令进行查看
其运作过程大致可以理解为:

  1. 如果主机 A 想发送数据给主机 B,主机 A 首先会检查自己的 ARP 缓存表,查看是否有主机 B 的 IP 地址和 MAC 地址的对应关系,如果有,则会将主机 B 的MAC 地址作为源 MAC 地址封装到数据帧中。 如果本地 ARP 缓存中没有对应关系, 主机 A 就会向局域网中广播 ARP 请求(包括发送方的 IP 地址、 MAC 地址、接收方的 IP 地址),每台主机接收到 ARP 请求后都检查自己的 IP 地址是否与ARP 请求中的接收方 IP 地址相同,若不相同则丢弃 ARP 请求包。
  2. 当交换机接受到此数据帧之后,发现此数据帧是广播帧,因此,会将此数据帧从非接收的所有接口发送出去。
  3. 当主机 B 接受到此数据帧后,会校对 IP 地址是否是自己的,并将主机 A 的 IP 地址和 MAC 地址的对应关系记录到自己的 ARP 缓存表中,同时会发送一个 ARP 响应,其中包括自己的 MAC 地址。
  4. 主机 A 在收到这个回应的数据帧之后,在自己的 ARP 缓存表中记录主机 B 的 IP地址和 MAC 地址的对应关系。而此时交换机已经学习到了主机 A 和主机 B 的MAC 地址了

ARP 协议的核心是 ARP 缓存表, ARP 的实质就是对缓存表的建立、更新、查询等操作, ARP 缓存表的核心是表项(entry)。 LwIP 使用一个 arp_table 数组描述 ARP 缓存表,数组的内容是表项的内容,具体见代码清单。每个表项都必须记录一对 IP 地址与MAC 地址的映射关系,此外还有一些基本的信息,如表项的状态、生命周期(生存时间)以及对应网卡的基本信息, LwIP 使用一个 etharp_entry 结构体对表项进行描述

static struct etharp_entry arp_table[ARP_TABLE_SIZE]; //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 */
  ip4_addr_t ipaddr; //记录目标 IP 地址。
  struct netif *netif; //对应网卡信息
  struct eth_addr ethaddr; //记录与目标 IP 地址对应的 MAC 地址
  u16_t ctime; //生存实际
  u8_t state; //表项的状态
};

关于表项的状态,有如下几种状态

/** ARP states */
enum etharp_state {
  ETHARP_STATE_EMPTY = 0, //空状态,表示此表项能被使用
  ETHARP_STATE_PENDING, //表示 ARP 已经发出了一个 ARP 请求包, 但是还未收到目标 IP 地址主机的应答
  ETHARP_STATE_STABLE, //稳定状态
  ETHARP_STATE_STABLE_REREQUESTING_1, //当稳定状态发送一个ARP请求报,首先暂时设置未 _1
  ETHARP_STATE_STABLE_REREQUESTING_2  //当稳定状态发送一个ARP请求报,之后设置未 _2,最后恢复稳定状态
#if ETHARP_SUPPORT_STATIC_ENTRIES
  , ETHARP_STATE_STATIC
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};

ARP 缓存表在初始化的时候, 所有的表项都会被初始化为 ETHARP_STATE_EMPTY,也就是空状态, 表示这些表项能被使用

在需要添加表项的时候, LwIP 内核就会遍历ARP 缓存表, 找到合适的表项, 进行添加。如果 ARP 表项处于ETHARP_STATE_PENDING 状态, 表示 ARP 已经发出了一个 ARP 请求包, 但是还未收到目标 IP 地址主机的应答, 处于这个状态的缓存表项是有等待时间的, 它通过宏定义ARP_MAXPENDING 指定, 默认为 5 秒钟,如果从发出 ARP 请求包后的 5 秒内还没收到应答,那么该表项又会被删除;

如果收到应答后, ARP 就会更新缓存表的信息,记录目标 IP 地址与目标 MAC 地址的映射关系并且开始记录表项的生存时间,同时该表项的状态会变成 ETHARP_STATE_STABLE 状态。当要发送数据包的时候,而此时表项为ETHARP_STATE_PENDING 状态, 那么这些数据包就会暂时被挂载到表项的数据包缓冲队列上, 直到表项的状态为 ETHARP_STATE_STABLE, 才进行发送数据包。

对于状态为ETHARP_STATE_STABLE 的表项, 这些表项代表着 ARP 记录了 IP 地址与 MAC 地址的映射关系, 能随意通过 IP 地址进行数据的发送, 但是这些表项是具有生存时间的, 通过宏定义 ARP_MAXAGE 指定, 默认为 5 分钟,在这些时间, LwIP 会不断维护这些缓存表以保持缓存表的有效。当表项是 ETHARP_STATE_STABLE 的时候又发送一个 ARP 请求包,那么表项状态会暂时被设置为 ETHARP_STATE_STABLE_REREQUESTING_1, 然后被设置为 ETHARP_STATE_STABLE_REREQUESTING_2 状态, 这些是一个过渡状态, 当收到ARP 应答后, 表项又会被设置为 ETHARP_STATE_STABLE, 这样子能保持表项的有效。

ARP缓存表的超时处理

ARP 是动态处理的, 现在总结一下: ARP 表项的生存时间是 5分钟,而 ARP 请求的等待时间是 5 秒钟,当这些时间到达后,就会更新 ARP 表项,如果在物理链路层无法连通则会删除表项。这就需要 ARP 层有一个超时处理函数对 ARP 进行管理,这些操作都是根据 ARP 表项的 ctime 字段进行的,它记录着对应表项的生存时间,而超时处理函数是 etharp_tmr(),它是一个周期性的超时处理函数,每隔 1 秒就调用一次,当 ctime 的值大于指定的时间, 就会删除对应的表项,
etharp_tmr() 通过在超时检查函数中被调用:sys_check_timeouts(void) -> lwip_cyclic_timer(void *arg)

void
etharp_tmr(void)
{
  int i;

  LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\n"));
  /* 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
        && (state != ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
       ) {
      arp_table[i].ctime++; //如果 ARP 表项不是空的,那么就记录表项的时间。
      //当表项的时间大于表项的生存时间(5 分钟),或者表项状态
      //是 ETHARP_STATE_PENDING 处于等待目标主机回应 ARP 请求包,并且等待的时间超过
      //ARP_MAXPENDING(5 秒),那么 LwIP 就认为这些表项是无效了,就调用etharp_free_entry()函数删除表项
      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! */
        LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer: expired %s entry %d.\n",
                                   arp_table[i].state >= ETHARP_STATE_STABLE ? "stable" : "pending", i));
        /* clean up entries that have just been expired */
        etharp_free_entry(i); //删除表项
      } else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_1) {
        /* Don't send more than one request every 2 seconds. */
        arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
      } else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2) {
        /* Reset state to stable, so that the next transmitted packet will
           re-send an ARP request. */
        arp_table[i].state = ETHARP_STATE_STABLE;
      } else if (arp_table[i].state == ETHARP_STATE_PENDING) {
        /* still pending, resend an ARP query */
        etharp_request(arp_table[i].netif, &arp_table[i].ipaddr); //处于挂起状态,则再次发送ARP请求
      }
    }
  }
}

ARP报文

ARP 的请求与应答都是依赖 ARP 报文结构进行的, ARP 报文是放在以太网数据帧中进行发送的,所以下图会将以太网首部一同画出来
在这里插入图片描述
在 ARP 表建立前, 主机并不知道目标 MAC 地址, 所以在一开始的时候只能通过广播的方式将 ARP 请求包发送出去, 处于同一局域网的主机都能接收到广播的数据包。所以一开始目标 MAC 地址是 FF-FF-FF-FF-FF-FF,而以太网首部的帧类型是有多种,对于 ARP数据包来说,其值为 0x0806,对于 IP 数据报来说,其值为 0x0800,此处我们只需简单了解一下即可,无需记住。

接下来就是 ARP 报文部分, ARP 也是一种协议, 也有 ARP 首部,在 ARP 首部一开始的 2 个字节存储的是硬件类型,表示要知道目标网卡的硬件类型, 其中,值为 1 表示以太网地址,其他还可能表示令牌环地址; 接下来还有 2 字节的协议类型, 表示硬件地址要映射的协议地址类型, 其中, 0x0800 表示 IP 地址,其他还可能是 ICMP/IGMP 等;接下来有1 个字节表示硬件地址长度,指出该报文中硬件地址的长度,对于以太网,它的值为 6;还有 1 字节的协议地址长度, 对于 ARP 请求或应答来说,该值为 4; ARP 首部最后的 op 字段用于记录 ARP 操作的类型,分别是:
 ARP 请求,其值为 1。
 ARP 应答,其值为 2。
 RARP 请求,其值为 3。 //不关心
 RARP 应答,其值为 4 //不关心

对于 ARP 首部后面的四个字段分别是源 MAC 地址、 源 IP 地址、目标 MAC 地址、目标 IP 地址,这些就是比较简单的了
在 ARP 请求包中,除了目标 MAC 地址是未知以外,其他地址 3 个字段都应该填写正确,然后通过广播的形式将该 ARP 请求包发送出去,目标主机接收到该请求包后判断目标IP 地址与自身 IP 地址是否一致,如果一致则返回 ARP 应答;对应 ARP 应答包,只需要把自己的 MAC 地址填充进去,并且请求包的源主机信息与目标主机信息进行交换位置,然后把 op 字段设置为 2,就返回 ARP 应答包即可。
注意,在发送 ARP 请求包的时候,以太网首部的目标 MAC 地址是 FF-FF-FF-FF-FFFF,而 ARP 首目标 MAC 地址为 00-00-00-00-00-00-00(表示要填充),这里千万不要混淆。

在 LwIP 中, 使用了大量的数据结构对 ARP 进行描述, 比较麻烦, 我们暂时不用去学它, 只要知道原理是这样子的即可, 关于这些数据结构的定义位于 etharp.h 、 ethernet.h 等头文件中

/** An Ethernet MAC address */
struct eth_addr {
  PACK_STRUCT_FLD_8(u8_t addr[ETH_HWADDR_LEN]);
} PACK_STRUCT_STRUCT;

/** Ethernet header */
struct eth_hdr {
#if ETH_PAD_SIZE
  PACK_STRUCT_FLD_8(u8_t padding[ETH_PAD_SIZE]);
#endif
  PACK_STRUCT_FLD_S(struct eth_addr dest);
  PACK_STRUCT_FLD_S(struct eth_addr src);
  PACK_STRUCT_FIELD(u16_t type);
} PACK_STRUCT_STRUCT;

/** the ARP message, see RFC 826 ("Packet format") */
struct etharp_hdr {  //ARP报文
  PACK_STRUCT_FIELD(u16_t hwtype); //硬件类型
  PACK_STRUCT_FIELD(u16_t proto);  //协议类型
  PACK_STRUCT_FLD_8(u8_t  hwlen);  //硬件地址长度
  PACK_STRUCT_FLD_8(u8_t  protolen);  //协议地址长度
  PACK_STRUCT_FIELD(u16_t opcode);    //op 字段
  /* 以上是 ARP 报文首部 */
  PACK_STRUCT_FLD_S(struct eth_addr shwaddr);  //源 MAC 地址
  PACK_STRUCT_FLD_S(struct ip4_addr_wordaligned sipaddr); //源 ip 地址
  PACK_STRUCT_FLD_S(struct eth_addr dhwaddr); //目标 MAC 地址
  PACK_STRUCT_FLD_S(struct ip4_addr_wordaligned dipaddr); //目标 ip 地址
} PACK_STRUCT_STRUCT;

/* ARP message types (opcodes) */
enum etharp_opcode {  //op 字段操作
  ARP_REQUEST = 1,    //请求包
  ARP_REPLY   = 2     //应答包
};

发送ARP请求包

发送 ARP 请求包的时候,需要填充已知的目标 IP 地址、源 MAC 地址、源 IP 地址等,并且需要该 ARP 包进行广播出去,所以以太网首部的目标 MAC 地址为 FF-FF-FF-FF-FF-FF

/**
 * Send a raw ARP packet (opcode and all addresses can be modified)
 *发送原始 ARP 数据包(操作码和所有地址都可以修改)
 * @param netif the lwip network interface on which to send the ARP packet 用于发送 ARP 数据包的 lwip 网络接口
 * @param ethsrc_addr the source MAC address for the ethernet header 以太网头的源 MAC 地址
 * @param ethdst_addr the destination MAC address for the ethernet header 以太网头的目标 MAC 地址
 * @param hwsrc_addr the source MAC address for the ARP protocol header ARP 协议头的源 MAC 地址
 * @param ipsrc_addr the source IP address for the ARP protocol header ARP 协议头的源 IP 地址
 * @param hwdst_addr the destination MAC address for the ARP protocol header ARP 协议头的目标 MAC 地址
 * @param ipdst_addr the destination IP address for the ARP protocol header ARP 协议头的目标 IP 地址
 * @param opcode the type of the ARP packet 操作编码 ARP 数据包的类型
 * @return ERR_OK if the ARP packet has been sent
 *         ERR_MEM if the ARP packet couldn't be allocated
 *         any other err_t on failure
 */
static 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 ip4_addr_t *ipsrc_addr,
           const struct eth_addr *hwdst_addr, const ip4_addr_t *ipdst_addr,
           const u16_t opcode)
{
  struct pbuf *p;
  err_t result = ERR_OK;
  struct etharp_hdr *hdr;

  LWIP_ASSERT("netif != NULL", netif != NULL);

  /* allocate a pbuf for the outgoing ARP request packet */
  p = pbuf_alloc(PBUF_LINK, SIZEOF_ETHARP_HDR, PBUF_RAM); //申请 ARP 报文的内存空间
  /* could allocate a pbuf for an ARP request? */
  if (p == NULL) {
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
                ("etharp_raw: could not allocate pbuf for ARP request.\n"));
    ETHARP_STATS_INC(etharp.memerr);
    return ERR_MEM;
  }
  LWIP_ASSERT("check that first pbuf can hold struct etharp_hdr",
              (p->len >= SIZEOF_ETHARP_HDR));

  hdr = (struct etharp_hdr *)p->payload; //ARP 报文的数据区域,并且强制将起始地址转化成 ARP 报文首部
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_raw: sending raw ARP packet.\n"));
  hdr->opcode = lwip_htons(opcode); //填写 ARP 数据包的 op 字段

  LWIP_ASSERT("netif->hwaddr_len must be the same as ETH_HWADDR_LEN for etharp!",
              (netif->hwaddr_len == ETH_HWADDR_LEN));

  /* Write the ARP MAC-Addresses */
  SMEMCPY(&hdr->shwaddr, hwsrc_addr, ETH_HWADDR_LEN); //填写源 MAC 地址
  SMEMCPY(&hdr->dhwaddr, hwdst_addr, ETH_HWADDR_LEN); //填写目标 MAC 地址
  /* Copy struct ip4_addr_wordaligned to aligned ip4_addr, to support compilers without
   * structure packing. */
  IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->sipaddr, ipsrc_addr); //以太网首部源 MAC 地址
  IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->dipaddr, ipdst_addr); //以太网首部目标 MAC 地址

  hdr->hwtype = PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET); //填写 ARP 首部硬件类型
  hdr->proto = PP_HTONS(ETHTYPE_IP); //填写 ARP 首部协议类型
  /* set hwlen and protolen */
  hdr->hwlen = ETH_HWADDR_LEN; //填写 ARP 数据包硬件地址长度
  hdr->protolen = sizeof(ip4_addr_t); //填写 ARP 数据包协议地址长度

  /* send ARP query */
#if LWIP_AUTOIP
  /* If we are using Link-Local, all ARP packets that contain a Link-Local
   * 'sender IP address' MUST be sent using link-layer broadcast instead of
   * link-layer unicast. (See RFC3927 Section 2.5, last paragraph) */
  if (ip4_addr_islinklocal(ipsrc_addr)) {
    ethernet_output(netif, p, ethsrc_addr, &ethbroadcast, ETHTYPE_ARP);
  } else
#endif /* LWIP_AUTOIP */
  {
    ethernet_output(netif, p, ethsrc_addr, ethdst_addr, ETHTYPE_ARP); //调用底层发送函数将以太网数据帧发送出去
  }

  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 to a specific eth address.
 * Used to send unicast request to refresh the ARP table just before an entry
 * times out
 *
 * @param netif the lwip network interface on which to send the request 网卡接口
 * @param ipaddr the IP address for which to ask 目的IP地址
 * @param hw_dst_addr the ethernet address to send this packet to 要发送的以太网协议MAC地址FF
 * @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
 */
static err_t
etharp_request_dst(struct netif *netif, const ip4_addr_t *ipaddr, const struct eth_addr *hw_dst_addr)
{
  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, hw_dst_addr,
                    (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif), &ethzero,
                    ipaddr, ARP_REQUEST);
}

/**
 * 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 发送的目的IP地址
 * @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, const ip4_addr_t *ipaddr)
{
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_request: sending ARP request.\n"));
  return etharp_request_dst(netif, ipaddr, &ethbroadcast);
}

总的来说就是先调用 etharp_request()函数进行发送 ARP 请求包,在 etharp_request()函数中会调用 etharp_request_dst()函数进行发送,此时指定的目标 MAC 地址是 ethbroadcast,而在 etharp_request_dst()函数中会调用 etharp_raw()进行发送 ARP 请求包,层层调用,并且每层的参数都是越来越多的,这样子封装对于上层程序来说更加好处理,在 etharp_raw()函数中,会对 ARP 数据包进行封装,然后再封装到以太网数据帧中,最终调用以太网底层发送函数进行将以太网数据帧发送出去。

数据包接收流程

当数据通过网卡中接收回来的时候, LwIP 内核就需要将数据进行分解,如果是 IP 数据报则递交给 IP 协议去处理,如果是 ARP 数据包则交由 ARP 协议去处理。 LwIP 中数据包从网卡接收的函数是 ethernetif_input(),真正让 LwIP 内核去处理接收到的数据包是 ethernet_input()函数。

/**
 * @ingroup lwip_nosys
 * Process received ethernet frames. Using this function instead of directly
 * calling ip_input and passing ARP frames through etharp in ethernetif_input,
 * the ARP cache is protected from concurrent access.\n
 * Don't call directly, pass to netif_add() and call netif->input().
 *
 * @param p the received packet, p->payload pointing to the ethernet header  p->payload指向以太网帧的头部
 * @param netif the network interface on which the packet was received
 *
 * @see LWIP_HOOK_UNKNOWN_ETH_PROTOCOL
 * @see ETHARP_SUPPORT_VLAN
 * @see LWIP_HOOK_VLAN_CHECK
 */
err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{
  struct eth_hdr *ethhdr;
  u16_t type;
#if LWIP_ARP || ETHARP_SUPPORT_VLAN || LWIP_IPV6
  u16_t next_hdr_offset = SIZEOF_ETH_HDR;
#endif /* LWIP_ARP || ETHARP_SUPPORT_VLAN */

  LWIP_ASSERT_CORE_LOCKED();

  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);
    MIB2_STATS_NETIF_INC(netif, ifinerrors);
    goto free_and_return;
  }

  if (p->if_idx == NETIF_NO_INDEX) {
    p->if_idx = netif_get_index(netif);
  }

  /* points to packet payload, which starts with an Ethernet header */
  ethhdr = (struct eth_hdr *)p->payload;
  type = ethhdr->type;

  if (ethhdr->dest.addr[0] & 1) {
    /* this might be a multicast or broadcast packet */
    //这可能是多播或者广播数据包
    if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) {
#if LWIP_IPV4
      if ((ethhdr->dest.addr[1] == LL_IP4_MULTICAST_ADDR_1) &&
          (ethhdr->dest.addr[2] == LL_IP4_MULTICAST_ADDR_2)) {
        /* mark the pbuf as link-layer multicast */
        /* 将 pbuf 标记为链路层多播 */
        p->flags |= PBUF_FLAG_LLMCAST;
      }
#endif /* LWIP_IPV4 */
    }
    else if (eth_addr_cmp(&ethhdr->dest, &ethbroadcast)) {
      /* mark the pbuf as link-layer broadcast */
      /* 将 pbuf 标记为链路层广播 */
      p->flags |= PBUF_FLAG_LLBCAST;
    }
  }

  switch (type) {
#if LWIP_IPV4 && LWIP_ARP
    /* IP packet? */
    case PP_HTONS(ETHTYPE_IP): /* 如果是 IP 数据报 */
      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
        goto free_and_return;
      }
      /* skip Ethernet header (min. size checked above) */
      if (pbuf_remove_header(p, next_hdr_offset)) { /* 跳过以太网首部 */
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
                    ("ethernet_input: IPv4 packet dropped, too short (%"U16_F"/%"U16_F")\n",
                     p->tot_len, next_hdr_offset));
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("Can't move over header in packet"));
        goto free_and_return;
      } else {
        /* pass to IP layer */
        ip4_input(p, netif);  /* 传递到 IP 协议去处理 */
      }
      break;

    case PP_HTONS(ETHTYPE_ARP): //对于是 ARP 包
      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
        goto free_and_return;
      }
      /* skip Ethernet header (min. size checked above) */
      if (pbuf_remove_header(p, next_hdr_offset)) { /* 跳过以太网首部 */
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
                    ("ethernet_input: ARP response packet dropped, too short (%"U16_F"/%"U16_F")\n",
                     p->tot_len, next_hdr_offset));
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("Can't move over header in packet"));
        ETHARP_STATS_INC(etharp.lenerr);
        ETHARP_STATS_INC(etharp.drop);
        goto free_and_return;
      } else {
        /* pass p to ARP module */
        etharp_input(p, netif); /*传递到 ARP 协议处理 */
      }
      break;
#endif /* LWIP_IPV4 && LWIP_ARP */
    default:
#ifdef LWIP_HOOK_UNKNOWN_ETH_PROTOCOL
      if (LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(p, netif) == ERR_OK) {
        break;
      }
#endif
      ETHARP_STATS_INC(etharp.proterr);
      ETHARP_STATS_INC(etharp.drop);
      MIB2_STATS_NETIF_INC(netif, ifinunknownprotos);
      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;
}

上面函数中,对于IP协议,除去以太网帧首部成功,调用 ip4_input()函数将数据包递交到 IP协议去处理;
对于ARP协议,调用 etharp_input ()函数将数据包递交到ARP 协议去处理。

ARP 数据包的处理函数为 etharp _input(), 在这里它完成两个任务:

  1. 如果收到的是 ARP 应答包,说明本机之前发出的 ARP 请求包有了回应, 就根据应答包更新自身的 ARP 缓存表;
  2. 如果收到的是 ARP 请求包,如果包中的目标 IP 地址与主机 IP 地址匹配, 除了记录原主机的 IP 与 MAC 地址,更新自身的 ARP 表外,还要向源主机发送一个ARP 应答包。但是如果包中目标 IP 地址与主机 IP 地址不匹配,则尽可能记录源主机的 IP 与 MAC 地址,更新自身的 ARP 表,并丢弃该请求包,为什么说是尽可能呢,因为主机的 ARP 缓存表是有限的,不可能记录太多的 ARP 表项,所以在有空闲的表项时才记录,如果没有空闲的表项, ARP 觉得它自己已经尽力了,也记不住那么多表项。
/**
 * Responds to ARP requests to us. Upon ARP replies to us, add entry to cache
 * send out queued IP packets. Updates cache with snooped address pairs.
 *
 * Should be called for incoming ARP packets. The pbuf in the argument
 * is freed by this function.
 *
 * @param p The ARP packet that arrived on netif. Is freed by this function.
 * @param netif The lwIP network interface on which the ARP packet pbuf arrived.
 *
 * @see pbuf_free()
 */
void
etharp_input(struct pbuf *p, struct netif *netif)
{
  struct etharp_hdr *hdr;
  /* these are aligned properly, whereas the ARP header fields might not be */
  ip4_addr_t sipaddr, dipaddr;
  u8_t for_us;

  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ERROR("netif != NULL", (netif != NULL), return;);

  hdr = (struct etharp_hdr *)p->payload;

  /* RFC 826 "Packet Reception": */
  /* 判断 ARP 包的合法性 */
  if ((hdr->hwtype != PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET)) ||
      (hdr->hwlen != ETH_HWADDR_LEN) ||
      (hdr->protolen != sizeof(ip4_addr_t)) ||
      (hdr->proto != PP_HTONS(ETHTYPE_IP)))  {
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
                ("etharp_input: packet dropped, wrong hw type, hwlen, proto, protolen or ethernet type (%"U16_F"/%"U16_F"/%"U16_F"/%"U16_F")\n",
                 hdr->hwtype, (u16_t)hdr->hwlen, hdr->proto, (u16_t)hdr->protolen));
    ETHARP_STATS_INC(etharp.proterr);
    ETHARP_STATS_INC(etharp.drop);
    pbuf_free(p);
    return;
  }
  ETHARP_STATS_INC(etharp.recv);

#if LWIP_AUTOIP
  /* We have to check if a host already has configured our random
   * created link local address and continuously check if there is
   * a host with this IP-address so we can detect collisions */
  autoip_arp_reply(netif, hdr);
#endif /* LWIP_AUTOIP */

  /* Copy struct ip4_addr_wordaligned to aligned ip4_addr, to support compilers without
   * structure packing (not using structure copy which breaks strict-aliasing rules). */
  //拷贝源 IP 地址与目标 IP 地址
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr);
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr);

  /* this interface is not configured? */
  /* 看看主机网卡是否配置了 IP 地址 */
  if (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
    for_us = 0;
  } else {
    /* ARP packet directed to us? */
    /* 判断目标 IP 地址与主机 IP 地址是否一样 */
    for_us = (u8_t)ip4_addr_cmp(&dipaddr, netif_ip4_addr(netif));
  }

  /* 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 */
      /* 更新 ARP 缓存表 */
  etharp_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 请求包 */
      /* ARP request. If it asked for our address, we send out a
       * reply. In any case, we time-stamp any existing ARP entry,
       * and possibly send out an IP packet that was queued on it. */

      LWIP_DEBUGF (ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: incoming ARP request\n"));
      /* ARP request for our address? */
      if (for_us) { /* 是请求自己的 */
        /* send ARP response */ /* 做出回应 */
        etharp_raw(netif, 
                   (struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
                   (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
                   &hdr->shwaddr, &sipaddr,
                   ARP_REPLY);
        /* we are not configured? */
      } else if (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
        /* { for_us == 0 and netif->ip_addr.addr == 0 } */
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_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_input: ARP request was not for us.\n"));
      }
      break;
    case PP_HTONS(ARP_REPLY): /* 对于 ARP 应答包*/
      /* ARP reply. We already updated the ARP cache earlier. */
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_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 /* (LWIP_DHCP && DHCP_DOES_ARP_CHECK) */
      break;
    default:
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: ARP unknown opcode type %"S16_F"\n", lwip_htons(hdr->opcode)));
      ETHARP_STATS_INC(etharp.err);
      break;
  }
  /* free ARP packet */
  pbuf_free(p);
}

更新ARP缓存表

etharp_update_arp_entry()函数是用于更新 ARP 缓存表的,它会在收到一个 ARP 数据包的时候被调用,它会先查找一个 ARP 表项,如果没有找到这个 ARP 表项的记录,就会去新建一个 ARP 表项,然后重置 ARP 表项的参数(状态、网卡。 IP 地址与对应的 MAC 地址以及生存时间等),然后检测 ARP 表项中是否挂载数据包,如果有就将这些数据包发送出去,其源码具体见代码

/**
 * Update (or insert) a IP/MAC address pair in the ARP cache.
 *
 * If a pending entry is resolved, any queued packets will be sent
 * at this point.
 *
 * @param netif netif related to this entry (used for NETIF_ADDRHINT)
 * @param ipaddr IP address of the inserted ARP entry.  源IP地址
 * @param ethaddr Ethernet address of the inserted ARP entry.  源以太网MAC地址
 * @param flags See @ref etharp_state
 *
 * @return
 * - ERR_OK Successfully updated ARP cache.
 * - ERR_MEM If we could not add a new ARP entry when ETHARP_FLAG_TRY_HARD was set.
 * - ERR_ARG Non-unicast address given, those will not appear in ARP cache.
 *
 * @see pbuf_free()
 */
static err_t
etharp_update_arp_entry(struct netif *netif, const ip4_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)
{
  s16_t i;
  LWIP_ASSERT("netif->hwaddr_len == ETH_HWADDR_LEN", netif->hwaddr_len == ETH_HWADDR_LEN);
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: %"U16_F".%"U16_F".%"U16_F".%"U16_F" - %02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F"\n",
              ip4_addr1_16(ipaddr), ip4_addr2_16(ipaddr), ip4_addr3_16(ipaddr), ip4_addr4_16(ipaddr),
              (u16_t)ethaddr->addr[0], (u16_t)ethaddr->addr[1], (u16_t)ethaddr->addr[2],
              (u16_t)ethaddr->addr[3], (u16_t)ethaddr->addr[4], (u16_t)ethaddr->addr[5]));
  /* non-unicast address? */
  if (ip4_addr_isany(ipaddr) ||
      ip4_addr_isbroadcast(ipaddr, netif) ||
      ip4_addr_ismulticast(ipaddr)) {
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: will not add non-unicast IP address to ARP cache\n"));
    return ERR_ARG;
  }
  /* find or create ARP entry */
  /* 查找或者创建 ARP 表项,并且返回索引值 */
  i = etharp_find_entry(ipaddr, flags, netif);
  /* bail out if no entry could be found */
  if (i < 0) {
    return (err_t)i;
  }
  {
    /* mark it stable */
    arp_table[i].state = ETHARP_STATE_STABLE; /* 设置表项状态为 ETHARP_STATE_STABLE */
  }

  /* record network interface */
  arp_table[i].netif = netif; /* 记录网卡 */
  /* insert in SNMP ARP index tree */
  mib2_add_arp_entry(netif, &arp_table[i].ipaddr); /* 插入 ARP 索引树 */

  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: updating stable entry %"S16_F"\n", i));
  /* update address */
  SMEMCPY(&arp_table[i].ethaddr, ethaddr, ETH_HWADDR_LEN); /* 更新缓存表中的 MAC 地址 */
  /* reset time stamp */
  arp_table[i].ctime = 0; /* 重置生存时间 */
  /* this is where we will send out queued packets! */
  /* 如果表项上与未发送的数据包,那就将这些数据包发送出去 */
#if ARP_QUEUEING
  while (arp_table[i].q != NULL) {
    struct pbuf *p;
    /* remember remainder of queue */
    struct etharp_q_entry *q = arp_table[i].q; /* 定义 q 指向 ARP 表项中的数据包缓存队列 */
    /* pop first item off the queue */
    arp_table[i].q = q->next; /* 指向下一个数据包节点 */
    /* get the packet pointer */
    p = q->p; /* 获取 pbuf 数据包 */
    /* now queue entry can be freed */
    memp_free(MEMP_ARP_QUEUE, q); /* 释放 MEMP_ARP_QUEUE 类型的内存块 */
#else /* ARP_QUEUEING */
  if (arp_table[i].q != NULL) {
    struct pbuf *p = arp_table[i].q;
    arp_table[i].q = NULL;
#endif /* ARP_QUEUEING */
    /* send the queued IP packet */ /* 发送缓存队列的数据包 */
    ethernet_output(netif, p, (struct eth_addr *)(netif->hwaddr), ethaddr, ETHTYPE_IP);
    /* free the queued IP packet */
    pbuf_free(p);
  }
  return ERR_OK;
}

表项的更新方式,动态表项有两种方式,分别为ETHARP_FLAG_TRY_HARD 和 ETHARP_FLAG_FIND_ONLY。 前者表示无论如何都要创建一个表项, 如果 ARP 缓存表中没有空间了, 那就需要回收较老的表项, 将他们删除, 然后建立新的表项。 而如果是后者, 就让内核尽量更新表项, 如果 ARP 缓存表中没有空间了,那么也无能为力,实在是添加不了新的表项。
在这里插入图片描述

数据包发送流程

经过学习,我们知道一个数据包从底层传递进来的流程是怎么样的,如果是 ARP 数据包就会给 ARP 去处理,如果是 IP 数据报就使用 ip4_input()函数传递到上层。
那么如果上层协议想要发送数据,也肯定需要经过 ARP 协议将 IP 地址映射为 MAC 地址才能完成发送操作, IP 数据报通过 ip4_output()函数将上层数据包传递到ARP 协议处理,关于 IP 协议是怎么样传递的我们暂且不说,那么 ARP 通过 etharp_output()函数接收到 IP 数据报后,就会进行发送, ARP 会先从数据包中进行分析,看看这个 IP 数据报是单播数据包还是多播或者是广播数据包,然后进行不同的处理:
对于多播或者是广播数据包,这种处理就很简单,直接将数据包丢给网卡就行了(调用 ethernet_output()函数)。
对于单播包的处理稍微麻烦一点, ARP 协议需要根据 IP 地址找到对应的 MAC 地址,然后才能正确发送,如果找不到 MAC 地址的话,还要延迟发送数据包,ARP 协议首先会创建一个 ARP 表项,然后将数据包挂到 ARP 表项对应的缓存队列上,与此同时会发出一个 ARP 请求包,等待目标主机的回应后再发送 IP 数据报。
此处需要注意的是,对于 PBUFF_ERF、 PBUF_POOL、 PBUF_RAM 类型的数据包是不允许直接挂到 ARP 表项对应的缓存队列上的,因为此时内核需要等待目标主机的 ARP应答,而这段时间里,这些数据有可能会被上层改动,这是不允许的,所以 LwIP 需要将这些 pbuf 数据包拷贝到新的空间,等待发送。

etharp_output()函数被 IP 层的 ip4_output()函数调用, IP 层传递一个数据包到 ARP 中,etharp_output()会根据数据包的目标 IP 地址选择不同的处理,其源码具体见代码

const struct eth_addr ethbroadcast =
 {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};

 const struct eth_addr ethzero = {{0, 0, 0, 0, 0, 0}};

 /** 24 位 IANA IPv4 多播 OUI 为 01-00-5e: */
 #define LL_IP4_MULTICAST_ADDR_0 0x01
 #define LL_IP4_MULTICAST_ADDR_1 0x00
 #define LL_IP4_MULTICAST_ADDR_2 0x5e
/**
 * Resolve and fill-in Ethernet address header for outgoing IP packet.
 *
 * For IP multicast and broadcast, corresponding Ethernet addresses
 * are selected and the packet is transmitted on the link.
 *
 * For unicast addresses, the packet is submitted to etharp_query(). In
 * case the IP address is outside the local network, the IP address of
 * the gateway is used.
 *
 * @param netif The lwIP network interface which the IP packet will be sent on.
 * @param q The pbuf(s) containing the IP packet to be sent.  将要发送的数据包
 * @param ipaddr The IP address of the packet destination. 要发送的目的IP地址
 *
 * @return
 * - ERR_RTE No route to destination (no gateway to external networks),
 * or the return type of either etharp_query() or ethernet_output().
 */
err_t
etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
{
  const struct eth_addr *dest;
  struct eth_addr mcastaddr;
  const ip4_addr_t *dst_addr = ipaddr;

  LWIP_ASSERT_CORE_LOCKED();
  LWIP_ASSERT("netif != NULL", netif != NULL);
  LWIP_ASSERT("q != NULL", q != NULL);
  LWIP_ASSERT("ipaddr != NULL", ipaddr != 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 (ip4_addr_isbroadcast(ipaddr, netif)) {
    /* broadcast on Ethernet also */
    dest = (const struct eth_addr *)&ethbroadcast; /* 如果是广播数据包,目标 MAC 地址设置为 FF-FF-FF-FF-FF-FF-FF */
    /* multicast destination IP address? */
  } else if (ip4_addr_ismulticast(ipaddr)) {
    /* Hash IP multicast address to MAC address.*/
    /* 如果是多播数据包,目标 MAC 地址设置为多播地址: 01-00-5E-XX-XX-XX*/
    mcastaddr.addr[0] = LL_IP4_MULTICAST_ADDR_0;
    mcastaddr.addr[1] = LL_IP4_MULTICAST_ADDR_1;
    mcastaddr.addr[2] = LL_IP4_MULTICAST_ADDR_2;
    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 {
    netif_addr_idx_t i; /* 如果是单播目标地 IP 地址 */
    /* outside local network? if so, this can neither be a global broadcast nor
       a subnet broadcast. */
       /* 判断目标 IP 地址是否与主机处于同一子网上,如果不是,则修改 IP 地址 */
    if (!ip4_addr_netcmp(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) &&
        !ip4_addr_islinklocal(ipaddr)) {
      {
        {
          /* interface has default gateway? */
          if (!ip4_addr_isany_val(*netif_ip4_gw(netif))) { /* 判断一下网关地址是否有效 */
            /* send to hardware address of default gateway IP address */
            dst_addr = netif_ip4_gw(netif); /* 发送到默认网关,让网关进行转发 */
            /* no default gateway available */
          } else {
            /* no route to destination error (default gateway missing) */
            return ERR_RTE;
          }
        }
      }
    }
#if LWIP_NETIF_HWADDRHINT
    if (netif->hints != NULL) {
      /* per-pcb cached entry was given */
      netif_addr_idx_t etharp_cached_entry = netif->hints->addr_hint;
      if (etharp_cached_entry < ARP_TABLE_SIZE) {
#endif /* LWIP_NETIF_HWADDRHINT */
        if ((arp_table[etharp_cached_entry].state >= ETHARP_STATE_STABLE) &&
#if ETHARP_TABLE_MATCH_NETIF
            (arp_table[etharp_cached_entry].netif == netif) &&
#endif
            (ip4_addr_cmp(dst_addr, &arp_table[etharp_cached_entry].ipaddr))) {
          /* the per-pcb-cached entry is stable and the right one! */
          ETHARP_STATS_INC(etharp.cachehit);
          return etharp_output_to_arp_index(netif, q, etharp_cached_entry);
        }
#if LWIP_NETIF_HWADDRHINT
      }
    }
#endif /* LWIP_NETIF_HWADDRHINT */

    /* find stable entry: do this here since this is a critical path for
       throughput and etharp_find_entry() is kind of slow */
    for (i = 0; i < ARP_TABLE_SIZE; i++) { /* 遍历 ARP 缓存表 */
      if ((arp_table[i].state >= ETHARP_STATE_STABLE) &&
#if ETHARP_TABLE_MATCH_NETIF
          (arp_table[i].netif == netif) &&
#endif
          (ip4_addr_cmp(dst_addr, &arp_table[i].ipaddr))) {
        /* found an existing, stable entry */
        ETHARP_SET_ADDRHINT(netif, i);
        return etharp_output_to_arp_index(netif, q, i); /* 如果找到目标 IP 地址对应的表项,直接发送 */
      }
    }
    /* no stable entry found, use the (slower) query function:
       queue on destination Ethernet address belonging to ipaddr */
    return etharp_query(netif, dst_addr, q); /* 如果没有找到与目标 IP 地址对应的 ARP 表项 */
  }

  /* continuation for multicast/broadcast destinations */
  /* obtain source Ethernet address of the given interface */
  /* send packet directly on the link */
  /* 对于多播、广播数据包,直接能得到对应的 MAC 地址,可以进行发送*/
  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), dest, ETHTYPE_IP);
}

这个函数是 ARP 找到了 IP 地址与 MAC 地址对应的表项,从而能直接进行发送,除此之外, ARP 还需要更新 ARP 表项,我们知道, LwIP 中的 ARP 表项生存时间是 5 分钟(300 秒),那么在 APP 表项的生存时间即将到来的时候, ARP 需要更新表项,为什么要在发送数据的时候更新呢?因为如果不发送数据,那就没必要更新 ARP 表项,这样子表项在生存时间到来的时候就会被系统删除,回收 ARP 表项空间,而一直使用的 ARP 表项需要是谁更新,更新的方式也有两种:
如果 ARP 表项还差 15 秒就过期了, LwIP 会通过广播的方式发送一个 ARP 请求包,试图得到主机的回应。
而如果 ARP 表项还差 30 秒就过期了,那么 LwIP 会通过单播的方式向目标主机发送一个请求包并试图得到回应。
在这种情况下发送 ARP 请求包的时候,表项的状态会由 ETHARP_STATE_STABLE变成 ETHARP_STATE_STABLE_REREQUESTING_1, 如果目标主机回应了,那就更新ARP 缓存表中的表项。
当然,如果还没那么快到期的话,那就直接调用 ethernet_output()函数将数据包传递给网卡进行发送。 函数源码具体见代码

#define ARP_MAXAGE 300

 /* 即将到期的时间 */
 #define ARP_AGE_REREQUEST_USED_UNICAST (ARP_MAXAGE - 30)
 #define ARP_AGE_REREQUEST_USED_BROADCAST (ARP_MAXAGE - 15)

/** Just a small helper function that sends a pbuf to an ethernet address
 * in the arp_table specified by the index 'arp_idx'.
 */
static err_t
etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, netif_addr_idx_t arp_idx)
{
  LWIP_ASSERT("arp_table[arp_idx].state >= ETHARP_STATE_STABLE",
              arp_table[arp_idx].state >= ETHARP_STATE_STABLE);
  /* if arp table entry is about to expire: re-request it,
     but only if its state is ETHARP_STATE_STABLE to prevent flooding the
     network with ARP requests if this address is used frequently. */
     /* 如果 arp 表项即将过期: LwIP 会重新请求它,但只有当它的状态是 ETHARP_STATE_STABLE 才能请求*/
  if (arp_table[arp_idx].state == ETHARP_STATE_STABLE) {
    if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST) {/* 还差 15 秒到期 */
      /* issue a standard request using broadcast */
      if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) { /* 使用广播方式发出请求包 */
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
      }
    } else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST) { /* 还差 30 秒到期 */
      /* issue a unicast request (for 15 seconds) to prevent unnecessary broadcast */
      if (etharp_request_dst(netif, &arp_table[arp_idx].ipaddr, &arp_table[arp_idx].ethaddr) == ERR_OK) { /* 发出单播请求(持续 15 秒),以防止不必要的广播 */
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
      }
    }
  }

  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), &arp_table[arp_idx].ethaddr, ETHTYPE_IP);
}

etharp_query()函数

如果在 ARP 缓存表中没有找到目标 IP 地址对应的表项, 那么 ARP 协议就会创建一个表项, 这也是 ARP 协议的核心处理, 对于刚创建的表项, 它在初始化网卡信息后会被设置为 ETHARP_STATE_PENDING 状态,与此同时一个 ARP 请求包将被广播出去, 这个时候的表项是无法发送数据的,只有等待到目标主机回应了一个 ARP 应答包才能发送数据,那么这些数据在这段时间中将被挂到表项的等待队列上,在 ARP 表项处于ETHARP_STATE_STABLE 状态完成数据的发送,函数源码具体见代码

err_t
etharp_query(struct netif *netif, const ip4_addr_t *ipaddr, struct pbuf *q)
{
  struct eth_addr *srcaddr = (struct eth_addr *)netif->hwaddr;
  err_t result = ERR_MEM;
  int is_new_entry = 0;
  s16_t i_err;
  netif_addr_idx_t i;

  /* non-unicast address? */ /* 检是否为单播地址 */
  if (ip4_addr_isbroadcast(ipaddr, netif) ||
      ip4_addr_ismulticast(ipaddr) ||
      ip4_addr_isany(ipaddr)) {
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: will not add non-unicast IP address to ARP cache\n"));
    return ERR_ARG;
  }

  /* find entry in ARP cache, ask to create entry if queueing packet */
  /* 在 ARP 缓存中查找表项,如果没有则尝试创建表项 */
  i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif);

  /* could not find or create entry? */
  /* 没有发现表项或者没有创建表项成功 */
  if (i_err < 0) {
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not create ARP entry\n"));
    if (q) {
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: packet dropped\n"));
      ETHARP_STATS_INC(etharp.memerr);
    }
    return (err_t)i_err;
  }
  LWIP_ASSERT("type overflow", (size_t)i_err < NETIF_ADDR_IDX_MAX);
  //找到对应的表项或者创建表项成功
  i = (netif_addr_idx_t)i_err;

  /* mark a fresh entry as pending (we just sent a request) */
  /* 将新表项标记为待处理 */
  if (arp_table[i].state == ETHARP_STATE_EMPTY) {
    is_new_entry = 1;
    arp_table[i].state = ETHARP_STATE_PENDING; 
    /* record network interface for re-sending arp request in etharp_tmr */
    arp_table[i].netif = netif; /* 记录网络接口 */
  }

  /* { i is either a STABLE or (new or existing) PENDING entry } */
  LWIP_ASSERT("arp_table[i].state == PENDING or STABLE",
              ((arp_table[i].state == ETHARP_STATE_PENDING) ||
               (arp_table[i].state >= ETHARP_STATE_STABLE)));

  /* do we have a new entry? or an implicit query request? */
  if (is_new_entry || (q == NULL)) {/* 是否有新的表项 */
    /* try to resolve it; send out ARP request */
    result = etharp_request(netif, ipaddr); /* 发送 ARP 请求包*/
    if (result != ERR_OK) {/* 无法发送 ARP 请求 */
      /* 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;
    }
  }

  /* packet given? */
  LWIP_ASSERT("q != NULL", q != NULL);
  /* stable entry? */ /* 表项状态是否稳定 */
  if (arp_table[i].state >= ETHARP_STATE_STABLE) {
    /* we have a valid IP->Ethernet address mapping */
    ETHARP_SET_ADDRHINT(netif, i);
    /* send the packet *//* 发送数据包 */
    result = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);
    /* pending entry? (either just created or already pending */
  } else if (arp_table[i].state == ETHARP_STATE_PENDING) { /* 如果表项是 ETHARP_STATE_PENDING 状态 */
    /* entry is still pending, queue the given packet 'q' */
    struct pbuf *p; /* 将给数据包'q'排队 */
    int copy_needed = 0;
    /* IF q includes a pbuf that must be copied, copy the whole chain into a
     * new PBUF_RAM. See the definition of PBUF_NEEDS_COPY for details. */
    p = q; /* 如果 q 包含必须拷贝的 pbuf,请将整个链复制到一个新的 PBUF_RAM */
    while (p) {
      LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == 0));
      if (PBUF_NEEDS_COPY(p)) {
        copy_needed = 1; //需要拷贝
        break;
      }
      p = p->next;
    }
    if (copy_needed) {
      /* copy the whole packet into new pbufs */
      p = pbuf_clone(PBUF_LINK, PBUF_RAM, q); /* 将整个数据包复制到新的 pbuf 中 */
    } else {
      /* referencing the old pbuf is enough */
      p = q;
      pbuf_ref(p); /* 引用旧的 pbuf 就足够了 */
    }
    /* 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); /* 分配一个新的 arp 队列表项 */
      if (new_entry != NULL) {
        unsigned int qlen = 0;
        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;
          qlen++;
          while (r->next != NULL) {
            r = r->next;
            qlen++;
          }
          r->next = new_entry;
        } else {
          /* queue did not exist, first item in queue */
          arp_table[i].q = new_entry; /* 队列不存在,数据包就是队列的第一个节点 */
        }
#if ARP_QUEUE_LEN
        if (qlen >= ARP_QUEUE_LEN) {
          struct etharp_q_entry *old;
          old = arp_table[i].q;
          arp_table[i].q = arp_table[i].q->next;
          pbuf_free(old->p);
          memp_free(MEMP_ARP_QUEUE, old);
        }
#endif
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"U16_F"\n", (void *)q, i));
        result = ERR_OK;
      } else {
        /* the pool MEMP_ARP_QUEUE is empty */
        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;
      }
#else /* ARP_QUEUEING */
      /* always queue one packet per ARP request only, freeing a previously queued packet */
      /* 如果只是挂载单个数据包,那么始终只为每个 ARP 请求排队一个数据包,就需要释放先前排队的数据包 */
      if (arp_table[i].q != NULL) {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: dropped previously queued packet %p for ARP entry %"U16_F"\n", (void *)q, (u16_t)i));
        pbuf_free(arp_table[i].q);
      }
      arp_table[i].q = p;
      result = ERR_OK;
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"U16_F"\n", (void *)q, (u16_t)i));
#endif /* ARP_QUEUEING */
    } 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;
    }
  }
  return result;
}

在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大文梅

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

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

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

打赏作者

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

抵扣说明:

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

余额充值