前言

参考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

【lwip】07-链路层收发以太网数据帧源码分析的更多相关文章

  1. zeromq源码分析笔记之线程间收发命令(2)

    在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socke ...

  2. ZRender源码分析2:Storage(Model层)

    回顾 上一篇请移步:zrender源码分析1:总体结构 本篇进行ZRender的MVC结构中的M进行分析 总体理解 上篇说到,Storage负责MVC层中的Model,也就是模型,对于zrender来 ...

  3. ZRender源码分析4:Painter(View层)-中

    回顾 上一篇说到:ZRender源码分析3:Painter(View层)-上,接上篇,开始Shape对象 总体理解 先回到上次的Painter的render方法 /** * 首次绘图,创建各种dom和 ...

  4. ZRender源码分析3:Painter(View层)-上

    回顾 上一篇说到:ZRender源码分析2:Storage(Model层),这次咱看来看看Painter-View层 总体理解 Painter这个类主要负责MVC中的V(View)层,负责将Stora ...

  5. SOFA 源码分析 — 链路数据透传

    前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...

  6. 前端Vue 源码分析-逻辑层

    Vue 源码分析-逻辑层 预期的效果: 监听input的输入,input在输入的时候,会触发 watch与computed函数,并且会更新原始的input的数值.所以直接跟input相关的处理就有3处 ...

  7. 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  8. [Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)

    一.简要介绍 ABP vNext 框架本身就是围绕着 DDD 理念进行设计的,所以在 DDD 里面我们能够见到的实体.仓储.值对象.领域服务,ABP vNext 框架都为我们进行了实现,这些基础设施都 ...

  9. 精尽 MyBatis 源码分析 - 基础支持层

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. 744. 寻找比目标字母大的最小字母--LeetCode

    来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/find-smallest-letter-greater-than-target 著作权归领扣网络所有. ...

  2. [CF1523C] Compression and Expansion (DP/贪心)

    C. Compression and Expansion 题面 一个合法的表单由横向 N N N 行数字链,纵向一层或多层数字链组成,第 k k k 层的数字链(可以想象为前面打了 k k k 个制表 ...

  3. 【JAVA】学习路径35-InputStream使用例子

    import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; pu ...

  4. Spring MVC组件之HandlerAdapter

    Spring MVC组件之HandlerAdapter HandlerAdapter概述 HandlerAdapter组件是一个处理器Handler的适配器.HandlerAdapter组件的主要作用 ...

  5. django_day02

    django_day02 外键 表示一对多 多对一 class Book(models.Model): name = models.CharField(max_length=32) publisher ...

  6. openstack中Cinder组件简解

    一,Cinder组件介绍 概念 cinder组件作用: 块存储服务,为运行实例提供稳定的数据块存储服务 块存储服务,提供对 volume 从创建到删除整个生命周期的管理 二,常用操作 1.Volume ...

  7. 使用Apache Flink 和 Apache Hudi 创建低延迟数据湖管道

    近年来出现了从单体架构向微服务架构的转变.微服务架构使应用程序更容易扩展和更快地开发,支持创新并加快新功能上线时间.但是这种方法会导致数据存在于不同的孤岛中,这使得执行分析变得困难.为了获得更深入和更 ...

  8. 微软出品自动化神器Playwright,不用写一行代码(Playwright+Java)系列(一) 之 环境搭建及脚本录制

    一.前言 半年前,偶然在视频号刷到某机构正在直播讲解Playwright框架的使用,就看了一会,感觉还不错,便被种草,就想着自己有时间也可以自己学一下,这一想着就半年多过去了. 读到这,你可能就去百度 ...

  9. 使用ESP8266nodeMCU 向微信推送模板数据

    使用HTTPS协议向微信公众号推送消息,(使用ESP8266的低成本实现) 前几天被朋友问到这个东西的实现方式,花了一下午时间研究一下,特此记录.没有排版比较乱. 一丶前往微信公众平台注册微信微信公众 ...

  10. SQL语句中过滤条件放在on、where、having的区别和联系

    摘要:SQL语句中,过滤条件放在不同筛选器on.where和having的区别和联系. 综述   在<SQL语句中过滤条件放在on和where子句中的区别和联系>中,介绍了多表关联SQL语 ...