精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

LwIP中的ARP實現是什么

汽車電子技術 ? 來源:物聯網IoT開發 ? 作者: 杰杰mcu ? 2023-02-14 10:12 ? 次閱讀

前言

從前面的文章,我們知道,ARP協議的核心是ARP緩存表,而ARP協議的實質就是對緩存表項(entry)的建立、更新、查詢等操作。

那么,LwIP中是是怎么實現ARP協議的呢?

ARP緩存表的數據結構

LwIP使用一個arp_table數組描述ARP緩存表,數組的內容是表項的內容,每個表項都必須記錄一對IP地址與MAC地址的映射關系,此外還有一些基本的信息,如表項的狀態、生命周期(生存時間)以及對應網卡的基本信息,LwIP使用一個etharp_entry結構體對表項進行描述。

而且LwIP預先定義了緩存表的大小,ARP_TABLE_SIZE默認為10,也就是最大能存放10個表項,由于這個表很小,LwIP對表的操作直接采用遍歷方式,遍歷每個表項并且更改其中的內容。

static struct etharp_entry arp_table[ARP_TABLE_SIZE];

struct etharp_q_entry 
{
  struct etharp_q_entry *next;
  struct pbuf *p;
};

struct etharp_entry 
{
#if ARP_QUEUEING
  /** 指向此ARP表項上掛起的數據包隊列的指針. */
  struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
  /** 指向此ARP表項上的單個掛起數據包的指針. */
  struct pbuf *q;
#endif 
  ip4_addr_t ipaddr;    //記錄目標IP地址
  struct netif *netif;    //對應網卡信息
  struct eth_addr ethaddr;    //記錄與目標IP地址對應的MAC地址
  u16_t ctime;    //生存時間
  u8_t state;    //表項的狀態
};

因為APR協議在沒找到MAC地址的時候是不會發送數據的,因此這些數據會暫時存儲在ARP表項中,因此LwIP實現了ARP表項掛載數據的結構,etharp_q_entry指向的是數據包緩存隊列,etharp_q_entry是一個結構體,LwIP為了方便管理pbuf數據包,直接再一次封裝這個結構體,讓數據包能形成隊列的形式,其實簡單理解為數據包就行了。而q指向的就是一個pbuf數據包。

圖片

ARP表項的pbuf

圖片

ARP表項的pbuf隊列

除此之外,ARP表項還有很重要的信息,那就是IP地址 MAC地址,狀態、生存時間等信息。

而對于ARP表項的狀態,LwIP還枚舉了多種不同的狀態:

/** ARP states */
enum etharp_state {
  ETHARP_STATE_EMPTY = 0,
  ETHARP_STATE_PENDING,
  ETHARP_STATE_STABLE,
  ETHARP_STATE_STABLE_REREQUESTING_1,
  ETHARP_STATE_STABLE_REREQUESTING_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緩存表是一個動態更新的過程,為什么要動態更新呢?因為以太網的物理性質并不能保證數據傳輸的是可靠的。以太網發送數據并不會知道對方是否已經介紹成功,而兩臺主機的物理線路不可能一直保持有效暢通,那么如果不是動態更新的話,主機就不會知道另一臺主機是否在工作中,這樣子發出去的數據是沒有意義的。

比如兩臺主機A和B,一開始兩臺主機都是處于連接狀態,能正常進行通信,但是某個時刻主機B斷開了,但是主機A不會知道主機B是否正常運行,因為以太網不會提示主機B已經斷開,那么主機A會一直按照MAC地址發送數據,而此時在物理鏈路層就已經是不通的,那么這些數據是沒有意義的,而如果ARP動態更新的話,主機A就會發出ARP請求包,如果得不到主機B的回應,則說明無法與主機B進行通信,那么就會刪除ARP表項,就無法進行通信。

ARP緩存表的超時處理

ARP表項的生存時間是5分鐘,而ARP請求的等待時間是5秒鐘,當這些時間到達后,就會更新ARP表項,如果在物理鏈路層無法連通則會刪除表項。這就需要ARP層有一個超時處理函數對ARP進行管理,這些操作都是根據ARP表項的ctime字段進行的,它記錄著對應表項的生存時間,而超時處理函數是etharp_tmr(),它是一個周期性的超時處理函數,每隔1秒就調用一次,當ctime的值大于指定的時間,就會刪除對應的表項。

LwIP中實現的函數是:etharp_tmr(void)。

由于LwIP的ARP表是比較小的,LwIP采用直接遍歷ARP緩存表,更新ARP表的內容,而當表項的時間大于表項的生存時間(5分鐘),或者表項狀態是ETHARP_STATE_PENDING處于等待目標主機回應ARP請求包,并且等待的時間超過ARP_MAXPENDING(5秒),那么LwIP就認為這些表項是無效了,就調用etharp_free_entry()函數刪除表項。

void
etharp_tmr(void)
{
  int i;

  LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\\n"));

  /* 遍歷ARP表,從ARP表中刪除過期的表項 */
  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++;

       /* 等待表項穩定或者表項已經過期*/
      if ((arp_table[i].ctime >= ARP_MAXAGE) ||
          ((arp_table[i].state == ETHARP_STATE_PENDING)  &&
           (arp_table[i].ctime >= ARP_MAXPENDING))) 
      {
        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) 
      {
        /* 過渡階段 */
        arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
      } 
      else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2) 
      {
        /* 進入ETHARP_STATE_STABLE狀態 */
        arp_table[i].state = ETHARP_STATE_STABLE;
      } 
      else if (arp_table[i].state == ETHARP_STATE_PENDING) 
      {
        /*仍然掛起,重新發送ARP請求 */
        etharp_request(arp_table[i].netif, &arp_table[i].ipaddr);
      }
    }
  }
}

發送ARP請求包

發送ARP請求包的時候,需要填充已知的目標IP地址、源MAC地址、源IP地址等,并且需要該ARP包進行廣播出去,所以以太網首部的目標MAC地址為FF-FF-FF-FF-FF-FF

LwIP先調用etharp_request()函數進行發送ARP請求包,在etharp_request()函數中會調用etharp_request_dst()函數進行發送,此時指定的目標MAC地址是ethbroadcast,而在etharp_request_dst()函數中會調用etharp_raw()進行發送ARP請求包,層層調用,并且每層的參數都是越來越多的,這樣子封裝對于上層程序來說更加好處理,在etharp_raw()函數中,會對ARP數據包進行封裝,然后再封裝到以太網數據幀中,最終調用以太網底層發送函數進行將以太網數據幀發送出去。

LwIP的實現函數是etharp_raw()

/* --------------------------------------------------------------------------------------------- */
err_t
etharp_request(struct netif *netif, const ip4_addr_t *ipaddr)
{
  return etharp_request_dst(netif, ipaddr, ebroadcast);
}
/* --------------------------------------------------------------------------------------------- */

const struct eth_addr ethbroadcast = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};    //FF-FF-FF-FF-FF-FF
const struct eth_addr ethzero = {{0, 0, 0, 0, 0, 0}};    //00-00-00-00-00-00

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),
                    ezero,
                    ipaddr, 
                    ARP_REQUEST);
}
/* --------------------------------------------------------------------------------------------- */

* @param netif                用于發送ARP數據包的lwip網絡接口
* @param ethsrc_addr        以太網頭的源MAC地址
* @param ethdst_addr        以太網頭的目標MAC地址
* @param hwsrc_addr         ARP協議頭的源MAC地址
* @param ipsrc_addr         ARP協議頭的源IP地址
* @param hwdst_addr         ARP協議頭的目標MAC地址
* @param ipdst_addr         ARP協議頭的目標IP地址
* @param opcode                ARP數據包的類型
* @return ERR_OK            如果已發送ARP數據包
* 如果無法分配ARP數據包,則為ERR_MEM
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));

  /* 填寫源MAC地址與目標MAC地址 */
  SMEMCPY(&hdr->shwaddr, hwsrc_addr, ETH_HWADDR_LEN);
  SMEMCPY(&hdr->dhwaddr, hwdst_addr, ETH_HWADDR_LEN);

  /* 以太網首部源ip地址與目標ip地址 */
  IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->sipaddr, ipsrc_addr);
  IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->dipaddr, ipdst_addr);

  //填寫ARP首部硬件類型與協議類型
  hdr->hwtype = PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET);
  hdr->proto = PP_HTONS(ETHTYPE_IP);

  /* 填寫ARP數據包硬件地址長度與協議地址長度 */
  hdr->hwlen = ETH_HWADDR_LEN;
  hdr->protolen = sizeof(ip4_addr_t);

  /* 發送請求包 */
#if LWIP_AUTOIP
  if (ip4_addr_islinklocal(ipsrc_addr)) 
  {
    ethernet_output(netif, p, ethsrc_addr, ebroadcast, ETHTYPE_ARP);
  }
  else
#endif /* LWIP_AUTOIP */
  {
    ethernet_output(netif, p, ethsrc_addr, ethdst_addr, ETHTYPE_ARP);
  }

  ETHARP_STATS_INC(etharp.xmit);
  /* 釋放內存 */
  pbuf_free(p);
  p = NULL;

  return result;
}

ARP數據包處理

以太網是有自己獨立的尋址方式(MAC地址),而對于TCP/IP的上層協議(如TCP協議、IP協議),它們是以IP地址作為網絡的標識,如果沒有IP地址則無法進行收發數據。當數據通過網卡中接收回來的時候,LwIP內核就需要將數據進行分解,如果是IP數據報則遞交給IP協議去處理,如果是ARP數據包則交由ARP協議去處理。

真正讓LwIP內核去處理接收到的數據包是ethernet_input()函數。代碼太多了,簡單截取部分代碼。

err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{
  struct eth_hdr *ethhdr;
  u16_t type;

  LWIP_ASSERT_CORE_LOCKED();

  //校驗數據長度
  if (p->len <= SIZEOF_ETH_HDR) {
    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);
  }

  /* ethhdr指針指向以太網幀頭部,并且強制轉換成eth_hdr結構 */
  ethhdr = (struct eth_hdr *)p->payload;

  //獲取類型
  type = ethhdr->type;

  if (ethhdr->dest.addr[0] & 1) 
  {
    /*  這可能是多播或廣播數據包,如果目標IP地址的第一個字節的bit0是1,
        那么有可能是多播或者是廣播數據包,所以,還需要進行判斷,
        如果是多播的,就將pbuf標記為鏈路層多播。 */
    if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) {
      if ((ethhdr->dest.addr[1] == LL_IP4_MULTICAST_ADDR_1) &&
          (ethhdr->dest.addr[2] == LL_IP4_MULTICAST_ADDR_2)) 
      {
       /* 將pbuf標記為鏈路層多播 */
        p->flags |= PBUF_FLAG_LLMCAST;
      }
    }
    else if (eth_addr_cmp(ehdr->dest, ebroadcast)) 
    {
      /* 將pbuf標記為鏈路層廣播 */
      p->flags |= PBUF_FLAG_LLBCAST;
    }
  }

  switch (type) {
     /* 如果是IP數據報 */
    case PP_HTONS(ETHTYPE_IP):
      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
        goto free_and_return;
      }
      /* 去掉太網首部 */
      if (pbuf_remove_header(p, next_hdr_offset)) 
      {
        goto free_and_return;
      } 
      else
      {
        /* 遞交到IP層處理 */
        ip4_input(p, netif);
      }
      break;

    //對于是ARP包
    case PP_HTONS(ETHTYPE_ARP):
      if (!(netif->flags & NETIF_FLAG_ETHARP)) 
      {
        goto free_and_return;
      }
      /* 去掉太網首部 */
      if (pbuf_remove_header(p, next_hdr_offset)) 
      {
        ETHARP_STATS_INC(etharp.lenerr);
        ETHARP_STATS_INC(etharp.drop);
        goto free_and_return;
      } 
      else 
      {
        /* 傳遞到ARP協議處理 */
        etharp_input(p, netif);
      }
      break;

    default:
      ETHARP_STATS_INC(etharp.proterr);
      ETHARP_STATS_INC(etharp.drop);
      MIB2_STATS_NETIF_INC(netif, ifinunknownprotos);
      goto free_and_return;
  }

  return ERR_OK;

free_and_return:
  pbuf_free(p);
  return ERR_OK;
}

ARP數據包的處理

重點來了,我們主要是講解對收到的ARP數據包處理

ARP數據包的處理函數為etharp_input(),在這里它完成兩個任務:

如果收到的是ARP應答包,說明本機之前發出的ARP請求包有了回應,就根據應答包更新自身的ARP緩存表;

如果收到的是ARP請求包,如果包中的目標IP地址與主機IP地址匹配,除了記錄原主機的IP與MAC地址,更新自身的ARP表外,還要向源主機發送一個ARP應答包。但是如果如果包中目標IP地址與主機IP地址不匹配,則盡可能記錄源主機的IP與MAC地址,更新自身的ARP表,并丟棄該請求包,為什么說是盡可能呢,因為主機的ARP緩存表是有限的,不可能記錄太多的ARP表項,所以在有空閑的表項時才記錄,如果沒有空閑的表項,ARP覺得它自己已經盡力了,也記不住那么多表項。

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;

  /* 判斷ARP包的合法性,判斷ARP包的合法性,已經類型是否為以太網、硬件地址長度是否為ETH_HWADDR_LEN、
     協議地址長度是否為sizeof(ip4_addr_t)以及協議是否為ARP協議,如果都滿足則表示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)))  {
    ETHARP_STATS_INC(etharp.proterr);
    ETHARP_STATS_INC(etharp.drop);
    pbuf_free(p);
    return;
  }
  ETHARP_STATS_INC(etharp.recv);


  //拷貝源IP地址與目標IP地址
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr);
  IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr);

  /* 看看主機網卡是否配置了IP地址 */
  if (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
    for_us = 0;
  } 
  else 
  {
     /* 判斷ARP數據包的目標IP地址與主機IP地址是否一樣 */
    for_us = (u8_t)ip4_addr_cmp(&dipaddr, netif_ip4_addr(netif));
  }

  /* 更新ARP緩存表項 */
  etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
                          for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);

 /* 更新完畢,根據包的類型處理 */
  switch (hdr->opcode) 
  {
    /* ARP請求包 */
    case PP_HTONS(ARP_REQUEST):
      if (for_us) {
         /* 是請求自己的,那就要做出應答 */
        etharp_raw(netif,
                   (struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
                   (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
                   &hdr->shwaddr, &sipaddr,
                   ARP_REPLY);

      } 
       /* 不是給自己的,如果不是給自己的,原因有兩種,一種是網卡自身尚未配置IP地址,這樣子就只打印相關調試信息。
          另一種是ARP包中的目標IP地址與主機IP地址不符合,也不用做出回應,直接丟棄即可,并輸出相關調試信息*/
      else if (ip4_addr_isany_val(*netif_ip4_addr(netif))) 
      {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: we are unconfigured, ARP request ignored.\\n"));
      }
      else
      {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: ARP request was not for us.\\n"));
      }
      break;

    /* 對于ARP應答包 不用處理,前面已經更新ARP表項了*/
    case PP_HTONS(ARP_REPLY):
      break;
    default:
      ETHARP_STATS_INC(etharp.err);
      break;
  }
  pbuf_free(p);
}

更新ARP表項

etharp_update_arp_entry()函數是用于更新ARP緩存表的,它會在收到一個ARP數據包的時候被調用,它會先查找一個ARP表項,如果沒有找到這個ARP表項的記錄,就會去新建一個ARP表項,然后重置ARP表項的參數(狀態、網卡。IP地址與對應的MAC地址以及生存時間等),然后檢測ARP表項中是否掛載數據包,如果有就將這些數據包發送出去。

表項的更新方式,動態表項有兩種方式,分別為ETHARP_FLAG_TRY_HARD和ETHARP_FLAG_FIND_ONLY。前者表示無論如何都要創建一個表項,如果ARP緩存表中沒有空間了,那就需要回收較老的表項,將他們刪除,然后建立新的表項。而如果是后者,就讓內核盡量更新表項,如果ARP緩存表中沒有空間了,那么也無能為力,實在是添加不了新的表項。

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;
  if (ip4_addr_isany(ipaddr) ||
      ip4_addr_isbroadcast(ipaddr, netif) ||
      ip4_addr_ismulticast(ipaddr)) {
    return ERR_ARG;
  }

 /* 查找或者創建ARP表項,并且返回索引值 */
  i = etharp_find_entry(ipaddr, flags, netif);

  /* 如果索引值不合法,更新ARP表項失敗 */
  if (i < 0) {
    return (err_t)i;
  }

  /* 設置表項狀態為ETHARP_STATE_STABLE */
  arp_table[i].state = ETHARP_STATE_STABLE;

  /* 記錄網卡 */
  arp_table[i].netif = netif;

  /* 插入ARP索引樹 */
  mib2_add_arp_entry(netif, &arp_table[i].ipaddr);

  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: updating stable entry %"S16_F"\\n", i));

  /* 更新緩存表中的MAC地址 */
  SMEMCPY(&arp_table[i].ethaddr, ethaddr, ETH_HWADDR_LEN);

  /* 重置生存時間 */
  arp_table[i].ctime = 0;

  /* 如果表項上與未發送的數據包,那就將這些數據包發送出去 */
#if ARP_QUEUEING        //使用隊列方式
  while (arp_table[i].q != NULL) 
  {
    struct pbuf *p;
    /* 定義q指向ARP表項中的數據包緩存隊列 */
    struct etharp_q_entry *q = arp_table[i].q;

    /* 指向下一個數據包節點 */
    arp_table[i].q = q->next;

    /* 獲取pbuf數據包 */
    p = q->p;

    /* 釋放MEMP_ARP_QUEUE類型的內存塊 */
    memp_free(MEMP_ARP_QUEUE, q);
#else 
  if (arp_table[i].q != NULL) {
    struct pbuf *p = arp_table[i].q;
    arp_table[i].q = NULL;
#endif 
     /* 發送緩存隊列的數據包 */
    ethernet_output(netif, p, (struct eth_addr *)(netif->hwaddr), ethaddr, ETHTYPE_IP);
    /* free the queued IP packet */
    pbuf_free(p);
  }
  return ERR_OK;
}

圖片

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地址選擇不同的處理。

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;

  if (ip4_addr_isbroadcast(ipaddr, netif)) 
  {
    /* 如果是廣播數據包,目標MAC地址設置為FF-FF-FF-FF-FF-FF-FF */
    dest = (const struct eth_addr *)ebroadcast;
  } 
  else if (ip4_addr_ismulticast(ipaddr)) 
  {
    /* 如果是多播數據包,目標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;
  } 
  else 
  {
    /* 如果是單播目標地IP地址 */
    netif_addr_idx_t i;
    /* 判斷目標IP地址是否與主機處于同一子網上,
       如果不是,則修改IP地址,發向網關,請求網關轉發,
       則需要修改IP地址,IP地址為網關的IP地址,目的是為了讓網關進行轉發。*/
    if (!ip4_addr_netcmp(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);
      if (!ip4_addr_islinklocal(&iphdr->src))
#endif 
      {
#ifdef LWIP_HOOK_ETHARP_GET_GW
        dst_addr = LWIP_HOOK_ETHARP_GET_GW(netif, ipaddr);
        if (dst_addr == NULL)
#endif 
        {
           /* 判斷一下網關地址是否有效 */
          if (!ip4_addr_isany_val(*netif_ip4_gw(netif)))
          {
            /* 發送到默認網關,讓網關進行轉發 */
            dst_addr = netif_ip4_gw(netif);

          }
          else
          {
            /* 沒有默認網關可用,返回錯誤 */
            return ERR_RTE;
          }
        }
      }
    }

    /* 遍歷ARP緩存表 */
    for (i = 0; i < ARP_TABLE_SIZE; i++)
    {
      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))) 
      {
        /* 如果找到目標IP地址對應的表項,直接發送 */
        ETHARP_SET_ADDRHINT(netif, i);
        return etharp_output_to_arp_index(netif, q, i);
      }
    }
    /* 如果沒有找到與目標IP地址對應的ARP表項 */
    return etharp_query(netif, dst_addr, q);
  }

  /* 而對于多播、廣播數據包,直接能得到對應的MAC地址,可以進行發送*/
  return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), dest, ETHTYPE_IP);
}

在上一個函數中,會調用etharp_output_to_arp_index()這個函數,因為是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)

static err_t
etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, netif_addr_idx_t arp_idx)
{
  /* 如果arp表項即將過期:LwIP會發送一個ARP請求包,但只有當它的狀態是ETHARP_STATE_STABLE才能請求*/
  if (arp_table[arp_idx].state == ETHARP_STATE_STABLE) 
  {
    /* 還差15秒到期 */
    if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST) 
    {
      /* 使用廣播方式發出請求包 */
      if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) 
      {
        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
      }
    } 
    /* 還差30秒到期 */
    else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST) 
    {
      /* 發出單播請求(持續15秒),以防止不必要的廣播 */
      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;
      }
    }
  }

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

而如果在ARP緩存表中沒有找到目標IP地址對應的表項,LwIP就會調用etharp_query()函數,那么ARP協議就會創建一個表項,這也是ARP協議的核心處理,對于剛創建的表項,它在初始化網卡信息后會被設置為ETHARP_STATE_PENDING狀態,與此同時一個ARP請求包將被廣播出去,這個時候的表項是無法發送數據的,只有等待到目標主機回應了一個ARP應答包才能發送數據,那么這些數據在這段時間中將被掛到表項的等待隊列上,在ARP表項處于ETHARP_STATE_STABLE狀態完成數據的發送。

函數的處理邏輯是很清晰的,首先調用etharp_find_entry()函數在ARP緩存表中查找表項,如果沒有找到就嘗試創建表項并且返回表項的索引,當然ARP緩存表中可能存在表項,可能為新創建的表項(ETHARP_STATE_EMPTY),也可能為ETHARP_STATE_PENDING或者ETHARP_STATE_STABLE狀態。如果是新創建的表項,那么表項肯定沒有其他信息,LwIP就會初始化一些信息,如網卡,然后就將表項設置為ETHARP_STATE_PENDING狀態。

掛載的這些數據在等待到目標主機產生ARP應答的時候會發送出去,此時的發送就是延時了,所以在沒有ARP表項的時候,發送數據會產生延時,在指定等待ARP應答時間內如果等不到目標主機的應答,那么這個表項將被系統回收,同時數據也無法發送出去。

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;

  /* 檢是否為單播地址 */
  if (ip4_addr_isbroadcast(ipaddr, netif) ||
      ip4_addr_ismulticast(ipaddr) ||
      ip4_addr_isany(ipaddr)) {
    return ERR_ARG;
  }

  /* 在ARP緩存中查找表項,如果沒有則嘗試創建表項 */
  i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif);

  /* 沒有發現表項或者沒有創建表項成功 */
  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;

 /* 將新表項標記為待處理 */
  if (arp_table[i].state == ETHARP_STATE_EMPTY)
  {
    is_new_entry = 1;
    //設置表項的狀態
    arp_table[i].state = ETHARP_STATE_PENDING;

    /* 記錄網卡 */
    arp_table[i].netif = netif;
  }

  /* 是否有新的表項 */
  if (is_new_entry || (q == NULL)) 
  {
    /* 發送ARP請求包*/
    result = etharp_request(netif, ipaddr);
    if (result != ERR_OK)
    {

    }
    if (q == NULL) {
      return result;
    }
  }

  LWIP_ASSERT("q != NULL", q != NULL);
  /* 表項狀態是否穩定 */
  if (arp_table[i].state >= ETHARP_STATE_STABLE)
  {
    ETHARP_SET_ADDRHINT(netif, i);

    /* 發送數據包 */
    result = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);


  } 
  else if (arp_table[i].state == ETHARP_STATE_PENDING)
  {
    /* 如果表項是ETHARP_STATE_PENDING狀態 */
    /* 將給數據包'q'排隊 */
    struct pbuf *p;
    int copy_needed = 0;

    /* 如果q包含必須拷貝的pbuf,請將整個鏈復制到一個新的PBUF_RAM */
    p = q;
    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) 
    {
      /* 將整個數據包復制到新的pbuf中 */
      p = pbuf_clone(PBUF_LINK, PBUF_RAM, q);
    }
    else 
    {
      /* 引用舊的pbuf就足夠了 */
      p = q;
      pbuf_ref(p);
    }
    /* packet could be taken over? */
    if (p != NULL) {

#if ARP_QUEUEING        /* 如果使用隊列 */
      struct etharp_q_entry *new_entry;

      /* 分配一個新的arp隊列表項 */
      new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);

      if (new_entry != NULL) 
      {
        unsigned int qlen = 0;
        new_entry->next = 0;
        new_entry->p = p;
        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

        result = ERR_OK;
      }
      else 
      {
        /* 申請內存失敗 */
        pbuf_free(p);
        result = ERR_MEM;
      }
#else 
      /* 如果只是掛載單個數據包,那么始終只為每個ARP請求排隊一個數據包,就需要釋放先前排隊的數據包*/
      if (arp_table[i].q != NULL)
      {
        pbuf_free(arp_table[i].q);
      }
      arp_table[i].q = p;
      result = ERR_OK;
#endif
    } else {
      ETHARP_STATS_INC(etharp.memerr);
      result = ERR_MEM;
    }
  }
  return result;
}

圖片

ARP發送流程

總得來說,整個ARP的工作流程是很清晰的。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • MAC地址
    +關注

    關注

    1

    文章

    50

    瀏覽量

    11733
  • ARP
    ARP
    +關注

    關注

    0

    文章

    50

    瀏覽量

    14735
  • ip地址
    +關注

    關注

    0

    文章

    294

    瀏覽量

    17006
  • LwIP
    +關注

    關注

    2

    文章

    85

    瀏覽量

    27089
收藏 人收藏

    評論

    相關推薦

    基于DWC_ether_qos的以太網驅動開發-LWIPARP模塊介紹

    TCP/IP通訊第一步需要先調通ARP,否則TCP/IP包都不知道MAC地址要發給誰。這一篇來基于LWIPARP實現進行相關的分析。
    的頭像 發表于 09-18 09:34 ?1757次閱讀
    基于DWC_ether_qos的以太網驅動開發-<b class='flag-5'>LWIP</b>的<b class='flag-5'>ARP</b>模塊介紹

    如何手動往esp32 arp列表添加自定義的arp綁定信息?

    我目前需要手動往esp32 arp列表添加自定義的arp綁定信息,而非arp請求獲得,因為另一方設備無法完成arp應答 如IP為:192.
    發表于 06-07 08:14

    淺談如何防治ARP病毒

    淺談如何防治ARP病毒近期, 現一種新的“ARP欺騙”木馬病毒在互聯網上迅速擴散.主要表現為用戶頻繁斷網、IE瀏覽器頻繁出錯以及一些常用軟件出現故障等問題。Arp病毒在局域網感染較多
    發表于 10-10 15:24

    LWIP ip數據包發送局域網外單播包的數據網關怎么知道要發給誰?

    最近在看llwip基于arp部分實現源碼,想咨詢下LWIP關于其ip數據包發送局域網外單播包的一點疑惑?就是lwip如果發現是非本地單播包會
    發表于 08-23 04:35

    請問戰艦LWIP移植是怎么實現內存管理的?

    如題,最近在移植LWIP,參考原子戰艦V3,由于我的系統沒實現內存管理,因此,涉及到malloc的函數我全部使用全局數據區來開辟空間(暫時先這么粗略地實現),但對內存池的內存分配我實現是
    發表于 09-02 04:36

    LWIP移植過程中板子與PC機不能建立通信是怎么回事?

    在做LWIP移植過程,板子與PC機不能建立通信,仿真發現:接收來自pc機的ARP request packet 的目的IP地址是PC機的IP地址,串口輸出結果:etharp_
    發表于 10-24 02:59

    LwIP是什么意思

    是:用少量的資源消耗實現一個較為完整的TCP/IP協議棧,其中“完整”主要指的是TCP協議的完整性,實現的重點是在保持TCP協議主要功能的基礎上減少對RAM 的占用。此外LwIP既可以移植到操作系統上運行
    發表于 08-24 06:24

    掌握LwIPARP實現原理與作用

    ARP request,ARP response;即一個 ARP 查詢報文,一個 ARP 回復報文。學習目標:掌握 ARP 報文的作用。掌
    發表于 06-21 11:44

    ARP報文及其在Lwip實現

    1、ARP報文及其在Lwip實現  對于網絡世界來說,有 IP 地址就代表了身份。不過在我們常用的網絡拓撲類型,IP 地址并不能準確表達我們的身份。在 ipv4
    發表于 10-19 11:55

    STM32移植LWIP問題

    出這個 Assertion \"arp_table[i].q == NULL\" failed atline 369 in ..\\\\LWIP\\\\lwip
    發表于 07-30 14:31

    ARP協議攻擊及其解決方案

    由于ARP協議的設計缺陷,使得ARP協議在使用的過程存在著盜用IP地址和ARP欺騙等嚴重的安全問題。本文分析ARP攻擊的基本原理,并提出相
    發表于 06-11 10:17 ?16次下載

    TCPIP協議棧的實現lwip

    TCPIP協議棧的實現lwip方便初學者剛開始接觸lwip,有個大概的了解與認識。
    發表于 03-14 15:40 ?13次下載

    ARP是什么意思?ARP是什么協議?ARP協議用于什么地方

    ARP是什么意思?ARP是什么協議?ARP協議用于什么地方 ARP是什么意思?ARP是什么協議?ARP
    發表于 11-10 18:00 ?1.9w次閱讀

    arp攻擊原理_arp攻擊怎么解決

    相信絕大數的用戶對ARP病毒都不會陌生,如果本機遭受到ARP攻擊,電腦數據就會向指定地址傳送,一般最為明顯的現象就是電腦無故出現斷網的情況,并且網絡時連時斷,會成為擾亂局域網其它電腦上網的罪魁禍首。那么
    的頭像 發表于 01-11 16:12 ?3w次閱讀
    <b class='flag-5'>arp</b>攻擊原理_<b class='flag-5'>arp</b>攻擊怎么解決

    靜態 ARP 表項的潛在問題

    在計算機網絡,地址解析協議(ARP)將 IP 地址轉換為物理地址(MAC 地址),以便在局域網實現數據的傳輸。靜態 ARP 表項的設置為
    的頭像 發表于 07-29 11:51 ?328次閱讀
    靜態 <b class='flag-5'>ARP</b> 表項的潛在問題