Linux 网络基础之网络IP层(十二)路由、路由表,分片和组装

目录

一、路由、路由表

什么是路由?

内网路由和公网路由

路由表

查路由表

二、分片和组装

分片和组装的引入

组装的过程

IP报头的第二行字段

16位标识:

3位标志:

13位片偏移:

分片的过程

三、总结


接着上一篇文章,我们接着讲解路由和路由表,以及 IP 报头的第二行字段。

一、路由、路由表

什么是路由?

路由,简单来说就是在复杂的网络结构里,为 IP 数据包找到一条从源主机通往目标主机的传输路径,它的过程就像我们问路一样,是 一跳一跳(Hop by Hop)接力完成的。

这里的一跳 ,指的是数据链路层里相邻两个设备之间的传输区间,在以太网中就是一段从源 MAC 地址到下一跳 MAC 地址的帧传输。IP 数据包的全程传输,目标 IP 从头到尾不会改变,负责端到端的寻址;而每一跳的 MAC 地址会不断变化,只负责当前一段链路的转发。

当主机 A 要给主机 B 发数据包时,数据包会先发给网关路由器 A;路由器 A 查看目标 IP,对照自己的路由表,判断无法直接送达,就转发给下一跳路由器 B;路由器 B 再查路由表,继续转给路由器 C,接着传给路由器 D,最后由路由器 D 交付给主机 B。每经过一台路由器就是一跳,每台路由器都依靠自己的路由表判断转发方向,反复接力,直到数据包抵达最终的目标 IP。

内网路由和公网路由

路由可以按场景分为内网路由和公网路由 ,但它们本质是同一种路由原理,只是作用范围、地址类型不一样:

路由本身的核心逻辑完全一致,都是查路由表、一跳一跳转发 ,区别只在网络范围和 IP 类型。内网路由 发生在局域网、运营商内网这类私有网络里,转发的是私有 IP ,比如家庭内网、西安 / 陕西的运营商内网,只在小范围内部转发,不会出公网;公网路由 发生在全球广域网,转发的是公网 IP,在国家、国际骨干网之间跨区域转发。

内网路由只负责内部设备互通,不需要 NAT;而公网路由必须配合 NAT,内网私有 IP 先通过出口路由器伪装成公网 IP,再进入公网路由转发。不管是内网还是公网,每一台路由器都靠路由表判断下一跳,一跳一跳把数据包送到目的地,底层原理没有区别。

路由表

路由表是路由的核心,它记录了各个网段对应的下一跳地址,路由器收到数据包后,通过查询路由表,就能决定数据包是直接交付给内网主机,还是转发给下一台路由器,这就是整个路由转发的完整逻辑。

查路由表

我们可以用 route 命令查看 Linux 内核路由表,下图就是这台 Ubuntu 机器的路由规则。

Destination:目标网段 / IP

Gateway:下一跳网关(发给谁)

Genmask:子网掩码

Iface:从哪个网卡发出去(这里都是 eth0)

关键两行:

default 默认路由:所有不在本地网段的流量,全都发给网关、走 eth0 网卡上网。

还可以使用 ifconfig 命令查看本机网卡信息, 这里有两个网卡:

eth0:物理/外网网卡

inet 10.0.16.7:本机内网 IP 地址

netmask 255.255.252.0:子网掩码

所有上网流量都走这个网卡

lo(loopback):本地回环网卡

127.0.0.1,只用于本机自己访问自己,不会走外网

这台主机拥有两个网络接口,eth0 连接 192.168.10.0/24 网段,eth1 连接 192.168.56.0/24 网段,路由表则为数据包转发提供了明确规则。路由表中的 Destination 是目标网络地址,Genmask 是子网掩码,Gateway 是下一跳地址,Iface 是发送接口;Flags 里的 U 标志表示条目有效,G 标志表示下一跳是路由器地址,没有 G 标志的条目则表示目标网络与本机接口直接相连,无需路由器转发。

当目标地址是 192.168.56.3 时,主机会依次对比路由表条目:先和第一条的子网掩码做与运算,得到的结果与 192.168.10.0 不符;再和第二条的子网掩码做与运算,结果正好匹配 192.168.56.0,于是选择从 eth1 接口发送数据包。由于 192.168.56.0/24 是 eth1 直接相连的网络,主机可以直接把数据包发给目标主机,不需要经过路由器转发。

当目标地址是 202.10.1.2 时,主机会依次对比路由表前几项,发现都不匹配,这时就会匹配到 default 默认路由条目,将数据包从 eth0 接口发往默认网关 192.168.10.1,后续由这个路由器根据它的路由表决定下一跳地址。简单来说,路由表的匹配逻辑是优先匹配具体网段,找不到匹配项时,就会把数据包交给默认路由,也就是上一级网关处理。

二、分片和组装

分片和组装的引入

我们之前在TCP的滑动窗口说过,滑动窗口范围内的数据可以直接发送给对方,暂时不需要逐个应答,但这里会产生一个疑问 ------既然对方接收能力充足,为什么我们不一次性把数据全部发完,反而要分批次发送?

核心原因就是 IP 报文过长会触发分片机制:当 IP 报文携带的数据量过大时,网络层会将这一个大 IP 报文拆分成多个小报文进行传输,这个拆分过程就叫分片;等到所有分片抵达目标主机后,再由对方网络层重新拼接、还原成完整的原始报文,这个过程就是组装

IP 报文承载的数据可以是 TCP,也可以是 UDP,我们这里以 TCP 为例。IP 报文封装完成后,会向下交付给数据链路层,而以太网协议规定数据链路层帧的有效载荷最大长度为 MTU(1500 字节),这就意味着网络层下发的整个 IP 报文(IP 头部 + TCP 数据载荷)总大小不能超过 1500 字节。 一旦超出这个上限,必须在进行分片

那谁来组装呢?

由对方的网络层进行组装,也就是双方的网络层进行分片和组装。既不能在传输层拆分,也不能在数据链路层拆分。分片后的报文会由接收方的网络层负责识别、重组,完成最终的组装

那我们从网络通信的角度来讲,建不建议一个IP报文进行过多的分片和组装呢?

从网络通信的角度来说,我们并不建议让 IP 报文进行过多分片与组装。一方面,频繁拆分、重组会增加处理开销,大幅降低传输效率;另一方面,只要任意一个分片丢失,整个原始报文都无法完成组装,会直接提升整体的丢包概率。

双方进行分片和组装,双方的上层会知道吗?

双方不会知道。分片和组装全程由双方网络层自动完成,上层的传输层、应用层完全感知不到,上层只会收到完整的原始数据,不会察觉底层发生了拆分与重组。

我们来看一个很关键的问题:如果发送方把一个 IP 报文拆成了 4 片发送,而接收方只收到了 3 片,那发送方该怎么补发?

有两种补发方法 :

  1. 只补发丢了的那1片
  2. 4片全部补发

答案是选第2种方法,把 4 片全部重传,而不是只补发丢失的那 1 片。因为 IP 层本身没有对单个分片的确认机制,接收方只会在收到所有分片并组装成完整报文后才会向上层交付,而不会单独对某一个分片进行确认。所以一旦有任何一片丢失,接收方就无法组装出完整报文,传输层只能通过超时重传或快重传机制,把整个原始报文重新发送一遍,而不是单独补发丢失的分片。这就意味着,只要一个报文被分片,它的丢包概率就会被放大 ------ 所有分片里只要有一个丢了,整个报文都得重传,效率也会随之降低,这也是为什么我们不建议过度依赖网络层分片的原因。

那对 IP 报文进行分片,是由网络层决定的吗?

并不是网络层主动决定的,而是取决于传输层交给网络层的数据大小。如果传输层交付给 IP 层的数据过大,导致 IP 报文总长度超过了数据链路层的 MTU (1500字节),IP 层就必须进行分片;如果传输层交付的数据足够小,IP 报文总长度不超过 MTU,就不需要分片。所以,是否分片的主动权其实在传输层,这也是为什么 TCP 不会直接根据对方的滑动窗口一次性把数据全部发完的重要原因之一 ------ 它会通过 MSS(最大分段大小)机制提前把数据切割成合适的大小,让 IP 层不需要再分片,以此避免分片带来的效率和丢包问题。

最还有一个细节:即使发送方和接收方的以太网ui订单 MTU 都是 1500 字节,传输过程中也依然可能出现分片。因为数据在传输途中会经过不同的链路,中间路由器连接的网络可能支持更小的 MTU。比如发送方的 IP 报文是 1200 字节(加上 IP 头后仍小于 1500),但中间某一段链路的 MTU 只有 1000 字节,那么负责转发的路由器就必须对这个 IP 报文进行分片,才能继续传输。这种中途分片同样会带来丢包率上升的问题,所以 TCP 还会通过路径 MTU 发现(PMTUd)机制,主动探测整条路径上的最小 MTU,从源头就避免报文在传输途中被分片,进一步提升传输效率和可靠性。

我们可以计算一下,如果 TCP 和 IP 采用标准报头(20字节),那传输层给网络层的数据应该多大,才不会产生分片问题?

我们知道以太网的 MTU 是 1500 字节,IP 标准报头占 20 字节,TCP 标准报头也占 20 字节,所以留给 TCP 数据的最大空间就是 1500 - 20 - 20 = 1460 字节。这个 1460 字节就是 TCP 的 MSS (最大段尺寸,Max Segment Size),它规定了传输层交给网络层的有效载荷,单个数据包不能超过这个大小,这样 IP 报文总长度就不会超过 1500 字节,也就不需要分片了。

那问题来了,双方在收发时,怎么知道对方传输层的MMS是多少呢?理论上双方都应该先知道对方传输层的MMS是多少,这样发送数据时就以对方的MMS为基准进行数据发送,从而减小分片的概率。

TCP 在三次握手阶段会交换双方的 MSS 值。在 TCP 报头的选项字段里,有一个专门的 MSS 字段,双方在握手时会把自己支持的最大段尺寸告诉对方,连接建立完成后,双方就都知道了对方的 MSS,发送数据时就会以对方的 MSS 为上限,提前把数据切割成合适的大小,从源头上避免 IP 层分片,这也是 TCP 设计中提升传输效率和可靠性的关键细节之一。

组装的过程

分片与组装并不是推荐的操作,反而因为传输效率低,还会显著增加丢包概率,所以日常通信中我们会尽量避免它的发生。但 IP 报头第二行的三个字段,专门用来处理分片与组装,这也说明真实网络里分片是会发生的。如果分片和组装真实发生了,那二者又是如何实现的呢?

这里我们先讲组装,因为把一个东西拆分容易,如果再重新组装就会变难,所以我们先学习如何组装。理解组装后再回头看分片,思路会清晰很多。

既然是组装,那就得现在接收方的视角,接收方会在网络层收到多个 IP 报文,网络层先做的应是先识别发来的这些报文是否分片,如果没有分片的报文就正常向上交付,如果识别出分片的报文,那又是如何识别出这个报文分了多少片?也就是怎么把分的这些片重新拼接成原始的报文,这里也就涉及到许多子问题,怎么判断哪个分片是报文的开头,哪个是结尾,如果其中某个分片丢了呢?再搞懂了上面的问题后,才能进行正确的组装。

其实分片并不是把一个IP报文(IP报头+有效载荷)从左到右依次进行划分,比如分了三片,第一个分片保留原报文头、后续分片不带头部。而是每一个分片都会独立生成一份 IP 头部 ,分片之后的每一片,本身都是一个完整独立的 IP 报文,带着自己的 IP 头部在网络中传输,这些头部里就包含了标识、标志位、片偏移等信息,用来支撑接收方完成识别、排序与组装。这些分片的报头是 IP 层在执行分片操作时,自动为每一个分片生成的独立 IP 报头

IP报头的第二行字段

下面我们先从IP报头中剩余的几个字段讲起:

16位标识:

首先是 16 位标识,这个 16 位标识就是整个IP报文的唯一标识,通过这个标识就表明了这个IP报文的唯一性,如果IP报文被分片了,那接收方收到的多个分片报文后,只要它们的 16 位标识相同,就能判断出这些分片都来自同一个原始报文。

3位标志:

其次是 3 位标志,其中第一个标志位不用,默认为 0;

第二个标志位 (DF位) 表示是否分片,为 1 表示禁止分片,为 0 表示可以分片。

第三个标志位 (MF位) 表示更多分片,是分片的结束标记,也就是说如果一个IP报文分片了,那最后一个分片报文报头中的这个 3 位标志的最后一个标志位就是 0,前面的分片的最后一个标志位就是1 (每个片都有报头),因此第三个标志位就代表是不是最后一个分片,类似于结束的标记。

13位片偏移:

13 位片偏移字段指的是本片数据在原始IP报文的数据载荷区中的偏移量,不包含IP报头。还需要注意的是这里的偏移单位不是 1 字节,而是 8 字节,也就是说片偏移值 ×8 = 真实字节偏移。

举个例子:原始 IP 报文的数据是 1460 字节,分成两片:

第 1 片数据:前 800 字节 → 片偏移 = 0

第 2 片数据:后 660 字节 → 片偏移 = 800÷8 = 100

接收方就知道:第 2 片的数据要拼在原始数据 800 字节(100×8)的位置后面。它不管 IP 头,只管分片数据在原始数据中的位置。

那网络层是怎么识别一个报文是被分片的呢?

网络层识别一个 IP 报文是否被分片,需要同时检查第三个 MF 标志位和 13 位片偏移字段,不能仅靠其中一个判断。仅看 MF 位是不够的,因为未分片的原始报文和分片后的最后一片报文,它们的 MF 位都为 0,无法直接区分。所以必须结合片偏移字段综合判断:当 MF 为 0 且片偏移也为 0 时,说明这是一个完整的、未被分片的报文;

而只要 MF 位不为 0 或片偏移不为 0,二者满足其一,就说明这个报文一定是分片后的报文。比如分片后的第一片 MF 为 1、片偏移为 0,中间片 MF 为 1、片偏移大于 0,最后一片 MF 为 0、片偏移大于 0,这些情况都属于分片报文,接收方会将它们缓存起来,等待所有分片到齐后再重组。

如何把分片聚合在一起?

IP 分片的聚合过程是通过 IP 报头中的三个核心字段协同完成的。接收方首先会提取所有分片的 16 位标识,将标识相同的分片归为一组,证明它们属于同一个原始报文。接下来,接收方会通过 13 位片偏移和 3 位标志位来确定分片的顺序:片偏移为 0 的分片是报文的起始部分,而 3 位标志中的 MF 位为 0 的分片是最后一片。对于中间的分片,则根据片偏移的大小按升序排列,因为当前分片的片偏移加上其数据长度,正好等于下一个分片的片偏移,以此类推。当所有分片收集完毕并按片偏移排序后,接收方就可以将这些分片的数据载荷拼接起来,还原出完整的原始 IP 报文。

分片的过程

弄懂了组装的过程,下面我们再返回来看一下分片的过程:我们通过一个例子来描述 :

假设网络层有一个大小为 3000 字节的一个IP报文(IP报头+有效载荷=3000字节),是怎么分片的?传输层的MTU是1500字节,也就是超过1500字节就得分片。

前提条件 :

  • 原始 IP 报文总长度 == 3000 字节(20 字节 IP 报头 + 2980 字节有效载荷)
  • 链路层 MTU == 1500 字节(每个 IP 分片的最大总长度,不能超过这个数)
  • 每个分片都必须带一份 IP 报头(固定 20 字节),所以每个分片能承载的最大数据量 == 1500 - 20 = 1480 字节
  • 片偏移的单位是8 字节,所以分片数据长度必须是 8 的倍数(除了最后一片),偏移值 = 数据字节数 ÷8

原始数据载荷 2980 字节,按 "优先占满 MTU" 的规则拆分:

分片 1 :

  • 总长度:1500 字节(20 字节报头 + 1480 字节数据)
  • 16 位标识:1234(所有分片都和原始报文一致)
  • 3 位标志:MF=1(表示后面还有分片,不是最后一片)
  • 13 位片偏移:0(数据从原始报文的第 0 字节开始,0 ÷ 8 = 0)

分片 2:

  • 总长度:1500 字节(20 字节报头 + 1480 字节数据)
  • 16 位标识:1234
  • 3 位标志:MF=1(后面还有分片)
  • 13 位片偏移:185(前一片数据 1480 字节,1480 ÷ 8 = 185,表示数据从原始报文的第 1480 字节开始)

分片 3(最后一片):

  • 剩余数据:2980 - 1480 - 1480 = 20 字节
  • 总长度:40 字节(20 字节报头 + 20 字节数据)
  • 16 位标识:1234
  • 3 位标志:MF=0(表示这是最后一片,后面没有分片了)
  • 13 位片偏移:370(前两片数据共 2960 字节,2960 ÷ 8 = 370,表示数据从原始报文的第 2960 字节开始)

需要注意的是:

  1. 分片只拆分数据,不拆分 IP 报头:每一片都是独立的 IP 报文,自带 20 字节报头,所以总长度 = 报头 + 数据。
  2. 片偏移必须除以 8:图里也标注了,偏移值是数据字节数 ÷8,不是直接用字节数,这是最容易出错的地方。
  3. MF 位区分首尾:只有最后一片的 MF=0,前面的分片 MF 都为 1,接收方靠这个判断是否收齐所有分片。
  4. 标识相同才是一组:所有分片的 16 位标识都和原始报文一致,接收方靠这个把同属一个报文的分片归为一组。

问题1 : 16 位总长度指的是IP报文最大的总长度,2 的 16 次方 65535 大于 2 的 13 次方,就算 2 的13 次方拉满也表示不了 2 的 16 次方个偏移量,那此时怎么办?(虽然真实情况下不可能有 2 的 16 次方的数据大小和偏移量)

首先,16 位的总长度字段决定了 IP 报文的最大总长度是 2¹⁶=65535 字节,而片偏移只有 13 位,最大只能表示 2¹³=8192 个单位。如果偏移直接用字节数表示,8192 个单位最多只能覆盖 8192 字节的数据,远小于 65535 字节的报文上限,这就会出现偏移字段不够用的问题。为了解决这个矛盾,IP 协议约定,片偏移字段的单位是 8 字节,也就是说,片偏移值要乘以 8 才是真实的字节偏移量。这样一来,8192 个单位就能覆盖 8192×8=65536 字节,刚好能匹配 16 位总长度的最大范围,完美解决了字段位数不足的问题。

正因为有了这个约定,分片数据的长度也必须满足对应的规则:除了最后一个分片,前面所有分片的数据长度都必须是 8 的整数倍,这样它们的片偏移值才会是整数,接收方通过乘以 8 就能还原出正确的字节偏移量。这个约定的本质,是用 "除以 8" 的方式,在有限的 13 位字段里,承载足够大的偏移范围,而 "除以 8" 也不是随便选的数字,它刚好是 2 的 3 次方,3 + 13 == 16,在二进制中相当于把数据的低 3 位置 0,既保证了偏移计算的连续性,也刚好能让以太网 MTU 场景下,分片数据长度 1480 字节成为 8 的倍数,让前几片刚好能占满 MTU,最后再把剩余数据单独分成一片,形成了我们常见的分片方式。


问题2 : 片要不要管理?先描述再组织

在 Linux 内核里,IP 分片是需要专门管理的,并且内核中也有相应的结构体。

内核通过一个叫 ipq 的结构体来管理同一组 IP 分片,每个分片本身由 sk_buff 结构体存储。收到分片时,内核会根据源 IP、目的 IP、协议和 16 位标识找到或创建对应的 ipq,再按片偏移将分片插入链表中。收到最后一个分片后,内核会检查所有分片是否连续,若收齐则拼接成完整报文向上交付;若超时未收齐,则通过定时器自动清理 ipq 和所有分片,释放内存。

所以内核并不是简单地把分片堆起来等组装,而是靠专门的结构体来分组、排序、跟踪进度,再配合超时机制来清理无效分片,这就是 IP 分片在内核里的完整管理方式。

三、总结

本文深入解析了网络路由与IP分片机制。路由是通过路由表实现的数据包转发过程,分为内网路由和公网路由,核心都是基于路由表进行Hop-by-Hop转发。IP分片机制则用于处理超过MTU限制的大数据包,通过16位标识、3位标志和13位片偏移字段实现分片重组。文章详细阐述了分片组装过程、TCP的MSS机制避免分片的方法,以及内核如何管理IP分片。重点指出分片会降低传输效率并增加丢包风险,建议通过合理设置MSS值来避免分片。

谢谢大家的观看!

相关推荐
Chengbei116 小时前
对标PentestGPT!新一代去中心化集群式AI全自动渗透测试工具
网络·人工智能·网络安全·去中心化·区块链·系统安全
猪脚踏浪6 小时前
docker 删除镜像
linux
zetion_36 小时前
uptime kuma 飞书告警
linux·飞书
用户805533698036 小时前
嵌入式Linux驱动开发——Pinctrl 子系统架构深度解析
linux·嵌入式
人生苦短1286 小时前
CentOS 7.9 部署 PostgreSQL 15.17 + PostGIS 3.4.8 操作文档
linux·postgresql·centos
一个心烑6 小时前
【layui页面编辑下拉框处理的三种方式】
linux·python·layui
z200509306 小时前
【linux学习】linux工具篇(下)
linux·学习
vortex56 小时前
virsh 使用指南:KVM 虚拟化管理的命令行艺术
linux·运维·服务器
行走的大喇叭6 小时前
Linux kernel目录、配置文件介绍
linux·单片机·嵌入式硬件