🔥 星光编译者 · 个人主页
📚 学习专栏: 《C/C++ 成长笔记》 · 《Linux 实践手册》 · 《数据结构与算法》
🌄 向云端飞扬,编译属于自己的代码星河。
☕ 写在开篇
你好,这里是 星光编译者。
这里记录我在 C/C++、Linux、数据结构与算法 学习中遇到的真实问题、亲手验证过的代码,以及那些容易被忽略的实现细节。
比起简单罗列结论,我更愿意从问题出发,把一个知识点的来由讲清楚,把“为什么会这样”和“应该怎样解决”说明白,让每一次踩坑都沉淀成可以复用的经验。
如果这篇记录能帮你少绕一点路,或让某个模糊的地方忽然变得清晰,那么这次分享便有了意义。愿我们在一次次阅读、编译与调试中稳步向前,慢慢搭起属于自己的技术世界。
🔥 本文定位:已经写过 TCP、UDP 收发程序之后,继续向下追问:目的 IP 明明不在本机所在网络,数据为什么仍然能到达?
💡 学习目标:看懂 IPv4 首部;算清 CIDR、子网掩码与地址范围;理解私有地址、NAT 和默认网关;能根据路由表判断出口与下一跳。
📌 阅读范围:以普通 IPv4 单播与 Linux 主机为主。基础路由示例假设没有策略路由、隧道、NAT 或过滤干预;涉及这些机制时单独补充条件。配套计算实验可离线运行。
在 Socket 程序里,客户端只需要把服务器地址交给 connect 或 sendto。至于数据先经过家里的路由器,还是先经过机房网关,应用通常没有直接安排。
这份“看起来很省事”的背后,至少藏着五个问题:
- IP 地址只写了终点,沿途设备怎样知道下一步往哪里走?
- 192.168.1.10 和 192.168.1.200 一定在同一个子网吗?
- 发往远端服务器的数据,为什么先交给了网关的 MAC 地址?
- 两个家庭都使用 192.168.1.0/24,为什么没有立刻冲突?
- 路由器转发报文时,哪些字段保持不变,哪些字段必须更新?
把这些问题串起来,网络层就不再是一张需要死记硬背的首部图。它会变成一条可以计算、可以查表、可以逐跳追踪的转发路径。
文章目录
一、网络层解决什么问题:终点与下一跳
1.1 能在一条链路上收发,还不等于能跨网络通信
先从一个小场景出发:主机 A 要把数据发给主机 B。如果双方就在同一个可直接通信的以太网链路上,发送方可以把帧交给对方网卡。
但实际通信经常跨越不同的网络。办公室、家庭、云服务器所在机房,都可能有不同的地址前缀和接入设备。远端服务器的网卡不在当前链路上,发送方无法直接把一个以太网帧一路送过去。
这时需要网络层:使用 IP 地址标识源与目的,再借助路由把数据报逐跳送向目的网络。 每一跳具体怎样传输,由该段链路使用的技术承担。

可以把三个层次的问题分开:
| 传输层 | 数据应当交给哪个通信端点? | TCP / UDP、端口、连接状态 |
| 网络层 | 数据报发往哪个 IP,接下来走哪个出口? | 源 / 目的 IP、前缀、路由表 |
| 数据链路层 | 在当前链路上,下一段怎样传递? | 以太网 MAC、帧等 |
这里的“IP 找主机”只是便于入门的说法。更准确地讲,地址通常配置在网络接口上:同一设备可以有多个接口,一个接口也可以有多个 IP 地址。不能简单认为“设备、网卡、IP 地址”永远一一对应。
1.2 主机也要查路由,路由器也可以接收自己的数据
主机发送报文之前,也需要选择出口与下一跳。因此“只有路由器才需要路由表”是一个常见误解。
主机通常主要发送和接收自己的业务数据;路由器的典型职责则是在网络之间转发其他节点的数据。不过,这不是外形上的绝对分类:Linux 主机启用适当的转发能力并配置路由后,也能承担路由器的角色;路由器自身也可能运行管理服务,接收发给自己的报文。
学习时可以把主机与路由器统称为网络节点,但要分清两种处理方向:
报文是发给本节点的 → 交给本地协议栈继续处理
报文需要经过本节点 → 在允许转发的条件下,选择下一跳
1.3 IP 提供尽力而为的交付
网络层会努力将数据报送向目的地址,但普通 IP 不承诺必达、顺序不变、没有重复,也不负责像 TCP 那样通过确认和重传实现可靠字节流。
一条可用路由只是“知道下一步往哪里走”,并不证明下一跳正常、沿途没有丢包,或应用一定接收成功。后面排查网络时,这个边界很有用:查到路由与业务可达,是不同层面的证据。
二、拆开 IPv4 首部:长度、协议、TTL 与校验和
2.1 先按功能看,再逐个记字段
IPv4 首部最小 20 字节,存在选项时可以更长,最大 60 字节。下图固定部分每一行表示 32 位,横向宽度按字段位数绘制;选项区域的高度仅作示意,实际长度可以是 0~40 字节。

| Version | 4 | IPv4 中为 4 |
| IHL | 4 | 首部长度,单位是 4 字节 |
| DSCP / ECN | 6 + 2 | 区分服务标记与显式拥塞通知相关信息 |
| Total Length | 16 | 当前 IPv4 数据报总长度,含首部 |
| Identification | 16 | 与源、目的、协议等信息一起参与分片重组识别 |
| Flags | 3 | 保留位、DF、MF |
| Fragment Offset | 13 | 分片数据在原始 IP 载荷中的位置,单位为 8 字节 |
| TTL | 8 | 限制报文的转发生命 |
| Protocol | 8 | 指明上层协议,例如 TCP、UDP、ICMP |
| Header Checksum | 16 | 只校验 IPv4 首部 |
| Source / Destination Address | 各 32 | 源地址与目的地址 |
| Options / Padding | 可变 | 选项与补齐,最多共 40 字节 |
不用一次背完全部细节。可以先把它们分为“版本与长度”“交付与分用”“分片”“生存与差错检测”四组,随后沿着报文处理过程理解。
2.2 IHL 与 Total Length,单位不一样
IHL 保存的不是字节数,而是多少个 32 位字:
IPv4 首部字节数 = IHL × 4
IHL = 5 → 20 字节,无选项的常见情况
IHL = 15 → 60 字节,最大首部
IP 载荷字节数 = Total Length – IHL × 4
假设一份不带选项的 IPv4 报文,里面承载 UDP 首部 8 字节和应用数据 100 字节:
IPv4 首部 = 20
IP 载荷 = 8 + 100 = 108
Total Length = 20 + 108 = 128 字节
以太网首部、FCS 等链路开销不包含在这个 128 字节里。抓包显示的帧长度、IP 总长度、UDP 长度、应用正文长度,可以同时都正确,却各自表示不同范围。
Total Length 的 16 位限制意味着 IPv4 数据报总长最多为 65535 字节。这个数是格式上限,不是路径上可以无条件一次传递的推荐大小。
2.3 Protocol 用于分用,不是端口号
目的节点接收完整 IP 数据报后,需要知道应交给哪一种上层协议。常见值如下:
| 1 | ICMP |
| 6 | TCP |
| 17 | UDP |
例如,Protocol 为 17 时交给 UDP 处理,UDP 再根据端口等信息找到 socket。协议号与端口是两套字段。 不能把 TCP 的协议号 6 理解为“TCP 使用 6 号端口”。
使用普通 TCP、UDP socket 时,IPv4 首部通常由内核生成。应用提供目的地址、正文及必要选项即可,不必把这 20 字节手工拼到应用数据前面。
2.4 TTL 为什么每经过路由器就减少
如果路由配置出了环,报文可能从路由器 A 转到 B,再被 B 转回 A。没有生存限制,它就可能不断消耗链路资源。
TTL 用来限制这种情况。现代转发中,路由器每转发一次至少减 1;如果不能在递减后继续转发,就丢弃报文,并在规则允许时返回 ICMP Time Exceeded。端主机接收自己的报文,不是一次“继续路由转发”。
TTL 初始值取决于系统与配置,不能统一背成 64。抓包里看到 61,也不能在不知道初始值、路径变化等条件时直接断言“中间恰好有三个设备”。普通二层交换机转发帧不属于一次 IP 路由跳转。
traceroute 的基本思路正与 TTL 有关:让探测报文以不同 TTL 逐步深入,再观察中途节点的响应。但节点可能不回应或限制回应,星号不能直接当作该处断网的证明。IPv4 路由器的 TTL 处理要求
2.5 首部校验和不是 CRC
IPv4 首部使用的是 16 位反码和校验,不是 CRC。基本步骤是:计算时将校验和字段置零,把首部按 16 位分组相加,将高位进位回卷到低 16 位,再对结果取反。
接收端把包含校验和字段的首部一起求和,正常结果应为全 1。它用于发现部分传输差错,不是加密认证,也不保证发现任何一种可能的篡改。
由于 TTL 位于首部内,路由器转发时修改 TTL 后,也必须相应更新首部校验和。更新不一定意味着每次都从头遍历整个首部,具体实现可以采用增量计算。
更要注意范围:这个校验和只覆盖 IPv4 首部,不覆盖 TCP / UDP 正文。以太网常见的 CRC 校验与这里是不同机制。IPv4 首部与校验和定义
2.6 旧首部图里的 TOS,今天怎样理解
早期首部图通常把第二个字节标为 TOS,并介绍优先级、低时延、吞吐量等位。学习历史格式可以帮助阅读旧资料,但分析现代报文时,通常应按 6 位 DSCP + 2 位 ECN 理解。
DSCP 可用于区分服务处理,ECN 参与拥塞通知。给报文设置某个标记,不会自动保证整条互联网路径都按发送方愿望提供低延迟服务;沿途设备是否采用、保留或重新标记,取决于网络策略。DS 字段定义、ECN 字段定义
三、分片如何定位:ID、DF、MF 与偏移量
3.1 MTU 约束的是当前链路能承载多大的 IP 报文
以常见以太网接口 MTU 1500 为例,它通常表示链路层可以承载 1500 字节的 IP 报文,不包括以太网自身的首部与尾部。不同链路、隧道和配置可能有不同 MTU。
如果一份 IPv4 数据报超过待发送链路的 MTU,且允许分片,发送节点可能将其拆成多个 IPv4 分片。这里包括源主机,也包括途中符合条件的 IPv4 路由器。
IP 分片发生在 IP 层,不能说成“把 IP 报文放到链路层以后,由以太网自动切开”。 每个分片都会拥有自己的 IP 首部,然后分别被链路层承载。
3.2 三组字段怎样配合
- Identification:同一原始数据报的分片使用相同标识。重组还要结合源 IP、目的 IP 和 Protocol 等条件,ID 不是全互联网永久唯一的包编号。
- DF,Don’t Fragment:为 1 表示不允许进行 IPv4 分片。需要分片才能转发的路由器会丢弃该报文,并按规则报告相应 ICMP 错误;本机发送也可能收到“消息过长”等错误。
- MF,More Fragments:为 1 表示后面还有分片,为 0 表示本片是最后一片,或者原报文根本没有分片。
- Fragment Offset:指向原始 IP 载荷 的位置,单位为 8 字节,不把原始 IP 首部计入偏移。
所以只看到 MF=0,不能直接判断“没有分片”。必须结合偏移:offset>0, MF=0 恰好是末片常见的组合。
3.3 把 4000 字节载荷切到 MTU 1500 的链路
假设原始 IP 载荷有 4000 字节,不带 IP 选项,所有分片首部都为 20 字节,DF 为 0,初始偏移为 0。
每个非末片最大载荷 = floor((1500 – 20) / 8) × 8
= 1480 字节

| 第 1 片 | 0~1479 | 1480 | 1500 | 0 | 1 |
| 第 2 片 | 1480~2959 | 1480 | 1500 | 185 | 1 |
| 第 3 片 | 2960~3999 | 1040 | 1060 | 370 | 0 |
第二片字段值为 1480 / 8 = 185,不是 1480。接收方用 Offset × 8 得到实际字节位置。
“非末片长度必须是 8 的倍数”中的长度,准确指 非末片的数据部分长度,不是包含 IP 首部后的总长度。这里非末片载荷 1480 能被 8 整除,总长度 1500 却不能;这并不矛盾。
如果 IP 载荷承载的是 UDP,原始 UDP 首部位于原始 IP 载荷的开头;普通后续分片不会再各自复制一份 UDP 首部。不能把每片都当作一份独立 UDP 消息。
3.4 末片到达,不代表已经收齐
正常 IPv4 转发中,中间路由器不会每经过一跳都先把所有分片拼好再重新转发;重组通常在目的主机进行。目的主机需要收齐相应范围的数据,再向上层交付。
末片可能最先到,某个中间片也可能一直丢失。因此 MF=0 只是帮助确定结尾,不能证明重组成功。重组超时或资源限制都可能让整份数据报被丢弃。
工程上应尽量控制报文大小,结合路径 MTU 等机制减少分片依赖。分片丢失后的恢复并不是由 IPv4 自动重传完成的;TCP、UDP 应用或其他上层协议各自承担不同职责。IPv6 的分片规则也不同,路由器不执行 IPv6 分片,不能套用这一节的途中分片过程。IPv6 分片规则
四、子网划分:从按位与走到 CIDR
4.1 一个地址要配合前缀,才能知道网络边界
192.168.1.10 只是一个 32 位地址。它属于 /24、/25 还是 /26,会直接影响同子网判断。
CIDR 写法中的 /n,表示前 n 位是网络前缀,其余位用于子网内地址区分。常规掩码对应连续的 1,后接连续的 0:
/24 → 11111111.11111111.11111111.00000000
→ 255.255.255.0
/28 → 11111111.11111111.11111111.11110000
→ 255.255.255.240
IP 地址与掩码按位与,便得到网络地址:
网络地址 = IP 地址 & 子网掩码
掩码既不是默认网关,也不是“可连接设备的最大编号”。它首先定义的是哪一部分地址属于网络前缀。
4.2 用 192.168.20.68/28 手算一次
前三个字节在这个例子里不变,重点看最后一个字节:
68 = 01000100
240 = 11110000
——– 按位与
64 = 01000000
网络地址:192.168.20.64/28
/28 留下 4 位主机部分,所以一共有 2^4 = 16 个地址。本段范围是 .64~.79:
| 子网掩码 | 255.255.255.240 |
| 网络地址 | 192.168.20.64 |
| 定向广播地址 | 192.168.20.79 |
| 常规可分配主机地址 | 192.168.20.65~192.168.20.78 |
| 常规主机地址数 | 14 |

也可以用“步长”辅助检查:最后一个掩码字节为 240,块大小就是 256-240=16。边界依次为 0、16、32、48、64、80……68 落在 64~79 这一块里。
步长法是方便计算,不应替代位运算的理解。当边界跨越其他字节时,仍然要找到真正发生划分的那一组位。
4.3 “前面三段相同”为什么还不够
看下面两个地址:
A:192.168.1.10/25 → 网络地址 192.168.1.0
B:192.168.1.200/25 → 网络地址 192.168.1.128
它们前面三个十进制数完全一样,但 /25 已经使用了最后一个字节中的最高位,所以并不属于同一个 /25 子网。
反过来,前面三段不同也可能属于同一前缀,例如 192.168.0.10/23 与 192.168.1.20/23 都属于 192.168.0.0/23。
判断网络边界,要看地址与前缀一起计算后的结果。 真正能否直接通信,还需要二层连接、路由及访问策略等条件成立。仅把两台隔离设备配置成同一段地址,并不会让它们自动连上。
双方掩码配置不一致时,也可能出现“一方认为对端在本地,另一方认为应走网关”的不对称行为。这时不要只反复检查 IP 字符串,要同时核对前缀长度与实际路由。
4.4 地址数为什么常常要减 2
对于普通广播子网,主机部分全 0 表示网络地址,全 1 表示该子网的定向广播地址,因而常用公式是:
子网总地址数 = 2^(32 – 前缀长度)
常规可用主机数 = 2^(32 – 前缀长度) – 2
但这个公式不能无条件用于所有前缀:/31 可以用于点到点链路,两端地址按相应规则使用;/32 表示单个地址,常用于主机路由等场景,不是“负一个可用主机”。IPv4 点到点 /31 的使用规则
4.5 A、B、C 类地址,放在历史位置理解
早期分类寻址用地址高位区分 A、B、C 类,并采用固定网络边界。它帮助早期系统组织地址,但分配粒度过粗,容易浪费。
| A 类 | 0~127 | 传统默认 /8,其中有特殊用途地址 |
| B 类 | 128~191 | 传统默认 /16 |
| C 类 | 192~223 | 传统默认 /24 |
| D 类 | 224~239 | 多播,不套普通主机划分 |
| E 类 | 240~255 | 历史保留范围,其中另有特殊用途地址 |
这些数值是历史位模式范围,不是可分配公网主机清单。现代 CIDR 根据明确的前缀长度划分、分配与聚合,不能看到 192 开头就自动认定掩码为 /24。
CIDR 还允许合适的连续前缀聚合,减少路由项。但聚合需要地址连续、边界对齐,并且转发可达性允许,不能任意把两条看起来相近的路由合并。CIDR 与路由聚合说明
五、特殊地址、私有地址与公网地址
5.1 先把几类常见特殊地址分清
| 普通广播子网的网络地址 | 主机部分全 0 | 通常不作为该子网普通主机地址 |
| 普通广播子网的定向广播地址 | 主机部分全 1 | 路由器不一定允许跨网络转发定向广播 |
| 255.255.255.255 | 受限广播 | 不是一个普通远端服务器地址 |
| 127.0.0.0/8 | 环回 | 常见 127.0.0.1,用于本机通信 |
| 0.0.0.0 | 未指定地址等语境 | socket 中可表示通配绑定,不是可连接的远端服务地址 |
| 169.254.0.0/16 | IPv4 链路本地 | 不属于 RFC 1918 三段私有地址 |
| 100.64.0.0/10 | 共享地址空间 | 常见于运营商级 NAT 场景,也不是 RFC 1918 私有段 |
| 192.0.2.0/24、198.51.100.0/24、203.0.113.0/24 | 文档示例地址 | 用于讲解,不能当作真实可用公网目标 |
这里尤其容易混淆 0.0.0.0 与 0.0.0.0/0:前者是一个具体地址值;后者是匹配所有 IPv4 目的地址的前缀,常用于默认路由。相同的数字,在 socket 地址与路由前缀里语义不同。
环回通信发生在本机协议栈内,不需要数据经过物理网卡发到交换机。因此本机能访问 127.0.0.1,并不能证明物理网线、网关或公网访问正常。
5.2 私有 IPv4 地址只有这三段

| 10.0.0.0/8 | 10.0.0.0~10.255.255.255 | 16,777,216 |
| 172.16.0.0/12 | 172.16.0.0~172.31.255.255 | 1,048,576 |
| 192.168.0.0/16 | 192.168.0.0~192.168.255.255 | 65,536 |
表中的数量是地址总数,不是在任意实际子网中都可分配给主机的数量。172.16.0.0/12 也可以继续划分成许多更小的子网。
最值得单独记住的是:不是所有 172.* 都是私有地址。 172.15.1.1 和 172.32.1.1 都不属于上面的 /12 范围。RFC 1918 私有地址范围
5.3 两家都使用 192.168.1.10,为什么不冲突
当两个私有网络处于相互独立的地址和路由环境中时,可以重复使用相同的私有地址。它们并没有把 192.168.1.10 当作全互联网唯一的定位标识。
但一旦通过 VPN、专线或网络合并,开始需要在统一环境中互相访问,重叠前缀就会产生歧义:同一个目的地址到底属于哪一边?这时往往要重新规划地址,或通过隔离与转换机制处理。
所以“不同子网 IP 可以随便重复”也不严谨。是否允许重复,取决于它们是否处于需要统一寻址的路由范围里。
5.4 非私有地址,不等于可用公网地址
环回、多播、文档、链路本地、共享地址等,都已经说明:从三段私有范围中排除一个地址,不能自动证明它是全球可达的普通单播地址。
地址是不是可分配、是否可全球转发,还要看其用途登记、实际分配、路由通告与网络策略。不要在真实局域网里随意拿一个“看起来没人用”的公网段来配置;访问真实拥有者的网络时可能发生冲突。IANA IPv4 特殊用途地址登记
六、地址为什么不够用:DHCP、NAT 与 IPv6
6.1 32 位地址空间,不等于能同时容纳同等数量的独立公网设备
IPv4 有 2^32 = 4,294,967,296 个地址组合,但其中包含各种特殊用途范围;同一设备还可能使用多个接口、多个地址。因此这个数既不是可分配公网地址的总数,也不是设备数量的简单上限。
CIDR 提高了分配与路由组织的灵活性,却不会把 32 位变成更多位。面对地址需求,动态配置、地址复用和更大的地址空间分别解决不同问题。
6.2 DHCP 解决的是配置与租期管理
DHCP 可以自动向客户端提供地址及租期,并提供掩码、默认网关、DNS 等配置选项。设备下次接入时可能拿到不同地址,也可能继续使用原来的租约;使用保留分配时还可以相对固定。
很多家用设备同时具备路由、交换、无线接入、NAT、DHCP 服务等能力,但这些是不同功能。不能因为盒子叫“路由器”,就认定所有路由设备都自动承担 DHCP 服务。
DHCP 也不必然给客户端分配公网地址。家庭环境里,它常常只是分配私有地址。它能提高地址管理效率,却不会凭空增加 IPv4 地址组合。
6.3 NAT 让私有网络通过转换参与外部通信
普通不做 NAT 的路由转发,通常不改源、目的 IP。NAT 则会按规则修改地址;常见的 NAPT 还会修改 TCP / UDP 端口,以区分共享外部地址的多个会话。
下面是一个说明用途的映射,外侧地址使用文档保留段:
内网请求:192.168.1.10:51000 → 198.51.100.20:443 / TCP
外侧报文:203.0.113.9:40001 → 198.51.100.20:443 / TCP
回包到达 203.0.113.9:40001 后,设备根据映射还原到内网端点。
这不是路由器“知道了所有应用”,而是地址转换设备维护了必要的状态和规则。它还需要处理受修改字段影响的校验信息。传统 NAT 与 NAPT 的基本模型
一个家庭路由器的 WAN 地址可能仍是私有地址或共享地址,外侧还有运营商 NAT。反过来,也有网络无需多级 NAT。不要把所有互联网访问都画成“每经过一台路由器就改一次 IP”。
私有地址本身不能作为普通公网路由的端点直接访问,但内网服务仍可通过端口映射、反向代理、VPN 或隧道等安排对外提供能力。直接部署到拥有公网可达入口的环境,只是常见方案之一;有一个公网地址也仍需考虑监听地址、路由和访问规则。
6.4 IPv6 扩展地址空间,也改变了协议格式
IPv6 地址长 128 位,基本首部格式、邻居发现和分片方式等都与 IPv4 不同。它不是把 IPv4 地址写长一点即可互通的同一种报文。
IPv4-only 与 IPv6-only 端点通常需要双栈、协议转换、代理等过渡安排才能交换相应业务。实际部署使用哪种方案,要看网络支持与应用需求;不能用“IPv6 尚未普及”这类没有时间和地域条件的句子替代判断。
这一节先建立三组对应关系即可:DHCP 管配置,NAT 做转换与复用,IPv6 提供更大的地址空间和不同的协议格式。 后续学习 NAT 专题时,再深入映射表、连接跟踪与穿透问题。
七、路由表如何选路:最长前缀匹配
7.1 一条路由,至少要看清目的前缀、出口和下一跳
假设一台 Linux 主机有两个接口:
eth0:192.168.10.2/24
eth1:192.168.56.2/24
用现代 ip 命令查看地址和主要路由表:
ip -4 -brief address
ip -4 route show table main
下面是教学拓扑的示意输出,不是要求读者机器必须出现的接口名和地址:
default via 192.168.10.1 dev eth0
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.2
192.168.56.0/24 dev eth1 proto kernel scope link src 192.168.56.2
| 192.168.56.0/24 | 这条路由匹配的目的网络前缀 |
| dev eth1 | 从哪个接口发出 |
| via 192.168.10.1 | 指定下一跳路由器地址 |
| scope link | 目的位于该链路可达范围 |
| src 192.168.56.2 | 该路由的首选源地址提示,不是目的地址 |
| default | 0.0.0.0/0 的简写 |
没有 via 的直连前缀路由,通常表示不必再把数据交给另一个 IP 路由器;在以太网场景中,需要解析的邻居通常就是目的主机。
7.2 为什么不能“从上往下碰到第一条就选”
假设还有一条更具体的路由:
192.168.56.128/25 via 192.168.10.254 dev eth0
目的 192.168.56.200 同时属于 /24 和 /25。在同一张表、同类可用候选的基础查找中,要优先使用更长的匹配前缀,也就是 /25。

| 192.168.10.8 | 192.168.10.0/24 | eth0,直接交付目的邻居 |
| 192.168.56.3 | 192.168.56.0/24 | eth1,直接交付目的邻居 |
| 192.168.56.200 | 192.168.56.128/25 | eth0,经 192.168.10.254 |
| 198.51.100.20 | 0.0.0.0/0 | eth0,经 192.168.10.1 |
匹配的位越多,描述的目的范围越具体。这就是最长前缀匹配的直观含义。路由表显示顺序不是选路算法。 即使默认路由显示在第一行,也不会自动压过更具体的匹配路由。
7.3 默认路由是范围最宽的候选
0.0.0.0/0 的掩码没有任何一个必须匹配的前缀位,因此可以匹配所有 IPv4 地址。当没有更具体的合适路由时,它就承担“其余目的往这里交”的角色。
但默认网关并不是全互联网地图。主机把数据交给它后,网关还需要根据自己的表继续决定下一跳。路径上每个节点只需做本次转发所需的决策,不要求发送方事先列出所有路由器。
路由表中也可能没有默认路由;若某个目的找不到可用路由,就不能凭空猜一个出口。某些 unreachable、blackhole 等类型的路由还会主动使匹配流量失败或丢弃,它们并不表示“有前缀就能送达”。
7.4 metric、策略路由与多路径放在哪里
同一前缀的可比路由可能使用 metric 等属性区分优先级,也可能形成多路径。不能把低 metric 理解成“可以压过任何更长前缀”。
Linux 还有路由策略数据库:规则可能先根据源地址、标记、入接口等条件决定查哪张表。此时只看 main 表可能解释不了实际路径。
ip -4 rule show
ip -4 route show table all
ip -4 route get 192.168.56.200
ip route get 查询内核对指定目的的选路结果,本身不会发送网络探测报文。若真实业务受源地址或其他策略影响,应提供相应条件再查询,例如在本机确实拥有该源地址时使用 from 参数。ip-route 手册:路由属性与 get
7.5 旧的 route -n 输出也要会读
旧环境可能仍使用 route -n。它的常见列可以这样对应:
| Destination + Genmask | 合起来描述目的前缀 |
| Gateway | 下一跳网关;具体显示方式取决于工具 |
| Iface | 出口接口 |
| U | 路由处于可用标记状态 |
| G | 经网关转发 |
看到 U 不能证明远端在线;看到 G 也不能说“这就是访问公网”,因为去另一个私有网段同样可能经过网关。新环境优先使用 ip,理解旧输出则有助于阅读既有排查记录。
八、走完一条路径:目的 IP 与下一跳 MAC
8.1 发给远端时,第一帧交给谁
继续使用双接口主机例子。现在它要发往 198.51.100.20,查表后选到:
default via 192.168.10.1 dev eth0
在普通以太网、没有 NAT 和隧道的这个例子中,发送方形成的第一段通信可以描述为:
IP:源 192.168.10.2,目的 198.51.100.20
以太网:源 主机 eth0 的 MAC,目的 网关 192.168.10.1 对应的 MAC
IP 目的地址仍指向远端。以太网目的 MAC 则用于把当前帧送到下一跳。把帧交给网关,不等于把 IP 目的地址改成网关。
如果邻居缓存中还没有网关的链路地址,IPv4 以太网通常通过 ARP 获取。这里查询的是本地下一跳网关,不是让 ARP 穿越整个互联网寻找远端服务器。

8.2 路由器收到后,为什么不能照搬原来的帧
路由器在入接口收到帧后,对其中的 IP 报文进行相应处理,选择出接口与下一跳,再按出链路的要求重新封装。
以两段以太网链路为例,原帧的目的 MAC 是当前路由器入接口;离开路由器后,新的源 MAC 变为出接口的 MAC,目的 MAC 变为下一跳的 MAC。它们服务于不同链路,当然不能把旧的一对 MAC 一路沿用。
与此同时,TTL 递减,IPv4 首部校验和更新。普通路由场景下,源与目的 IP 通常保持不变;如果路径上执行 NAT、隧道或某些其他处理,就要按相应机制继续分析。
8.3 直连目的与远端目的,真正差在哪里
| 经直连路由到同链路主机 | 最终目的主机 IP | 通常是目的主机的 MAC |
| 经网关去其他网络 | 最终目的主机 IP | 下一跳路由器的 MAC |
这张表适用于基础以太网示例;代理 ARP、特殊链路和隧道等会引入额外处理。排查真实网络时,最终以实际路由和邻居解析结果为准。
一个实用的提问顺序是:先问“IP 要去哪里”,再问“本次选了哪个出口和下一跳”,最后问“这个链路上怎样把它交出去”。这样就不会把目的 IP、默认网关和 MAC 混成一个概念。
九、互联网由谁连接:标准、地址与运营网络
9.1 互联网不是一台巨大的中心路由器
把家里的默认网关画到云朵里,只是省略了细节。实际互联网由许多独立运营的网络通过连接、路由交换和商业关系互联,设备与组织承担不同职责。

| IETF | 制定和维护互联网协议相关标准 | 规定各方怎样互操作,不替用户逐包选出口 |
| IEEE 等标准组织 | 制定以太网、无线局域网等技术标准 | 支持链路层和物理接入的互操作 |
| IANA、区域互联网注册机构 RIR | 按各自职责管理和分配号码资源 | 管资源登记与分配,不是报文必经站 |
| ISP、网络运营者 | 建设和运营接入、骨干及相关网络 | 在自己的网络内执行转发和策略 |
| IXP | 为网络之间互联提供交换基础设施 | 支持参与网络交换流量,不是所有流量必经点 |
| 云、机房与内容服务运营者 | 提供计算、存储、应用与内容入口 | 承载或接入业务服务 |
地址管理可以概括为 IANA 向 RIR 等按职责管理号码资源,再由相应体系继续分配或登记;实际层级与政策有细节差异。不要把这个管理关系画成用户报文依次经过的线路。IANA 号码资源职责
9.2 静态路由与动态路由,回答的是表从哪里来
前面讨论的是已有候选路由时,怎样选择下一跳。至于这些候选从哪里来,是另一个问题。
- 接口地址配置会形成相应的直连路由信息。
- 管理员可以配置静态路由和默认路由。
- 路由协议可以交换可达信息,结合算法和策略形成路由。
距离向量、链路状态与最短路径算法,是继续学习动态路由时的重要概念。例如,链路状态类协议可以根据网络拓扑运行最短路径计算;跨自治系统的 BGP 则还涉及路径属性和运营策略。
因此,“路由器永远寻找物理距离最近的路线”不成立。路径选择可能考虑度量、策略、资源和故障等约束,数据去程与回程也不必完全相同。
入门阶段,先把三件事分开就足够实用:协议与管理产生路由信息,查表决定本次下一跳,接口与邻居机制完成这一跳的发送。
十、动手计算与 Linux 排查
10.1 先离线算对,再到机器上观察
文章包的 code/ipv4_lab.py 使用 Python 标准库实现了三个小实验,不发网络报文,也不修改系统配置。Windows、Linux 均可运行:
python3 code/ipv4_lab.py subnet 192.168.20.68/28
python3 code/ipv4_lab.py fragments –payload 4000 –mtu 1500
python3 code/ipv4_lab.py route 192.168.56.200
python3 code/ipv4_lab.py selftest
Windows 里可以根据安装方式把 python3 换成 python。从文章目录运行,或者使用脚本的实际路径。
| 子网计算 | network=.64,last_address(广播)=.79,主机范围=.65~.78 |
| 分片计算 | 载荷 1480 / 1480 / 1040;Offset 0 / 185 / 370 |
| 路由模拟 | /25 胜出,via 192.168.10.254,dev eth0 |
| 自检 | 覆盖子网边界、/31、/32、分片对齐与最长前缀匹配 |
路由模拟使用第七节的固定教学路由表,不会读取你电脑的真实路由;分片模拟限定 IPv4 无选项、DF=0,只计算切片结果,不能用它证明某台系统实际会选择分片。
子网计算也可以用几行标准库代码复现:
from ipaddress import IPv4Interface
iface = IPv4Interface('192.168.20.68/28')
net = iface.network
print(iface.ip) # 192.168.20.68
print(net.netmask) # 255.255.255.240
print(net.network_address) # 192.168.20.64
print(net.broadcast_address) # 192.168.20.79
IPv4Interface 同时保留接口地址和所在前缀,适合这种“输入主机地址 / 前缀,计算网络”的练习。它与只描述网络边界的 IPv4Network 用途略有不同。Python ipaddress 文档
10.2 Linux 上按地址、选路、邻居的顺序看
下面的命令用于查看当前环境:
# 地址与接口概况
ip -4 -brief address
ip -brief link
# 主路由表、策略规则,以及所有表
ip -4 route show table main
ip -4 rule show
ip -4 route show table all
# 查询一个目的的选路结果,不发送探测包
ip -4 route get 192.168.56.3
# 邻居缓存与协议统计
ip -4 neigh show
ip -s link
这里 192.168.56.3 是教学地址,请替换为你要排查的实际目的。真实接口名可能是 ens33、enp0s3、wlan0 等,不能把 eth0 当成所有系统的固定名称。
检查时,不妨把每条证据写成完整句子:“本机使用哪个源地址,经哪个接口,交给哪个下一跳。”这比只截一张路由表更容易发现前提不一致的问题。

10.3 再用探测与抓包定位哪一步出了问题
确认自己拥有权限,且工具可用后,可以对实际目的进行少量探测:
# 替换成真实网关与目标
ping -c 3 192.168.10.1
tracepath -n 192.168.56.3
# 替换接口名,抓取少量指定目标的 IPv4 报文
sudo tcpdump -i eth0 -nn -vv -c 20 'ip and host 192.168.56.3'
tracepath、tcpdump 不一定预装。抓包若没有相应流量可能持续等待,按 Ctrl+C 停止即可。
需要把观察现象与结论范围对应起来:
| route get 返回某个出口 | 当前查询条件下有相应选路结果 | 对端在线、业务端口可用 |
| 下一跳邻居长期无法解析 | 应检查链路、地址、VLAN、邻居可达性等 | 仅凭这一项确定所有根因 |
| ping 无响应 | 可能丢包、过滤、禁回显或对端异常 | 必然没有路由 |
| tracepath 中间不回应 | 可能不产生或限制差错响应 | 该跳之后业务一定中断 |
| 能到目标但应用失败 | 继续检查监听地址、端口、服务和访问规则 | 应用层协议必定正确 |
抓包还要注意位置和卸载:本机发送方向的首部校验和提示,有时与网卡卸载时机有关。不能看到一次“bad checksum”就直接断言网线上发送了损坏报文,应结合接收端观察与实际通信判断。
10.4 一个可以复用的排查闭环
遇到“程序连不上”时,可以顺着以下思路逐步收窄范围:
这不是固定套用的万能清单,而是一条依赖关系:上一步判断清楚,下一步的观察才有明确对象。尤其在多网卡、VPN 和多级 NAT 环境中,先弄清实际路径,通常比盲目修改应用代码更有效。
十一、把易错结论放回正确条件
| 网络层为报文提前安排整条固定线路 | 普通 IP 根据各节点的路由与策略逐跳转发 |
| 只有路由器才查路由 | 主机发送数据也要选路 |
| IPv4 首部固定 20 字节 | 最小 20,存在选项时最多 60 |
| IHL=5 表示首部 5 字节 | 单位为 4 字节,实际为 20 字节 |
| IPv4 首部校验和使用 CRC | 使用 16 位反码和,且只覆盖首部 |
| TTL 的初始值总是 64 | 由系统与配置决定,转发时至少递减 1 |
| 分片偏移从原始 IP 首部开始数 | 从原始 IP 载荷开始,单位为 8 字节 |
| 每片都是独立的 UDP 消息 | 普通后续 IP 分片不各自复制 UDP 首部 |
| MF=0 就是没分片 | 也可能是非零偏移的末片 |
| 前三段相同就是同一子网 | 要结合前缀按位计算 |
| 所有子网主机数都是 2^h-2 | 普通广播子网常用;/31、/32 等要分别处理 |
| 所有 172 开头的地址都是私有地址 | RFC 1918 只包含 172.16.0.0/12 |
| 不在私有段的地址就是普通公网 IP | 还存在环回、文档、多播、链路本地等范围 |
| 默认路由必须位于最后一行 | 显示位置不决定优先级,基础查找使用最长前缀匹配 |
| 把数据交给网关就要改目的 IP | 普通路由中 IP 目的仍是远端,当前帧指向下一跳 |
| 每经过路由器都做 NAT | 普通路由与地址转换是不同功能 |
| 有路由、能 ping,就说明服务正常 | 还需确认端口、监听与应用协议 |
把一份数据报放在完整路径里回看,很多字段就有了明确位置:地址决定去向,前缀描述范围,路由表决定下一跳,链路机制完成当前交付,TTL 与长度约束则限制它怎样继续前进。
🌟 写在最后
理解网络层的关键,是始终区分“最终目的”和“当前下一跳”。前者写在 IP 报文里,后者由本次选路决定;把二者接起来的,是出口接口和当前链路的交付机制。
下一次看见 connect 超时,或者发现“同一段 IP 却无法通信”,可以先算前缀、查实际路由,再观察下一跳。这样排查的每一步都有依据,首部、掩码和路由表也就真正连成了一套知识。
接下来学习数据链路层和 ARP 时,还可以带着一个问题继续往下走:路由已经选好了,链路上的节点究竟怎样找到彼此,并把这一帧交出去?
如果这篇文章帮你厘清了某个细节,欢迎点赞、收藏,也欢迎交流你在 Linux 网络编程中遇到的实际现象。我们下一篇见。






