LWIP學習記錄---ARP協議(2)ARP數據包接收過程

来源:https://www.cnblogs.com/maxwell-01/archive/2023/02/26/17156855.html
-Advertisement-
Play Games

(一)ARP之 數據包接收過程 ​ ​ 先看一下整個數據流的傳輸過程。 首先etherneti_input()函數 從底層網卡驅動接收到原始數據,若是ip包或者ARP包則調用ethernet_input()。 s32_t ethernetif_input(struct netif *netif) { ...


(一)ARP之 數據包接收過程

先看一下整個數據流的傳輸過程。


  1. 首先etherneti_input()函數 從底層網卡驅動接收到原始數據,若是ip包或者ARP包則調用ethernet_input()。

    s32_t ethernetif_input(struct netif *netif)
    {
      struct ethernetif *ethernetif;          //網路介面信息結構體,此處無用。
      struct eth_hdr *ethhdr;                  //乙太網幀 頭部結構體指針。
      struct pbuf *p;
    
      ethernetif = netif->state;
    
      p = low_level_input(netif);            //!!調用底層函數讀取一個數據包。
    
      if (p == NULL) 
      {
      	return 0;
      }
      
      ethhdr = p->payload;                  //將ethhdr指針指向數據包中乙太網頭部
    
      switch (htons(ethhdr->type)) {       //判斷數據包中的幀類型 ,要大小端轉換。
      case ETHTYPE_IP:
      case ETHTYPE_ARP:
        /* full packet send to tcpip_thread to process */
        if (netif->input(p, netif)!=ERR_OK)  //調用netif->input 進行處理。
         { 
           pbuf_free(p);                      //釋放Pbuf
           p = NULL;
         }
        break;
    
      default:
        pbuf_free(p);
        p = NULL;
        break;
      }
    
      return 1;
    }
    


可見,此函數未作實質性的處理,只是判斷乙太網中幀類型,並調用中netif->input函數指針處理,此處指向的就是 ethernet_input函數。我們來看看ethernet_input函數做了哪些事情。(不停地套娃~)
err_t ethernet_input(struct pbuf *p, struct netif *netif)
{
  struct eth_hdr* ethhdr;                  //乙太網幀頭部結構體指針
  u16_t type;

  if (p->len <= SIZEOF_ETH_HDR) {     //長度校驗,ARP包必須包含在第一個PBUF的數據區。
    goto free_and_return;
  }

  ethhdr = (struct eth_hdr *)p->payload;   //乙太網幀指針 指向乙太網幀頭部

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

  switch (type) {
    case PP_HTONS(ETHTYPE_IP):
#if ETHARP_TRUST_IP_MAC
      /* update ARP table */
      etharp_ip_input(netif, p);   //使用IP頭部以及 乙太網頭部MAC 更新 ARP表。
#endif /* ETHARP_TRUST_IP_MAC */
          
      /* skip Ethernet header */
      if(pbuf_header(p, -ip_hdr_offset)) {  //去掉乙太網頭部
        goto free_and_return;       //若操作失敗,則釋放pbuf
      } else {
        /* pass to IP layer */
        ip_input(p, netif);         //若頭部去除成功,則調用IP輸入處理函數處理數據。
      }
      break;
      
    case PP_HTONS(ETHTYPE_ARP):    //若是ARP數據包
      /* pass p to ARP module */
      etharp_arp_input(netif, (struct eth_addr*)(netif->hwaddr), p);  //調用ARP數據包處理函數。
      break;


    default:
      goto free_and_return;
  }

  return ERR_OK;

free_and_return:
  pbuf_free(p);
  return ERR_OK;
}


​ 這個函數也很簡單,就是根據數據包的type 類型進行判斷 是IP包還是ARP包?是IP包則去掉 **乙太網幀頭部**,調用**ip_input** 函數處理,若是ARP包,則調用**etharp_arp_input() **處理數據包。

那我們接下來看看數據包是怎麼處理的呢?

(二)ARP之 數據包處理過程

從之前講述的知識可以瞭解到 etharp_arp_input()函數有兩個功能:

  • 若接收到是ARP應答包,則需要根據應答信息更新ARP緩存表。

  • 若接收到ARP請求包,則需要:

    1. 若這個請求包,與本機IP地址不符合,則不需要應答,但是要將請求包中的 IP和MAC加入到自己的緩存表中,以備到時候需要使用。

    2. 若這個請求包,與本機IP地址符號,除了要將源主機的IP與MAC加入緩存表之外,還要回覆一個應答,告訴本機的MAC地址是多少。


好,有了上面的思路,我們具體來看一看代碼是怎麼實現的。

(1)etharp_arp_input()函數分析

細細品味~~

static void etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
{
  struct etharp_hdr *hdr;      //ARP數據包包頭部結構指針。
  struct eth_hdr *ethhdr;      //乙太網頭部結構體指針 
  ip_addr_t sipaddr, dipaddr;
  u8_t for_us;                //指示ARP包是否發送給本機的。


  /* drop short ARP packets: we have to check for p->len instead of p->tot_len here
     since a struct etharp_hdr is pointed to p->payload, so it musn't be chained! */
  if (p->len < SIZEOF_ETHARP_PACKET) {          //arp 數據包不能分裝在兩個PBUF中,不然不能用指針來操作內部了。
    pbuf_free(p);
    return;
  }

  ethhdr = (struct eth_hdr *)p->payload;
  hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);  //hdr 指向ARP數據包首部

  /* RFC 826 "Packet Reception": */
  if ((hdr->hwtype != PP_HTONS(HWTYPE_ETHERNET)) ||  //判斷ARP數據包的合法型,要是不合法,則直接刪除該數據包。
      (hdr->hwlen != ETHARP_HWADDR_LEN) ||
      (hdr->protolen != sizeof(ip_addr_t)) ||
      (hdr->proto != PP_HTONS(ETHTYPE_IP)))  {

    pbuf_free(p);
    return;
  }

  /* Copy struct ip_addr2 to aligned ip_addr, to support compilers without
   * structure packing (not using structure copy which breaks strict-aliasing rules). */
    
  //由於arp 數據包中的IP地址地段並不是位元組對齊的,不能直接使用,需要將IP 拷貝到sipaddr,dipaddr中使用。
  IPADDR2_COPY(&sipaddr, &hdr->sipaddr);
  IPADDR2_COPY(&dipaddr, &hdr->dipaddr);

   //1.判斷請求包是否給我
  if (ip_addr_isany(&netif->ip_addr)) { //若本機的網卡IP未配置,那麼肯定不是發給我們的
    for_us = 0;
  } else {
    /* ARP packet directed to us? */
    for_us = (u8_t)ip_addr_cmp(&dipaddr, &(netif->ip_addr)); //若配置了本機IP,那比較一下IP是否相同。
  }

 //2.那麼接下來添加一下ARP緩存表
 //若這個ARP包(不論請求還是相應包)是給我們的,那麼更新ARP表。
   如果不是給我們的,也要更新ARP表,然後不設置ETHARP_TRY_HARD標誌
       
  etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
                   for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);

    
  /* 3.now 處理 ARP數據包 */
  switch (hdr->opcode) {
  /* 是ARP請求包嗎? */
  case PP_HTONS(ARP_REQUEST):
    if (for_us) {   //是ARP請求包,且是發給我的

      hdr->opcode = htons(ARP_REPLY); //改變標誌為為 應答包

      IPADDR2_COPY(&hdr->dipaddr, &hdr->sipaddr);   //ip地址交換
      IPADDR2_COPY(&hdr->sipaddr, &netif->ip_addr);

      //設置4個MAC地址欄位
      ETHADDR16_COPY(&hdr->dhwaddr, &hdr->shwaddr);  //ARP首部中接收端MAC
      ETHADDR16_COPY(&ethhdr->dest, &hdr->shwaddr);
      ETHADDR16_COPY(&hdr->shwaddr, ethaddr);
      ETHADDR16_COPY(&ethhdr->src, ethaddr);

      /* hwtype, hwaddr_len, proto, protolen and the type 這些欄位保持不變 */

      /*發送ARP應答包*/
      netif->linkoutput(netif, p);

    } else if (ip_addr_isany(&netif->ip_addr)) { //如果這個ARP請求包不是給我的,不做任何處理。
  
    } else {                                    //如果這個ARP請求包不是給我的,不做任何處理。

    }
    break;
  case PP_HTONS(ARP_REPLY): //如果是ARP應答包,前面已經處理過了,現在什麼也不用做。
    break;
  default:
  
    break;
  }
  /* free ARP packet */
  pbuf_free(p);
}



​ 總結一下,這個函數就是實現了上面上述的思路。更新ARP表項的這個動作是放在函數**etharp_update_arp_entry()**中完成的關於這個函數,後續會詳細講解。此處說明一些,標誌位**ETHARP_FLAG_TRY_HARD ** 的含義:
  • ETHARP_FLAG_TRY_HARD :告訴函數無論如何要創建這個地址對的表項,如果所有的ARP表項都用完了,就刪除最老的表項。

  • ETHARP_FLAG_FIND_ONLY:遍歷整個緩存表尋找空閑表項,如果找不到,就不添加了。

(2)ARP緩存表的更新

ARP緩存表的更新是調用etharp_update_arp_entry()來實現的。在介紹這個函數之前要先介紹etharp_find_entry()這個函數。

1. etharp_find_entry()

該函數功能是 尋找 一個與IP地址符合的ARP表項 或者 創建一個新的ARP表項,並返回該表項的索引號


如果 ipaddr 非零,則返回一個處於pending或stable 狀態的表項。
* 若沒有,則返回一個empty表項,且該表項ip地段要設置為ipaddr。這樣etharp_find_entry()函數返回後,調用者需要將empty 狀態,改變為pending狀態。再如果,沒有empty表項呢?那麼根據傳入的關鍵字來做處理,若是ETHART_TRY_HARD,則**刪除**所有表項中年齡最老的那個,建立新表項。

若ipaddr為0,則要返回一個empty狀態的表項。

所有這個過程要怎麼實現呢?要迴圈遍歷3 遍嗎,並不需要。Lwip 一邊判斷表項是否匹配,一邊記錄索引編號最低的empty表項,一邊記錄年齡最大的stable表項,一邊記錄年齡最大且緩存隊列為空的Pending表項,一邊記錄年齡最大且緩存隊列不為空的Pending表項。

static s8_t
etharp_find_entry(ip_addr_t *ipaddr, u8_t flags)
{
  s8_t old_pending = ARP_TABLE_SIZE, old_stable = ARP_TABLE_SIZE;
  s8_t empty = ARP_TABLE_SIZE;
  u8_t i = 0, age_pending = 0, age_stable = 0;
  /* oldest entry with packets on queue */
  s8_t old_queue = ARP_TABLE_SIZE;
  /* its age */
  u8_t age_queue = 0;

  /**
   * 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) {
    u8_t state = arp_table[i].state;
    /* no empty entry found yet and now we do find one? */
    if ((empty == ARP_TABLE_SIZE) && (state == ETHARP_STATE_EMPTY)) {
      empty = i;
    } else if (state != ETHARP_STATE_EMPTY) 
        state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE);
      /* if given, does IP address match IP address in ARP entry? */
      if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) {
        /* found exact IP address match, simply bail out */
        return i;
      }
      /* pending entry? */
      if (state == ETHARP_STATE_PENDING) {
        /* pending with queued packets? */
        if (arp_table[i].q != NULL) {
          if (arp_table[i].ctime >= age_queue) {
            old_queue = i;
            age_queue = arp_table[i].ctime;
          }
        } else
        /* pending without queued packets? */
        {
          if (arp_table[i].ctime >= age_pending) {
            old_pending = i;
            age_pending = arp_table[i].ctime;
          }
        }
      /* stable entry? */
      } else if (state >= ETHARP_STATE_STABLE) {
        {
          /* remember entry with oldest stable entry in oldest, its age in maxtime */
          if (arp_table[i].ctime >= age_stable) {
            old_stable = i;
            age_stable = arp_table[i].ctime;
          }
        }
      }
    }
  }
  /* { we have no match } => try to create a new entry */
   
  /* don't create new entry, only search? */
  if (((flags & ETHARP_FLAG_FIND_ONLY) != 0) ||
      /* or no empty entry found and not allowed to recycle? */
      ((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_FLAG_TRY_HARD) == 0))) {
    return (s8_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 }
   */ 

  /* 1) empty entry available? */
  if (empty < ARP_TABLE_SIZE) {
    i = empty;
  } else {
    /* 2) found recyclable stable entry? */
    if (old_stable < ARP_TABLE_SIZE) {
      /* recycle oldest stable*/
      i = old_stable;
    } else if (old_pending < ARP_TABLE_SIZE) {
      /* recycle oldest pending */
      i = old_pending;
    /* 4) found recyclable pending entry with queued packets? */
    } else if (old_queue < ARP_TABLE_SIZE) {
      /* recycle oldest pending (queued packets are free in etharp_free_entry) */
      i = old_queue;
      /* no empty or recyclable entries found */
    } else {
      return (s8_t)ERR_MEM;
    }
    etharp_free_entry(i);
  }
    arp_table[i].state == ETHARP_STATE_EMPTY);

  /* IP address given? */
  if (ipaddr != NULL) {
    /* set IP address */
    ip_addr_copy(arp_table[i].ipaddr, *ipaddr);
  }
  arp_table[i].ctime = 0;
  return (err_t)i;
}

此函數較為複雜,稍微整理一下流程:

  • 對於每個表項,判斷是否是empty,

    • 若不是,則表明已經被占用,立即檢查IP是否匹配?

      • 若匹配,則返回該索引值

      • 若不匹配,判斷是否是pending狀態,且該索引的數據包指針是否為空?該函數試圖分別記錄生存時間最長的有數據緩存或無數據緩存的表項索引。如果一個表項也不是Pending狀態,則判斷是不是stable狀態?

        • 對於stable狀態,與pending狀態處理過程類似,該函數試圖記錄生產時間最長的stable的表項索引。


2.etharp_updata_arp_entry()

​ 接下來看看這個函數就比較輕鬆了。先看代碼:

tatic err_t
etharp_update_arp_entry(struct netif *netif, ip_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)
{
  s8_t i;
    
  if (ip_addr_isany(ipaddr) ||             //地址有效性驗證,不能為廣播,多播地址建立表項
      ip_addr_isbroadcast(ipaddr, netif) ||
      ip_addr_ismulticast(ipaddr)) {
    return ERR_ARG;
  }
    
  /* 查找或新建一個表項 */
  i = etharp_find_entry(ipaddr, flags);
  /*若不合法,返回 */
  if (i < 0) {
    return (err_t)i;
  }
    /* mark it stable */
    arp_table[i].state = ETHARP_STATE_STABLE;   //將對應的表項狀態改為stable
    
  /* 更新這個表項的網路介面 */
  arp_table[i].netif = netif;
    
  /* 更新緩存標誌的MAC地址 */
  ETHADDR32_COPY(&arp_table[i].ethaddr, ethaddr);
  /* reset time stamp */
  arp_table[i].ctime = 0;
    
  /* 該表項中有未發生出去的數據包,則將這些數據包發送出去! */
  while (arp_table[i].q != NULL) {
    struct pbuf *p;
      
    struct etharp_q_entry *q = arp_table[i].q;  //指向緩衝隊列頭部。此結構前文有過介紹。

    arp_table[i].q = q->next;                 //緩衝隊列指針指向下一個結點

    p = q->p;                                 //取得緩衝隊列第一個數據包pbuf

    memp_free(MEMP_ARP_QUEUE, q);             //釋放etharp_q_entry記憶體池空間

    etharp_send_ip(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr); //發送數據包
    pbuf_free(p);
  }
  return ERR_OK;
}

函數的執行流程如下:

​ 首先調用entry_find_entry()找到一個緩衝表項,然後設置表項的相應成員(state,netif,ethaddr,cttime),若這個表項中有未發送的數據,那麼就發送這個數據包。根據返回的arp表項的不同狀態會有以下三種情況:

  • 如果是empty狀態的表項,那麼就填充表項的欄位,並設置未stable狀態。

  • 如果是pending 狀態的表項,那麼就說明已經有與相同IP地址的MAC地址返回,則改變未Stable狀態。

  • 如果是stable 狀態的表項,則只需要將ctime複位即可。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、單元測試框架簡介 1. 什麼是單元測試 單元測試是指在軟體開發過程中,針對軟體的最小單位(函數,方法)進行正確性的檢查測試。 2. 常用單元測試框架 2.1 Java 類別 junit testng 2.2 Python 類別 unittest pytest 3. 單元測試框架主要作用 測試發現 ...
  • 【深進1.例1】求區間和 題目描述 給定 $n$ 個正整數組成的數列 $a_1, a_2, \cdots, a_n$ 和 $m$ 個區間 $[l_i,r_i]$,分別求這 $m$ 個區間的區間和。 輸入格式 共 $n+m+2$ 行。 第一行,為一個正整數 $n$ 。 第二行,為 $n$ 個正整數 $ ...
  • 原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此聲明。 簡介 日常編程工作中,Java集合會經常被使用到,且經常需要對集合做一些類似過濾、排序、對象轉換之類的操作。 為了簡化這類操作,Java8添加了一套新的Stream API,使用方式就像寫SQL一樣,大大簡化了這 ...
  • 這篇文章主要描述分散式數據存儲系統中的數據分片方法,包括哈希方法、一致性哈希方法、帶有限負載的一致性哈希方法以及帶虛擬節點的一致性哈希方法。 ...
  • Lambda 表達式以及方法引用 Java 8 的新特性筆記,重點講的是: Lambda 函數式介面 方法引用 Steam 流 Lambda 表達式 Lambda 的基礎使用不記錄,記錄 JDK 8 實戰 書上的一些底層和核心筆記。 行為參數化 一個貫徹 Lambda 表達式的一個模式、編程規範。 ...
  • 服務端 工作需要又需要用到socketTCP通訊,這麼多年了,終於稍微能寫點了。讓我說其實也說不出個啥來,看了很多的非同步後稍微對非同步socket的導流 endreceive後 再beginreceive 形成一個內迴圈有了個認識,加上我自己的封包拆包機制,然後再仿那些其它的大多數代碼結構弄點onRe ...
  • 輸入系統 常見的輸入設備有鍵盤、滑鼠、遙控桿、書寫板、觸摸屏等等,用戶通過這些輸入設備與Linux系統進行數據交換。 內核中怎樣表示一個輸入設備 // include/linux/input.h struct input_dev { const char *name; //設備名稱 const ch ...
  • Mac支持 NTFS 系統版本 CPU型號 Ventura 13.2 Apple M1 安裝brew 前往官網查看官網安裝教程,安裝過程中可能存在安裝失敗的問題,基本安裝失敗都是網路的問題。可以嘗試使用知乎大佬金牛肖馬的國內加速安裝腳本。 brew官網 國內加速安裝 安裝macfuse 是安裝mac ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...