【lwip】08-ARP協定一圖筆記及原始碼實現

2022-11-02 12:01:17

前言

主要簡述TCPIP協定族相關的。

ARP協定原始碼在etharp.c和etharp.h中,也是本次筆記的主要內容。

ARP原始碼實現的重要資料結構:

  • ARP快取表。
  • ARP報文。

原文:李柱明部落格

8.1 IP地址與MAC地址

TCP/IP協定的網路層有自己的IP地址。

單看網路層,傳輸封包時只需要知道目標主機的IP地址即可。

但是網路層封包下傳到鏈路層時,鏈路層需要知道下一個節點的MAC地址,才能發包。

為了實現網路層對MAC地址無感,又能實現封包收發,就需要把IP地址和MAC地址繫結。

一個網路卡,有IP地址,而網路卡對接物理裝置時,物理裝置有MAC地址,可以把IP地址和網路卡裝置MAC地址繫結。

而有時候,IP地址可能是動態的,即是當前網路卡裝置根據需求被賦予不同的IP,所以IP地址與MAC地址對映也需要動態才能更好地把網路層和鏈路層分割。

8.2 ARP協定簡介

地址解析協定,即ARP(Address Resolution Protocol),是根據IP地址獲取實體地址的一個TCP/IP協定。

主機A知道主機B的ip地址,但是在二層鏈路,也就是資料鏈路層,是通過mac地址進行轉發的,通過ARP協定實現IP和MAC地址繫結。

ARP協定有靜態獲取和動態獲取:

  • 靜態獲取:即是手動設定ARP對映表。
  • 動態獲取:主機通過ARP協定主機獲取、主機維護ARP對映表。

8.3 ARP協定報文

ARP請求和應答分組的格式如圖:

乙太網首部:

  • 目的MAC(6):鏈路層的資料框的下一個目標結點裝置的MAC。當攜帶ARP請求報文時,乙太網目的地址MAC為廣播地址:0xFFFFFF。
  • 源MAC(6):當前裝置的MAC。
  • 幀型別(2):為0x0806時,表示ARP報文。

ARP報文:

  • 硬體型別(2):硬體地址的型別。

    • 為1即表示乙太網地址。
    • 其它還能表示令牌環地址等。
  • 協定型別(2):表示硬體地址要對映的協定地址型別。

    • 0x0800表示IP地址。
    • 其它還能表示ICMP/IGMP等。
  • 硬體地址長度(1):硬體地址的長度,以位元組為單位。

    • 乙太網上IP地址的ARP請求或應答:該欄位為MAC地址的長度,6。
  • 協定地址長度(1):

    • 乙太網上IP地址的ARP請求或應答:該欄位為IP地址長度,4。
  • OP欄位(2):操作欄位。

    • 1:ARP請求。
    • 2:ARP應答。
    • 3:RARP請求。
    • 4:RARP應答。
  • 傳送端乙太網地址(6)。

  • 傳送端IP地址(4)。

  • 目的乙太網地址(6)。

  • 目的IP地址(4)。

小筆記:

  • 看到上述的報文簡述後,會發現乙太網的資料框首部和ARP請求資料包中都有傳送端的硬體地址。這個很正常,因為兩者所處的OSI層級不一樣,前者是鏈路層處理的資料框頭需要的硬體地址。後者是網路層處理的ARP請求資料包中的重要資訊。
  • ARP請求有些資訊會留空,如ARP請求時,目的端的硬體地址不需要填充。而協定中保留,是為了實現ARP請求和ARP應答的報文欄位一致。

ARP簡要互動:

對於一個ARP請求來說,除目的端硬體地址外的所有其他的欄位都有填充值。

當系統收到一份目的端為本機的ARP請求報文後,它就把硬體地址填進去,然後用兩個目的端地址分別替換兩個傳送端地址,並把操作欄位置為2(ARP應答),最後把它傳送回去。

ARP互動報文例子圖,wireshark分析:

8.4 ARP快取表

8.4.1 ARP快取表簡介

每臺主機或路由器在其記憶體中具有一個ARP快取表(ARP table),這張表包含IP地址到MAC地址的對映關係。

網路層的IP封包需要經過鏈路層轉發時,可以直接查詢快取表是否有這個IP對映的MAC。

如果有,目標鏈路層資料框的目標MAC就直接使用這個MAC,就能轉發了。

如果沒有,通過ARP協定,往鏈路層區域網內廣播一下,詢問下有沒有這個IP對應的結點裝置,如果有就把這個IP對應的鏈路層裝置的MAC返回到這個主機,然後這個主機的鏈路層使用這個MAC傳送資料框出去。而且可以把這個MAC及其對應的IP儲存到自己的ARP快取表中,方便下次直接使用。當然,這個對映也有過期檢查,需要超時機制維護。

8.4.2 LWIP中的快取表

lwip的快取表:static struct etharp_entry arp_table[ARP_TABLE_SIZE];

ARP_TABLE_SIZE預設為10,即是預設能快取10條ARP對映記錄。

8.4.3 ARP快取表資料結構

struct etharp_entry

struct etharp_entry {
#if ARP_QUEUEING
  /* 指向此ARP表項上掛起的封包佇列的指標. */
  struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
  /* 指向此ARP表項上的單個掛起的封包佇列的指標 */
  struct pbuf *q;
#endif /* ARP_QUEUEING */
  /* 目標IP地址 */
  ip4_addr_t ipaddr;
  /* 當前ARP對映記錄對應網路卡資訊 */
  struct netif *netif;
  /* 目標IP對應的MAC地址 */
  struct eth_addr ethaddr;
  /* 當前netry的生存時間 */
  u16_t ctime;
  /* 當前netry的狀態資訊 */
  u8_t state;
};

8.4.4 ARP快取表資料緩衝佇列

struct etharp_q_entry *q;

這個欄位用於指向快取表的封包緩衝佇列。

在IP層傳送一個封包時,會先在ARP對映表中查詢與目的IP地址對應的MAC地址,這樣才能封裝乙太網幀,才能在鏈路層把封包傳送出去。

但是如果IP層傳送一個封包時,在ARP對映表中查不到對應的硬體地址MAC,就傳送一個ARP請求包,在請求過程中,把這個IP層的封包快取到這個佇列q先,直到請求成功後,獲取這個IP層封包IP對應的MAC地址或請求失敗為止。

對於PBUFF_ERFPBUF_POOLPBUF_RAM型別的封包是不允許直接掛到ARP entry的掛起快取佇列上的,因為核心等待目標主機的ARP應答期間,這些資料有可能會被上層改動,所以LwIP需要將這些pbuf封包拷貝到新的空間,等待傳送。

這個佇列的資料結構:

  • 在memp.h的MEMP_ARP_QUEUE記憶體池中有這個資料結構的記憶體資源。共有MEMP_NUM_ARP_QUEUE個,預設為30個。
#if ARP_QUEUEING
struct etharp_q_entry {
  struct etharp_q_entry *next; /* 下一個節點 */
  struct pbuf *p; /* pbuf */
};
#endif /* ARP_QUEUEING */

8.4.5 ARP快取表entry狀態資訊

u8_t state;

/** ARP states */
enum etharp_state {
  ETHARP_STATE_EMPTY = 0, /* 空閒態 */
  ETHARP_STATE_PENDING, /* pending態 */
  ETHARP_STATE_STABLE, /* 有效態 */
  ETHARP_STATE_STABLE_REREQUESTING_1, /* 有效過渡態1 */
  ETHARP_STATE_STABLE_REREQUESTING_2 /* 有效過渡態2 */
#if ETHARP_SUPPORT_STATIC_ENTRIES
  , ETHARP_STATE_STATIC /* 靜態entry */
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};

當前netry的狀態資訊:

  • ETHARP_STATE_EMPTY:空狀態。當前entry資源無效,可以被填充使用。
  • ETHARP_STATE_PENDING:PENDING態。當前entry正在ARP請求,但是還沒收到ARP響應。
  • ETHARP_STATE_STABLE:有效態。當前entry記錄的IP地址與MAC地址對映有效。
  • ETHARP_STATE_STABLE_REREQUESTING_1:有效過渡態1。就是為了防止entry塊過期前頻繁發起ARP請求。
  • ETHARP_STATE_STABLE_REREQUESTING_2:有效過渡態2。
  • ETHARP_STATE_STATIC:靜態條目。手動設定的ARP對映,一直有效。

當表項是ETHARP_STATE_STABLE的時候又傳送一個ARP請求包,那麼表項狀態會暫時被設定為THARP_STATE_STABLE_REREQUESTING_1,然後被設定為ETHARP_STATE_STABLE_REREQUESTING_2狀態,這些是一個過渡狀態,當收到ARP應答後,表項又會被設定為ETHARP_STATE_STABLE,這樣能保持表項的有效。

比如,每個IP層的封包都會先遍歷ARP快取表,如果找到有效的條目後,直接使用該條目,然後會繼續呼叫etharp_output_to_arp_index()把資料傳送出去,原始碼如下:

  • 在傳送IP包時檢查當前被使用的ARP entry是否塊過期,如果快過期,需要發起ARP請求更新當前條目,更新有兩種級別:

    • ARP_AGE_REREQUEST_USED_UNICAST:預設在條目超時前30秒,發起單播級別的ARP請求,減少不必要的廣播。
    • ARP_AGE_REREQUEST_USED_BROADCAST:預設在條目超時前15秒,發起廣播級別的ARP請求。
/* 為避免由於ARP表項超時導致穩定使用的連線中斷,需要在ARP表項過期前重新請求,更新ARP快取表 */
#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);
  /* 在entry快過期前,發起ARP請求進行更新。
    為了防止在這段時間頻繁發起ARP請求,所以引入有效過渡態。
    這裡為有效態才能發起ARP請求。 */
  if (arp_table[arp_idx].state == ETHARP_STATE_STABLE) {
    if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST) {
      /* 使用廣播級別(過期前15秒),發起標準ARP請求 */
      if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) {
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1; /* 更新為有效過渡態1 */
      }
    } else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST) {
      /* 發出單播ARP請求(過期前30秒),以防止不必要的廣播 */
      if (etharp_request_dst(netif, &arp_table[arp_idx].ipaddr, &arp_table[arp_idx].ethaddr) == ERR_OK) {
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1; /* 更新為有效過渡態1 */
      }
    }
  }
  /* IP層傳送資料 */
  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), &arp_table[arp_idx].ethaddr, ETHTYPE_IP);
}

8.4.6 ARP快取表超時處理

ARP快取表每條對映記錄都是有有效期的(靜態除外),在struct etharp_entryu16_t ctime;這個欄位中記錄這點錢entry的生存時間。

ARP超時處理常式是etharp_tmr(),是一個週期定時函數。

相關宏:

ARP_TMR_INTERVAL:該函數的節拍為ARP_TMR_INTERVAL,預設為1000,即是1秒跑一次。

ARP_MAXAGE:用於限制ARP條目最大生存時間,預設為300,且節拍為1秒,所以ARP的entry預設最大生存時間是5分鐘。

ARP_MAXPENDING:限制ARP請求響應超時,也是重發ARP請求的次數,預設為5,且節拍為1秒,所以ARP請求響應預設超時為5秒。

/**
 * 清除ARP表中過期的表項
 * 該函數應該每隔ARP_TMR_INTERVAL毫秒(1秒)呼叫一次,以使ARP表項過期
 */
void
etharp_tmr(void)
{
  int i;

  LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\n"));
  /* 遍歷、刪除ARP表中過期的表項 */
  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
    u8_t state = arp_table[i].state;
    if (state != ETHARP_STATE_EMPTY /* 跳過空閒態的entry */
#if ETHARP_SUPPORT_STATIC_ENTRIES
        && (state != ETHARP_STATE_STATIC) /* 跳過靜態的entry */
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
       ) {
      arp_table[i].ctime++; /* 記錄當前entry的生存時間 */
      if ((arp_table[i].ctime >= ARP_MAXAGE) ||
          ((arp_table[i].state == ETHARP_STATE_PENDING)  &&
           (arp_table[i].ctime >= ARP_MAXPENDING))) { /* entry生存時間超時或者ARP請求超時都要清空本entry */
        /* 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));
        /* 清理剛剛過期的條目 */
        etharp_free_entry(i);
      } else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_1) {
        /* 過渡態1更新到過渡態2,為了防止上層在兩秒能發起多次ARP更新請求 */
        arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
      } else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2) {
        /* 恢復到有效態。允許ARP */
        arp_table[i].state = ETHARP_STATE_STABLE;
      } else if (arp_table[i].state == ETHARP_STATE_PENDING) {
        /* 還沒收到ARP響應,重發ARP請求 */
        etharp_request(arp_table[i].netif, &arp_table[i].ipaddr);
      }
    }
  }
}

8.4.7 ARP快取表entry更新

能觸發ARP快取表entry更新的情況:

  1. ARP超時處理。把過期或者ARP請求超時的arp entry刪除。

  2. IP層傳送資料。經過ARP協定時:

    • arp entry更新:在對應arp entry快過期前發起ARP請求(更新arp entry)。
    • arp entry新建:ARP快取表中沒有找到對應entry時,新建一個。

新建arp entry邏輯:

呼叫etharp_find_entry()函數獲取一個entry。

該函數的引數u8_t flags;表示申請arp entry資源的方式:

  • ETHARP_FLAG_TRY_HARD:允許覆蓋已有arp entry。

    • 在需要新建arp entry,且沒有發現空閒的arp entry時,強制覆蓋一條。優先被覆蓋的順序:empty entry > oldest stable entry > oldest pending entry without queued packets > oldest pending entry with queued packets
  • ETHARP_FLAG_FIND_ONLY:唯讀模式。如果ARP快取表中沒有匹配IP(和網路卡)的arp entry,則返回失敗,封包處理終止。

8.5 ARP協定超時機制框圖

8.6 ARP收發報文資料流圖

8.7 ARP報文組包原始碼實現

8.7.1 ARP報文資料結構

/** the ARP message, see RFC 826 ("Packet format") */
struct etharp_hdr {
  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); /* 操作欄位 */
  PACK_STRUCT_FLD_S(struct eth_addr shwaddr); /* 源硬體地址 */
  PACK_STRUCT_FLD_S(struct ip4_addr_wordaligned sipaddr); /* 源協定地址 */
  PACK_STRUCT_FLD_S(struct eth_addr dhwaddr); /* 目標硬體地址 */
  PACK_STRUCT_FLD_S(struct ip4_addr_wordaligned dipaddr); /* 目標協定地址 */
} PACK_STRUCT_STRUCT;

8.7.2 ARP報文組建傳送函數(基函數)

ARP請求包是通過etharp_raw()函數進行組包和傳送的,然後通過封裝該函數得出不同需求的ARP請求函數供給上層使用。

/**
 * Send a raw ARP packet (opcode and all addresses can be modified)
 *
 * @param netif the lwip network interface on which to send the ARP packet
 * @param ethsrc_addr the source MAC address for the ethernet header
 * @param ethdst_addr the destination MAC address for the ethernet header
 * @param hwsrc_addr the source MAC address for the ARP protocol header
 * @param ipsrc_addr the source IP address for the ARP protocol header
 * @param hwdst_addr the destination MAC address for the ARP protocol header
 * @param ipdst_addr the destination IP address for the ARP protocol header
 * @param opcode the type of the ARP packet
 * @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);

  /* 鏈路層組包,為ARP報文申請記憶體資源 */
  p = pbuf_alloc(PBUF_LINK, SIZEOF_ETHARP_HDR, PBUF_RAM);
  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;
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_raw: sending raw ARP packet.\n"));
  hdr->opcode = lwip_htons(opcode); /* 操作欄位 */

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

  SMEMCPY(&hdr->shwaddr, hwsrc_addr, ETH_HWADDR_LEN); /* 源MAC欄位 */
  SMEMCPY(&hdr->dhwaddr, hwdst_addr, ETH_HWADDR_LEN); /* 目標MAC欄位 */
  IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->sipaddr, ipsrc_addr); /* 源IP欄位 */
  IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->dipaddr, ipdst_addr); /* 目標IP欄位 */

  hdr->hwtype = PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET); /* 硬體型別 */
  hdr->proto = PP_HTONS(ETHTYPE_IP); /* 協定型別 */
  hdr->hwlen = ETH_HWADDR_LEN; /* 硬體地址長度 */
  hdr->protolen = sizeof(ip4_addr_t); /* 協定地址長度 */

  /* 傳送ARP包 */

#if LWIP_AUTOIP
  /* 如果我們源IP為本地鏈路的IP,說明本ARP報文為ARP探測包,用於檢查當前鏈路這個目標IP是否被佔用了。所以需要用乙太網幀的目標MAC需要設定為廣播MAC(參見RFC3927第2.5節最後一段)*/
  if (ip4_addr_islinklocal(ipsrc_addr)) {
    ethernet_output(netif, p, ethsrc_addr, &ethbroadcast, ETHTYPE_ARP); /* 傳送乙太網幀 */
  } else
#endif /* LWIP_AUTOIP */
  { /* 非ARP探測包,可按指定MAC封裝乙太網首部的目標MAC */
    ethernet_output(netif, p, ethsrc_addr, ethdst_addr, ETHTYPE_ARP); /* 傳送乙太網幀 */
  }
  /* lwip狀態記錄 */
  ETHARP_STATS_INC(etharp.xmit);
  /* 釋放當前ARP報文資源 */
  pbuf_free(p);
  p = NULL;
  return result;
}

8.7.3 傳送ARP請求包

etharp_request_dst()

  • 發起ARP請求。
  • 可指定目標MAC。
/**
 * 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
 * @param hw_dst_addr the ethernet address to send this packet to
 * @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); /* ARP請求 */
}

etharp_request_dst()

  • 發起ARP標準請求包。(廣播包)
/**
 * 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, 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);
}

8.7.4 傳送ARP IP探測包

etharp_acd_probe()

  • 發起ARP IP探測。
  • 用於傳送探測訊息進行地址衝突檢測。
/**
 * Send an ARP request packet probing for an ipaddr.
 * Used to send probe messages for address conflict detection.
 *
 * @param netif the lwip network interface on which to send the request
 * @param ipaddr the IP address to probe
 * @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_acd_probe(struct netif *netif, const ip4_addr_t *ipaddr)
{
  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
                    (struct eth_addr *)netif->hwaddr, IP4_ADDR_ANY4, &ethzero,
                    ipaddr, ARP_REQUEST);
}

8.7.5 傳送ARP IP宣告包

etharp_acd_announce()

  • 發起ARP IP宣告。
  • 用於傳送地址衝突檢測的公告訊息。
/**
 * Send an ARP request packet announcing an ipaddr.
 * Used to send announce messages for address conflict detection.
 *
 * @param netif the lwip network interface on which to send the request
 * @param ipaddr the IP address to announce
 * @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_acd_announce(struct netif *netif, const ip4_addr_t *ipaddr)
{
  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
                    (struct eth_addr *)netif->hwaddr, ipaddr, &ethzero,
                    ipaddr, ARP_REQUEST);
}

8.8 封包傳送分析

8.8.1 資料發包處理簡述(ARP相關)

主要分析ARP協定層的處理。

IP層封包通過ip4_output()函數傳遞到ARP協定處理。通過ARP協定獲得目標IP主機的MAC地址才能封裝乙太網幀,在鏈路層轉發。

etharp_output()收到IP層發來的封包後按一下邏輯分支處理:

  • 對於廣播或者多播的封包:呼叫ethernet_output()函數直接把封包丟給網路卡即可。

    • MAC的多播地址範圍:01:00:5E:00:00:00 —— 01:00:5E:7F:FF:FF
  • 對於單播封包:

    • 遍歷ARP快取表:遍歷時,可以從當前網路卡上次傳送封包使用的arp entry開始查起,找到就呼叫etharp_output_to_arp_index()把IP封包轉交給鏈路層轉發。

      • etharp_output_to_arp_index()概述裡面會更新維護ARP快取表當前arp entry。
    • 發起ARP請求:如果快取表中沒有當前IP封包目標IP對映的MAC地址,就需要呼叫etharp_query(),把IP封包轉交給ARP協定處理。

      • etharp_query()會發起ARP請求,在ARP請求過程中,把這些IP層的封包儲存到當前ARP條目的entry的掛起快取佇列中。直到收到ARP響應或者ARP請求超時為止。

對於PBUFF_ERFPBUF_POOLPBUF_RAM型別的封包是不允許直接掛到ARP entry的掛起快取佇列上的,因為核心等待目標主機的ARP應答期間,這些資料有可能會被上層改動,所以LwIP需要將這些pbuf封包拷貝到新的空間,等待傳送。

8.8.2 etharp_output():IP封包是否ARP協定處理

解析和填寫乙太網地址頭為傳出IP封包。

/**
 * 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.
 *
 * @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;
  /* tcpip核心鎖上鎖檢查 */
  LWIP_ASSERT_CORE_LOCKED();
  /* 引數校驗 */
  LWIP_ASSERT("netif != NULL", netif != NULL);
  LWIP_ASSERT("q != NULL", q != NULL);
  LWIP_ASSERT("ipaddr != NULL", ipaddr != NULL);

  if (ip4_addr_isbroadcast(ipaddr, netif)) {
    /* 目標IP為廣播地址 */
    /* 目標MAC也設定為廣播地址:FF-FF-FF-FF-FF-FF-FF */
    dest = (const struct eth_addr *)&ethbroadcast;
  } else if (ip4_addr_ismulticast(ipaddr)) {
    /* 目標IP為多播地址 */
    /* 目標MAC也設定為多播地址:01:00:5E:00:00:00 —— 01:00:5E:7F:FF:FF */
    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);
    dest = &mcastaddr;
  } else { /* 目標IP為單播地址 */
    netif_addr_idx_t i;
    /* 判斷目標IP是否和原IP主機處於同一個子網上,如果不是,則修改要查詢MAC的的IP為當前網路卡的閘道器IP */
    if (!ip4_addr_net_eq(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) &&
        !ip4_addr_islinklocal(ipaddr)) { /* 不是同一個子網,也不是本地鏈路地址,需要把封包轉發到閘道器 */
#if LWIP_AUTOIP
      struct ip_hdr *iphdr = LWIP_ALIGNMENT_CAST(struct ip_hdr *, q->payload);
      /* 根據RFC 3297 2.6.2章(轉發規則),如果IP地址為本地鏈路地址(169.254.0.0/16),這樣的IP封包是不能通過路由器轉發的 */
      if (!ip4_addr_islinklocal(&iphdr->src)) /* 源IP地址不是本地鏈路地址 */
#endif /* LWIP_AUTOIP */
      {
#ifdef LWIP_HOOK_ETHARP_GET_GW
        /* 閘道器勾點函數,可以自定義選擇閘道器 */
        dst_addr = LWIP_HOOK_ETHARP_GET_GW(netif, ipaddr);
        if (dst_addr == NULL)
#endif /* LWIP_HOOK_ETHARP_GET_GW */
        {
          /* 檢視網路卡是否有預設閘道器 */
          if (!ip4_addr_isany_val(*netif_ip4_gw(netif))) {
            /* 獲取網路卡預設閘道器 */
            dst_addr = netif_ip4_gw(netif);
          } else { /* 沒找到有效閘道器 */
            /* 沒有路由到目的地錯誤(預設閘道器丟失) */
            return ERR_RTE;
          }
        }
      }
    }
#if LWIP_NETIF_HWADDRHINT
    if (netif->hints != NULL) {
      /* 網路卡中上次發包時使用的arp entry優先遍歷 */
      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) && /* arp entry有效 */
#if ETHARP_TABLE_MATCH_NETIF
            (arp_table[etharp_cached_entry].netif == netif) && /* arp entry對應網路卡匹配 */
#endif
            (ip4_addr_eq(dst_addr, &arp_table[etharp_cached_entry].ipaddr))) { /* 找到目標IP的MAC對映 */
          ETHARP_STATS_INC(etharp.cachehit); /* 記錄lwip arp相關狀態 */
          /* 傳送IP封包 */
          return etharp_output_to_arp_index(netif, q, etharp_cached_entry);
        }
#if LWIP_NETIF_HWADDRHINT
      }
    }
#endif /* LWIP_NETIF_HWADDRHINT */

    /* 如果網路卡指定先遍歷的arp entry沒有找到合法對映,就需要遍歷ARP快取表 */
    for (i = 0; i < ARP_TABLE_SIZE; i++) {
      if ((arp_table[i].state >= ETHARP_STATE_STABLE) && /* arp entry有效 */
#if ETHARP_TABLE_MATCH_NETIF
          (arp_table[i].netif == netif) && /* 網路卡匹配對應 */
#endif
          (ip4_addr_eq(dst_addr, &arp_table[i].ipaddr))) { /* 目標IP對應 */
        /* 找到目標IP的MAC對映 */
        /* 把當前arp entry索引儲存到網路卡中,以便下次快速遍歷 */
        ETHARP_SET_ADDRHINT(netif, i);
        /* 傳送IP封包 */
        return etharp_output_to_arp_index(netif, q, i);
      }
    }
    /* 在ARP快取表中沒有找到對應的ARP entry,就需要發起ARP 請求 */
    return etharp_query(netif, dst_addr, q);
  }

  /* 對於廣播或組播的封包,直接知道了目標硬體欄位的MAC地址,可以直接往鏈路層傳送 */
  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), dest, ETHTYPE_IP);
}

8.8.3 etharp_output_to_arp_index():需要維護arp entry的IP封包轉發函數

etharp_output_to_arp_index()這個函數實現維護arp entry超時前更新,然後把封包通過ethernet_output()轉交給鏈路層。

其原始碼參考前面快取表狀態。

8.8.4 etharp_query():需要發起ARP請求的IP封包轉發函數

如果IP封包為單播包,且在 ARP 快取表中沒有找到對應的MAC地址,就需要呼叫etharp_query()函數發起ARP請求處理。

主要內容:

  • IP地址校驗。只能是有效的單播IP地址。
  • 找到可操作的arp entry。
  • 發起ARP請求。
  • 拷貝pbuf儲存到arp entry快取佇列。
/**
 * Send an ARP request for the given IP address and/or queue a packet.
 *
 * If the IP address was not yet in the cache, a pending ARP cache entry
 * is added and an ARP request is sent for the given address. The packet
 * is queued on this entry.
 *
 * If the IP address was already pending in the cache, a new ARP request
 * is sent for the given address. The packet is queued on this entry.
 *
 * If the IP address was already stable in the cache, and a packet is
 * given, it is directly sent and no ARP request is sent out.
 *
 * If the IP address was already stable in the cache, and no packet is
 * given, an ARP request is sent out.
 *
 * @param netif The lwIP network interface on which ipaddr
 * must be queried for.
 * @param ipaddr The IP address to be resolved.
 * @param q If non-NULL, a pbuf that must be delivered to the IP address.
 * q is not freed by this function.
 *
 * @note q must only be ONE packet, not a packet queue!
 *
 * @return
 * - ERR_BUF Could not make room for Ethernet header.
 * - ERR_MEM Hardware address unknown, and no more ARP entries available
 *   to query for address or queue the packet.
 * - ERR_MEM Could not queue packet due to memory shortage.
 * - ERR_RTE No route to destination (no gateway to external networks).
 * - ERR_ARG Non-unicast address given, those will not appear in ARP cache.
 *
 */
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;

  /* 如果是廣播、組播或者無效的單播,不需要發起ARP請求,不用在繼續往下處理 */
  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;
  }

  /* 按規則找到arp entry進行操作 */
  i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif);

  if (i_err < 0) { /* 沒找到能用的arp entry */
    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; /* 找到合法的arp entry */

  if (arp_table[i].state == ETHARP_STATE_EMPTY) { /* 是新的arp entry,做下標記 */
    is_new_entry = 1; /* 標記下當前為新的arp entry */
    arp_table[i].state = ETHARP_STATE_PENDING; /* 更新為pending態 */
    /* 儲存網路卡到arp entry中 */
    arp_table[i].netif = netif;
  }

  /* arp請求中或者arp 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)) { /* 新的arp entry或者隱式ARP請求都需要重新發起ARP請求 */
    /* 傳送ARP請求 */
    result = etharp_request(netif, ipaddr);
    if (result != ERR_OK) {
      /* ARP請求傳送失敗 */
      /* 該故障可能只是暫時的,而且在etharp_tmr()週期定時函數中會重新發出ARP請求,所以這裡先跳過 */
    } else {
      /* ARP請求傳送成功 */
      if ((arp_table[i].state == ETHARP_STATE_PENDING) && !is_new_entry) {
        /* 隱式ARP請求,傳送請求成功,重置下ctime,不讓其快過期 */
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: reset ctime for entry %"S16_F"\n", (s16_t)i));
        arp_table[i].ctime = 0;
      }
    }
    if (q == NULL) {
      return result; /* 隱式ARP請求的話,不需要往下儲存資料了 */
    }
  }

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

  if (arp_table[i].state >= ETHARP_STATE_STABLE) { /* 當前條目已經有效 */
    /* 更新下當前網路卡傳送IP封包時使用的arp entry,方便下次優先遍歷 */
    ETHARP_SET_ADDRHINT(netif, i);
    /* 可以直接把IP封包交給鏈路層轉發 */
    result = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);
  } else if (arp_table[i].state == ETHARP_STATE_PENDING) { /* ARP請求中 */
    /* 需要把當前IP封包放到當前arp entry的快取佇列中 */
    struct pbuf *p;
    int copy_needed = 0;
    /* 如果q這個pbuf鏈中包含一個可改動的pbuf(即是PBUFF_ERF、PBUF_POOL、PBUF_RAM),都需要拷貝整個pbuf鏈到arp entry的快取佇列中. */
    p = q;
    while (p) {
      LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == NULL));
      if (PBUF_NEEDS_COPY(p)) { /* 遍歷pbuf鏈中各個pbuf,是否含有可改動的pbuf */
        copy_needed = 1; /* 需要拷貝整個pbuf鏈 */
        break;
      }
      p = p->next;
    }
    if (copy_needed) {
      /* 將整個包複製到新的pbuf中 */
      p = pbuf_clone(PBUF_LINK, PBUF_RAM, q);
    } else {
      /* 參照舊的pbuf就足夠了 */
      p = q;
      pbuf_ref(p);
    }

    if (p != NULL) {
#if ARP_QUEUEING
      struct etharp_q_entry *new_entry;
      /* 申請一個新的 arp entry queue 節點資源 */
      new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
      if (new_entry != NULL) {
        unsigned int qlen = 0; /* 記錄佇列節點數 */
        new_entry->next = NULL;
        new_entry->p = p; /* 把IP層需要傳送的封包先放到當前arp entry queue節點 */
        if (arp_table[i].q != NULL) {
          /* 佇列已經存在,將新條目插入到佇列尾 */
          struct etharp_q_entry *r;
          r = arp_table[i].q;
          qlen++;
          while (r->next != NULL) {
            r = r->next;
            qlen++;
          }
          r->next = new_entry;
        } else {
          /* 佇列不存在,當前節點放到佇列首部 */
          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; /* ARP請求處理完畢 */
      } else { /* ARP快取節點MEMP_ARP_QUEUE記憶體池沒有資源了 */
        pbuf_free(p); /* 釋放拷貝的的pbuf資源 */
        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 */ /* arp entry快取佇列只能是單包設定 */
      /* 對於每個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 { /* 拷貝pbuf連結串列時PBUF_RAM記憶體堆空間不足 */
      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; /* 搞完 */
}

8.8.5 etharp_find_entry():查詢可被新建的arp entry

  • 優先尋找匹配IP(和網路卡)的arp entry。
  • 然後再尋找最不重要的arp entry。優先被覆蓋的順序:empty entry > oldest stable entry > oldest pending entry without queued packets > oldest pending entry with queued packets
/**
 * Search the ARP table for a matching or new entry.
 *
 * If an IP address is given, return a pending or stable ARP entry that matches
 * the address. If no match is found, create a new entry with this address set,
 * but in state ETHARP_EMPTY. The caller must check and possibly change the
 * state of the returned entry.
 *
 * If ipaddr is NULL, return a initialized new entry in state ETHARP_EMPTY.
 *
 * In all cases, attempt to create new entries from an empty entry. If no
 * empty entries are available and ETHARP_FLAG_TRY_HARD flag is set, recycle
 * old entries. Heuristic choose the least important entry for recycling.
 *
 * @param ipaddr IP address to find in ARP cache, or to add if not found.
 * @param flags See @ref etharp_state
 * @param netif netif related to this address (used for NETIF_HWADDRHINT)
 *
 * @return The ARP entry index that matched or is created, ERR_MEM if no
 * entry is found or could be recycled.
 */
static s16_t
etharp_find_entry(const ip4_addr_t *ipaddr, u8_t flags, struct netif *netif)
{
  s16_t old_pending = ARP_TABLE_SIZE; /* pending態沒有阻塞資料最老的arp entry */
  s16_t old_stable = ARP_TABLE_SIZE; /* 有效態最老的arp entry */
  s16_t empty = ARP_TABLE_SIZE;
  s16_t i = 0;
  s16_t old_queue = ARP_TABLE_SIZE; /* pending態有阻塞資料最老的arp entry */
  /* 對應的age */
  u16_t age_queue = 0, age_pending = 0, age_stable = 0;

  LWIP_UNUSED_ARG(netif); /* 防止編譯警告 */

  /**
   * a) do a search through the cache, remember candidates
   * b) select candidate entry
   * c) create new entry
   */

  /* a) in a single search sweep, do all of this
   * 1) remember the first empty entry (if any)
   * 2) remember the oldest stable entry (if any)
   * 3) remember the oldest pending entry without queued packets (if any)
   * 4) remember the oldest pending entry with queued packets (if any)
   * 5) search for a matching IP entry, either pending or stable
   *    until 5 matches, or all entries are searched for.
   */

  for (i = 0; i < ARP_TABLE_SIZE; ++i) { /* 遍歷ARP快取表 */
    u8_t state = arp_table[i].state;
    if ((empty == ARP_TABLE_SIZE) && (state == ETHARP_STATE_EMPTY)) {
      /* 檢索到空閒的arp entry */
      LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_find_entry: found empty entry %d\n", (int)i));
      /* 記住第一個空閒的arp entry */
      empty = i;
    } else if (state != ETHARP_STATE_EMPTY) { /* 當前遍歷到的arp entry不是空閒態 */
      LWIP_ASSERT("state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE",
                  state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE);
      if (ipaddr && ip4_addr_eq(ipaddr, &arp_table[i].ipaddr) /* 匹配當前arp entry的IP */
#if ETHARP_TABLE_MATCH_NETIF
          && ((netif == NULL) || (netif == arp_table[i].netif)) /* 匹配當前arp entry的網路卡 */
#endif /* ETHARP_TABLE_MATCH_NETIF */
         ) {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: found matching entry %d\n", (int)i));
        /* 已經找到符合要求,且已經存在的arp entry了 */
        return i;
      }
      if (state == ETHARP_STATE_PENDING) { /* peding態 */
        if (arp_table[i].q != NULL) { /* 有阻塞封包 */
          if (arp_table[i].ctime >= age_queue) { /* 當前arp entry更老 */
            /* 更新值 */
            old_queue = i;
            age_queue = arp_table[i].ctime;
          }
        } else
        { /* 沒有阻塞封包 */
          if (arp_table[i].ctime >= age_pending) { /* 當前arp entry更老 */
            /* 更新值 */
            old_pending = i;
            age_pending = arp_table[i].ctime;
          }
        }
      } else if (state >= ETHARP_STATE_STABLE) { /* 有效態 */
#if ETHARP_SUPPORT_STATIC_ENTRIES
        /* 不要處理靜態的arp entry */
        if (state < ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
        {
          if (arp_table[i].ctime >= age_stable) { /* 當前arp entry更老 */
            /* 更新值 */
            old_stable = i;
            age_stable = arp_table[i].ctime;
          }
        }
      }
    }
  }

  /* 已經遍歷ARP快取表完畢,沒找到匹配IP已有的arp entry。 */

  if (((flags & ETHARP_FLAG_FIND_ONLY) != 0) || /* 唯讀模式,沒有匹配的arp entry就直接退出 */
      ((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_FLAG_TRY_HARD) == 0))) { /* 非唯讀模式,但是沒找到空閒arp entry,又不能覆蓋,也直接退出 */
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty entry found and not allowed to recycle\n"));
    return (s16_t)ERR_MEM;
  }

  /* b) choose the least destructive entry to recycle:
   * 1) empty entry
   * 2) oldest stable entry
   * 3) oldest pending entry without queued packets
   * 4) oldest pending entry with queued packets
   *
   * { ETHARP_FLAG_TRY_HARD is set at this point }
   */

  if (empty < ARP_TABLE_SIZE) { /* 找到空閒的arp entry */
    i = empty;
    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting empty entry %d\n", (int)i));
  } else { /* 沒找到空閒的arp entry */
    if (old_stable < ARP_TABLE_SIZE) { /* 2) 先找最老的有效態arp entry */
      /* 回收最老的有效態arp entry */
      i = old_stable;
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest stable entry %d\n", (int)i));
      /* stable態arp entry上不應該存在排隊的封包 */
      LWIP_ASSERT("arp_table[i].q == NULL", arp_table[i].q == NULL);
    } else if (old_pending < ARP_TABLE_SIZE) { /* 3) 再找沒有阻塞資料的pending態的arp entry */
      /* 回收這條arp entry */
      i = old_pending;
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %d (without queue)\n", (int)i));
    } else if (old_queue < ARP_TABLE_SIZE) {  /* 4) 最後才找有阻塞資料的pending態的arp entry */
      /* 回收這條arp entry */
      i = old_queue;
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %d, freeing packet queue %p\n", (int)i, (void *)(arp_table[i].q)));
    } else { /* 沒找到 */
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty or recyclable entries found\n"));
      return (s16_t)ERR_MEM;
    }

    LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
    /* 找到需要需要回收的arp entry,將其回收 */
    etharp_free_entry(i);
  }

  /* 確保當前arp entry已經為空閒態,且索引不超限制 */
  LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
  LWIP_ASSERT("arp_table[i].state == ETHARP_STATE_EMPTY",
              arp_table[i].state == ETHARP_STATE_EMPTY);

  if (ipaddr != NULL) {
    /* 儲存IP到新建arp entry */
    ip4_addr_copy(arp_table[i].ipaddr, *ipaddr);
  }
  arp_table[i].ctime = 0; /* 重置age */
#if ETHARP_TABLE_MATCH_NETIF
  arp_table[i].netif = netif; /* 儲存網路卡到新建arp entry */
#endif /* ETHARP_TABLE_MATCH_NETIF */
  return (s16_t)i; /* 返回找到符合要求的arp entry */
}

8.9 封包接收分析

主要分析ARP協定層的處理。

通過前面分析了乙太網鏈路層收到資料框後j由ethernet_input(),如果是一個合法的乙太網資料框,並且協定是ARP型別,就會上傳到etharp_input()處理。

etharp_input()主要內容是:

  1. 檢查ARP報文。

  2. ARP請求報文:如果是請求報文的目標IP和原生的匹配,就組裝ARP響應報文並行送出去。

  3. ARP響應報文:

    1. 更新ARP快取表。
    2. 把阻塞在arp entry快取佇列的IP資料包傳送出去。
    3. 釋放pbuf。因為ARP報文到此已經處理完畢。

etharp_input()

/**
 * 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;
  ip4_addr_t sipaddr, dipaddr;
  u8_t for_us, from_us;

  LWIP_ASSERT_CORE_LOCKED();

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

  hdr = (struct etharp_hdr *)p->payload; /* 把ARP報文拿出來 */

  /* RFC 826 "Packet Reception": */
  /* 檢查ARP包的合法性 */
  if ((hdr->hwtype != PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET)) || /* 硬體地址型別只支援乙太網型別 */
      (hdr->hwlen != ETH_HWADDR_LEN) || /* 硬體地址長度檢查(MAC型別為6) */
      (hdr->protolen != sizeof(ip4_addr_t)) || /* 協定地址長度檢查。目前只支援IPV4 */
      (hdr->proto != PP_HTONS(ETHTYPE_IP)))  /* 協定地址型別,IPv4型別 */
    { /* ARP報文校驗不通過 */
    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 */
    pbuf_free(p);
    return;
  }
  /* ARP報文校驗通過,記錄下 */
  ETHARP_STATS_INC(etharp.recv);

#if LWIP_ACD
  /* We have to check if a host already has configured our ip address and
   * continuously check if there is a host with this IP-address so we can
   * detect collisions.
   * acd_arp_reply ensures the detection of conflicts. It will handle possible
   * defending or retreating and will make sure a new IP address is selected.
   * etharp_input does not need to handle packets that originate "from_us".
   */
  acd_arp_reply(netif, hdr); /* IP衝突處理。AUTOIP協定範疇,如果開啟了AUTOIP,每一個收到的ARP包都會先經過這個處理。 */
#endif /* LWIP_ACD */

  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr); /* 把ARP報文的源IP地址提前出來 */
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr); /* 把ARP報文的目標IP地址提前出來 */

  if (ip4_addr_isany_val(*netif_ip4_addr(netif))) { /* 主機網路卡沒有設定IP地址 */
    for_us = 0; /* 這個ARP包不是給我們的 */
    from_us = 0; /* 也不是從我們這裡發出去再被轉回來的 */
  } else { /* 主機網路卡已經被設定了IP,可以繼續分析ARP報文 */
    /* ARP報文是否是指向我們的 */
    for_us = (u8_t)ip4_addr_eq(&dipaddr, netif_ip4_addr(netif));
    /* 收到的這個ARP報文是否是從我們這裡發出的 */
    from_us = (u8_t)ip4_addr_eq(&sipaddr, netif_ip4_addr(netif));
  }

  /* 如果這個ARP報文是給我們的:
      -> 更新ARP快取表; 
      -> 如果是一個ARP請求報文,那就組裝ARP響應報文回去。
      -> 如果是一個ARP響應報文,那就把當前arp entry中的快取佇列資料發出去。
     如果這個ARP報文不是給我們的:
      ->  如果快取表中有空閒的arp entry,我們也可以儲存這個IP-MAC對映 */
  etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
                          for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY); /* 更新ARP快取表 */

  /* 檢查當前ARP報文的型別 */
  switch (hdr->opcode) {
    case PP_HTONS(ARP_REQUEST): /* ARP請求 */
      /* ARP request. 如果是詢問我們這個IP對映的MAC地址,那麼就組裝ARP響應報文回去 */
      LWIP_DEBUGF (ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: incoming ARP request\n"));
      if (for_us && !from_us) { /* 這個ARP請求是詢問我們的 */
        /* 傳送ARP響應 */
        etharp_raw(netif,
                   (struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
                   (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
                   &hdr->shwaddr, &sipaddr,
                   ARP_REPLY);
      } else if (ip4_addr_isany_val(*netif_ip4_addr(netif))) { /* 我們還沒有設定IP */
        /* { 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"));
      } else { /* 這個ARP請求不是給我們的 */
        /* { 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 reply. 已經更新了ARP快取表 */
      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: incoming ARP reply\n"));
      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); /* 收到的ARP報文異常,記錄下 */
      break;
  }
  /* ARP報文處理完畢,釋放資源 */
  pbuf_free(p);
}

8.10 LWIP ARP一圖筆記