前言
最近在学习zynq中的lwip协议族,找不到很好的记笔记的地方,所以就用csdn记录一下自己的学习过程。现在对lwip不熟悉,只是把官方的lwip echo server例程跑了一下,能跑通就一点点的照着学了,笔记都是根据自己的理解写的,而且部分内容可能也只针对lwip echo server例程有效,笔记可以供有缘人参考,但不敢保证全对,有不对的地方也期待有高人指点一二。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_40356705/article/details/136824649
一、概述
- 原型
err_t ethernet_input(struct pbuf *p, struct netif *netif)
- 参数
struct pbuf *p :接收到的数据包,p->payload指向以太网头部
struct netif *netif :接收数据包的网络接口 - 作用
函数用于接收从网络接口传来的以太网数据包,进行基本的长度和类型检查,并准备进行后续的处理(如 IP 层处理、ARP 处理等)。如果数据包无效或不支持,则执行错误统计和丢弃操作。
二、函数体
// 定义 ethernet_input 函数,用于处理从网络接口接收的以太网数据包
err_t ethernet_input(struct pbuf *p, struct netif *netif)
{
// 定义一个指向以太网头部结构的指针
struct eth_hdr *ethhdr;
// 定义一个16位无符号整数变量,用于存储以太网数据包的类型
u16_t type;
// 如果定义了LWIP_ARP、ETHARP_SUPPORT_VLAN或LWIP_IPV6,则定义一个变量来存储下一个头部的偏移量
#if LWIP_ARP || ETHARP_SUPPORT_VLAN || LWIP_IPV6
u16_t next_hdr_offset = SIZEOF_ETH_HDR;
#endif /* LWIP_ARP || ETHARP_SUPPORT_VLAN || LWIP_IPV6 */
// 确保 lwIP 核心已被锁定,防止并发问题
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);
// 跳转到 free_and_return 标签,执行数据包释放和返回错误代码的操作(该部分代码未给出)
goto free_and_return;
}
// 如果数据包的 if_idx 字段为 NETIF_NO_INDEX,则设置其值为网络接口索引
if (p->if_idx == NETIF_NO_INDEX) {
p->if_idx = netif_get_index(netif);
}
// 获取数据包的负载(payload)指针,并强制转换为以太网头部结构的指针
ethhdr = (struct eth_hdr *)p->payload;
// 输出调试信息,打印目的 MAC 地址、源 MAC 地址和数据包类型
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;
// 如果定义了ETHARP_SUPPORT_VLAN,则支持VLAN标签
#if ETHARP_SUPPORT_VLAN
// 检查数据包类型是否为VLAN类型
if (type == PP_HTONS(ETHTYPE_VLAN)) {
// 将ethhdr指针转换为指向带有VLAN头的以太网头部的指针
struct eth_vlan_hdr *vlan = (struct eth_vlan_hdr *)(((char *)ethhdr) + SIZEOF_ETH_HDR);
// 设置下一个头部(即VLAN头部之后)的偏移量
next_hdr_offset = SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR;
// 检查数据包长度是否足够包含以太网头部和VLAN头部
if (p->len <= SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR) {
// 数据包长度不足,不是有效的以太网/VLAN数据包
ETHARP_STATS_INC(etharp.proterr); // 增加以太网协议错误统计
ETHARP_STATS_INC(etharp.drop); // 增加丢弃数据包统计
MIB2_STATS_NETIF_INC(netif, ifinerrors); // 增加网络接口输入错误统计
goto free_and_return; // 跳转到free_and_return标签,释放数据包并返回错误代码
}
// 如果定义了VLAN检查相关的宏(可以是钩子函数或常量检查),则执行VLAN检查
#if defined(LWIP_HOOK_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) /* 如果未定义,则允许所有VLAN */
// 根据定义的宏使用不同的VLAN检查方法
#ifdef LWIP_HOOK_VLAN_CHECK
if (!LWIP_HOOK_VLAN_CHECK(netif, ethhdr, vlan)) {
#elif defined(ETHARP_VLAN_CHECK_FN)
if (!ETHARP_VLAN_CHECK_FN(ethhdr, vlan)) {
#elif defined(ETHARP_VLAN_CHECK)
if (VLAN_ID(vlan) != ETHARP_VLAN_CHECK) {
#endif
// VLAN检查失败,忽略此数据包(不是为我们的VLAN)
pbuf_free(p); // 释放数据包
return ERR_OK; // 返回无错误代码(通常用于忽略数据包的情况)
}
#endif /* defined(LWIP_HOOK_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) */
// 更新数据包类型,为VLAN内部的实际类型
type = vlan->tpid;
}
#endif /* ETHARP_SUPPORT_VLAN */
// 如果定义了LWIP_ARP_FILTER_NETIF,则使用ARP过滤来可能改变网络接口
#if LWIP_ARP_FILTER_NETIF
netif = LWIP_ARP_FILTER_NETIF_FN(p, netif, lwip_htons(type));
#endif /* LWIP_ARP_FILTER_NETIF*/
// 检查以太网头部的目的地址是否为多播或广播地址
if (ethhdr->dest.addr[0] & 1) {
// 如果目的地址的第一个字节的最低位为1,则可能是多播或广播地址
if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) {
// 检查是否为IPv4多播地址
#if LWIP_IPV4
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;
}
#endif /* LWIP_IPV4 */
}
#if LWIP_IPV6
else if ((ethhdr->dest.addr[0] == LL_IP6_MULTICAST_ADDR_0) &&
(ethhdr->dest.addr[1] == LL_IP6_MULTICAST_ADDR_1)) {
// 检查是否为IPv6多播地址
// 标记pbuf为链路层多播
p->flags |= PBUF_FLAG_LLMCAST;
}
#endif /* LWIP_IPV6 */
else if (eth_addr_cmp(ðhdr->dest, ðbroadcast)) {
// 检查是否为广播地址
p->flags |= PBUF_FLAG_LLBCAST; /* 将pbuf标记为链路层多播 */
}
}
switch (type) { // 根据type的值进行条件判断
#if LWIP_IPV4 && LWIP_ARP // 如果定义了LWIP_IPV4和LWIP_ARP
/* IP packet? */
case PP_HTONS(ETHTYPE_IP): // 如果是IP数据包
if (!(netif->flags & NETIF_FLAG_ETHARP)) { // 检查网络接口是否启用了ARP功能
goto free_and_return; // 如果没有启用ARP,则跳转到清理和返回代码部分
}
/* skip Ethernet header (min. size checked above) */
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 {
/* pass to IP layer */
ip4_input(p, netif); // 将数据包传递给IP层处理
}
break; // 跳出case
case PP_HTONS(ETHTYPE_ARP): // 如果是ARP数据包
if (!(netif->flags & NETIF_FLAG_ETHARP)) { // 检查网络接口是否启用了ARP功能
goto free_and_return; // 如果没有启用ARP,则跳转到清理和返回代码部分
}
/* skip Ethernet header (min. size checked above) */
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); // 统计ARP包长度错误次数
ETHARP_STATS_INC(etharp.drop); // 统计ARP包丢弃次数
goto free_and_return; // 跳转到清理和返回代码部分
} else {
/* pass p to ARP module */
etharp_input(p, netif); // 将数据包传递给ARP模块处理
}
break; // 跳出case
#endif /* LWIP_IPV4 && LWIP_ARP */
#if PPPOE_SUPPORT // 如果定义了PPPOE_SUPPORT
case PP_HTONS(ETHTYPE_PPPOEDISC): /* PPP Over Ethernet Discovery Stage */
pppoe_disc_input(netif, p); // 处理PPP Over Ethernet的发现阶段数据包
break; // 跳出case
case PP_HTONS(ETHTYPE_PPPOE): /* PPP Over Ethernet Session Stage */
pppoe_data_input(netif, p); // 处理PPP Over Ethernet的会话阶段数据包
break; // 跳出case
#endif /* PPPOE_SUPPORT */
} // 结束switch语句
#if LWIP_IPV6
case PP_HTONS(ETHTYPE_IPV6): /* IPv6 */
/* 跳过以太网头部 */
/* skip Ethernet header */
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层处理 */
/* pass to IPv6 layer */
ip6_input(p, netif);
}
break; /* 跳出case */
#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统计信息,增加未知协议的数据包数 */
MIB2_STATS_NETIF_INC(netif, ifinunknownprotos);
/* 跳转到清理和返回代码部分 */
goto free_and_return;
}
/* 这意味着pbuf已经被释放或消费,所以调用者不需要再次释放它 */
/* This means the pbuf is freed or consumed,
so the caller doesn't have to free it again */
return ERR_OK;
free_and_return:
/* 释放pbuf */
pbuf_free(p);
/* 返回成功状态 */
return ERR_OK;
}
总体来说,该函数主要是用来分数据包,根据包类型的不同和工程支持的类型不同将收到的数据包上传给不同的处理函数。系统进入该程序后会首先对包长度进行判断,将过短的包抛弃。对于符合规范长度的数据包,先跟据地址进行多播判断,对于多播数据包,系统将打一个多播标签以提示上层函数。根据系统支持的功能的不同进行判断,如果功能支持(如是否支持ipv6)且类型符合,会将数据包交给不同的处理函数。对于ipv4,系统主要进行两个判断,将ip包发给 ip4_input(p, netif)处理,将arp包给etharp_input(p, netif)处理。
在lwip echo sever例程中,由于设置不支持ipv6,实际上系统只执行ip4_input或etharp_input函数中的一个(前提是数据包符合tcp/ip协议规范)
三、调用关系
配置完成后,系统进入main函数进行for循环,在for循环中,调用了xemacif_input函数,该函数最终调用的是xemacpsif_input函数,在xemacpsif_input函数中,运行了回调函数netif->input(p, netif),该回调函数在xemac_add中注册为ethernet_input函数(也就是本次分析的函数)。ethernet_input调用了ip4_input或etharp_input函数