草莓熊Lotso个人主页❄️个人专栏:《C知识分享》 《Linux 入门到实践零基础也能懂》✨生活是默默的坚持毅力是永久的享受 博主简介文章目录前言一. 尝试理解公网1.1 NAT 技术缓解 IP 地址枯竭的关键1.2 公网的层级结构从国际到家庭1.3 报文的跨网传输流程二. 路由网络层的核心功能2.1 路由的基本概念2.2 路由表的结构与转发逻辑2.3 缺省路由2.4 路由表生成算法三. IP 报文的分片与组装3.1 MTU 与分片的由来3.2 为什么 IP 分片不应该是主流3.3 MSSTCP 避免 IP 分片的手段3.4 IP 首部中的分片相关字段3.5 分片的具体过程3.6 组装的具体过程3.7 分片内存对齐协议设计的天才之处3.7.1 协议解耦IP 与底层链路无关3.7.2 8 字节对齐用 13 位表达 16 位偏移3.7.3 最后一个分片的特殊处理四. Linux 内核 IP 分片与组装源码解读4.1 IP 分片函数ip_fragment ()4.2 IP 组装函数ip_defrag ()结尾前言当你在浏览器输入www.baidu.com并按下回车时数据是如何从你的电脑穿越层层网络到达百度服务器的为什么不同局域网内的主机可以使用相同的192.168.1.x地址却不会冲突为什么大文件传输时不会因为数据包过大而被丢弃这些问题的答案都藏在 TCP/IP 协议栈的网络层中。网络层的核心协议 ——IP 协议负责为每一个数据包找到从源主机到目的主机的最佳路径解决 “去哪里和怎么走” 的问题。本文将从公网的本质讲起深入解析路由转发的核心逻辑最后拆解 IP 分片与组装的底层实现带你彻底搞懂网络层的工作原理。一. 尝试理解公网1.1 NAT 技术缓解 IP 地址枯竭的关键我们都知道 IPv4 地址只有 32 位理论上最多只能提供约 43 亿个地址。但全球联网设备早已远超这个数字这其中NAT网络地址转换技术功不可没。路由器的双 IP 结构每个家用路由器都有两个 IP 地址WAN 口 IP运营商分配的公网 IP或上级运营商的私有 IPLAN 口 IP局域网网关 IP通常为192.168.1.1/24NAT 的工作原理子网内的主机与外网通信时路由器会将 IP 首部中的源私有 IP 替换为自己的 WAN 口 IP并记录端口映射关系。回复报文到达时再根据映射关系将目的 IP 替换回对应的私有 IP。私有 IP 地址段RFC 1918 规定10.0.0.0/8前 8 位为网络号共 1677 万个地址172.16.0.0/12前 12 位为网络号共 104 万个地址192.168.0.0/16前 16 位为网络号共 65536 个地址核心结论同一个子网内 IP 地址不能重复不同子网 IP 地址可以重复。NAT 技术让私有 IP 不会出现在公网中变相极大地缓解了 IP 地址不足的问题。1.2 公网的层级结构从国际到家庭真实的公网并不是一个扁平的网络而是一个多层级的树形结构我们可以简化为以下四层国际骨干网由各国的国际路由器组成通过海底光缆连接。每个国家向 ICANN 申请 IP 地址段例如中国拥有5.0.0.0/8这样的大段地址。国内骨干网国家将 IP 地址进一步划分给各个省份例如陕西分配到5.1.0.0/16河北分配到5.5.0.0/16。市级骨干网省份再将地址划分给各个城市例如西安分配到5.1.16.0/20。局域网城市运营商不再使用公网 IP而是为用户分配私有 IP构建家庭或企业局域网。1.3 报文的跨网传输流程以 “俄罗斯用户访问中国西安的一台服务器” 为例报文的传输过程如下俄罗斯用户的报文首先到达俄罗斯的国际路由器国际路由器通过BGP 协议查询路由表发现5.0.0.0/8属于中国将报文转发给中国的国际路由器中国的国际路由器查询路由表发现5.1.0.0/16属于陕西转发给陕西的省间路由器陕西的省间路由器查询路由表发现5.1.16.0/20属于西安转发给西安的市级路由器西安的市级路由器通过 NAT 转换将报文转发给对应的局域网主机关键逻辑报文转发的唯一依据是网络号。路由器将目的 IP 与路由表中的子网掩码进行按位与运算得到网络号后再转发到对应的下一跳地址。二. 路由网络层的核心功能2.1 路由的基本概念路由在复杂的网络结构中找出一条通往终点的路线。一跳Hop数据链路层中的一个传输区间在以太网中就是从源 MAC 地址到目的 MAC 地址的帧传输。点对点通信IP 协议实现了从源主机到目的主机的端到端通信而数据链路层只负责一跳内的通信。2.2 路由表的结构与转发逻辑每个主机和路由器都维护一张路由表我们可以在 Linux 系统中使用route命令查看whbiv-ye4ege8iyo5i3z3clix9:~$ route-nKernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface0.0.0.0192.168.0.10.0.0.0 UG10000eth0192.168.0.00.0.0.0255.255.0.0 U10000eth0192.168.0.10.0.0.0255.255.255.255 UH10000eth0路由表关键字段解释字段含义Destination目的网络地址Gateway下一跳地址0.0.0.0 表示直连网络Genmask子网掩码Flags标志位U 有效G 下一跳是路由器H 目的是主机Iface发送接口转发逻辑最长前缀匹配将目的 IP 与路由表中每一条的子网掩码进行按位与运算得到网络号后与对应的 Destination 字段比较选择前缀最长子网掩码中 1 最多的匹配条目按照该条目的下一跳地址和发送接口转发报文转发示例目的 IP 为192.168.56.3与255.255.255.0按位与得到192.168.56.0匹配对应条目从 eth1 接口直接发送到目的主机目的 IP 为202.10.1.2与所有条目都不匹配使用缺省路由0.0.0.0转发给网关192.168.10.12.3 缺省路由当目的 IP 与路由表中所有条目都不匹配时报文会被转发到缺省路由指定的下一跳地址。缺省路由就像我们问路时遇到的 “扫地大妈”她不知道具体地址但会告诉你去问更专业的人。在路由表中缺省路由的 Destination 为0.0.0.0Genmask 为0.0.0.0Flags 为UG。2.4 路由表生成算法路由表的生成有两种方式静态路由由网络管理员手动配置适用于小型网络动态路由通过路由协议自动生成适用于大型网络距离向量算法RIP链路状态算法OSPF边界网关协议BGP用于互联网骨干网的路由协议三. IP 报文的分片与组装3.1 MTU 与分片的由来以太网帧的数据部分有一个最大长度限制称为MTU最大传输单元标准值为1500 字节。如果 IP 数据报的大小超过了 MTU就需要进行分片将一个大的数据报分割成多个小的分片每个分片独立传输。重要原则IP 分片对传输层透明。传输层TCP/UDP不需要关心数据是否被分片分片和组装的工作完全由 IP 层负责。3.2 为什么 IP 分片不应该是主流虽然 IP 协议支持分片但分片会带来严重的性能问题丢包率大幅上升如果一个分片丢失整个 IP 数据报都会被丢弃需要重传所有分片。例如将一个报文分成 4 个分片每个分片的丢包率为 1%则整个报文的丢包率为1 - 0.99^4 ≈ 3.94%是原来的 4 倍。增加路由器负担分片需要路由器进行额外的计算和处理。3.3 MSSTCP 避免 IP 分片的手段为了避免 IP 分片TCP 引入了MSS最大段大小的概念。MSS 是 TCP 报文段中数据部分的最大长度计算公式为MSS MTU - IP首部长度 - TCP首部长度在标准以太网中MTU1500IP 首部 20 字节TCP 首部 20 字节因此MSS1460 字节。TCP 会将应用层数据分割成不超过 MSS 大小的段这样 IP 层就不需要再进行分片了。这也解释了为什么 TCP 的滑动窗口是一段段发送的而不是一次性发送所有数据 —— 核心目的就是为了避免 IP 分片降低丢包率。3.4 IP 首部中的分片相关字段IP 首部中有三个字段专门用于分片和组装字段长度作用16 位标识ID16 位唯一标识一个 IP 数据报。同一个数据报的所有分片具有相同的 ID。3 位标志Flags3 位第 1 位保留第 2 位 DF1 表示禁止分片第 3 位 MF1 表示还有更多分片MF0 表示最后一个分片。13 位片偏移Fragment Offset13 位表示当前分片的数据在原始数据报数据区中的偏移量以 8 字节为单位。3.5 分片的具体过程示例假设 IP 层有一个大小为 3000 字节的报文包含 20 字节 IP 首部和 2980 字节有效载荷MTU1500 字节如何分片计算分片数量每个分片的最大有效载荷 MTU - IP 首部 1500 - 20 1480 字节2980 字节有效载荷需要分成 3 片1480 1480 20 2980 字节每个分片的字段设置分片 1总长度20 1480 1500 字节标识111与原始报文相同标志位MF1还有更多分片片偏移00×80 字节分片 2总长度20 1480 1500 字节标识111标志位MF1片偏移185185×81480 字节分片 3总长度20 20 40 字节标识111标志位MF0最后一个分片片偏移370370×82960 字节3.6 组装的具体过程目的主机的 IP 层接收到分片后按照以下步骤进行组装识别分片如果MF1或者MF0且片偏移0则该报文是一个分片。聚合分片根据标识字段将属于同一个数据报的所有分片聚合在一起。检查完整性必须收到片偏移为 0 的分片第一片必须收到 MF0 的分片最后一片所有分片的片偏移必须连续前一个分片的片偏移 数据长度 / 8 下一个分片的片偏移组装数据报按照片偏移的顺序将所有分片的数据部分拼接起来恢复成原始的 IP 数据报。传递给上层将组装好的 IP 数据报传递给传输层处理。3.7 分片内存对齐协议设计的天才之处很多人会有一个疑问IP 首部的16 位总长度字段决定了 IP 报文最大可以达到2^1665535字节但片偏移只有 13 位怎么可能完整表达最大 65535 字节报文的所有偏移量这看似是协议的漏洞实则是设计者精心设计的内存对齐机制用最少的比特位实现了最大的表达能力。3.7.1 协议解耦IP 与底层链路无关首先要明确一个核心设计原则IP 协议要与底层数据链路层解耦IP 协议本身规定最大报文长度为 65535 字节这是由 16 位总长度字段决定的与底层链路无关以太网的 MTU1500 字节只是当前最常用的链路限制如果未来换成无线 LAN、光纤等其他链路MTU 可能会发生变化IP 协议不应该因为底层链路的变化而修改自身的核心设计这就是 “各管各的” 解耦思想类比高速路限速 120 码但汽车的设计最高时速可以达到 200 码。你可以根据限速调整车速但不能要求所有汽车都只能造到 120 码的最高时速。3.7.2 8 字节对齐用 13 位表达 16 位偏移为了解决 13 位片偏移无法覆盖 16 位总长度的问题协议设计者引入了8 字节对齐规则除最后一个分片外所有分片的有效载荷长度必须是 8 的整数倍。这个规则的本质是所有非最后分片的片偏移值乘以 8 后低 3 位一定都是 0。既然低 3 位永远是 0那就不需要在片偏移字段中存储它们只需要存储高 13 位即可。存储时将实际偏移量除以 8右移 3 位只存高 13 位读取时将片偏移值乘以 8左移 3 位自动补上低 3 位的 0这样一来13 位片偏移最大可以表示2^13 * 8 65536字节的偏移量刚好覆盖 IP 报文的最大长度 65535 字节。用 13 位就实现了原本需要 16 位才能完成的功能这种极致的优化堪称协议设计的典范。3.7.3 最后一个分片的特殊处理为什么最后一个分片不需要遵守 8 字节对齐规则最后一个分片后面没有其他分片不需要通过片偏移来定位下一个分片的起始位置即使最后一个分片的长度不是 8 的整数倍也不会影响整个报文的组装例如我们之前的 3000 字节示例最后一个分片只有 20 字节有效载荷不是 8 的整数倍但完全不影响组装补充结合之前的 3 位标志位我们只用了3位标志13位片偏移共 16 个比特位就同时实现了 “区分是否分片”、“标识分片位置”、“判断是否是最后一个分片” 三个核心功能这也是为什么说 IP 分片的字段设计是天才之作。四. Linux 内核 IP 分片与组装源码解读Linux 内核中 IP 分片和组装的核心函数位于net/ipv4/ip_output.c和net/ipv4/ip_input.c中。4.1 IP 分片函数ip_fragment ()intip_fragment(structsock*sk,structsk_buff*skb,int(*output)(structsock*,structsk_buff*)){structiphdr*iphip_hdr(skb);intmtudst_mtu(skb_dst(skb));inthleniph-ihl*4;intlenskb-len-hlen;// 有效载荷长度intptr0;structsk_buff*skb2;// 检查是否禁止分片if(iph-frag_offhtons(IP_DF)){icmp_send(skb,ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED,htonl(mtu));kfree_skb(skb);return-EMSGSIZE;}// 循环分片while(ptrlen){intfrag_sizemin(mtu-hlen,len-ptr);// 除了最后一个分片其他分片的长度必须是8的整数倍if(frag_sizelen-ptr)frag_size~7;// 分配新的skbskb2alloc_skb(frag_sizehlenLL_MAX_HEADER,GFP_ATOMIC);if(!skb2){kfree_skb(skb);return-ENOMEM;}// 复制IP首部skb_reserve(skb2,LL_MAX_HEADER);skb_put(skb2,hlenfrag_size);skb_copy_to_linear_data(skb2,iph,hlen);// 复制数据部分skb_copy_bits(skb,hlenptr,skb2-datahlen,frag_size);// 设置分片字段iph2ip_hdr(skb2);iph2-frag_offhtons((ptr/8)|(ptrfrag_sizelen?IP_MF:0));iph2-tot_lenhtons(skb2-len);ip_send_check(iph2);// 发送分片output(sk,skb2);ptrfrag_size;}kfree_skb(skb);return0;}核心逻辑解读首先检查 DF 标志位如果禁止分片则发送 ICMP需要分片但 DF 置位 错误计算每个分片的大小确保除最后一个分片外其他分片的长度是 8 的整数倍为每个分片分配新的 skb复制 IP 首部和对应的数据部分设置片偏移和 MF 标志位重新计算 IP 首部校验和调用输出函数发送每个分片4.2 IP 组装函数ip_defrag ()structsk_buff*ip_defrag(structnet*net,structsk_buff*skb,u32 user){structiphdr*iphip_hdr(skb);structipq*qp;interr;// 查找或创建对应的IP队列qpip_find(net,iph-id,iph-saddr,iph-daddr,iph-protocol,user);if(!qp){kfree_skb(skb);returnERR_PTR(-ENOMEM);}// 将分片加入队列errip_frag_queue(qp,skb);if(err){ipq_put(qp);returnERR_PTR(err);}// 检查是否所有分片都已到达if(ip_frag_complete(qp)){structsk_buff*retip_frag_reasm(qp);ipq_put(qp);returnret;}ipq_put(qp);returnERR_PTR(-EINPROGRESS);}核心逻辑解读根据 IP 标识、源 IP、目的 IP 和协议查找对应的分片队列如果队列不存在则创建一个新的队列将当前分片加入队列并按照片偏移排序检查是否所有分片都已到达且完整如果完整则调用ip_frag_reasm()组装成完整的 IP 数据报并返回核心考点总结公网与 NAT私有 IP 地址段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16NAT 技术通过地址转换实现多个主机共享一个公网 IP路由转发路由转发的核心逻辑最长前缀匹配路由表关键字段目的网络、子网掩码、下一跳、发送接口缺省路由用于处理所有不匹配的目的地址IP 分片与组装MTU1500 字节MSS1460 字节标准以太网分片相关字段16 位标识、3 位标志DF/MF、13 位片偏移以 8 字节为单位分片会大幅增加丢包率TCP 通过 MSS 机制避免 IP 分片接收方根据标识字段聚合分片根据片偏移排序并检查完整性网络层是整个 TCP/IP 协议栈的 “导航系统”它为每一个数据包指明了前进的方向。理解了网络层的工作原理你就掌握了互联网通信的核心逻辑。结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语IP 协议虽然已经诞生了 40 多年但它依然是互联网的基石。从最初的 IPv4 到现在的 IPv6虽然地址长度从 32 位扩展到了 128 位但路由和分片的核心思想并没有改变。希望本文能够帮助你彻底搞懂网络层 IP 协议的工作原理。如果你觉得本文对你有帮助欢迎点赞、收藏、关注我会持续分享更多 Linux 底层和网络编程的技术干货。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど