📚 第四章 IP协议
📄 一句话概括

路由表里可能有好几条都能匹配,规则只有一条:取最长的前缀。 「最长」意味着「最具体」,所以默认路由就是长度为零的那条。

📝 本节和前面几节的关系

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 秒实验

  1. 算一下:一个 /24 里给某台主机加一条 /32 路由,会影响其他主机吗?
  2. 想一下:为什么「默认路由」的前缀长度是 0?
  3. 在你电脑上敲 route print,找一下有几条 0.0.0.0 开头的。

常见误区

  1. 以为「路由表越具体越优先」。 是「前缀越长越优先」,而「具体」只是它的通俗说法。
  2. 以为「同网段通信也要过路由器」。 直连路由是「直接交付」,本网段不经过路由器。
  3. 以为「路由表里有重叠是配置错了」。 重叠是常态,它正是流量工程和聚合的基础。
自测三问(点开对答案) 1. 最长前缀匹配是什么意思? —— **多条都能匹配时,取前缀最长(最具体)的那条**,默认路由因为长度 0 所以永远排最后。 2. 默认网关在路由表里长什么样? —— **0.0.0.0/0 指向网关地址**,它就是「兜底」那一行。 3. 路由的三个来源是什么? —— **直连(配了 IP 就有)、静态(手工写)、动态(协议学)**。

下一节看「抽象化」:IP 怎么做到不关心底层是什么网。

相关笔记