路由表里可能有好几条都能匹配,规则只有一条:取最长的前缀。 「最长」意味着「最具体」,所以默认路由就是长度为零的那条。
4.2.1 讲了「IP 是位置」,这一节讲「怎么从位置找到路」。 2.2 提过「窄腰」,路由控制就是那个窄腰里唯一的动作。
一、路由表长什么样
每一条就是「一个目的网络 + 一个下一跳」:
| 目的网络 | 下一跳 | 备注 |
|---|---|---|
| 0.0.0.0/0 | 192.168.3.1 | 默认路由 |
| 192.168.0.0/16 | 192.168.3.1 | 大段 |
| 192.168.3.0/24 | 直接交付 | 本网段 |
| 10.0.0.0/8 | 10.1.1.1 | 专线 |
第三行的「直接交付」是个特殊值:它表示「不用给路由器,我自己 ARP 一下就能送到」。 所以本网段的流量根本不经过路由器——这就是「同网段通信快」的原因,不是线路好,是「少一跳」。 第一行那条 0.0.0.0/0 是最妙的设计: 它的前缀长度是 0,所以「任何地址都匹配它」,但「匹配得最短」。 于是「其余所有情况」自动有了出路,路由表不需要为「互联网上每一个网段」写一条。
二、最长前缀匹配
同一个目的 IP 可能同时命中好几条,规则是「取最长的」:
| 目的 IP | 命中的前缀 | 长度 | 结果 |
|---|---|---|---|
| 192.168.3.152 | 192.168.3.0/24 | /24 | 直接交付 |
| 192.168.7.7 | 192.168.0.0/16 | /16 | 192.168.3.1 |
| 8.8.8.8 | 0.0.0.0/0 | /0 | 192.168.3.1 |
第一行同时命中三条(默认、/16、/24):取 /24 是因为「它最像这个地址」。 这套规则的好处是「加一条细的不用动粗的」——想给某个 IP 单独指一条路,加一条 /32 就行, 不影响其他任何流量。 第三行说明了 /0 的作用:它永远匹配、永远排最后,所以它是「兜底」,不是「优先」。 「默认网关」这个词就是这么来的——它是「没有更具体匹配时的那一跳」。
三、路由是怎么来的
三条来源,优先级从高到低:
| 来源 | 怎么来 | 例子 |
|---|---|---|
| 直连 | 配了 IP 就自动有 | 192.168.3.0/24 |
| 静态 | 手工写 | 10.0.0.0/8 走专线 |
| 动态 | 协议学来的 | OSPF、BGP |
第一行那个「自动有」很关键: 给网卡配上 192.168.3.152/24,系统立刻就会生成一条「192.168.3.0/24 直连」。 所以「本网段能通」这件事,不需要任何配置——只要 IP 和掩码对了就行。 第二行和第三行的区别在「规模」:几条路由手写没问题,几十万条就必须靠协议学。 BGP 的表有上百万条,靠人写是不可能的——所以「动态路由协议」存在的唯一理由是「规模」。 还有一个「优先级」的问题:同一条前缀如果从两个来源都学到了,要选一个。 一般规则是「静态优先于动态、直连优先于静态」,而不同的动态协议之间靠「管理距离」排序。 这套规则在排障时很有用——「为什么流量不走我配的那条路」,答案往往是「有更优的来源」。
四、动手 Demo:路由表怎么选出一条路
给一张五条目的路由表,让五个目的地址分别去查,看命中的是哪条。 注意第一行同时命中三条,最后取的是 /24。
import ipaddress
table = [('0.0.0.0/0', '192.168.3.1'), ('192.168.0.0/16', '192.168.3.1'),
('192.168.3.0/24', '直接交付'), ('10.0.0.0/8', '10.1.1.1'),
('172.16.5.0/24', '172.16.0.1')]
nets = [(ipaddress.ip_network(c), h) for c, h in table]
for ip in ('192.168.3.152', '192.168.7.7', '8.8.8.8'):
a = ipaddress.ip_address(ip)
best, ln, nh = None, -1, ''
for n, h in nets:
if a in n and n.prefixlen > ln:
best, ln, nh = n, n.prefixlen, h
print('%-15s -> %-18s /%d %s' % (ip, best, ln, nh))
实跑输出:
--------------------------------------------------------------------
路由表怎么选出一条路
--------------------------------------------------------------------
**路由表里可能有好几条都能匹配,规则只有一条:取最长的前缀。**
**「最长」意味着「最具体」,也就是「最像这个目的地」。**
目的网络 下一跳 备注
---------------------------------------------------
0.0.0.0/0 192.168.3.1 默认路由
192.168.0.0/16 192.168.3.1 到 192.168 大段
192.168.3.0/24 直接交付 本网段
10.0.0.0/8 10.1.1.1 专线
172.16.5.0/24 172.16.0.1 分支
目的 IP 命中的前缀 前缀长度 下一跳
--------------------------------------------------------------
192.168.3.152 192.168.3.0/24 /24 直接交付
192.168.7.7 192.168.0.0/16 /16 192.168.3.1
10.2.3.4 10.0.0.0/8 /8 10.1.1.1
172.16.5.9 172.16.5.0/24 /24 172.16.0.1
8.8.8.8 0.0.0.0/0 /0 192.168.3.1
**第一行 192.168.3.152 同时命中三条**(默认、/16、/24),**取 /24 那条**——因为它最具体。
**第五行 8.8.8.8 只命中默认路由**,**所以「默认路由」其实就是 0.0.0.0/0,长度为零,永远排最后。**
**这套规则有个很好的性质**:**加一条更细的路由,不需要动已有的粗路由。**
**所以「临时给某个 IP 单独指一条路」这种操作,只要加一条 /32 就行。**
ipaddress 那个 in 运算符做的是「按位与之后比较」:
所以 a in n 这个写法看起来很自然,实际是一次掩码运算。
手算的话就是「地址与掩码相与,看是否等于网络地址」——这两件事是一回事,只是写法不同。
「最长前缀」这套规则有个很实用的推论:路由表可以「重叠」,而且重叠是常态。
运营商把大段路由(/8)和细分路由(/24)同时宣告,
细分的那条会「覆盖」大段里的那一小块——这就是「流量工程」最基本的手段:
想让某段流量绕路,就宣告一条更细的路由。
所以「路由表里有重叠」不是配置错误,是设计。
反过来说,「表里出现两条完全一样的路由」才是问题——
那会让设备在两条之间反复选,也叫「路由振荡」。
30 秒实验
- 算一下:一个 /24 里给某台主机加一条 /32 路由,会影响其他主机吗?
- 想一下:为什么「默认路由」的前缀长度是 0?
- 在你电脑上敲 route print,找一下有几条 0.0.0.0 开头的。
常见误区
- 以为「路由表越具体越优先」。 是「前缀越长越优先」,而「具体」只是它的通俗说法。
- 以为「同网段通信也要过路由器」。 直连路由是「直接交付」,本网段不经过路由器。
- 以为「路由表里有重叠是配置错了」。 重叠是常态,它正是流量工程和聚合的基础。
自测三问(点开对答案)
1. 最长前缀匹配是什么意思? —— **多条都能匹配时,取前缀最长(最具体)的那条**,默认路由因为长度 0 所以永远排最后。 2. 默认网关在路由表里长什么样? —— **0.0.0.0/0 指向网关地址**,它就是「兜底」那一行。 3. 路由的三个来源是什么? —— **直连(配了 IP 就有)、静态(手工写)、动态(协议学)**。下一节看「抽象化」:IP 怎么做到不关心底层是什么网。
- ☐ (本节完,下一节 4.2.3 数据链路的抽象化)