我家里到 Cloudflare 的真实路径,用 tracepath example.com 抓的(输出删减,网段隐了几位):
1: 网关(我家路由器) 3.7 毫秒
2: 无应答
3: 无应答
4: 无应答
5: 骨干网某跳 33.2 毫秒
6: 无应答
7: 骨干网某跳 58.4 毫秒
10: 2400:cb00:71:2::4 212.7 毫秒 不对称 9
延迟从 3.7 ms 爬到 212 ms,中间几跳干脆摆烂不回话。这张「半张残缺的地图」是本章最好的引子:网络层负责在任意两台主机之间找路、把包一站站送过去,但它的承诺只有四个字,尽力而为。路怎么找、包怎么编址、为什么有的跳不回话,这一章全讲。
IPv4 地址 32 位,习惯写成四段点分十进制,比如 192.168.1.130。它的结构是一个约定:前缀是网络号(你住在哪个小区),后缀是主机号(小区里第几户)。切分位置用前缀长度表示,/24 意思是前 24 位是网络号,配合子网掩码 255.255.255.0 是同一个意思的两种写法。
拿 /26 做一道完整的手算题,这道题会做的考试白送分,不会做的路由都配不对:192.168.1.130/26 属于哪个子网?
前缀 26 位,主机位剩 6 位,掩码是 255.255.255.192。最后一个字节的块大小为 256 − 192 = 64,所以子网边界落在 0、64、128、192。130 落在 128 到 191 之间,答案是:
子网: 192.168.1.128/26
可用主机: 192.168.1.129 ~ 192.168.1.190 (2^6 − 2 = 62 台)
广播地址: 192.168.1.191
减 2 是因为主机位全 0 是网络地址本身,全 1 是广播地址,都不能分给机器。这套「块大小、边界、范围」的手感值得练熟,后面 NAT、防火墙规则全都建立在它上面。
IPv4 只有约 43 亿个地址,早就分完了,今天的互联网靠两根拐棍活着:CIDR 取消 A/B/C 类的固定边界、按需分配前缀,加上路由聚合把碎成渣的小网络重新捏成大块,路由表才没爆;NAT 让一坨私有地址(10/8、172.16/12、192.168/16)躲在少数公网 IP 后面,见 5.5 节。
一个包到达每台机器(包括你自己的笔记本)后,机器都做同一件事:查路由表,决定下一跳给谁。我笔记本上的真实输出(虚拟网卡的行略去):
$ ip route
default via 10.99.188.18 dev wlo1 proto dhcp metric 600
10.99.188.0/24 dev wlo1 proto kernel scope link src 10.99.188.123 metric 600
两行就够讲清楚机制。第一行是默认路由:default 即 0.0.0.0/0,匹配一切,兜底用,意思是「其他规则都不认识的目的,统统交给网关 10.99.188.18」。第二行是内核根据本机网卡配置自动生成的直连路由:去往 10.99.188.0/24 的不用出网关,直接从 wlo1 这块网卡发出去。去外网走第一行,访问同网段走第二行,你的机器每天都在这两类规则之间分拣。
匹配规则只有一条:最长前缀匹配。目的地同时命中多条路由时,选前缀最长的(最具体的)那条。这条规则是 CIDR 能玩聚合的前提:运营商可以把下游几十个网络聚合成一个大前缀对外广播,流量到了边界再被更长前缀的明细路由接走。没有最长前缀匹配,聚合过的地址就再也拆不开了。
静态路由手写只适合玩具网络,真实网络的路由表靠路由协议自动维护,两大流派:
| 维度 | 距离向量(RIP) | 链路状态(OSPF) |
|---|---|---|
| 每台路由器知道什么 | 到各网络的距离表 | 全网拓扑图 |
| 和谁交换 | 只跟邻居 | 泛洪给全域 |
| 算法 | Bellman-Ford 迭代收敛 | Dijkstra 本地最短路 |
| 收敛 | 慢 | 快 |
| 度量 | 跳数,上限 15 | 带宽/代价可自定义 |
| 适用 | 小网络 | 大型自治系统内部 |
距离向量的哲学是「听邻居的传闻」:我到 B 网络 3 跳,你到我 1 跳,那你去 B 就 4 跳。传闻的问题在坏消息上,经典故障叫计数到无穷。设 A—B—C 三台路由器串成线,C 那头挂了:
明明坏消息一步就能到,却绕着圈子走了十几轮,这就是「坏消息传得慢」。修补手段有水平分割(从哪个接口学来的路由,不再从那个接口广播回去)、毒性逆转(学来的路由以 16 跳广播回去,明示此路不通)等,但 RIP 的天花板摆在那,大网络都用链路状态:每台路由器把邻居关系泛洪给全域,人人手里有同一张完整地图,自己跑 Dijkstra,收敛又快又可控。
ICMP 挂在 IP 旁边,专管报错和诊断,不运应用数据。两个最重要的工具都是它撑的。
ping 发 Echo Request、等 Echo Reply,你大概用过,但输出里的信息值得读细。我机器上的真实输出:
64 字节,来自 2606:4700:10::ac42:93f3: icmp_seq=1 ttl=54 时间=192 毫秒
64 字节,来自 2606:4700:10::ac42:93f3: icmp_seq=2 ttl=54 时间=180 毫秒
已发送 3 个包,已接收 2 个包,33.3333% packet loss
rtt min/avg/max/mdev = 179.8/186.0/192.1/6.1 ms
ttl=54 有故事:Linux 发包的 TTL 初值是 64,每过一个路由器减 1,到 0 就地丢弃并回信。54 说明对面是 Linux 系的设备,且路上正好过了 10 跳。TTL 这个字段的存在本身就是为了防路由环路:万一路由表配出环,包在里面转圈,靠 TTL 自杀,免得永远占着带宽。
traceroute(或 tracepath)把 TTL 变成探路工具:先发 TTL=1 的包,第一跳路由器减到 0,回一个 ICMP Time Exceeded,于是你知道了第一跳的地址;再发 TTL=2 的,得到第二跳;如此递增,把沿途每一跳都钓出来。5 章开头那张残缺地图里的大片「无应答」,通常是中间路由器对 ICMP 限速或禁用,只说明它不回话,路照走,包照转。
NAT(网络地址转换)让一片私有地址共享一个公网 IP 出门,是 IPv4 耗尽后的续命核心。转换发生在路由器上,靠一张表:
转换表(NAT 路由器上):
内网 公网 对端
192.168.1.103:51776 ↔ 203.0.113.5:40001 ↔ 93.184.216.34:80
192.168.1.104:51800 ↔ 203.0.113.5:40002 ↔ 93.184.216.34:80
出包:把源地址端口换成公网侧的,在表里记一笔;回包:按目的端口查表换回去。端口在这里是必需品,光换 IP 区分不开回程,两台内网主机同时访问同一个网站,靠 40001 和 40002 这两个公网端口才能各回各家。
NAT 附赠一个副作用:外网无法主动连进来(表里没有的映射,包进来直接被扔),内网拓扑对外不可见,多少算个免费的防火墙。代价也不小,端到端原则被破坏,两台都在 NAT 后面的机器想直连(P2P 下载、联机游戏)就得打洞,聊天和语音应用为此写了一堆补丁代码。
IPv6 的思路是釜底抽薪:128 位地址,人人都有公网地址,理论上不再需要 NAT;SLAAC 让设备听路由器广播一个前缀、自己生成接口标识,不用 DHCP 就能拿到地址;首部校验和被删掉(链路层和传输层都有校验,IP 层那点收益不值得每个路由器重算一遍);分片也只允许源主机做,路由器从分片苦差里解脱。
几个真实会撞上的坑,附我自己的误判:
ping -s 1472)立刻复现。curl -v 或 nc),别急着拔网线。default 那一行。10.1.7.35/22 属于哪个子网?可用主机范围和广播地址各是什么?192.168.1.0/24 划成 4 个等大的子网,每个的前缀、范围、可用主机数分别是什么?10.0.0.0/8 via 10.99.188.99,那么去往 10.99.188.50 的包走哪条?去往 10.200.0.1 的呢?