IP协议说明

文章目录

  • 前言
  • 一、IP协议的简介
  • 二、IP数据报
    • [1.IP 数据报结构](#1.IP 数据报结构)
    • [2.IP 数据报的分片解析](#2.IP 数据报的分片解析)
    • [3.IP 数据报的分片重装](#3.IP 数据报的分片重装)
  • [三、IP 数据报的输出](#三、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 字节大小。

(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数据报的内容:

c 复制代码
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 分片,这些分片的数据组合起来就是应用程序发送的数据与传输层的首部。
    lwIP实现它的函数为 ip4_frag,代码如下(示例):
c 复制代码
/**
* 如果 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 函数发送该分片,其他分片一样操作。

newpbuf 的 payload 指针指向的地址由左边的 payload 指针经过偏移得来的。

3.IP 数据报的分片重装

由于 IP 分组在网络传输过程中到达目的地点的时间是不确定的,所以后面的分组可能比前面的分组先达到目的地点。 为此, lwIP 内核需要将接收到的分组暂存起来,等所有的分组都接收完成之后,再将数据传递给上层。

在 lwIP 中,有专门的结构体负责缓存这些分组,这个结构体为 ip_reassdata 重装数据链表,该结构体在 ip4_frag.h 文件中定义:

c 复制代码
/* 重装数据结构体 */
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 首部的前 8 字节强制转换成三个字段,其中 next_pbuf 指针用来链接这些 IP 分组,形成了单向链表,而 start和 end 字段用来描述分组的顺序, lwIP 系统根据这些数值对分组进行排序。

三、IP 数据报的输出

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

c 复制代码
	err_t
	ip4_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);
	}

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

四、IP 数据报的输入

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

c 复制代码
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,如果不是这四个协议,则丢弃该

数据报。

相关推荐
Deryck_德瑞克1 小时前
Java网络通信—TCP
java·网络·tcp/ip
CV金科1 小时前
蓝桥杯—STM32G431RBT6(IIC通信--EEPROM(AT24C02)存储器进行通信)
stm32·单片机·嵌入式硬件·算法·蓝桥杯
GodK7771 小时前
IP 数据包分包组包
服务器·网络·tcp/ip
千年死缓1 小时前
go+redis基于tcp实现聊天室
redis·tcp/ip·golang
梁诚斌1 小时前
VSOMEIP代码阅读整理(1) - 网卡状态监听
运维·服务器·网络
ZachOn1y2 小时前
计算机网络:计算机网络概述 —— 描述计算机网络的参数
网络·tcp/ip·计算机网络·考研必备
我命由我123452 小时前
SSL 协议(HTTPS 协议的关键)
网络·经验分享·笔记·学习·https·ssl·学习方法
嵌入式详谈3 小时前
基于STM32的智能风扇控制系统设计
stm32·单片机·嵌入式硬件
两点王爷3 小时前
使用WebClient 快速发起请求(不使用WebClientUtils工具类)
java·网络
wusam3 小时前
螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习03(网络及IP规划)
运维·服务器·网络·docker·容器