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,如果不是这四个协议,则丢弃该
数据报。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/842967.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL数据分析常用函数

SQL 中有许多常用的函数&#xff0c;可以用于处理和操作数据。以下是一些常见的SQL 函数&#xff1a; 1. 字符串函数&#xff1a; CONCAT(str1, str2, …): 用于把多个文本字符串合并成一个长字符串(参数中有null时返回null)。 select concat(一起,学, SQL); -- 输出结果:一…

RPC 框架

RPC 全称 Remote Procedure Call——远程过程调用。 RPC技术简单说就是为了解决远程调用服务的一种技术&#xff0c;使得调用者像调用本地服务一样方便透明。RPC是一种通过网络从远程计算机程序上请求服务&#xff0c;不需要了解底层网络技术的协议。 集群和分布式 集群&…

基于Freertos的工训机器人

一. 工训机器人 V1 1. 实物 将自制的F4开发板放置车底板下方&#xff0c;节省上方空间&#xff0c;且能保证布线方便整齐。 2. SW仿真 使用SolidWorks进行仿真&#xff0c;且绘制3D打印件。 工训仿真 3.3D打印爪测试 机械爪测试 二. 工训机器人 V2 1. 实物 工训机器人V2不同于…

国密协议网关与IPSec VPN技术:保障数据安全传输的新途径

国密协议网关IPSec VPN隧道技术是一种结合了国家密码管理局&#xff08;简称国密&#xff09;的加密算法和IPSec VPN隧道技术的安全通信解决方案。 IPSec&#xff08;Internet Protocol Security&#xff09;是互联网协议安全的一种标准&#xff0c;用于保护网络通信的安全性和…

共筑信创新生态:DolphinDB 与移动云 BC-Linux 完成兼容互认

近日&#xff0c;DolphinDB 数据库软件 V2.0 与中国移动通信集团公司的移动云天元操作系统 BC-Linux 完成兼容性适配认证。经过双方共同严格测试&#xff0c;DolphinDB 性能及稳定性等各项指标表现优异&#xff0c;满足功能及兼容性测试要求。 此次 DolphinDB 成功通过移动云 B…

微服务-Nacos-安装-集成SpringBoot

微服务-SpringCloud-ALibaba-Nacos Nacos 是阿里巴巴推出的 SpringCloud的组件 官网:什么是 Nacos 主要是为了解决微服务的架构中 服务治理的问题服务治理就是进行服务的自动化管理&#xff0c;其核心是服务的注册与发现。 服务注册&#xff1a;服务实例将自身服务信息注册…

使用BigDecimal定义的实体类字段返回给前台的是字符串类型,如何返回数字类型

目录 前言&#xff1a; 问题现象&#xff1a; 解决方法&#xff1a; 效果&#xff1a; 前言&#xff1a; 做项目的时候数据字段通常定义为bigdecimal类型&#xff0c;方便进行运算&#xff0c;但是发现接口调用后返回给前台的是字符串&#xff0c;这篇博文讲的是如何将定义…

1109 擅长C(测试点0,1,2,3)

当你被面试官要求用 C 写一个“Hello World”时&#xff0c;有本事像下图显示的那样写一个出来吗&#xff1f; ..C.. .C.C. C...C CCCCC C...C C...C C...C CCCC. C...C C...C CCCC. C...C C...C CCCC. .CCC. C...C C.... C.... C.... C...C .CCC. CCCC. C...C C...C C...C C…

【香橙派 AIpro】OrangePi AIpro :教育、机器人、无人机领域的超级AI大脑,华为昇腾处理器驱动的AI开发板新标杆

【OrangePi AIpro&#xff1a;教育、机器人、无人机领域的超级AI大脑&#xff0c;华为昇腾处理器驱动的AI开发板新标杆】 文章目录 一、开箱与初印象1. 初印象2. 上手开机3. 安装和运行 TightVNC 远程桌面3.1. 安装 TightVNC 服务器3.2. 启动 VNC 服务器3.3. 在 Windows 上使用…

Java 字符串处理

Java 是一种广泛使用的编程语言&#xff0c;而字符串处理是 Java 编程中非常重要的一部分。Java 提供了丰富的字符串操作功能&#xff0c;通过 String 类和 StringBuilder、StringBuffer 类来处理字符串。 一、Java 字符串的创建 1. 使用字面量 在 Java 中&#xff0c;字符串…

应急响应-网页篡改-技术操作只指南

初步判断 网页篡改事件区别于其他安全事件地明显特点是&#xff1a;打开网页后会看到明显异常。 业务系统某部分出现异常字词 网页被篡改后&#xff0c;在业务系统某部分网页可能出现异常字词&#xff0c;例如&#xff0c;出现赌博、色情、某些违法APP推广内容等。2019年4月…

Oracle创建用户时提示ORA-65096:公用用户名或角色名无效

Oracle创建用户时提示“ORA-65096&#xff1a;公用用户名或角色名无效” 如下图所示&#xff1a; 解决方法&#xff1a;在新增用户名前面加上C##或者c##就可以解决无效问题&#xff0c;具体什么原因还不清楚&#xff0c;需要再研究一下。

一机实现All in one,NAS如何玩转虚拟机!

常言道&#xff0c;中年男人玩具有三宝 充电器、路由器、NAS 你问我NAS的魔力在哪里&#xff1f; 一机实现All in one洒洒水啦 那NAS又如何玩转虚拟机呢? 跟我来 0基础也能轻松get! NAS如何玩转虚拟机 铁威马NAS的VirtualBox的简单易用&#xff0c;可虚拟的系统包括Win…

python核心编程(二)

python面向对象 一、基本理论二、 面向对象在python中实践2.1 如何去定义类2.2 通过类创建对象2.3 属性相关2.4 方法相关 三、python对象的生命周期,以及周期方法3.1 概念3.2 监听对象的生命周期 四、面向对象的三大特性4.1 封装4.2 继承4.2.1 概念4.2.1 目的4.2.2 分类4.2.3 t…

cgicc开发(文件上传)

//cgicc文件上传封装 void UploadSoftware() {// 初始化CGIC环境Cgicc cgi;// 获取上传的文件file_iterator fileIter cgi.getFile("button_browse"); //from表单中,输入为文件属性(typefile)的name属性值if (fileIter cgi.getFiles().end()){ #if (DEBUG true)co…

软件设计师中级 重点 笔记

文章目录 下午题目网络DNS域名解析分类&#xff1a;域名协议简介网络设备 算法软件工程实体联系图&#xff08;E-R图&#xff09; 其它 下午题目 数据流图补充原则 22年下半年真题 更早-真题大全 答题技巧 网络 DNS域名解析分类&#xff1a; 递归查询的顺序&#xff1a;1.本…

电脑重要文件如何加密保护?教你两种方法

加密是保护电脑重要文件的常见方法&#xff0c;可以有效避免文件数据泄露。那么&#xff0c;电脑重要文件该如何加密保护呢&#xff1f;下面小编就来教你两种方法&#xff0c;帮助你解决文件安全问题。 超级加密3000 超级加密3000是一款专业的电脑数据加密软件&#xff0c;可以…

流量被劫持?不怕,轻松Get 防“窃”技巧!

流量劫持是一种恶意行为&#xff0c;攻击者会在用户访问网站时&#xff0c;将其流量重定向到第三方站点上&#xff0c;导致用户访问的不是原始目标站点。这种行为不仅会影响网站的品牌形象&#xff0c;还会导致用户流失和信息泄露等严重后果。本文将探讨网站如何应对流量劫持。…

SurfaceFinger layer创建过程

SurfaceFinger layer创建过程 引言 本篇博客重点分析app创建Surface时候&#xff0c;SurfaceFlinger是如何构建对应的Layer的主要工作有那些&#xff01; 这里参考的Android源码是Android 13 aosp&#xff01; app端创建Surface 其核心流程可以分为如下接部分: app使用w,h,fo…

window.location.search取不到值

window.location.search window.location.search没有值的原因&#xff1a; URL中使用了 hash &#xff08;指URL中带有#符号&#xff09;,导致URL后面携带的参数被location.hash截取走了&#xff0c;你再使用window.location.search得到的就是空值 打印 window.location 其实…