目录
1. IP地址
1.1 IP地址概述
1.2 IP地址编址
1.3 特殊IP地址
1.3.1 受限广播地址
1.3.2 直接广播地址
1.3.3 多播地址
1.3.4 环回地址
1.3.5 本网络本主机
2. 局域网与广域网的概念
2.1 局域网
2.2 广域网
3. 网络地址转换(NAT)
4. IP 数据报
5. IP 数据报的数据结构
6. IP 数据报分片
7. IP 数据报发送
8. IP 数据报接收
IP 协议(Internet Protocol),又称之为网际协议,IP 协议处于IP 层工作,它是整个TCP/IP 协议栈的核心协议,上层协议都要依赖IP 协议提供的服务,IP 协议负责将数据报从源主机发送到目标主机,通过IP 地址作为唯一识别码,简单来说,不同主机之间的IP地址是不一样的,在发送数据报的过程中,IP 协议还可能对数据报进行分片处理,同时在接收数据报的时候还可能需要对分片的数据报进行重装等等。
IP 协议是一种无连接的不可靠数据报交付协议,协议本身不提供任何的错误检查与恢复机制。
为了标识互联网中的每台主机的身份,设计人员为每个接入网络中的主机都分配一个IP 地址(Internet Protocol Address),是一个32 位的整数地址,只有合法的IP 地址才能接入互联网中并且与其他主机进行通信,IP 地址是软件地址,不是硬件地址,硬件MAC 地址是存储在网卡中的,应用于本地网络中寻找目标主机。而IP 地址能让一个网络中的主机能够与另一个网络中的主机进行通信,无需理会这些主机之间的MAC地址。
简述一下主机与路由器连入网络的通信的方法:一台主机通常只有一条链路连接到网络,一般只有一个网卡;当主机中想发送一个数据报时,它就在该链路上发送,一个网卡对应一个MAC 地址与IP 地址,当然,主机也可能有多个网卡,这样子主机就有多个MAC 地址与IP 地址。
每个IP 地址长度为32 比特(4 字节),因此总共有 个可能的IP 地址,大约有40亿个IP 地址能被使用的。这些地址一般按所谓点分十进制记法(dotted-decimal notation)书写,即地址中的每个字节用它的十进制形式书写,各字节间以点分隔开。例如, IP 地址为192.168.0.122,192 是该地址第一个8 比特的十进制等价数,168 是该地址第二个8 比特的十进制等价数,依次类推。
每个主机的IP 地址的一部分都由其所在的子网决定的,所以又出现了IP 地址分类编址的概念,网络的类型决定了IP 地址将如何划分成网络部分和节点部分,在分类编址中,设计者把所有的IP 地址划分为5 大类,分别为A、B、C、D、E 五类,每一类地址都觉定了其中IP 地址的一部分组成。
(1)A类地址第1字节为网络地址,其它3个字节为主机地址。它的第1个字节的第一位固定为0.
(2)A类地址网络号范围:0.0.0.0---127.0.0.0 地址范围0.0.0.0到127.255.255.255
(3)A类地址中的私有地址和保留地址:
① 10.X.X.X是私有地址(所谓的私有地址就是在互联网上不使用,而被用在局域网络中的地址)。
范围(10.0.0.0---10.255.255.255)
② 127.X.X.X是保留地址,用做循环测试用的。
(1) B类地址第1字节和第2字节为网络地址,其它2个字节为主机地址。它的第1个字节的前两位固定为10.
(2) B类地址网络号范围:128.0.0.0---191.255.0.0。地址范围128.0.0.0到191.255.255.255。
(3) B类地址的私有地址和保留地址
① 172.16.0.0---172.31.255.255是私有地址
② 169.254.X.X是保留地址。如果你的IP地址是自动获取IP地址,而你在网络上又没有找到可用的DHCP服务器。就会得到其中一个IP。
191.255.255.255是广播地址,不能分配
(1)C类地址第1字节、第2字节和第3个字节为网络地址,第4个字节为主机地址。另外第1个字节的前三位固定为110。
(2)C类地址网络号范围:192.0.0.0---223.255.255.0。地址范围 192.0.0.0到223.255.255.255
(3) C类地址中的私有地址:
192.168.X.X是私有地址。(192.168.0.0---192.168.255.255)
(1) D类地址不分网络地址和主机地址,它的第1个字节的前四位固定为1110。
(2) D类地址范围:224.0.0.0---239.255.255.255
(1) E类地址不分网络地址和主机地址,它的第1个字节的前五位固定为11110。
(2) E类地址范围:240.0.0.0---255.255.255.254
IP地址如果只使用ABCDE类来划分,会造成大量的浪费:一个有500台主机的网络,无法使用C类地址。但如果使用一个B类地址,6万多个主机地址只有500个被使用,造成IP地址的大量浪费。因此,IP地址还支持VLSM技术,可以在ABC类网络的基础上,进一步划分子网。
一些特殊用途的地址,这些地址是不允许分配给任何一个网络的主机使用的。
广播通信是一对所有的通信方式,受限广播地址用于定义整个互联网,如果设备想使IP 数据报被整个网络所接收,就发送这个目的地址全为1 的广播包,但这样会给整个互联网带来灾难性的负担,所以在任何情况下,路由器都会禁止转发目的地址为255.255.255.255 的广播数据包,因此这样的数据包仅会出现在本地网络中(局域网),255.255.255.255 这个地址指本网段内的所有主机, 相当于“房子里面的人都听着”通知所有主机。
直接广播地址是主机号全为1 而得到的地址,广播地址代表本网络内的所有主机,使用该地址可以向网络内的所有主机发送数据,比如一个IP 地址是192.168.0.181,这是C 类地址,所以它的主机号只有一个字节,那么对主机号全取1 得到一个广播地址192.168.0.255,向这个地址发送数据就能让同一网络下的所有主机接收到。
A、B、C 三类地址的广播地址结构如下:
注意:这个地址在IP 数据报中只能作为目的地址。另外,直接广播地址使一个网段中可分配给设备的地址数减少了1 个。
多播地址用在一对多的通信中,即一个发送者,多个接收者,不论接受者员数量的多少,发送者只发送一次数据包。多播地址属于分类编址中的D 类地址, D 类地址只能用作目的地址,而不能作为主机中的源地址。
127 网段的所有地址都称为环回地址,主要用来测试网络协议是否工作正常的作用。比如在电脑中使用ping 命令去ping 127.1.1.1 就可以测试本地TCP/IP 协议是否正常。用通俗的话表示,就是“我自己”,不能以127 网段中的IP 地址作为主机地址,因此A 类地址又少了一个可用网络号。
IP 地址32bit 全为0 的地址(0.0.0.0)表示的是本网络本主机,这个IP 地址在IP 数据报中只能用作源IP 地址,这发生在当设备启动时但又不知道自己的IP 地址情况下。在使用DHCP 分配IP 地址的网络环境中,这样的地址是很常见的,主机为了获得一个可用的IP 地址,就给DHCP 服务器发送IP 数据报,并用这样的地址(0.0.0.0)作为源地址,目的地址为255.255.255.255(因为主机这时还不知道DHCP 服务器的IP 地址),然后DHCP服务器就会知道这个主机暂时没有IP 地址,那么就会分配一个IP 给这个主机。
局域网(Local Area Network,缩写为LAN),又称内网,指覆盖局部区域(如办公室或楼层)的计算机网络,局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的日程安排、电子邮件和传真通信服务等功能,是在一定区域内由多个计算机连接组成的网络,比如腾讯、阿里的内网,在内部的计算机上的数据可以互联互通、数据共享等。简单来说,当我们使用的开发板接入路由器的时候,电脑的网络也接入路由器,那么开发板与电脑就组成局域网,数据在链路层上是互联互通的。当然,板子也能通过网线直连电脑,这样子也是组成一个局域网络,数据可以在两个主机之间进行通信。
广域网(Wide Area Network,缩写为 WAN),又称广域网、外网、公网。是连接不同地区计算机以进行通信的网络,这种网络通常会跨越很大的范围,覆盖的范围从几十公里到几千公里,它能连接多个地区、城市并能提供远距离通信服务,但是注意的是我们说的广域网并不等同于互联网。
互联网就是由无数个局域网,通过广域网线路汇聚互联起来,就形成了互联网。互联网的特点是开放、互联,如果一个公司的局域网没有连接到互联网,那这个局域网就不属于互联网,仅仅属于内部通信的网络。
示意图虽然简单,却把LAN、WAN、Internet 三者全包含了。无线路由器把电脑、手机等设备连接到局域网LAN 上,并分配IP 地址,即局域网IP,我们可以称之为LAN-IP,LAN-IP 所到之处,就是局域网的范围,像我们电脑的IP 地址(192.168.0.xxx)就是一个局域网IP,而路由器的地址就是运营商给我们的一个IP 地址,这个IP 地址是有效的,可以看做是WAN-IP(实际上这个IP 地址也是运营商的局域网IP 地址(相对运营商来说),这个地址在运营商中转换成一个真正的广域网IP 地址,但是这些事情我们暂时无需理会,只要把路由器的IP 地址看做是WAN-IP 即可)。
而运营商是一个边界,国家与国家之间有边界,网络之间也有边界,运营商就是局域网LAN 与广域网WAN 的边界。局域网LAN-IP 可以在局域网内部有效,但是无法跨越边界进入广域网中,LAN-IP 是路由器分配给我们的IP,那么我们想要跨越边界进入广域网中,就需要将LAN-IP 变成有效的的IP 地址,也就是WAN-IP,那么在路由器中就需要将IP 地址进行转换,完成LAN-IP<—>WAN-IP 地址转换(NAT),关于地址转换的内容我们稍后讲解统一换成特别通行证才可以在广域网里继续邀游。
当持有WAN-IP 的IP 包顺利到达下一个边界Internet Gateway,这是通往互联网Internet 的最后一道关卡,即边界。左边是广域网,右边是互联网,也需要做WAN-IP 与Global-IP(互联网公共IP)的转换才能进入互联网中,我们知道这种概念即可,无需过多深入。
NAT 英文全称是“Network Address Translation”,中文意思是“网络地址转换”,它是一个IETF(Internet Engineering Task Force, Internet 工程任务组)标准,允许一个整体机构以一个公用IP(Internet Protocol)地址出现在Internet 上。顾名思义,它是一种把内部私有网络地址(IP 地址)翻译成合法网络IP 地址的技术。因此NAT 在一定程度上,能够有效的解决IP 地址不足的问题,它是一个方便且得到了广泛应用的技术,当然,NAT 也让主机之间的通信变得复杂,导致了通信效率的降低。
具有NAT 功能的路由器必须拥有一个内部地址与一个外部地址,内部地址是为了与局域网的用户通信而使用的,它使用一个特定的内部IP 地址,如192.168.0.1(也是局域网的网关),外部地址是与广域网进行通信而使用的,这是一个有效的IP 地址,通常为运营商分配给我们,假设运营商分配给我们的IP 地址是一个C 类网络地址223.166.166.66,假设我们电脑上的IP 地址是192.168.0.181,端口号是5555。
那么在局域网的电脑想要与外网进行通信的时候,比如我们想访问百度(假设百度的IP 地址是123.125.115.110,端口号是80),那么电脑就会把这些数据报发送到路由器中,请求路由器帮忙转发,假设这些数据报格式简化为:
(源IP 地址,端口号,目标IP 地址,端口号)
(192.168.0.181,5555,123.125.115.110, 80)
具有NAT 功能的路由器会在内部维护一个NAT 转换表,当路由器收到局域网的IP 数据报时,就会为这个数据报分配一个路由器内部的NAT 端口,假设为6666,并且路由器会将原始IP 数据报中源IP 地址与端口号(192.168.0.181,5555)转换成一个有效IP 地址与端口号(223.166.166.66,6666),然后转换后的路由器发出的数据就是:
(223.166.166.66,6666,123.125.115.110, 80)
当百度响应这个数据报的时候,就会返回一个响应信息,它返回的目标IP 地址就是我们路由器的有效IP 地址和端口号(223.166.166.66,6666),那么路由器收到的数据报就是:
(123.125.115.110, 80,223.166.166.66,6666)
当路由器收到这个数据报之后,会在NAT 转换表中查找端口号为6666 的连接,并且把数据报中的目标IP 地址与端口号(223.166.166.66,6666)转换成局域网内我们电脑的IP 地址与端口号(192.168.0.181,5555),并且将数据报转发到我们的主机上,那么我们电脑上收到的数据报就是:
(123.125.115.110, 80,192.168.0.181,5555)
这样子的数据报对于电脑来说,是很正常的通信方式,但是它并不知道路由器已经转换过这些数据报的IP 地址与端口内容,所以NAT 对于所有用户来说是透明的,通过这样子的两次转换,局域网就实现了与广域网的通信,在NAT 转换中,NAT 端口号是一个关键的因素,路由器应及时为每个连接分配唯一的端口号,并且要及时回收那些不使用的端口号。
IP 数据报(也可称之为IP 数据报或者IP 分组,为了统一,下文均采用IP 数据报),IP 数据报与ARP 报文都是一种报文格式,都有自己的组织形式,与ARP 报文一样,由两部分组成,一部分是IP 首部,另一部分是数据区域,一个IP 数据报的首部长度是不定的,通常为20~60 字节,根据选项决定。而数据区域理论上可以多达65535 个字节,但是很少有数据是那么大的,并且受限于网卡硬件。
IP 数据报的格式
版本(号):占据4bit 空间。这个字段规定了数据报的IP 协议版本,对于IPv4,该值为4;对于IPv6,该值为6。通过查看版本号,路由器能够确定如何解释IP 数据报的其他部分,不同的IP 版本使用不同的数据报格式。目前版本的IP(即IPv4)的数据报格式如图 所示,而新版本的IP(IPv6)的数据报格式又有所不同,此处我们就不做过多讨论IPv6 版本的IP 数据报格式,若无特殊说明,所写的IP 数据报内容均为IPv4。
部首长度:占据4bit 空间,用于记录IP 首部的数据的长度,为什么需要记录首部长度呢?因为IP 首部中包含了一些可变的数据选项,故需要这4bit 记录首部的长度,以便区分数据部分的起始位置,当然啦,4bit 的部首长度单位是字,只有这样子才能最大记录60个字节的数据(15*4=60)。
服务类型(TOS):占据8bit 空间,服务类型(TOS)包含在IPv4 首部中,以便使不同类型的IP 数据报(例如,一些特别要求低时延、高吞吐量或可靠性的数据报)能相互区别开来。提供特定等级的服务是一个由路由器管理员决定的策略问题,简单来说就路由器根据是这个字段的值来为数据报提供(选择)最合理的路径。
数据报长度:字段占据16bit 空间。这是IP 数据报的总长度(首部加上数据区域),以字节为单位。因为该字段长为16bit,所以整个IP 数据报的理论最大长度为65535 字节,然而,数据报很少有超过1500 字节的,这是因为底层链路硬件不允许那么大的数据报出现在链路上,以太网数据帧的最大长度为1500 个字节,当有一个很大的IP 数据报出现的时候,就需要进行分片处理;而如果IP 数据报的数据很少的时候,比如少于46 个字节,那么在以太网进行发送数据的时候会填充一定的字节以满足以太网帧最小长度,那么在接收方就要根据这个字段的内容进行提取有效数据。
标识、标志、分片偏移量:这三个字段与IP 数据报分片有关,这是一个我们将很快要深入学习的一个问题,在后文详细讲解。但更有趣的是,新版本的IP(即IPv6)数据报则不允许对数据进行分片处理。
标识:字段用于表示IP 层发送出去的每一份IP 数据报,在发送每一份报文,该值加1,在分片的时候,该字段会被复制到每个分片数据报中,在目标接收主机中,使用该字段判断这些数据是否属于同一个IP 数据报。
标志位(3bit):第一位保留未用;第二位是不分片标志位,如果该位为1,则表示IP 数据报在发送的过程中不允许进行分片,如果这个IP 数据报的大小超过链路层能承载的大小,这个IP 数据报将被丢弃,如果该位为0 则表示IP 层在必要的时候可以对其进行分片处理;第三位为更多分片位,如果为1 则表示该分片数据报不是整个IP 数据报的最后一个分片,如果为0 则表示是整个IP 数据报的最后一个分片。
分片偏移量:占据13bit 空间,表示当前分片所携带的数据在整个IP 数据报中的相对偏移位置(以8 字节为单位),目标主机必须收到以0 偏移量开始到最高偏移量的所有分片,才能将分片进行重装为一个完整的IP 数据报,并且重装IP 数据报的依据就是分片的偏移量。
生存时间(Time-To-Live,TTL):该字段用来确保数据报不会永远在网络中循环(例如由于长时间的路由选择环路)。每当IP 数据报由一台路由器处理时,该字段的值减1,若TTL 字段减为0,则该数据报必须丢弃,同时会返回一个ICMP 差错报文给源主机,这样子数据就不会永远在网络中漂流而占据资源。
上层协议字段占据8bit 空间:该字段仅在一个IP 数据报到达其最终目的地才会有用。该字段的值指示了IP 数据报的数据部分应交给哪个特定的传输层协议。例如,值为6 表明数据部分要交给TCP,而值为17 表明数据要交给UDP。在IP 数据报中的协议号所起的作用,类似于运输层报文段中端口号字段所起的作用。协议字段是将网络层与运输层绑定到一起的粘合剂,而端口号是将运输层和应用层绑定到一起的粘合剂,此处了解这个概念即可。
首部检验和:字段占据16bit 空间。首部检验和用于帮助路由器检测收到的IP 数据报首部是否发生错误,而对应IP 数据报中的数据区域校验那是上层协议处理的事情。首部检验和是这样计算的:将首部中的每2 个字节当作一个数,用反码运算对这些数求和,该和的反码(被称为因特网检验和)存放在检验和字段中。路由器要对每个收到的IP 数据报计算其首部检验和,如果数据报首部中携带的检验和与计算得到的检验和不一致,则表示出现错误,路由器一般会丢弃检测出错误的IP 数据报。注:IP 数据报在到达每个路由器上都必须重新计算检验和并再次存放到原处,因为TTL 字段以及可能的选项字段会改变。
源IP 地址与目标IP 地址就不用过多解释了,源主机在生存ip 数据包的时候会在源IP地址字段中插入它的IP 地址,在目标IP 地址字段中插入其想要发送的最终目标IP 地址。
选项:字段占据0~40 个字节。它允许IP 首部被扩展,首部选项在日常生活中还是比较少使用的,因此在每个IP 数据报首部中必须存在的字段是不包括选项字段的,这样能够节约开销,如果与选项字段就添加,而如果没有就无需理会。在LwIP 中只识别选项字段,但是不会处理选项字段的内容。因为选项的存在的确是件复杂的事,IP 数据报首部长度可变,故不能预先确定数据字段从何处开始,而且还因为有些数据报要求处理选项,而有些数据报则不要求,故导致一台路由器处理一个IP 数据报所需的时间变化很大,这些考虑对于高性能路由器和主机上的IP 协议处理来说特别重要,由于这样或那样的原因,在IPv6数据报首部中已去掉了IP 数据报中的选项字段。
数据区域:(也可以称之为有效载荷),这是IP 数据报的最后的一个字段,也是最重要的内容,因为有数据区域才会有数据报首部的存在,在大多数情况下,IP 数据报中的数据字段包含要交付给目标IP 地址的运输层(TCP 协议或UDP 协议),当然,数据区域也可承载其他类型的报文,如ICMP 报文。
IP 数据报封装在以太网帧的格式
为了描述IP 数据报首部的信息,LwIP 定义了一个ip_hdr 的结构体作为描述IP 数据报首部,同时还定义了很多获取IP 数据报首部的宏定义与设置IP 数据报首部的宏定义.
/* The IPv4 header */
struct ip_hdr {
/* version / header length */
PACK_STRUCT_FLD_8(u8_t _v_hl);
/* type of service */
PACK_STRUCT_FLD_8(u8_t _tos);
/* total length */
PACK_STRUCT_FIELD(u16_t _len);
/* identification */
PACK_STRUCT_FIELD(u16_t _id);
/* fragment offset field */
PACK_STRUCT_FIELD(u16_t _offset);
#define IP_RF 0x8000U /* reserved fragment flag */
#define IP_DF 0x4000U /* don't fragment flag */
#define IP_MF 0x2000U /* more fragments flag */
#define IP_OFFMASK 0x1fffU /* mask for fragmenting bits */
/* time to live */
PACK_STRUCT_FLD_8(u8_t _ttl);
/* protocol*/
PACK_STRUCT_FLD_8(u8_t _proto);
/* checksum */
PACK_STRUCT_FIELD(u16_t _chksum);
/* source and destination IP addresses */
PACK_STRUCT_FLD_S(ip4_addr_p_t src);
PACK_STRUCT_FLD_S(ip4_addr_p_t dest);
} PACK_STRUCT_STRUCT;
/* Macros to get struct ip_hdr fields: */
#define IPH_V(hdr) ((hdr)->_v_hl >> 4)
#define IPH_HL(hdr) ((hdr)->_v_hl & 0x0f)
#define IPH_HL_BYTES(hdr) ((u8_t)(IPH_HL(hdr) * 4))
#define IPH_TOS(hdr) ((hdr)->_tos)
#define IPH_LEN(hdr) ((hdr)->_len)
#define IPH_ID(hdr) ((hdr)->_id)
#define IPH_OFFSET(hdr) ((hdr)->_offset)
#define IPH_OFFSET_BYTES(hdr) ((u16_t)((lwip_ntohs(IPH_OFFSET(hdr)) & IP_OFFMASK) * 8U))
#define IPH_TTL(hdr) ((hdr)->_ttl)
#define IPH_PROTO(hdr) ((hdr)->_proto)
#define IPH_CHKSUM(hdr) ((hdr)->_chksum)
/* Macros to set struct ip_hdr fields: */
#define IPH_VHL_SET(hdr, v, hl) (hdr)->_v_hl = (u8_t)((((v) << 4) | (hl)))
#define IPH_TOS_SET(hdr, tos) (hdr)->_tos = (tos)
#define IPH_LEN_SET(hdr, len) (hdr)->_len = (len)
#define IPH_ID_SET(hdr, id) (hdr)->_id = (id)
#define IPH_OFFSET_SET(hdr, off) (hdr)->_offset = (off)
#define IPH_TTL_SET(hdr, ttl) (hdr)->_ttl = (u8_t)(ttl)
#define IPH_PROTO_SET(hdr, proto) (hdr)->_proto = (u8_t)(proto)
#define IPH_CHKSUM_SET(hdr, chksum) (hdr)->_chksum = (chksum)
一个链路层帧能承载的最大数据量叫做最大传送单元(Maximum Transmission Unit,MTU)。因为每个IP 数据报都必须封装在链路层帧中从一台路由器传输到下一台路由器,故链路层协议的MTU 严格地限制着IP 数据报的长度。对IP 数据报长度具有严格限制并不是主要问题,问题在于在发送方与目的地路径上的每段链路可能使用不同的链路层协议,且不同的硬件可能具有不同的MTU,这就需要有一个很好的处理方式,随之而来的就是IP 数据报分片处理。
分片处理是将IP 数据报中的数据分片成两个或更多个较小的IP 数据报,用单独的链路层帧封装这些较小的IP 数据报;然后向输出链路上发送这些帧,每个这些较小的数据报都称为分片,由于IP 数据报的分片偏移量是用8 的整数倍记录的,所以每个数据报中的分片数据大小也必须是8 的整数倍。
所有分片数据报在其到达目标主机的传输层之前需要在IP 层完成重新组装(也称之为重装)。IPv4 协议的设计者觉得如果在每个IP 层中组装分片数据包,那么将严重影响路由器的性能,例如一台路由器,在收到数据分片后又进行重装,然后再转发,这样子的处理是万万不可的,所以 IPv4 的设计者决定将数据报的重新组装工作放到端系统中,而不是放到网络路由器中,什么是端系统呢?简单来说就是数据包中的目标IP 地址的主机,在这台机器上的IP 层进行数据分片的重装,这样子数据分片可以任意在各个路由之间进行转发,而路由器就无需理会数据分片是在哪里重装,只要数据分片不是给路由器的,那么就将其转发出去即可,当然,这样子的处理就会是的每个数据分片到达目标IP 地址的主机时间是不一样的。
那么怎么样处理每个分片的数据呢?其实在发送主机中,它会把需要分片的数据进行切割(分片),按照数据的偏移量进行切割,切割后形成的每个IP 数据报(即分片)具有与初始IP 数据报几乎一样的IP 数据报首部,为什么说是几乎一样而不是全部一样呢,因为IP 数据报首部的标志、分片偏移量这两个字段与分片有关,不同的分片,这些信息可能不一样,不同的分片数据报长度也是不一样的,校验和字段也是不一样的。但是源IP 地址、目标IP 地址与标识号肯定是一样的,每个分片上的分片偏移量字段是不一样的。
IP 是一种不可靠的服务,一个或多个分片可能永远到达不了目的地。因为这种原因,为了让目标主机相信它已经收到了初始IP 数据报的最后一个分片,其最后一个分片上的标志字段(最后一位)被设置为0。而所有其他分片的标志被设为1。另外,为了让目的主机确定是否丢失了一个分片(且能按正确的顺序重新组装分片),使用偏移字段指定该分片应放在初始IP 数据报的哪个位置。
例子:
一个主机打算发送4000 字节的IP 数据报(20 字节IP 首部加上3980 字节IP 数据区域,假设没有IP 数据报首部选项字段),且该数据报必须通过一条MTU 为1500 字节的以太网链路。这就意味着源始IP 数据报中3980 字节数据必须被分配为3 个独立的数据报分片(其中的每个分片也是一个IP 数据报)。假定初始IP 数据报贴上的标识号为666,那么第一个分片的数据报总大小为1500 字节(1480 字节数据大小+20 字节IP 数据报首部),分片偏移量为0,第二个分片的数据报大小也为1500 字节,分片偏移量为185(185*8=1480),第三个分片的数据报大小为1040(3980-1480-1480+20),分片偏移量为370(185+185)。
IP 协议是网络层的主要协议,在上层传输协议(如TCP/UDP)需要发送数据时,就会将数据封装起来,然后传递到IP 层,IP 层首先会根据上层协议的目标IP 地址选择一个合适的网卡进行发送数据,当IP 协议获得数据后将其封装成IP 数据报的格式,填写IP 数据报首部对应的各个字段,如目标IP 地址、源IP 地址、协议类型、生存时间等重要信息。最后在IP 层通过回调函数netif->output(即etharp_output()函数)将IP 数据报投递给ARP协议,再调用网卡底层发送函数进行发送,这样子自上而下的数据就发送出去,IP 协议以目标IP 地址作为目标主机的身份地址。
err_t
ip4_output(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;
LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
if ((netif = ip4_route_src(src, dest)) == NULL) {
LWIP_DEBUGF(IP_DEBUG, ("ip4_output: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));
IP_STATS_INC(ip.rterr);
return ERR_RTE;
}
return ip4_output_if(p, src, dest, ttl, tos, proto, netif);
}
err_t
ip4_output_if(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)
{
#if IP_OPTIONS_SEND
return ip4_output_if_opt(p, src, dest, ttl, tos, proto, netif, NULL, 0);
}
err_t
ip4_output_if_opt(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, void *ip_options,
u16_t optlen)
{
#endif /* IP_OPTIONS_SEND */
const ip4_addr_t *src_used = src;
if (dest != LWIP_IP_HDRINCL) {
if (ip4_addr_isany(src)) {
src_used = netif_ip4_addr(netif);
}
}
#if IP_OPTIONS_SEND
return ip4_output_if_opt_src(p, src_used, dest, ttl, tos, proto, netif,
ip_options, optlen);
#else /* IP_OPTIONS_SEND */
return ip4_output_if_src(p, src_used, dest, ttl, tos, proto, netif);
#endif /* IP_OPTIONS_SEND */
}
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)
{
#if IP_OPTIONS_SEND
return ip4_output_if_opt_src(p, src, dest, ttl, tos, proto, netif, NULL, 0);
}
err_t
ip4_output_if_opt_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, void *ip_options,
u16_t optlen)
{
#endif /* IP_OPTIONS_SEND */
struct ip_hdr *iphdr;
ip4_addr_t dest_addr;
#if CHECKSUM_GEN_IP_INLINE
u32_t chk_sum = 0;
#endif /* CHECKSUM_GEN_IP_INLINE */
LWIP_ASSERT_CORE_LOCKED();
LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
MIB2_STATS_INC(mib2.ipoutrequests);
/* Should the IP header be generated or is it already included in p? */
if (dest != LWIP_IP_HDRINCL) {
u16_t ip_hlen = IP_HLEN;
#if IP_OPTIONS_SEND
u16_t optlen_aligned = 0;
if (optlen != 0) {
#if CHECKSUM_GEN_IP_INLINE
int i;
#endif /* CHECKSUM_GEN_IP_INLINE */
if (optlen > (IP_HLEN_MAX - IP_HLEN)) {
/* optlen too long */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output_if_opt: optlen too long\n"));
IP_STATS_INC(ip.err);
MIB2_STATS_INC(mib2.ipoutdiscards);
return ERR_VAL;
}
/* round up to a multiple of 4 */
optlen_aligned = (u16_t)((optlen + 3) & ~3);
ip_hlen = (u16_t)(ip_hlen + optlen_aligned);
/* First write in the IP options */
if (pbuf_add_header(p, optlen_aligned)) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output_if_opt: not enough room for IP options in pbuf\n"));
IP_STATS_INC(ip.err);
MIB2_STATS_INC(mib2.ipoutdiscards);
return ERR_BUF;
}
MEMCPY(p->payload, ip_options, optlen);
if (optlen < optlen_aligned) {
/* zero the remaining bytes */
memset(((char *)p->payload) + optlen, 0, (size_t)(optlen_aligned - optlen));
}
#if CHECKSUM_GEN_IP_INLINE
for (i = 0; i < optlen_aligned / 2; i++) {
chk_sum += ((u16_t *)p->payload)[i];
}
#endif /* CHECKSUM_GEN_IP_INLINE */
}
#endif /* IP_OPTIONS_SEND */
/* generate IP header */
if (pbuf_add_header(p, IP_HLEN)) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output: not enough room for IP header in pbuf\n"));
IP_STATS_INC(ip.err);
MIB2_STATS_INC(mib2.ipoutdiscards);
return ERR_BUF;
}
iphdr = (struct ip_hdr *)p->payload;
LWIP_ASSERT("check that first pbuf can hold struct ip_hdr",
(p->len >= sizeof(struct ip_hdr)));
IPH_TTL_SET(iphdr, ttl);
IPH_PROTO_SET(iphdr, proto);
#if CHECKSUM_GEN_IP_INLINE
chk_sum += PP_NTOHS(proto | (ttl << 8));
#endif /* CHECKSUM_GEN_IP_INLINE */
/* dest cannot be NULL here */
ip4_addr_copy(iphdr->dest, *dest);
#if CHECKSUM_GEN_IP_INLINE
chk_sum += ip4_addr_get_u32(&iphdr->dest) & 0xFFFF;
chk_sum += ip4_addr_get_u32(&iphdr->dest) >> 16;
#endif /* CHECKSUM_GEN_IP_INLINE */
IPH_VHL_SET(iphdr, 4, ip_hlen / 4);
IPH_TOS_SET(iphdr, tos);
#if CHECKSUM_GEN_IP_INLINE
chk_sum += PP_NTOHS(tos | (iphdr->_v_hl << 8));
#endif /* CHECKSUM_GEN_IP_INLINE */
IPH_LEN_SET(iphdr, lwip_htons(p->tot_len));
#if CHECKSUM_GEN_IP_INLINE
chk_sum += iphdr->_len;
#endif /* CHECKSUM_GEN_IP_INLINE */
IPH_OFFSET_SET(iphdr, 0);
IPH_ID_SET(iphdr, lwip_htons(ip_id));
#if CHECKSUM_GEN_IP_INLINE
chk_sum += iphdr->_id;
#endif /* CHECKSUM_GEN_IP_INLINE */
++ip_id;
if (src == NULL) {
ip4_addr_copy(iphdr->src, *IP4_ADDR_ANY4);
} else {
/* src cannot be NULL here */
ip4_addr_copy(iphdr->src, *src);
}
#if CHECKSUM_GEN_IP_INLINE
chk_sum += ip4_addr_get_u32(&iphdr->src) & 0xFFFF;
chk_sum += ip4_addr_get_u32(&iphdr->src) >> 16;
chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF);
chk_sum = (chk_sum >> 16) + chk_sum;
chk_sum = ~chk_sum;
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
iphdr->_chksum = (u16_t)chk_sum; /* network order */
}
#if LWIP_CHECKSUM_CTRL_PER_NETIF
else {
IPH_CHKSUM_SET(iphdr, 0);
}
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
#else /* CHECKSUM_GEN_IP_INLINE */
IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));
}
#endif /* CHECKSUM_GEN_IP */
#endif /* CHECKSUM_GEN_IP_INLINE */
} else {
/* IP header already included in p */
if (p->len < IP_HLEN) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output: LWIP_IP_HDRINCL but pbuf is too short\n"));
IP_STATS_INC(ip.err);
MIB2_STATS_INC(mib2.ipoutdiscards);
return ERR_BUF;
}
iphdr = (struct ip_hdr *)p->payload;
ip4_addr_copy(dest_addr, iphdr->dest);
dest = &dest_addr;
}
IP_STATS_INC(ip.xmit);
LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: %c%c%"U16_F"\n", netif->name[0], netif->name[1], (u16_t)netif->num));
ip4_debug_print(p);
#if ENABLE_LOOPBACK
if (ip4_addr_cmp(dest, netif_ip4_addr(netif))
#if !LWIP_HAVE_LOOPIF
|| ip4_addr_isloopback(dest)
#endif /* !LWIP_HAVE_LOOPIF */
) {
/* Packet to self, enqueue it for loopback */
LWIP_DEBUGF(IP_DEBUG, ("netif_loop_output()"));
return netif_loop_output(netif, p);
}
#if LWIP_MULTICAST_TX_OPTIONS
if ((p->flags & PBUF_FLAG_MCASTLOOP) != 0) {
netif_loop_output(netif, p);
}
#endif /* LWIP_MULTICAST_TX_OPTIONS */
#endif /* ENABLE_LOOPBACK */
#if IP_FRAG
/* don't fragment if interface has mtu set to 0 [loopif] */
if (netif->mtu && (p->tot_len > netif->mtu)) {
return ip4_frag(p, netif, dest);
}
#endif /* IP_FRAG */
LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: call netif->output()\n"));
return netif->output(netif, p, dest);
}
此处补充一下知识点:在上层应用一般不会调用ip4_output()这些函数,而是通过代码清单 所示的带参宏进行直接发送数据包,因为这些函数层层封装,直接调用底层的函数效率会更高,比如UDP 协议就是通过调用ip_output_if_src()将数据包发送到IP 层,因此IP 数据报发送流程示意图具
#define ip_output(p, src, dest, ttl, tos, proto) \
ip4_output(p, src, dest, ttl, tos, proto)
#define ip_output_if(p, src, dest, ttl, tos, proto, netif) \
ip4_output_if(p, src, dest, ttl, tos, proto, netif)
#define ip_output_if_src(p, src, dest, ttl, tos, proto, netif) \
ip4_output_if_src(p, src, dest, ttl, tos, proto, netif)
#define ip_output_hinted(p, src, dest, ttl, tos, proto, netif_hint) \
ip4_output_hinted(p, src, dest, ttl, tos, proto, netif_hint)
#define ip_output_if_hdrincl(p, src, dest, netif) \
ip4_output_if(p, src, LWIP_IP_HDRINCL, 0, 0, 0, netif)
#define ip_route(src, dest) \
ip4_route_src(src, dest)
#define ip_netif_get_local_ip(netif, dest) \
ip4_netif_get_local_ip(netif)
#define ip_debug_print(is_ipv6, p) ip4_debug_print(p)
一个IP 数据报从网卡进入到IP 协议,是通过ethernet_input()函数再到ip4_input()函数进入IP 协议中被处理,对于IPv4 版本的协议,所有的IP 数据报都需要经过ip4_input()才能进入IP 协议中,相对于IP 数据报发送,接收的流程会更加复杂,因为对于所有输入的IP 数据报,内核都要确认这些数据报是否是给自己的,并且还要保证这些数据报的格式必须是正确的;如不是给本地的数据报,IP 层还要将这些数据报进行转发或者丢弃,当然,如果对于分片的数据报,IP 层还需要负责将其组装起来,并且校验组装完成的数据报是否完整,如果不完整则丢弃它(关于组装数据报的部分比较麻烦,我们也无需了解太多,只要知道即可),当数据报是正确的,IP 层就会递交给上层协议(如UDP 协议、TCP 协议)
err_t
ip4_input(struct pbuf *p, struct netif *inp)
{
const 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 */
#if LWIP_RAW
raw_input_state_t raw_status;
#endif /* LWIP_RAW */
LWIP_ASSERT_CORE_LOCKED();
IP_STATS_INC(ip.recv);
MIB2_STATS_INC(mib2.ipinreceives);
/* identify the IP header */
iphdr = (struct ip_hdr *)p->payload;
if (IPH_V(iphdr) != 4) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_WARNING, ("IP packet dropped due to bad version number %"U16_F"\n", (u16_t)IPH_V(iphdr)));
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;
}
#ifdef LWIP_HOOK_IP4_INPUT
if (LWIP_HOOK_IP4_INPUT(p, inp)) {
/* the packet has been eaten */
return ERR_OK;
}
#endif
/* obtain IP header length in bytes */
iphdr_hlen = IPH_HL_BYTES(iphdr);
/* obtain ip length in bytes */
iphdr_len = lwip_ntohs(IPH_LEN(iphdr));
/* Trim pbuf. This is especially required for packets < 60 bytes. */
if (iphdr_len < p->tot_len) {
pbuf_realloc(p, iphdr_len);
}
/* header length exceeds first pbuf length, or ip length exceeds total pbuf length? */
if ((iphdr_hlen > p->len) || (iphdr_len > p->tot_len) || (iphdr_hlen < IP_HLEN)) {
if (iphdr_hlen < IP_HLEN) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("ip4_input: short IP header (%"U16_F" bytes) received, IP packet dropped\n", iphdr_hlen));
}
if (iphdr_hlen > p->len) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("IP header (len %"U16_F") does not fit in first pbuf (len %"U16_F"), IP packet dropped.\n",
iphdr_hlen, p->len));
}
if (iphdr_len > p->tot_len) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("IP (len %"U16_F") is longer than pbuf (len %"U16_F"), IP packet dropped.\n",
iphdr_len, p->tot_len));
}
/* free (drop) packet pbufs */
pbuf_free(p);
IP_STATS_INC(ip.lenerr);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipindiscards);
return ERR_OK;
}
/* verify checksum */
#if CHECKSUM_CHECK_IP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_IP) {
if (inet_chksum(iphdr, iphdr_hlen) != 0) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("Checksum (0x%"X16_F") failed, IP packet dropped.\n", inet_chksum(iphdr, iphdr_hlen)));
ip4_debug_print(p);
pbuf_free(p);
IP_STATS_INC(ip.chkerr);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinhdrerrors);
return ERR_OK;
}
}
#endif
/* copy IP addresses to aligned ip_addr_t */
ip_addr_copy_from_ip4(ip_data.current_iphdr_dest, iphdr->dest);
ip_addr_copy_from_ip4(ip_data.current_iphdr_src, iphdr->src);
/* match packet against an interface, i.e. is this packet for us? */
if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
#if LWIP_IGMP
if ((inp->flags & NETIF_FLAG_IGMP) && (igmp_lookfor_group(inp, ip4_current_dest_addr()))) {
/* IGMP snooping switches need 0.0.0.0 to be allowed as source address (RFC 4541) */
ip4_addr_t allsystems;
IP4_ADDR(&allsystems, 224, 0, 0, 1);
if (ip4_addr_cmp(ip4_current_dest_addr(), &allsystems) &&
ip4_addr_isany(ip4_current_src_addr())) {
check_ip_src = 0;
}
netif = inp;
} else {
netif = NULL;
}
#else /* LWIP_IGMP */
if ((netif_is_up(inp)) && (!ip4_addr_isany_val(*netif_ip4_addr(inp)))) {
netif = inp;
} else {
netif = NULL;
}
#endif /* LWIP_IGMP */
} else {
/* start trying with inp. if that's not acceptable, start walking the
list of configured netifs. */
if (ip4_input_accept(inp)) {
netif = inp;
} else {
netif = NULL;
#if !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF
/* Packets sent to the loopback address must not be accepted on an
* interface that does not have the loopback address assigned to it,
* unless a non-loopback interface is used for loopback traffic. */
if (!ip4_addr_isloopback(ip4_current_dest_addr()))
#endif /* !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF */
{
#if !LWIP_SINGLE_NETIF
NETIF_FOREACH(netif) {
if (netif == inp) {
/* we checked that before already */
continue;
}
if (ip4_input_accept(netif)) {
break;
}
}
#endif /* !LWIP_SINGLE_NETIF */
}
}
}
#if IP_ACCEPT_LINK_LAYER_ADDRESSING
/* Pass DHCP messages regardless of destination address. DHCP traffic is addressed
* using link layer addressing (such as Ethernet MAC) so we must not filter on IP.
* According to RFC 1542 section 3.1.1, referred by RFC 2131).
*
* If you want to accept private broadcast communication while a netif is down,
* define LWIP_IP_ACCEPT_UDP_PORT(dst_port), e.g.:
*
* #define LWIP_IP_ACCEPT_UDP_PORT(dst_port) ((dst_port) == PP_NTOHS(12345))
*/
if (netif == NULL) {
/* remote port is DHCP server? */
if (IPH_PROTO(iphdr) == IP_PROTO_UDP) {
const struct udp_hdr *udphdr = (const struct udp_hdr *)((const u8_t *)iphdr + iphdr_hlen);
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: UDP packet to DHCP client port %"U16_F"\n",
lwip_ntohs(udphdr->dest)));
if (IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(udphdr->dest)) {
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: DHCP packet accepted.\n"));
netif = inp;
check_ip_src = 0;
}
}
}
#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
/* broadcast or multicast packet source address? Compliant with RFC 1122: 3.2.1.3 */
#if LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING
if (check_ip_src
#if IP_ACCEPT_LINK_LAYER_ADDRESSING
/* DHCP servers need 0.0.0.0 to be allowed as source address (RFC 1.1.2.2: 3.2.1.3/a) */
&& !ip4_addr_isany_val(*ip4_current_src_addr())
#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
)
#endif /* LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING */
{
if ((ip4_addr_isbroadcast(ip4_current_src_addr(), inp)) ||
(ip4_addr_ismulticast(ip4_current_src_addr()))) {
/* packet source is not valid */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ip4_input: packet source is not valid.\n"));
/* free (drop) packet pbufs */
pbuf_free(p);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinaddrerrors);
MIB2_STATS_INC(mib2.ipindiscards);
return ERR_OK;
}
}
/* packet not for us? */
if (netif == NULL) {
/* packet not for us, route or discard */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: packet not for us.\n"));
#if IP_FORWARD
/* non-broadcast packet? */
if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp)) {
/* try to forward IP packet on (other) interfaces */
ip4_forward(p, (struct ip_hdr *)p->payload, 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;
}
/* packet consists of multiple fragments? */
if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0) {
#if IP_REASSEMBLY /* packet fragment reassembly code present? */
LWIP_DEBUGF(IP_DEBUG, ("IP packet is a fragment (id=0x%04"X16_F" tot_len=%"U16_F" len=%"U16_F" MF=%"U16_F" offset=%"U16_F"), calling ip4_reass()\n",
lwip_ntohs(IPH_ID(iphdr)), p->tot_len, lwip_ntohs(IPH_LEN(iphdr)), (u16_t)!!(IPH_OFFSET(iphdr) & PP_HTONS(IP_MF)), (u16_t)((lwip_ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK) * 8)));
/* reassemble the packet*/
p = ip4_reass(p);
/* packet not fully reassembled yet? */
if (p == NULL) {
return ERR_OK;
}
iphdr = (const struct ip_hdr *)p->payload;
#else /* IP_REASSEMBLY == 0, no packet fragment reassembly code present */
pbuf_free(p);
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("IP packet dropped since it was fragmented (0x%"X16_F") (while IP_REASSEMBLY == 0).\n",
lwip_ntohs(IPH_OFFSET(iphdr))));
IP_STATS_INC(ip.opterr);
IP_STATS_INC(ip.drop);
/* unsupported protocol feature */
MIB2_STATS_INC(mib2.ipinunknownprotos);
return ERR_OK;
#endif /* IP_REASSEMBLY */
}
#if IP_OPTIONS_ALLOWED == 0 /* no support for IP options in the IP header? */
#if LWIP_IGMP
/* there is an extra "router alert" option in IGMP messages which we allow for but do not police */
if ((iphdr_hlen > IP_HLEN) && (IPH_PROTO(iphdr) != IP_PROTO_IGMP)) {
#else
if (iphdr_hlen > IP_HLEN) {
#endif /* LWIP_IGMP */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("IP packet dropped since there were IP options (while IP_OPTIONS_ALLOWED == 0).\n"));
pbuf_free(p);
IP_STATS_INC(ip.opterr);
IP_STATS_INC(ip.drop);
/* unsupported protocol feature */
MIB2_STATS_INC(mib2.ipinunknownprotos);
return ERR_OK;
}
#endif /* IP_OPTIONS_ALLOWED == 0 */
/* send to upper layers */
LWIP_DEBUGF(IP_DEBUG, ("ip4_input: \n"));
ip4_debug_print(p);
LWIP_DEBUGF(IP_DEBUG, ("ip4_input: p->len %"U16_F" p->tot_len %"U16_F"\n", p->len, p->tot_len));
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_BYTES(iphdr);
#if LWIP_RAW
/* raw input did not eat the packet? */
raw_status = raw_input(p, inp);
if (raw_status != RAW_INPUT_EATEN)
#endif /* LWIP_RAW */
{
pbuf_remove_header(p, iphdr_hlen); /* Move to payload, no check necessary. */
switch (IPH_PROTO(iphdr)) {
#if LWIP_UDP
case IP_PROTO_UDP:
#if LWIP_UDPLITE
case IP_PROTO_UDPLITE:
#endif /* LWIP_UDPLITE */
MIB2_STATS_INC(mib2.ipindelivers);
udp_input(p, inp);
break;
#endif /* LWIP_UDP */
#if LWIP_TCP
case IP_PROTO_TCP:
MIB2_STATS_INC(mib2.ipindelivers);
tcp_input(p, inp);
break;
#endif /* LWIP_TCP */
#if LWIP_ICMP
case IP_PROTO_ICMP:
MIB2_STATS_INC(mib2.ipindelivers);
icmp_input(p, inp);
break;
#endif /* LWIP_ICMP */
#if LWIP_IGMP
case IP_PROTO_IGMP:
igmp_input(p, inp, ip4_current_dest_addr());
break;
#endif /* LWIP_IGMP */
default:
#if LWIP_RAW
if (raw_status == RAW_INPUT_DELIVERED) {
MIB2_STATS_INC(mib2.ipindelivers);
} else
#endif /* LWIP_RAW */
{
#if LWIP_ICMP
/* send ICMP destination protocol unreachable unless is was a broadcast */
if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), netif) &&
!ip4_addr_ismulticast(ip4_current_dest_addr())) {
pbuf_header_force(p, (s16_t)iphdr_hlen); /* Move to ip header, no check necessary. */
icmp_dest_unreach(p, ICMP_DUR_PROTO);
}
#endif /* LWIP_ICMP */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("Unsupported transport protocol %"U16_F"\n", (u16_t)IPH_PROTO(iphdr)));
IP_STATS_INC(ip.proterr);
IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinunknownprotos);
}
pbuf_free(p);
break;
}
}
/* @todo: this is not really necessary... */
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;
}