当前位置: 首页 > news >正文

IP协议说明

文章目录

  • 前言
  • 一、IP协议的简介
  • 二、IP数据报
    • 1.IP 数据报结构
    • 2.IP 数据报的分片解析
    • 3.IP 数据报的分片重装
  • 三、IP 数据报的输出
  • 四、IP 数据报的输入


前言

IP 指网际互连协议, Internet Protocol 的缩写,是 TCP/IP 体系中的网络层协议。设计 IP 的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP 只为主机提供一种无连接、不可靠的、尽力而为的数据包传输服务。


一、IP协议的简介

IP 协议是整个 TCP/IP 协议族的核心,也是构成互联网的基础。 IP 位于 TCP/IP 模型的网络层(相当于 OSI 模型的网络层),它可以向传输层提供各种协议的信息,例如 TCP、 UDP 等;对下可将 IP 信息包放到链路层,通过以太网、令牌环网络等各种技术来传送。 为了能适应异构网络, IP 强调适应性、简洁性和可操作性,并在可靠性做了一定的牺牲。

二、IP数据报

IP 层数据报也叫做 IP 数据报或者 IP 分组, IP 数据报组装在以太网帧中发送的,它通常由两个部分组成,即 IP 首部与数据区域, 其中 IP 的首部是 20 字节大小,数据区域理论上可以多达65535 个字节, 由于以太网网络接口的最大传输单元为 1500,所以一个完整的数据包不能超出 1500 字节大小。
 IP 数据报结构
(1) 版本: 占 4 位指 IP 协议的版本。通信双方使用的 IP 协议版本必须一致。广泛使用的IP 协议版本号为 4(即 IPv4)。
(2) 首部长度: 占 4 位可表示的最大十进制数值是 15。请注意,这个字段所表示数的单位是 32 位字长(1 个 32 位字长是 4 字节),因此,当 IP 的首部长度为 1111 时(即十进制的 15),首部长度就达到 60 字节。当 IP 分组的首部长度不是 4 字节的整数倍时,必须利用最后的填充字段加以填充。因此数据部分永远在 4 字节的整数倍开始,这样在实现 IP 协议时较为方便。首部长度限制为 60 字节的缺点是有时可能不够用。但这样做是希望用户尽量减少开销。最常用的首部长度就是 20 字节(即首部长度为 0101),这时不使用任何选项。
(3) 区分服务: 占 8 位, 用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。
(4) 总长度: 总长度指首部和数据之和的长度,单位为字节。总长度字段为 16 位,因此数据报的最大长度为 2^16-1=65534 字节。在 IP 层下面的每一种数据链路层都有自己的帧格式,其中包括帧格式中的数据字段的最大长度,这称为最大传送单元 MTU。当一个数据报封装成链路层的帧时,此数据报的总长度(即首部加上数据部分)一定不能超过下面的数据链路层的 MTU 值。
(5) 标识(identification): 占 16 位 IP 软件在存储器中维持一个计数器,每产生一个数据报,计数器就加 1,并将此值赋给标识字段。但这个“标识”并不是序号,因为 IP 是无连接服务,数据报不存在按序接收的问题。当数据报由于长度超过网络的 MTU 而必须分片时,这个标识字段的值就被复制到所有的数据报的标识字段中。相同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报。
(6) 标志(flag): 占 3 位但只有 2 位有意义的。

  1. 标志字段中的最低位记为 MF(More Fragment)。 MF=1 即表示后面“还有分片”的数据报。 MF=0 表示这已是若干数据报片中的最后一个。
  2. 标志字段中间的一位记为 DF(Don’ t Fragment),意思是“不能分片”。只有当 DF=0时才允许分片。
    (7) 片偏移: 占 13 位片偏移指出:较长的分组在分片后,某片在原分组中的相对位置。也就是说,相对用户数据字段的起点,该片从何处开始。片偏移以 8 个字节为偏移单位。这就是说,除了最后一个分片,每个分片的长度一定是 8 字节(64 位)的整数倍。
    (8) 生存时间: 占 8 位生存时间字段常用的的英文缩写是 TTL(Time To Live),表明是数据报在网络中的寿命。由发出数据报的源点设置这个字段。其目的是防止无法交付的数据报无限制地在因特网中兜圈子,因而白白消耗网络资源。最初的设计是以秒作为 TTL 的单位。每经过一个路由器时,就把 TTL 减去数据报在路由器消耗掉的一段时间。若数据报在路由器消耗的时间小于 1 秒,就把 TTL 值减 1。当 TTL 值为 0 时,就丢弃这个数据报。后来把 TTL 字段的功能改为“跳数限制”(但名称不变)。路由器在转发数据报之前就把 TTL 值减 1.若 TTL 值减少到零,就丢弃这个数据报,不再转发。因此, TTL 的单位不再是秒,而是跳数。 TTL 的意义是指明数据报在网络中至多可经过多少个路由器。显然,数据报在网络上经过的路由器的最大数值是 255。 若把 TTL 的初始值设为 1,就表示这个数据报只能在本局域网中传送。
    (9) 协议: 占 8 位协议字段指出此数据报携带的数据是使用何种协议,以便使目的主机的IP 层知道应将数据部分上交给哪个处理过程。
    (10) 首部检验和: 占 16 位这个字段只检验数据报的首部,但不包括数据部分。这是因为数据报每经过一个路由器,路由器都要重新计算一下首部检验和(一些字段,如生存时间、标志、片偏移等都可能发生变化)。不检验数据部分可减少计算的工作量。
    (11) 源地址: 占 32 位。
    (12) 目的地址: 占 32 位。
    (13) 数据区域: 这是 IP 数据报的最后的一个字段,也是最重要的内容, lwIP 发送数据报是把该层的首部封装到数据包里面,在 IP 层也是把 IP 首部封装在其中,因为有数据区域才会有数据报首部的存在,在大多数情况下, IP 数据报中的数据字段包含要交付给目标 IP 地址的运输层(TCP 协议或 UDP 协议),当然数据区域也可承载其他类型的报文,如 ICMP 报文等。

1.IP 数据报结构

在 lwIP 中,为了描述 IP 报文结构, 它在 ip4.h 文件中定义了一个 ip_hdr 结构体来描述 IP数据报的内容:

struct ip_hdr {
/* 版本号+首部长度+服务类型 */
PACK_STRUCT_FLD_8(u8_t _v_hl);
/* 服务类型 */
PACK_STRUCT_FLD_8(u8_t _tos);
/* 总长度(IP 首部+数据区) */
PACK_STRUCT_FIELD(u16_t _len);
/* 数据包标识(编号) */
PACK_STRUCT_FIELD(u16_t _id);
/* 标志+片偏移 */
PACK_STRUCT_FIELD(u16_t _offset);
/* IP 首部标志定义 */
#define IP_RF 0x8000U /* 保留 */
#define IP_DF 0x4000U /* 是否允许分片 */
#define IP_MF 0x2000U /* 后续是否还有更多分片 */
#define IP_OFFMASK 0x1fffU /* 片偏移域掩码 */
/* 生存时间(最大转发次数)+协议类型(IGMP:1、 UDP:17、 TCP:6) */
PACK_STRUCT_FLD_8(u8_t _ttl);
/* 协议*/
PACK_STRUCT_FLD_8(u8_t _proto);
/* 校验和(IP 首部) */
PACK_STRUCT_FIELD(u16_t _chksum);
/* 源 IP 地址/目的 IP 地址 */
PACK_STRUCT_FLD_S(ip4_addr_p_t src);
PACK_STRUCT_FLD_S(ip4_addr_p_t dest);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

2.IP 数据报的分片解析

TCP/IP 协议栈为什么具备分片的概念,因为应用程序处理的数据是不确定的,可能超出网络接口最大传输单元,为此 TCP/IP 协议栈引入了分片概念,它是以 MTU 为界限对这个大型的数据切割成多个小型的数据包。这些小型的数据叫做 IP 的分组和分片,它们在接收方进行重组处理,这样,接收方的应用程序接收到这个大型的数据了。总的来讲, IP 数据报的分片概念是为了解决 IP 数据报数据过大的问题而诞生。 注:以太网最大传输单元 MTU 为 1500。
假设 IP 数据报整体的大小为 4000 字节, IP 首部默认为 20 字节, 而数据区域为 3980。由于以太网最大传输单元为 1500, 所以 lwIP 内核会把这个数据报进行分片处理。

  1. 第一个 IP 分片:
    分片数据大小: 20(IP 首部) + 1480(数据区域)。
    标识: 888。
    标志: IP_MF = 1 后续还有分片。
    片偏移量: 片偏移量是 0, 单位是 8 字节, 本片偏移量相当于 0 字节。
  2. 第二片 IP 数据报:
    分片数据大小: 20(IP 首部) + 1480(数据区域)。
    标识: 888。
    标志: IP_MF = 1 后续还有分片。
    片偏移量: 片偏移量是 185(1480/8), 单位是 8 字节, 本片偏移量相当于 1480 字节。
  3. 第三片 IP 数据报:
    分片数据大小: 20(IP 首部) + 1020(数据区域)。
    标识: 888。
    标志: IP_MF = 0, 后续没有分片。
    片偏移量: 片偏移量是 370(185+185), 单位是 8 字节, 本片偏移量相当于 2960 字节。
    注:这些分片的标识都是一致的,而 IP_MF 表示后续有没有分片,若 IP_MF 为 0,则这个分片为最后一个分片。
     IP 数据报分片示意图
    从上图可以看出,一个大型的 IP 数据包经过网络层处理,它会被分成两个或者两个以上的 IP 分片,这些分片的数据组合起来就是应用程序发送的数据与传输层的首部。
    lwIP实现它的函数为 ip4_frag,代码如下(示例):
/**
* 如果 IP 数据报对 netif 来说太大,则将其分片,
将数据报切成 MTU 大小的块,然后按顺序发送通过将 pbuf_ref 指向 p
* @param p:要发送的 IP 数据包
* @param netif:发送的 netif
* @param dest:目的 IP 地址
* @return ERR_OK:发送成功, err_t:其他
*/
err_t
ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest)
{
struct pbuf *rambuf;
#if !LWIP_NETIF_TX_SINGLE_PBUF
struct pbuf *newpbuf;
u16_t newpbuflen = 0;
u16_t left_to_copy;
#endif
struct ip_hdr *original_iphdr;
struct ip_hdr *iphdr;
/* (1500 - 20)/8 = 偏移 185 */
const u16_t nfb = (u16_t)((netif->mtu - IP_HLEN) / 8);
u16_t left, fragsize;
u16_t ofo;
int last;
u16_t poff = IP_HLEN; /* IP 头部长度 */
u16_t tmp;
int mf_set;
original_iphdr = (struct ip_hdr *)p->payload; /* 指向数据报 */
iphdr = original_iphdr;
/* 判断 IP 头部是否为 20 */
if (IPH_HL_BYTES(iphdr) != IP_HLEN) {
return ERR_VAL;
}
/* tmp 变量获取标志和片偏移数值 */
tmp = lwip_ntohs(IPH_OFFSET(iphdr));
/* ofo = 片偏移 */
ofo = tmp & IP_OFFMASK;
/* mf_set = 分片标志 */
mf_set = tmp & IP_MF;
/* left = 总长度减去 IP 头部等于有效数据长度, 4000 - 20 = 3980 */
left = (u16_t)(p->tot_len - IP_HLEN);
/* 判断 left 是否为有效数据 */
while (left) {
/* 判断有效数据和偏移数据大小, fragsize = 1480 (3980 < 1480 ? 3980 : 1480) */
fragsize = LWIP_MIN(left, (u16_t)(nfb * 8));
/* rambuf 申请 20 字节大小的内存块 */
rambuf = pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM);
if (rambuf == NULL) {
goto memerr;
}
/* 这个 rambuf 有效数据指针指向 original_iphdr 数据报 */
SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
/* iphdr 指向有效区域地址 rambuf->payload */
iphdr = (struct ip_hdr *)rambuf->payload;
/* left_to_copy = 偏移数据大小(1480) */
left_to_copy = fragsize;
while (left_to_copy) {
struct pbuf_custom_ref *pcr;
/* 当前 pbuf 中数据的长度,plen = 3980 - 20 = 3960 */
u16_t plen = (u16_t)(p->len - poff);
/* newpbuflen = 1480 (1480 < 3960 ? 1480 : 3960) */
newpbuflen = LWIP_MIN(left_to_copy, plen);
if (!newpbuflen) {
poff = 0;
p = p->next;
continue;
}
/* pcr 申请内存 */
pcr = ip_frag_alloc_pbuf_custom_ref();
if (pcr == NULL) {
pbuf_free(rambuf);
goto memerr;
}
/* newpbuf 申请内存 1480 字节,
保存了这个数据区域偏移 poff 字节的数据(p->payload + poff) */
newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc,
(u8_t *)p->payload + poff, newpbuflen);
if (newpbuf == NULL) {
/* 释放内存 */
ip_frag_free_pbuf_custom_ref(pcr);
pbuf_free(rambuf);
goto memerr;
}
/* 增加 pbuf 的引用计数 */
pbuf_ref(p);
pcr->original = p;
pcr->pc.custom_free_function = ipfrag_free_pbuf_custom;
/* 将它添加到 rambuf 的链的末尾 */
pbuf_cat(rambuf, newpbuf);
/* left_to_copy = 0 (1480 - 1480) */
left_to_copy = (u16_t)(left_to_copy - newpbuflen);
if (left_to_copy) {
poff = 0;
p = p->next;
}
}
/* poff = 1500 (20 + 1480) */
poff = (u16_t)(poff + newpbuflen);
/* last = 0 (3980 <= (1500 - 20)) */
last = (left <= netif->mtu - IP_HLEN);
/* 设置新的偏移量和 MF 标志 */
tmp = (IP_OFFMASK & (ofo));
/* 判断是否是最后一个分片 */
if (!last || mf_set) {
/* 最后一个片段设置了 MF 为 0 */
tmp = tmp | IP_MF;
}
/* 分段偏移与标志字段 */
IPH_OFFSET_SET(iphdr, lwip_htons(tmp));
/* 设置数据报总长度 = 1500 (1480 + 20) */
IPH_LEN_SET(iphdr, lwip_htons((u16_t)(fragsize + IP_HLEN)));
/* 校验为 0 */
IPH_CHKSUM_SET(iphdr, 0);
/* 发送 IP 数据报 */
netif->output(netif, rambuf, dest);
IPFRAG_STATS_INC(ip_frag.xmit);
/* rambuf 释放内存 */
pbuf_free(rambuf);
/* left = 2500 (3980 - 1480) */
left = (u16_t)(left - fragsize);
/* 片偏移 ofo = 185(0 + 185) */
ofo = (u16_t)(ofo + nfb);
}
MIB2_STATS_INC(mib2.ipfragoks);
return ERR_OK;
memerr:
MIB2_STATS_INC(mib2.ipfragfails);
return ERR_MEM;
}
MIB2_STATS_INC(mib2.ipfragoks);
return ERR_OK;
memerr:
MIB2_STATS_INC(mib2.ipfragfails);
return ERR_MEM;
}

此函数非常简单,首先判断这个大型数据包的有效区域总长度,系统根据这个总长度划分数据区域,接着申请 20+sizeof(struct pbuf)字节的 rampbuf 来存储 IP 首部,然后根据 poff 数值让被分片数据包的 payload 指针偏移 poff 大小,它所指向的地址由 newpbuf 数据包的 payload指针指向,最后调用 netif->output 函数发送该分片,其他分片一样操作。
IP 数据报分片原理示意图
newpbuf 的 payload 指针指向的地址由左边的 payload 指针经过偏移得来的。

3.IP 数据报的分片重装

由于 IP 分组在网络传输过程中到达目的地点的时间是不确定的,所以后面的分组可能比前面的分组先达到目的地点。 为此, lwIP 内核需要将接收到的分组暂存起来,等所有的分组都接收完成之后,再将数据传递给上层。
在 lwIP 中,有专门的结构体负责缓存这些分组,这个结构体为 ip_reassdata 重装数据链表,该结构体在 ip4_frag.h 文件中定义:

/* 重装数据结构体 */
struct ip_reassdata {
struct ip_reassdata *next; /* 指向下一个重装节点 */
struct pbuf *p; /* 指向分组的 pbuf */
struct ip_hdr iphdr; /* IP 数据报的首部 */
u16_t datagram_len; /* 已收到数据的长度 */
u8_t flags; /* 标志是否最后一个分组 */
u8_t timer; /* 超时间隔 */
};

IP 分组重装示意图
可以看到,这些分片挂载到同一个重装节点上,它们挂载之前,是把 IP 首部的前 8 字节强制转换成三个字段,其中 next_pbuf 指针用来链接这些 IP 分组,形成了单向链表,而 start和 end 字段用来描述分组的顺序, lwIP 系统根据这些数值对分组进行排序。

三、IP 数据报的输出

无论是 UDP 还是 TCP,它们的数据段递交至网络层的接口是一致的,这个接口函数如下所示:

	err_tip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,u8_t ttl, u8_t tos,u8_t proto, struct netif *netif){struct ip_hdr *iphdr;ip4_addr_t dest_addr;if (dest != LWIP_IP_HDRINCL){u16_t ip_hlen = IP_HLEN;/* 第一步: 生成 IP 报头 */if (pbuf_header(p, IP_HLEN)){return ERR_BUF;}/* 第二步: iphdr 指向 IP 头部指针 */iphdr = (struct ip_hdr *)p->payload;/* 设置生存时间(最大转发次数) */IPH_TTL_SET(iphdr, ttl);/* 设置协议类型(IGMP:1、 UDP:17、 TCP:6) */IPH_PROTO_SET(iphdr, proto);/* 设置目的 IP 地址 */ip4_addr_copy(iphdr->dest, *dest);/* 设置版本号+设置首部长度 */IPH_VHL_SET(iphdr, 4, ip_hlen / 4);/* 服务类型 */IPH_TOS_SET(iphdr, tos);/* 设置总长度(IP 首部+数据区) */IPH_LEN_SET(iphdr, lwip_htons(p->tot_len));/* 设置标志+片偏移 */IPH_OFFSET_SET(iphdr, 0);/* 设置数据包标识(编号) */IPH_ID_SET(iphdr, lwip_htons(ip_id));/* 每发送一个数据包,编号加一 */++ip_id;/* 没有指定源 IP 地址 */if (src == NULL){/* 将当前网络接口 IP 地址设置为源 IP 地址 */ip4_addr_copy(iphdr->src, *IP4_ADDR_ANY4);}else{/* 复制源 IP 地址 */ip4_addr_copy(iphdr->src, *src);}}else{/* IP 头部已经包含在 pbuf 中 */iphdr = (struct ip_hdr *)p->payload;ip4_addr_copy(dest_addr, iphdr->dest);dest = &dest_addr;}IP_STATS_INC(ip.xmit);ip4_debug_print(p);/* 如果数据包总长度大于 MTU,则分片发送 */if (netif->mtu && (p->tot_len > netif->mtu)){return ip4_frag(p, netif, dest);}/* 如果数据包总长度不大于 MTU,则直接发送 */return netif->output(netif, p, dest);}

函数 ip4_output_if_src()流程图
此函数首先判断目标 IP 地址是否为 NULL,若目标 IP 地址不为空,则偏移 payload 指针添加 IP 首部,偏移完成之后设置 IP 首部字段信息,接着判断该数据包的总长度是否大于以太网传输单元,若大于,则调用 ip4_frag 函数对这个数据包分组并且逐一发送,否则直接调用ethrap_output 函数把数据包递交给 ARP 层处理。

四、IP 数据报的输入

数据包提交给网络层之前,系统需要判断接收到的数据包是 IP 数据包还是 ARP 数据包,若接收到的是 IP 数据包,则 lwIP 内核调用 ip4_input 函数处理这个数据包,该函数如下所示:

err_t
ip4_input(struct pbuf *p, struct netif *inp)
{
struct ip_hdr *iphdr;
struct netif *netif;
u16_t iphdr_hlen;
u16_t iphdr_len;
#if IP_ACCEPT_LINK_LAYER_ADDRESSING || LWIP_IGMP
int check_ip_src = 1;
#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING || LWIP_IGMP */
IP_STATS_INC(ip.recv);
MIB2_STATS_INC(mib2.ipinreceives);
/* 识别 IP 报头 */
iphdr = (struct ip_hdr *)p->payload;
/* 第一步:判断版本是否为 IPv4 */
if (IPH_V(iphdr) != 4)
{
ip4_debug_print(p);
pbuf_free(p); /* 释放空间 */
IP_STATS_INC(ip.err);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinhdrerrors);
return ERR_OK;
}
/* 以 4 字节(32 位)字段获得 IP 头的长度 */
iphdr_hlen = IPH_HL(iphdr);
/* 以字节计算 IP 报头长度 */
iphdr_hlen *= 4;
/* 以字节为单位获取 ip 长度 */
iphdr_len = lwip_ntohs(IPH_LEN(iphdr));
/* 修剪 pbuf。这对于< 60 字节的数据包尤其需要。 */
if (iphdr_len < p->tot_len)
{
pbuf_realloc(p, iphdr_len);
}
/* 第二步:标头长度超过第一个 pbuf 长度,或者 ip 长度超过总 pbuf 长度 */
if ((iphdr_hlen > p->len) || (iphdr_len > p->tot_len)
|| (iphdr_hlen < IP_HLEN))
{
if (iphdr_hlen < IP_HLEN)
{ }
if (iphdr_hlen > p->len)
{ }
if (iphdr_len > p->tot_len)
{ }
/* 释放空间 */
pbuf_free(p);
IP_STATS_INC(ip.lenerr);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipindiscards);
return ERR_OK;
}
/* 第三步:验证校验和 */
#if CHECKSUM_CHECK_IP
/* 省略代码 */
#endif
/* 将源 IP 地址与目标 IP 地址复制到对齐的 ip_data.current_iphdr_src 和
ip_data.current_iphdr_dest */
ip_addr_copy_from_ip4(ip_data.current_iphdr_dest, iphdr->dest);
ip_addr_copy_from_ip4(ip_data.current_iphdr_src, iphdr->src);
/* 第四步:匹配数据包和接口,即这个数据包是否发给本地 */
if (ip4_addr_ismulticast(ip4_current_dest_addr()))
{
#if LWIP_IGMP
/* 省略代码 */
#else /* LWIP_IGMP */
/* 如果网卡已经挂载了和 IP 地址有效 */
if ((netif_is_up(inp)) && (!ip4_addr_isany_val(*netif_ip4_addr(inp))))
{
netif = inp;
}
else
{
netif = NULL;
}
#endif /* LWIP_IGMP */
}
/* 如果数据报不是发给本地 */
else
{
int first = 1;
netif = inp;
do
{
/* 接口已启动并配置? */
if ((netif_is_up(netif)) &&
(!ip4_addr_isany_val(*netif_ip4_addr(netif))))
{
/* 单播到此接口地址? */
if (ip4_addr_cmp(ip4_current_dest_addr(),
netif_ip4_addr(netif)) ||
/* 或广播在此接口网络地址? */
ip4_addr_isbroadcast(ip4_current_dest_addr(), netif)
#if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF
|| (ip4_addr_get_u32(ip4_current_dest_addr()) ==
PP_HTONL(IPADDR_LOOPBACK))
#endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */
)
{
break;
}
#if LWIP_AUTOIP
if (autoip_accept_packet(netif, ip4_current_dest_addr()))
{
/* 跳出 if 循环 */
break;
}
#endif /* LWIP_AUTOIP */
}
if (first)
{
#if !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF
/* 检查一下目标 IP 地址是否是环回地址 */
if (ip4_addr_isloopback(ip4_current_dest_addr()))
{
netif = NULL;
break;
}
#endif /* !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF */
first = 0;
netif = netif_list;
}
else
{
netif = netif->next;
}
if (netif == inp)
{
netif = netif->next;
}
} while (netif != NULL);
}
#if IP_ACCEPT_LINK_LAYER_ADDRESSING
if (netif == NULL)
{
/* 远程端口是 DHCP 服务器? */
if (IPH_PROTO(iphdr) == IP_PROTO_UDP)
{
struct udp_hdr *udphdr = (struct udp_hdr *)
((u8_t *)iphdr + iphdr_hlen);
if (IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(udphdr->dest))
{
netif = inp;
check_ip_src = 0;
}
}
}
#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
#if LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING
if (check_ip_src
#if IP_ACCEPT_LINK_LAYER_ADDRESSING
&& !ip4_addr_isany_val(*ip4_current_src_addr())
#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
)
#endif /* LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING */
{
/* 第五步: IP 地址,源 IP 地址不能是多播或者广播地址 */
if ((ip4_addr_isbroadcast(ip4_current_src_addr(), inp)) ||
(ip4_addr_ismulticast(ip4_current_src_addr())))
{
/* 释放空间 */
pbuf_free(p);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinaddrerrors);
MIB2_STATS_INC(mib2.ipindiscards);
return ERR_OK;
}
}
/* 第六步:如果还没找到对应的网卡,数据包不是给我们的 */
if (netif == NULL)
{
/* 路由转发或者丢弃。如果 IP_FORWARD 宏定义被使能,则进行转发 */
#if IP_FORWARD
/* 非广播包? */
if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp))
{
/* 尝试在(其他)网卡上转发 IP 数据包 */
ip4_forward(p, iphdr, inp);
}
else
#endif /* IP_FORWARD */
{
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinaddrerrors);
MIB2_STATS_INC(mib2.ipindiscards);
}
/* 释放空间 */
pbuf_free(p);
return ERR_OK;
}
/* 第七步:如果数据报由多个片段组成(分片处理)? */
if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0)
{
/* 重装数据报*/
p = ip4_reass(p);
/* 如果重装没有完成 */
if (p == NULL)
{
return ERR_OK;
}
/* 分片重装完成,将数据报首部强制转换为 ip_hdr 类型 */
iphdr = (struct ip_hdr *)p->payload;
}
#if IP_OPTIONS_ALLOWED == 0
#if LWIP_IGMP
if ((iphdr_hlen > IP_HLEN) && (IPH_PROTO(iphdr) != IP_PROTO_IGMP))
{
#else
/* 第八步:如果 IP 数据报首部长度大于 20 字节,就表示错误 */
if (iphdr_hlen > IP_HLEN)
{
#endif /* LWIP_IGMP */
/* 释放空间 */
pbuf_free(p);
IP_STATS_INC(ip.opterr);
IP_STATS_INC(ip.drop);
/* u 不受支持的协议特性 */
MIB2_STATS_INC(mib2.ipinunknownprotos);
return ERR_OK;
}
#endif /* IP_OPTIONS_ALLOWED == 0 */
/* 第九步: 发送到上层协议 */
ip4_debug_print(p);
ip_data.current_netif = netif;
ip_data.current_input_netif = inp;
ip_data.current_ip4_header = iphdr;
ip_data.current_ip_header_tot_len = IPH_HL(iphdr) * 4;
#if LWIP_RAW
/* RAW API 输入 */
if (raw_input(p, inp) == 0)
#endif /* LWIP_RAW */
{
/* 转移到有效载荷(数据区域),不需要检查 */
pbuf_header(p, -(s16_t)iphdr_hlen);
/* 根据 IP 数据报首部的协议的类型处理 */
switch (IPH_PROTO(iphdr))
{
#if LWIP_UDP
/* UDP 协议 */
case IP_PROTO_UDP:
#if LWIP_UDPLITE
case IP_PROTO_UDPLITE:
#endif /* LWIP_UDPLITE */
MIB2_STATS_INC(mib2.ipindelivers);
/* IP 层递交给网络层的函数 */
udp_input(p, inp);
break;
#endif /* LWIP_UDP */
#if LWIP_TCP
/* TCP 协议 */
case IP_PROTO_TCP:
MIB2_STATS_INC(mib2.ipindelivers);
/* IP 层递交给网络层的函数 */
tcp_input(p, inp);
break;
#endif /* LWIP_TCP */
pbuf_free(p);/* 释放空间*/
IP_STATS_INC(ip.proterr);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinunknownprotos);
}
}
/* 全局变量清零*/
ip_data.current_netif = NULL;
ip_data.current_input_netif = NULL;
ip_data.current_ip4_header = NULL;
ip_data.current_ip_header_tot_len = 0;
ip4_addr_set_any(ip4_current_src_addr());
ip4_addr_set_any(ip4_current_dest_addr());
return ERR_OK;
}

第一步:判断 IP 数据报的版本是否是 IPv4,如果不是,那么 lwIP 会掉弃该数据报。
第二步:判断标头长度超过第一个 pbuf 长度,或者 ip 长度超过总 pbuf 长度,如果是,那么 lwIP 会丢弃该数据报。
第三步: 验证校验和,如果不正确, 那么 lwIP 会掉弃该数据报。
第四步: 匹配数据包和接口,这个数据包是否发给本地。
第五步:判断 IP 数据报是否是广播或者多播,如果是, 那么 lwIP 会丢弃该数据报。
第六步:如果到了这一步,没有发现网络接口, 那么 lwIP 会丢弃该数据报。
第七步:如果如 IP 数据报不能分片处理, 那么 lwIP 会丢弃该数据报。
第八步:如果 IP 数据报的 IP 首部大于 20 字节, 那么 lwIP 会丢弃该数据报。
第九步: 把数据包递交给上层。
第十步:判断该数据报的协议为 TCP/UDP/ICMP/IGMP,如果不是这四个协议,则丢弃该
数据报。

相关文章:

IP协议说明

文章目录 前言一、IP协议的简介二、IP数据报1.IP 数据报结构2.IP 数据报的分片解析3.IP 数据报的分片重装 三、IP 数据报的输出四、IP 数据报的输入 前言 IP 指网际互连协议&#xff0c; Internet Protocol 的缩写&#xff0c;是 TCP/IP 体系中的网络层协议。设计 IP 的目的是…...

第13章 层次式架构设计理论与实践

层次式架构的核心思想是将系统组成为一种层次结构&#xff0c;每一层为上层服务&#xff0c;并作为下层客户。其实不管是分层还是其他的架构都是为了解耦&#xff0c;更好的复用&#xff0c;只要秉承着这种思想去理解一切都迎刃而解了。 13.1 层次上体系结构概述 回顾一下软件…...

FreeRtos进阶——消息队列的操作逻辑

消息队列&#xff08;queue&#xff09; 在不同的任务之间&#xff0c;如果我们需要互相之间通信&#xff0c;使用全局变量进行通信&#xff0c;是一种不安全的通信的方式。为保证线程安全&#xff0c;我们需要引入消息队列的通信方式。 粗暴的消息队列 为保证线程的安全&am…...

WordPress搭建流程

1. 简介 WordPress 是一个 PHP 编写的网站制作平台。WordPress 本身免费,并且拥有众多的主题可以使用,适合用于搭建个人博客、公司官网、独立站等。 2. 环境准备 2.1 WordPress 下载 WordPress 可以在 Worpress中文官网 下载(如果后续要将后台调成中文的话,一定要从中文…...

数据集004:跌倒检测数据集 (含数据集下载链接)

数据集简介&#xff1a; 该数据集为跌倒检测数据集&#xff0c;属于imageclassify任务&#xff0c;分为fall和nofall两大类&#xff0c;累计共1000张图片&#xff0c;均为人工标注 xml格式&#xff0c;可用于yolo训练。 数据集链接&#xff1a;跌倒检测数据集&#xff08;1000…...

苹果与OpenAI合作在即:iOS 18中的ChatGPT引发期待与担忧

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

Android 逆向学习【2】——APK基本结构

APK安装在安卓机器上的&#xff0c;相当于就是windows的exe文件 APK实际上是个压缩包 只要是压缩的东西 .jar也是压缩包 里面是.class(java编译后的一些东西) APK是Android Package的缩写,即Android安装包。而apk文件其实就是一个压缩包&#xff0c;我们可以将apk文件的后…...

你对仲裁裁决不服怎么办?我教你四个狠招!

你对仲裁裁决不服怎么办&#xff1f;我教你四个狠招&#xff01; 这个标题是什么意思呢&#xff1f;也就是说&#xff0c;当你&#xff08;或用人单位&#xff09;向劳动仲裁委提出仲裁申请后&#xff0c;但劳动仲裁结果没有维护你的权益&#xff0c;或者你不满意&#xff0c;…...

绿色智能:低代码开发在AI机器学习中的深度应用与实践案例

随着科技的飞速进步&#xff0c;软件开发的方式也在不断演变。其中&#xff0c;低代码开发作为一种新兴的编程方式&#xff0c;以其高效、便捷的特性受到了广泛关注。同时&#xff0c;AI机器学习技术的发展也为软件开发带来了新的可能。本文将简要介绍低代码开发的概念&#xf…...

《NoSQL数据库技术与应用》 文档存储数据库MongoDB

搜索 《NoSQL数据库技术与应用》 教学设计 课程名称&#xff1a;NoSQL数据库技术与应用 授课年级&#xff1a; 20xx年级 授课学期&#xff1a; 20xx学年第一学期 教师姓名&#xff1a; 某某老师 2020年5月6日 课题 名称 第2章 文档存储数据库MongoDB 计划学时 4 课时 内容 分…...

设置AXI主寄存器切片和AXI数据FIFO

设置AXI主寄存器切片和AXI数据FIFO 打开MHS文件&#xff0c;并为每个AXI主机设置启用寄存器切片/启用数据FIFO。到 确定正确的设置&#xff0c;使用下表中的信息搜索MHS。 进行搜索时&#xff0c;将<intf_name>替换为相关的BUS_INTERFACE名称。 例如&#xff0c;BUS_INTE…...

Golang协程和通道

文章目录 协程&#xff08;goroutine&#xff09;基本介绍GMP模型协程间共享变量 通道&#xff08;channel&#xff09;基本介绍channel的定义方式channel的读写channel的关闭channel的遍历方式只读/只写channelchannel最佳案例select语句 协程&#xff08;goroutine&#xff0…...

Enable Full Line suggestions 启用全行建议

开启后效果如下&#xff1a; 直接提示可能要输入的参数...

Java 文件操作和输入输出流

在 Java 编程中&#xff0c;文件操作和输入输出流是非常常见和重要的任务&#xff0c;它们允许你读取和写入文件、处理数据流等。 文件操作概述 文件操作是指对文件进行创建、读取、写入、删除等操作的过程。在 Java 中&#xff0c;文件操作通常涉及到使用文件对象、输入输出…...

MyBatis中的Where标签:提升你的SQL查询效率

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 理解MyBatis的Where标签 MyBatis是一款优秀的持久层框架&#xff0c;它提供了许多强大的标签来帮助编写更优雅、高效的SQL语句。其中&#xff0c;<where>标签是使用频率极高的一个&#xff0c;它能够自动处理…...

Docker(三) 容器管理

1 容器管理概述 Docker 的容器管理可以通过 Docker CLI 命令行工具来完成。Docker 提供了丰富的命令&#xff0c;用于管理容器的创建、启动、停止、删除、暂停、恢复等操作。 以下是一些常用的 Docker 容器命令&#xff1a; 1、docker run&#xff1a;用于创建并启动一个容器。…...

自己动手写docker——Namespace

Linux Namespace linux Namespace用于隔离一系列的系统资源&#xff0c;例如pid&#xff0c;userid&#xff0c;netword等&#xff0c;借助于Linux Namespace&#xff0c;可以实现容器的基本隔离。 Namespce介绍 Namespace类型系统调用参数作用Mount NamespaceCLONE_NEWNS隔离…...

【前端学习笔记】HTML基础

HTML 一、HTML介绍1.HTML概念2.文档声明3.字符编码4. HTML标签5. HTML属性 二、标签1.meta标签2.语义标签3.布局标签4.列表5.超链接6.图片7.字符实体8.内联格式9.HTML 表格10.HTML 表单 三、HTML5新特性1. 本地存储2. Cookie3. 语义化标签4.多媒体元素5.表单增强6.Canvas7.SVG …...

JS Lab

如何用 JavaScript 在浏览器中弹窗如何在 JavaScript 中制作鼠标滑过按钮改变背景颜色如何在 JS 中点击按钮使数字增加如何在 JS 中循环打印多少次 HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title…...

Vue:快速上手

一、简介 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;…...

HTML并集,交集,子代,后代选择器

1,交集选择器 他们必须满足既是p又是.box的关系&#xff08;直接连写&#xff0c;没有任何符号&#xff09; p.box{color:red; } <div class"box"> 1 </div> <p class"box">2</p> <p>3</p> 2.并集选择器 将div,p,…...

关于pdfbox读取pdf

最近&#xff0c;想着将pdf的文件进行读取其内容&#xff0c;发现了一个比较好用的依赖pdfbox。目前使用这个依赖&#xff0c;进行实现一个简单实例&#xff0c;如果之后需要使用到更深的了解&#xff0c;会进行更新。这里提醒一下&#xff1a;jdk8尽量采用pdfbox3.x版本。 对…...

汽车电子零部件(14):TMS热管理系统

前言: TMS(thermal management system)热管理系统,这是新能源汽车诞生后随之而产生的一种新汽车零部件,一旦热管理失控会触发自燃,这种现象也是对EV来说是件头疼的事。汽车的热管理系统(TMS)是一个关键部件,有助于调节汽车电池组、车厢和其他车辆系统的温度。TMS的主要…...

代码模板,Cookie和Session

目录 代码模板 Cookie的基本使用 概念 Cookie的API public Cookie(String name, String value) 发送Cookie对象到客户端&#xff1a;使用response对象 创建Cookie对象并响应给浏览器 在服务器后端获取Cookie对象 Cookie[]cookiesrequset.getCookies(); Cookie的使用细…...

Nginx配置及优化

Nginx配置及优化 前言nginx.conf拆分理解上线 最近在配置Nginx的时候&#xff0c;偶尔一些细致的理论有些模糊&#xff0c;配置起来费了点功夫&#xff0c;今天来详细写一下我个人的理解&#xff0c;文章参考了一些官网和其他优秀博主的文章http://t.csdnimg.cn/GbID9。 前言 …...

HashMap在Go与Java的底层实现与区别

在Java中 在Java中hash表的底层数据结构与扩容等已经是面试集合类问题中几乎必问的点了。网上有对源码的解析已经非常详细了我们这里还是说说其底层实现。 基础架构 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable,…...

搜维尔科技:使用Haption Virtuose 6D 力反馈通过机器人和虚拟现实完成远程操作项目

使用Haption Virtuose 6D 力反馈通过机器人和虚拟现实完成远程操作项目 搜维尔科技&#xff1a;使用Haption Virtuose 6D 力反馈通过机器人和虚拟现实完成远程操作项目...

【Python】WHEELTEC GPS G60主代码读取传感器数据代码(Windows系统)

【Python】WHEELTEC惯导IMU主代码读取传感器数据代码 Windows系统&#xff0c;直接输入串口号即可 读取设备的移动速度&#xff08;km/h&#xff09; 注&#xff1a;该GPS传感器需要在室外条件运行&#xff0c;室内可能接收不到信号。 # coding: utf-8 # last modified:202310…...

【Vue】Vue2与Vue3的区别

目录 响应式系统组合式API更小的体积编译优化新的生命周期钩子更好的性能组件结构与模板TeleportFragments 静态节点标记异步组件Slots的改进更好的TypeScript支持Composition API的引入 响应式系统 Vue2使用Object.defineProperty来实现响应式系统&#xff0c;这意味着只有预…...

马斯克的 xAI 帝国!60亿融资背后的超级布局?

在全球科技竞技场&#xff0c;每个重大融资事件都是对行业格局的一次重塑。近日&#xff0c;埃隆马斯克的人工智能初创企业 xAI 成功完成了一轮规模空前的融资——60亿美元&#xff0c;此举无疑在业界投下了一枚震撼弹&#xff0c;标志着 AI 领域内一场新的竞赛拉开了序幕。 …...