【lwip】07-链路层收发以太网数据帧源码分析( 二 )

数据字段(46~1500字节):这个字段承载了上层数据报 。

  • 以太网的最大传输单元(MTU)是1500字节 。这意味着如果IP数据报超过了1500字节,则主机必须将该数据报分片 。
  • 数据字段的最小长度是46字节,如果数据报小于46字节,数据报必须被填充到46字节 。当采用填充时,传递到网络层的数据包括数据报和填充部分,网络层使用IP数据报首部中的长度字段来去除填充部分 。
【【lwip】07-链路层收发以太网数据帧源码分析】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_SIZEPACK_STRUCT_FLD_8(u8_t padding[ETH_PAD_SIZE]); /* 以太网帧前的填充字段,使后面数据字段地址和系统对齐 */#endifPACK_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_tethernet_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_SETvlan_prio_vid = LWIP_HOOK_VLAN_SET(netif, p, src, dst, eth_type); /* 使用钩子函数来处理获取VLAN帧的TCI字段 */#elif LWIP_VLAN_PCPvlan_prio_vid = -1;if (netif->hints && (netif->hints->tci >= 0)) {vlan_prio_vid = (u16_t)netif->hints->tci; /* 直接从网卡中获取VLAN帧的TCI字段 */}#endifif (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;}

经验总结扩展阅读