📚 第七章 路由协议
📄 一句话概括

路由就是「一张表」,表里写着「去哪个网段、交给谁」。 每个包出门前都要查一次,查的规则只有一条:前缀越长越优先。

📝 本节和前面几节的关系

第四章把 IP 地址讲完了,这一章讲「地址有了,路怎么找」。 5.1 说过「IP 只负责送」,这一节开始拆开「送」这个动作。

一、「路由」到底是个什么动作

先去掉玄学:一台主机或路由器收到包,看一眼目的地址,然后从路由表里找一条最合适的记录, 按记录上写的「下一跳」发出去。 就这么三步——查表、选一条、发出去。 注意是「下一跳」而不是「终点」: 路由表里写的不是「最终怎么走完整条路」,而是「这一步交给谁」。 这种「一跳一跳接力」的做法叫逐跳转发(hop by hop)——每一台设备只对自己这一跳负责。 这个设计的好处是「换一段不影响全段」: 中间某条链路断了,只要相邻那两台改一下表,后面的设备什么都不知道。 代价是「谁也说不清完整路径」——所以 traceroute 才要一个一个去问。

二、路由表里到底存什么

最小的路由表只要两列:「目的网段」和「下一跳」。 但实际会多几列,每一列都有用:

列 作用 没有会怎样
目的网段 匹配用 不知道往哪查
掩码 决定匹配多长 没法做最长前缀匹配
下一跳 交给谁 不知道往哪发
出口接口 从哪块网卡发 知道交给谁也不知道走哪条线
度量 多条都匹配时比优劣 并列时只能瞎选

第一列和第二列合起来才是「一个网段」:没有掩码,192.168.3.0 到底是 /24 还是 /16 就说不清。 这也是为什么 7.4.3 讲的 RIPv1 不带掩码会出大问题——它让「匹配多长」这件事没法准确表达。 第四列常被忽略:「下一跳」和「出口」是两件事。 点对点链路上,下一跳就是对面那台,出口也好定; 但在广播网段上,「交给谁」和「从哪块网卡发」必须分开写。

三、为什么按「网段」而不是按「主机」

如果按主机存路由,全世界的路由表会大到无法维护:光 IPv4 就有 42.9 亿个地址。 按网段存,同一条链路后面的所有主机共享一条记录——这就是「聚合」的价值。 算一下差距:

存法 条目数 能不能维护
按主机 约 42.9 亿 不可能
按 /24 网段 约 1678 万 勉强
按实际在用的前缀 约 95 万 可以

第三行是现实:全球 BGP 路由表大约一百万条,因为大家都把能合的网段合起来报。 一个运营商握着几万个 /24,可以合成几百条大前缀报出去—— 这一步叫「路由聚合」,是让互联网还能运转的关键工程手段。

四、最长前缀匹配:规则只有一条

一个目的地址可能同时匹配好几条路由:8.8.8.8 既在 0.0.0.0/0 里,也可能在 8.8.0.0/16 里。 这时候听谁的? 规则是「掩码最长的那条优先」——这叫最长前缀匹配。 道理很直白:前缀越长,说明这条记录描述的范围越精确。 /0 表示「所有我不知道的」,/32 表示「就这一台」。 精确的当然该赢过笼统的。 这条规则带来一个反直觉的结果:默认路由(/0)永远排在最后,所以它叫「默认」—— 不是「优先」,是「兜底」。 这也解释了为什么「配了默认网关却上不了网」时,要先去查「是不是有条更长的路由把流量引偏了」。

五、主机也要有路由表

很多人以为「路由是路由器的事」,其实每台电脑都有自己的路由表,而且规则一模一样。 它至少要有两条:一条「本网段直连」(同网段的人直接找,不用经谁)、 一条「默认路由」(其他所有流量交给网关)。 少了第一条,同网段的打印机都连不上;少了第二条,外网全断。 所以「能 ping 通网关但上不了网」和「连网关都 ping 不通」是两类故障: 前者多半是默认路由或 DNS 的问题,后者是链路或本网段路由的问题。 这个判断顺序比「重装驱动」有用得多。

六、动手 Demo:本机路由表里到底有什么

用 route print -4 把本机的 IPv4 路由表读出来,然后拿三个目标地址实际查一遍。 重点看「同网段」和「跨网段」这两条记录的区别。

import re, ipaddress, subprocess

raw = subprocess.run('route print -4', shell=True,
                     capture_output=True).stdout.decode('gbk', 'ignore')
rows, started = [], False
for ln in raw.splitlines():
    if '活动路由' in ln:
        started = True
        continue
    if not started:
        continue
    p = re.split(r'\s{2,}', ln.strip())
    if len(p) == 5 and re.match(r'^\d+\.\d+\.\d+\.\d+$', p[0]):
        rows.append(p)

cand = [(ipaddress.IPv4Network('%s/%s' % (r[0], r[1]), strict=False),
         r[2], r[3]) for r in rows]
for target in ('192.168.3.7', '192.168.5.7', '8.8.8.8'):
    ip = ipaddress.IPv4Address(target)
    hit = max([c for c in cand if ip in c[0]],
              key=lambda c: c[0].prefixlen)
    print(target, '->', hit[0], 'via', hit[1])

实跑输出:

--------------------------------------------------------------------
本机路由表里到底有什么
--------------------------------------------------------------------
**「路由」这个词听起来高深,其实就是一张表。**
**每个包出门之前,都要先在这张表里查一次「该往哪走」。**

  网络目标              掩码                  网关            接口                跃点
  -------------------------------------------------------------------------------------
  `0.0.0.0`             `0.0.0.0`             192.168.3.1     `192.168.3.152`     50
  `127.0.0.0`           `255.0.0.0`           在链路上        `127.0.0.1`         331
  `127.0.0.1`           `255.255.255.255`     在链路上        `127.0.0.1`         331
  `127.255.255.255`     `255.255.255.255`     在链路上        `127.0.0.1`         331
  `192.168.3.0`         `255.255.255.0`       在链路上        `192.168.3.152`     306
  `192.168.3.152`       `255.255.255.255`     在链路上        `192.168.3.152`     306
  `192.168.3.255`       `255.255.255.255`     在链路上        `192.168.3.152`     306
  `224.0.0.0`           `240.0.0.0`           在链路上        `127.0.0.1`         331
  `224.0.0.0`           `240.0.0.0`           在链路上        `192.168.3.152`     306
  `255.255.255.255`     `255.255.255.255`     在链路上        `127.0.0.1`         331
  `255.255.255.255`     `255.255.255.255`     在链路上        `192.168.3.152`     306

**一共 11 条活动路由**:
**只有第一条是真的「往外走」**,**其余都是「本机网段」和「特殊地址」自动加的。**
**第一行 `0.0.0.0/0` 就是默认路由**——**它的意思是「所有我不知道的,都交给 192.168.3.1」。**

**第五条 `192.168.3.0/24` 后面写的是「在链路上」**:
**意思是「这个网段的人我都直接能找到,不用经谁转发」。**
**同一台主机的路由表里,这两条决定了「家里通、外面也通」。**

**规则只有一条:谁的前缀长,就听谁的。**
**这叫「最长前缀匹配」。**
**下面拿三个目标实际查一遍:**

  目标              命中的网段           前缀长度     下一跳          出口
  ------------------------------------------------------------------------------------
  `192.168.3.7`     `192.168.3.0/24`     24           在链路上        `192.168.3.152`
  `192.168.5.7`     `0.0.0.0/0`          0            192.168.3.1     `192.168.3.152`
  `8.8.8.8`         `0.0.0.0/0`          0            192.168.3.1     `192.168.3.152`

**第二行最能说明问题**:
**192.168.5.7 和本机 192.168.3.152 长得很像,但它不在 192.168.3.0/24 里。**
**所以它只匹配上默认路由,被交给了网关。**
**这就是「同网段直发、跨网段找网关」的判断依据**——
**不是什么规则,就是把地址和掩码做了一次按位与。**

本机只有 11 条活动路由,其中只有第一条是「往外走」的。 其余十条里,两条是回环、两条是多播、四条是本机地址的特殊记录。 这些都不是「配出来的」,是系统自动加的——所以「路由表很乱」通常不是故障,是正常现象。 查表结果里第二行最能说明问题: 192.168.5.7 和本机 192.168.3.152 长得很像,但它不在 192.168.3.0/24 里。 所以它只匹配上默认路由,被交给了网关。 这就是「同网段直发、跨网段找网关」的判断依据—— 不是什么规则,就是把地址和掩码做了一次按位与。

30 秒实验

  1. 算一下:如果把全球 IPv4 地址按 /24 存路由,路由表大概多少条?
  2. 跑一下 route print -4,数一数你自己机器上有几条「在链路上」的记录。
  3. 想一下:为什么默认路由的前缀长度是 0,却排在最后?

常见误区

  1. 以为「路由表存的是完整路径」。 它只存下一跳,整条路径是很多台设备各自的表拼出来的。
  2. 以为「默认路由优先级最高」。 它是 /0,优先级最低,任何一条更长的前缀都能盖过它。
  3. 以为「路由只是路由器的事」。 每台主机都有路由表,没配默认网关就上不了网。
自测三问(点开对答案) 1. 路由表最少要有哪两列? —— **目的网段(加掩码)和下一跳。** 再加上出口接口,才知道从哪块网卡发。 2. 最长前缀匹配为什么这么定? —— **前缀越长描述的范围越精确**,所以精确的记录该盖过笼统的记录。 3. 「能 ping 通网关但上不了网」,先查什么? —— **默认路由和 DNS。** 能通网关说明链路和本网段路由没问题,问题在「出网」这一步。

下一节看这张表是「人填的」还是「自己长出来的」。

相关笔记