【lwip】07-鏈路層收發乙太網資料框原始碼分析

2022-11-01 15:01:30

前言

參考lwip的ethernet.cethernet.h檔案。
原文:李柱明部落格園

7.1 鏈路層概述

簡單概述。

相關術語:

  • 結點(node):執行鏈路層協定的裝置。如主機、路由器、交換機和WiFi接入點。
  • 鏈路(link):沿著通訊路徑連線相鄰結點的通訊通道。
  • 資料框:鏈路層每一幀封包size有限,如果上層傳遞下來的資料包size超出鏈路層每幀能接收的資料size極限,就會分片。(這裡不細說)

在TCP/IP協定族中,鏈路層主要有三個目的:

  1. 為IP模組傳送和接收IP資料包;
  2. 為ARP模組傳送ARP請求和接收ARP應答;
  3. 為RARP傳送RARP請求和接收RARP應答。

TCP/IP支援多種不同的鏈路層協定,這取決於網路所使用的硬體,如乙太網、令牌環網、FDDI(光纖分散式資料介面)及RS-232序列線路等。

資料包從源主機傳輸到目標主機時,資料包必須通過端到端路徑上的各段鏈路才能達到目標主機。

各種端到端的鏈路層協定由具體的端到端決定,鏈路層裝置只需要把資料包封裝在自己的鏈路層幀並轉發到下一個端裝置即可。

7.2 MAC地址的基本概念

MAC Address(Media Access Control Address),亦稱為EHA(Ethernet Hardware Address)、硬體地址、實體地址(Physical Address)。

在OSI模型中,第三層網路層負責 IP地址,第二層資料鏈路層則負責 MAC位址。

所以一個主機會有一個IP地址,而每個網路位置會有一個專屬於它的MAC位址。

MAC地址組成(48位元組):前3個位元組表示組織唯一標誌符(OUI)+後3個位元組由廠家自行分配的擴充套件識別符號。

7.3 乙太網幀結構

IEEE 802.2/802.3(RFC 1042)和乙太網的封裝格式(RFC 894)參考圖:

7.4 乙太網幀結構

IEEE 802.2/802.3(RFC 1042)和乙太網的封裝格式(RFC 894)參考圖:

目標MAC地址(6位元組):這個欄位包含目標網路卡的MAC地址,當一個網路卡收到一個乙太網資料框。

  • 如果該資料框的目標地址是網路卡自身的MAC地址或者是MAC廣播地址,它都將該幀的資料欄位的內容傳遞給網路層;
  • 如果收到了其他MAC地址的幀,則將該資料框丟棄。

源MAC地址(6位元組):這個欄位包含了傳輸該幀到區域網上的介面卡的MAC地址。
型別/長度欄位(2位元組):型別欄位允許乙太網複用多種網路層協定。是表示當前乙太網幀的資料區是哪個協定的封包。

  • 0x0800:表示當前資料框中裝載的資料為IPV4資料包。

  • 0x0806:表示當前資料框中裝載的資料為ARP資料包。

  • 0x0835:表示當前資料框中裝載的資料為RARP資料包。

  • 注意:

    • Ethernet II和IEEE802.3的幀格式主要的不同點在於前者定義的2位元組的型別,而後者定義的是2位元組的長度。型別欄位和長度欄位互斥,但是型別值和長度值是不相同的,以此來區別兩種幀格式。
    • 如果該欄位值大於等於0x0600時,表示封包中的協定型別,反之表示長度。
    • 長度欄位表示它後續資料的位元組長度,但不包括CRC校驗碼。

資料欄位(46~1500位元組):這個欄位承載了上層資料包。

  • 乙太網的最大傳輸單元(MTU)是1500位元組。這意味著如果IP資料包超過了1500位元組,則主機必須將該資料包分片。
  • 資料欄位的最小長度是46位元組,如果資料包小於46位元組,資料包必須被填充到46位元組。當採用填充時,傳遞到網路層的資料包括資料包和填充部分,網路層使用IP資料包首部中的長度欄位來去除填充部分。

CRC(4位元組):CRC欄位包含了乙太網的差錯校驗資訊。

在乙太網中MAC地址可分為3類:

  1. 單播地址。

    • 通常是對應指定網路卡。
    • 乙太網要求單播MAC地址第一個bit(最先發出)為0。意思就是裝置的MAC地址第一個bit必須是0,因為多播的第一個bit為1,這樣多播地址就不會與任務網路卡的mac地址衝突。
  2. 多播地址。

    • 乙太網要求多播MAC地址第一個bit(最先發出)為1。

      • IPV4對應的:01:00:5E:00:00:00 —— 01:00:5E:7F:FF:FF
      • IPV6對應的:33:33:xx:xx:xx:xx
  3. 廣播地址。

    • 乙太網要求廣播地址48bit全為1。FF:FF:FF:FF:FF:FF

7.5 乙太網幀報文資料結構

乙太網首部資料結構:struct eth_hdr

/** 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); /* 目標MAC位址列位 */
  PACK_STRUCT_FLD_S(struct eth_addr src); /* 源MAC位址列位 */
  PACK_STRUCT_FIELD(u16_t type); /* 協定型別欄位 */
} PACK_STRUCT_STRUCT;

7.6 傳送乙太網資料框

乙太網鏈路層發包使用ethernet_output()函數。

主要內容:

  • 填充乙太網幀各個欄位,如有VLAN,則VLAN也填充。
  • 通過鏈路層發出:netif->linkoutput(netif, p);
/**
 * @ingroup ethernet
 * Send an ethernet packet on the network using netif->linkoutput().
 * The ethernet header is filled in before sending.
 *
 * @see LWIP_HOOK_VLAN_SET
 *
 * @param netif the lwIP network interface on which to send the packet
 * @param p the packet to send. pbuf layer must be @ref PBUF_LINK.
 * @param src the source MAC address to be copied into the ethernet header
 * @param dst the destination MAC address to be copied into the ethernet header
 * @param eth_type ethernet type (@ref lwip_ieee_eth_type)
 * @return ERR_OK if the packet was sent, any other err_t on failure
 */
err_t
ethernet_output(struct netif * netif, struct pbuf * p,
                const struct eth_addr * src, const struct eth_addr * dst,
                u16_t eth_type) {
  struct eth_hdr *ethhdr;
  u16_t eth_type_be = lwip_htons(eth_type);

#if ETHARP_SUPPORT_VLAN && (defined(LWIP_HOOK_VLAN_SET) || LWIP_VLAN_PCP)
  s32_t vlan_prio_vid;
#ifdef LWIP_HOOK_VLAN_SET
  vlan_prio_vid = LWIP_HOOK_VLAN_SET(netif, p, src, dst, eth_type); /* 使用勾點函數來處理獲取VLAN幀的TCI欄位 */
#elif LWIP_VLAN_PCP
  vlan_prio_vid = -1;
  if (netif->hints && (netif->hints->tci >= 0)) {
    vlan_prio_vid = (u16_t)netif->hints->tci; /* 直接從網路卡中獲取VLAN幀的TCI欄位 */
  }
#endif
  if (vlan_prio_vid >= 0) { /* 如果需要開啟VLAN標籤,就需要在乙太網幀中組建VLAN欄位 */
    struct eth_vlan_hdr *vlanhdr;

    LWIP_ASSERT("prio_vid must be <= 0xFFFF", vlan_prio_vid <= 0xFFFF);

    if (pbuf_add_header(p, SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR) != 0) { /* pbuf的payload往前偏移,包含乙太網首部(包含VLAN欄位) */
      goto pbuf_header_failed;
    }
    vlanhdr = (struct eth_vlan_hdr *)(((u8_t *)p->payload) + SIZEOF_ETH_HDR); /* 這裡注意偏移。瞭解帶VLAN標籤的乙太網幀報文就知道下面的操作 */
    vlanhdr->tpid     = eth_type_be; /* 乙太網的型別欄位 */
    vlanhdr->prio_vid = lwip_htons((u16_t)vlan_prio_vid); /* VLAN標籤的TCI欄位 */

    eth_type_be = PP_HTONS(ETHTYPE_VLAN); /* 這裡才是VLAN的TPID欄位(程式碼實現的手法) */
  } else
#endif /* ETHARP_SUPPORT_VLAN && (defined(LWIP_HOOK_VLAN_SET) || LWIP_VLAN_PCP) */
  {
    if (pbuf_add_header(p, SIZEOF_ETH_HDR) != 0) { /* pbuf的payload往前偏移,包含乙太網首部 */
      goto pbuf_header_failed;
    }
  }

  LWIP_ASSERT_CORE_LOCKED(); /* tcpip核心上鎖確認 */

  ethhdr = (struct eth_hdr *)p->payload; /* 指標賦值 */
  ethhdr->type = eth_type_be; /* 協定型別欄位/如果開啟了VLAN,這裡才是VLAN的TPID欄位 */
  SMEMCPY(&ethhdr->dest, dst, ETH_HWADDR_LEN); /* 目標MAC欄位 */
  SMEMCPY(&ethhdr->src,  src, ETH_HWADDR_LEN); /* 源MAC欄位 */

  LWIP_ASSERT("netif->hwaddr_len must be 6 for ethernet_output!",
              (netif->hwaddr_len == ETH_HWADDR_LEN));
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE,
              ("ethernet_output: sending packet %p\n", (void *)p));

  /* 通過網路卡傳送乙太網幀 */
  return netif->linkoutput(netif, p);

pbuf_header_failed:
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
              ("ethernet_output: could not allocate room for header.\n"));
  LINK_STATS_INC(link.lenerr);
  return ERR_BUF;
}

7.7 接收乙太網資料框

乙太網鏈路層收包使用ethernet_input()函數。

該函數主要是根據乙太網幀首部的型別欄位,把包分發到不同的協定處理。

IP封包丟到:ip_input()

ARP封包丟到:etharp_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.<br>
 * Don't call directly, pass to netif_add() and call netif->input().
 *
 * @param p the received packet, p->payload pointing to the ethernet header
 * @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) {
    /* 只有一個乙太網報頭(或更少)的封包,不處理 */
    ETHARP_STATS_INC(etharp.proterr);
    ETHARP_STATS_INC(etharp.drop);
    MIB2_STATS_NETIF_INC(netif, ifinerrors);
    goto free_and_return;
  }

  /* 找到乙太網首部欄位 */
  ethhdr = (struct eth_hdr *)p->payload;
  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE,
              ("ethernet_input: dest:%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F", src:%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F", type:%"X16_F"\n",
               (unsigned char)ethhdr->dest.addr[0], (unsigned char)ethhdr->dest.addr[1], (unsigned char)ethhdr->dest.addr[2],
               (unsigned char)ethhdr->dest.addr[3], (unsigned char)ethhdr->dest.addr[4], (unsigned char)ethhdr->dest.addr[5],
               (unsigned char)ethhdr->src.addr[0],  (unsigned char)ethhdr->src.addr[1],  (unsigned char)ethhdr->src.addr[2],
               (unsigned char)ethhdr->src.addr[3],  (unsigned char)ethhdr->src.addr[4],  (unsigned char)ethhdr->src.addr[5],
               lwip_htons(ethhdr->type)));

  type = ethhdr->type;
#if ETHARP_SUPPORT_VLAN
  if (type == PP_HTONS(ETHTYPE_VLAN)) {
    struct eth_vlan_hdr *vlan = (struct eth_vlan_hdr *)(((char *)ethhdr) + SIZEOF_ETH_HDR);
    next_hdr_offset = SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR; /* 找到下一個協定層的首部。這裡就是乙太網幀首部長度 */
    if (p->len <= SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR) {
      /* 只有ethernet/vlan報頭(或更少)的封包,不處理 */
      ETHARP_STATS_INC(etharp.proterr);
      ETHARP_STATS_INC(etharp.drop);
      MIB2_STATS_NETIF_INC(netif, ifinerrors);
      goto free_and_return;
    }
#if defined(LWIP_HOOK_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) /* if not, allow all VLANs */
#ifdef LWIP_HOOK_VLAN_CHECK
    if (!LWIP_HOOK_VLAN_CHECK(netif, ethhdr, vlan)) { /* 優先使用VLAN勾點函數的過濾 */
#elif defined(ETHARP_VLAN_CHECK_FN)
    if (!ETHARP_VLAN_CHECK_FN(ethhdr, vlan)) { /* ETHARP_VLAN_CHECK_FN函數的過濾 */
#elif defined(ETHARP_VLAN_CHECK)
    if (VLAN_ID(vlan) != ETHARP_VLAN_CHECK) { /* 指定接收一個VLAN */
#endif
      /* 靜默忽略此報文:不是我們需要的VLAN */
      pbuf_free(p);
      return ERR_OK;
    }
#endif /* defined(LWIP_HOOK_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) */
    type = vlan->tpid; /* 這個欄位其實就是乙太網首部的協定型別欄位 */
  }
#endif /* ETHARP_SUPPORT_VLAN */

#if LWIP_ARP_FILTER_NETIF
  netif = LWIP_ARP_FILTER_NETIF_FN(p, netif, lwip_htons(type)); /* 一個硬體對映多個IP。找到對應的netif */
#endif /* LWIP_ARP_FILTER_NETIF*/

  if (p->if_idx == NETIF_NO_INDEX) {
    /* pbuf標記對應netif標識 */
    p->if_idx = netif_get_index(netif);
  }

  if (ethhdr->dest.addr[0] & 1) { /* 目標MAC分析,首個bit為1,說明是非單播包 */
    /* 多播或者廣播包 */
    if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) { /* 0x01 */
#if LWIP_IPV4
      if ((ethhdr->dest.addr[1] == LL_IP4_MULTICAST_ADDR_1) && /* 0x00 */
          (ethhdr->dest.addr[2] == LL_IP4_MULTICAST_ADDR_2)) { /* 0x5e */
        /* 01-00-5e-開頭的為IPV4鏈路層組播包:將pbuf標記為鏈路層組播 */
        p->flags |= PBUF_FLAG_LLMCAST;
      }
#endif /* LWIP_IPV4 */
    }
#if LWIP_IPV6
    else if ((ethhdr->dest.addr[0] == LL_IP6_MULTICAST_ADDR_0) && /* 0x33 */
             (ethhdr->dest.addr[1] == LL_IP6_MULTICAST_ADDR_1)) { /* 0x33 */
      /* 33-33-開頭的為IPV6鏈路層組播包:將pbuf標記為鏈路層組播 */
      p->flags |= PBUF_FLAG_LLMCAST;
    }
#endif /* LWIP_IPV6 */
    else if (eth_addr_cmp(&ethhdr->dest, &ethbroadcast)) {/* FF:FF:FF:FF:FF:FF */
      /* 將pbuf標記為鏈路層廣播 */
      p->flags |= PBUF_FLAG_LLBCAST;
    }
  }

  switch (type) { /* 乙太網幀協定型別欄位處理 */
#if LWIP_IPV4 && LWIP_ARP
    case PP_HTONS(ETHTYPE_IP): /* IPv4封包 */
      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
        /* 如果對應的netif不是乙太網裝置,那當前封包不能流入這個netif */
        goto free_and_return;
      }
      /* 鏈路層處理完畢,pbuf資料區指向跳過乙太網首部,指向乙太網幀資料欄位,然後遞交給網路層處理 */
      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 {
        /* 轉交給ipv4模組處理 */
        ip4_input(p, netif);
      }
      break;

    case PP_HTONS(ETHTYPE_ARP): /* ARP封包 */
      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
        /* 如果對應的netif不是乙太網裝置,那當前封包不能流入這個netif */
        goto free_and_return;
      }
      /* 鏈路層處理完畢,pbuf資料區指向跳過乙太網首部,指向乙太網幀資料欄位,然後遞交給網路層處理 */
      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 {
        /* 轉交給ARP模組處理 */
        etharp_input(p, netif);
      }
      break;
#endif /* LWIP_IPV4 && LWIP_ARP */
#if PPPOE_SUPPORT
    case PP_HTONS(ETHTYPE_PPPOEDISC): /* PPP乙太網上發現階段的封包 */
      /* 轉交給PPP分析並處理髮現階段的封包 */
      pppoe_disc_input(netif, p);
      break;

    case PP_HTONS(ETHTYPE_PPPOE): /* PPP乙太網上對談階段的封包 */
      /* 轉交給PPP分析並處理對談節點的封包 */
      pppoe_data_input(netif, p);
      break;
#endif /* PPPOE_SUPPORT */

#if LWIP_IPV6
    case PP_HTONS(ETHTYPE_IPV6): /* IPv6封包 */
      /* 鏈路層處理完畢,pbuf資料區指向跳過乙太網首部,指向乙太網幀資料欄位,然後遞交給網路層處理 */
      if ((p->len < next_hdr_offset) || pbuf_remove_header(p, next_hdr_offset)) {
        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
                    ("ethernet_input: IPv6 packet dropped, too short (%"U16_F"/%"U16_F")\n",
                     p->tot_len, next_hdr_offset));
        goto free_and_return;
      } else {
        /* 轉交給IPV6模組處理 */
        ip6_input(p, netif);
      }
      break;
#endif /* LWIP_IPV6 */

    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; /* 本協定棧無法處理的乙太網資料框,直接丟棄 */
  }

  /* 乙太網資料框有效,已經轉交處理了 */
  return ERR_OK;

free_and_return:
  pbuf_free(p);
  return ERR_OK; /* 乙太網資料框無效,丟棄這個封包 */
}

7.8 虛擬區域網VLAN原始碼分析

7.8.1 乙太網標準幀和VLAN幀的區別

乙太網標準資料框報文及帶VLAN標籤的乙太網標準資料框報文的差異:

VLAN欄位是插入在乙太網首部欄位中。再看程式碼實現,就明白了。

7.8.2 乙太網傳送帶VLAN資料框

VLAN欄位資料結構:

  • TPID和TCI欄位倒置。就是為了程式碼簡易。
/** VLAN header inserted between ethernet header and payload
 * if 'type' in ethernet header is ETHTYPE_VLAN.
 * See IEEE802.Q */
struct eth_vlan_hdr {
  PACK_STRUCT_FIELD(u16_t prio_vid);
  PACK_STRUCT_FIELD(u16_t tpid);
} PACK_STRUCT_STRUCT;

組建帶VLAN標籤的乙太網幀程式碼段:

  • 因為只能偏移整個乙太網幀首部,多偏移了型別欄位的兩個位元組。
  • 所以按上述VLAN資料結構來看,ACK_STRUCT_FIELD(u16_t tpid);實際指向的是乙太網型別欄位。
  • 而為了相容後面程式碼一致性,eth_type_be = PP_HTONS(ETHTYPE_VLAN);才是真正的VLAN的TPID欄位。
vlanhdr = (struct eth_vlan_hdr *)(((u8_t *)p->payload) + SIZEOF_ETH_HDR); /* 這裡注意偏移。瞭解帶VLAN標籤的乙太網幀報文就知道下面的操作 */
vlanhdr->tpid     = eth_type_be; /* 乙太網的型別欄位 */
vlanhdr->prio_vid = lwip_htons((u16_t)vlan_prio_vid); /* VLAN標籤的TCI欄位 */

eth_type_be = PP_HTONS(ETHTYPE_VLAN); /* 這裡才是VLAN的TPID欄位(程式碼實現的手法) */

7.8.3 乙太網接收帶VLAN資料框

ethernet_input()接收到資料框時VLAN部分程式碼處理:

如果對VLAN不感興趣,上面程式碼可以直接忽略VLAN部分,這樣會更加便於分析。

其中檢查過濾VLAN,有三種方式(僅能選其一),優先順序又高到低,描述如下:

  • LWIP_HOOK_VLAN_CHECK:VLAN勾點函數,檢查當前資料框是否是需要的VLAN。被ethernet_input()函數呼叫。
  • ETHARP_VLAN_CHECK_FN:也是檢查當前資料框是否是需要的VLAN。返回1表示接受該資料框。
  • ETHARP_VLAN_CHECK:指定一個VLAN ID,整個協定棧只接收該VLAN的流量。

7.8.4 開啟VLAN功能ETHARP_SUPPORT_VLAN

/**
 * ETHARP_SUPPORT_VLAN==1: support receiving and sending ethernet packets with
 * VLAN header. See the description of LWIP_HOOK_VLAN_CHECK and
 * LWIP_HOOK_VLAN_SET hooks to check/set VLAN headers.
 * Additionally, you can define ETHARP_VLAN_CHECK to an u16_t VLAN ID to check.
 * If ETHARP_VLAN_CHECK is defined, only VLAN-traffic for this VLAN is accepted.
 * If ETHARP_VLAN_CHECK is not defined, all traffic is accepted.
 * Alternatively, define a function/define ETHARP_VLAN_CHECK_FN(eth_hdr, vlan)
 * that returns 1 to accept a packet or 0 to drop a packet.
 */
#if !defined ETHARP_SUPPORT_VLAN || defined __DOXYGEN__
#define ETHARP_SUPPORT_VLAN             1
#endif

LWIP_HOOK_VLAN_CHECK勾點格式說明:

/**
 * LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr):
 * Called from ethernet_input() if VLAN support is enabled
 * Signature:\code{.c}
 *   int my_hook(struct netif *netif, struct eth_hdr *eth_hdr, struct eth_vlan_hdr *vlan_hdr);
 * \endcode
 * Arguments:
 * - netif: struct netif on which the packet has been received
 * - eth_hdr: struct eth_hdr of the packet
 * - vlan_hdr: struct eth_vlan_hdr of the packet
 * Return values:
 * - 0: Packet must be dropped.
 * - != 0: Packet must be accepted.
 */
#ifdef __DOXYGEN__
#define LWIP_HOOK_VLAN_CHECK(netif, eth_hdr, vlan_hdr)
#endif

7.9 一個硬體對映到多個IP

一個硬體對映到多個IP的實現需要開啟LWIP_ARP_FILTER_NETIF宏並定義LWIP_ARP_FILTER_NETIF_FN()函數。

LWIP_ARP_FILTER_NETIF_FN()函數會在ethernet_input()乙太網接收到資料框處理時被呼叫,會根據資料框的內容更新出的netif。

該函數程式碼實現思路說明:

  • 根據乙太網資料框協定型別區別出IP、ARP或者其它協定。
  • 再根據協定分析出目標協定地址IP。
  • 再遍歷netif連結串列,匹配IP。

注意,該函數呼叫是在VLAN過濾後才被呼叫的。因為VLAN屬於鏈路層,對映多個IP的判斷欄位屬於網路層。

/** Define this to 1 and define LWIP_ARP_FILTER_NETIF_FN(pbuf, netif, type)
 * to a filter function that returns the correct netif when using multiple
 * netifs on one hardware interface where the netif's low-level receive
 * routine cannot decide for the correct netif (e.g. when mapping multiple
 * IP addresses to one hardware interface).
 */
#ifndef LWIP_ARP_FILTER_NETIF
#define LWIP_ARP_FILTER_NETIF 1
#endif