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

收到的距离加一,比自己的小就换,下一跳记成发通告的那台。 RIP 的「距离」是跳数,和物理远近无关,所以它经常选错。

📝 本节和前面几节的关系

7.4.1 讲了「怎么发」,这一节讲「收到之后怎么处理」。 这段逻辑是 7.4.4 所有补丁要修的对象,先看清它原本长什么样。

一、加一、比较、替换、记下一跳

收到一条通告,四步:

一、把通告里的距离加一(因为我到发通告的那台要一跳)。 二、看看自己的表里有没有这个目的地,没有就直接收。 三、有的话比大小,新的更小就替换,否则丢弃。 四、如果收了,把下一跳记成「发这条通告的那台」。

第四步是后面所有麻烦的根源:因为「下一跳」这个信息只记在本地,不会随着通告传出去。 所以 A 只知道「经 B 能到那儿」,它不知道 B 是经谁学的。 如果 B 是经 A 学的,那这条路就是「A 到 B 再到 A」,根本不存在。 这就是 7.3.1 说的「只记结论不记来源」的具体形态—— 字面上看,「下一跳」记了,但「来源」没记。 7.4.4 的水平分割,就是把这个「来源」补上。

二、为什么用跳数而不用别的

1970 年代的选择,理由是「不需要知道链路带宽」: 那时候的设备拿不到接口速率,也没什么办法测延迟。 「数一跳」是唯一一种「不需要任何测量就能算出来的度量」。 代价是它跟「快慢」几乎无关: 一条经过三台 10 Mbps 路由器的路是 3 跳;一条经过两台千兆路由器的路是 2 跳。 RIP 会选后者——这次碰巧对了。 但如果千兆那条要绕 4 跳,RIP 就会选那条慢的。 所以「RIP 选路不准」不是 bug,是「它压根没有带宽这个概念」。 要按带宽选,就得让协议知道每条链路的速率——这正是 OSPF 的代价(cost)要做的事。

三、收敛要靠「轮」来数

RIP 的传播是「一轮一跳」:30 秒一轮,每轮信息前进一跳。 所以「网的直径」直接决定收敛时间——直径 5 跳的网,最坏要两分半才全知道。 这里有个容易被忽略的细节:同一轮里,所有路由器用的是「上一轮听到的表」。 所以「一轮」不是「一台传完再下一台」,而是「大家一起听、一起算」。 这也意味着「一台路由器刚启动时,要等好几轮才能把表填满」—— 这就是 RIP 路由器「开机后头一两分钟网络不稳」的原因。 所以「RIP 慢」有两层:一层是「周期 30 秒」,一层是「要传好几跳」。 两层乘起来才是真实的收敛时间。 很多人只记住「30 秒」,于是以为「换个更快的设备就能变快」—— 其实周期是协议规定的,设备再快也不改。

四、RIP 里没有「带宽」这个概念

这一点值得单独强调:RIP 的路由表里只有「目的、下一跳、距离、定时器」,没有「带宽」。 所以你没法告诉它「这条线是千兆,那条是 2M」——它一视同仁地算作一跳。 这带来两个实际后果:一是「备份链路被当成等价链路」, 主链路和备份链路都算 1 跳,流量可能一半走备份(那条只有 2M)。 二是「没法做负载均衡的精细控制」,因为等价路径在它眼里完全一样。 工程上的补救办法是「调跳数」:把备份链路的接口度量手动调大(比如设成 5), 让它在选路时永远比主链路差。 这是个「用协议之外的手段补协议的短板」的典型例子—— 能用,但很脆:一旦有人改了接口度量,整张表就变了。

五、动手 Demo:RIP 里一条路由是怎么长出来的

三台路由器串成 R1—R2—R3,只有 R3 直连 192.168.100.0/24。 看这条路由三个周期之内怎么传到 R1。

adj = {'R1': ['R2'], 'R2': ['R1', 'R3'], 'R3': ['R2']}
dist = {'R1': {}, 'R2': {}, 'R3': {'192.168.100.0/24': 0}}

for rnd in range(1, 4):
    newd = {k: dict(v) for k, v in dist.items()}
    for me in adj:
        for nb in adj[me]:
            for dst, d in dist[nb].items():
                if d + 1 < 16 and d + 1 < newd[me].get(dst, 99):
                    newd[me][dst] = d + 1
    dist = newd
    print('周期', rnd, {k: v.get('192.168.100.0/24') for k, v in dist.items()})

实跑输出:

--------------------------------------------------------------------
RIP 里一条路由是怎么长出来的
--------------------------------------------------------------------
**RIP 把「距离」直接当跳数用**:**直连是 0,经过一台路由器是 1,以此类推。**
**下面看三台路由器怎么把一条路由学全。**

  周期            R1 的表     R2 的表     R3 的表
  ------------------------------------------------
  第 1 个周期     没有        1           0
  第 2 个周期     2           1           0
  第 3 个周期     2           1           0

**R3 是 0(直连)**,**R2 听到 0 加一变成 1**,**R1 再听到 1 加一变成 2。**
**每 30 秒才传播一层,所以「网的直径」直接决定收敛时间**:
**直径 5 跳的网,最坏要两分半才全知道。**

**这里有个容易误解的地方**:**RIP 里的「距离」和物理远近没关系。**
**一台离你 3 米但隔了两台路由器的主机,距离是 2;一台在另一个城市但直连的,距离是 0。**
**所以 RIP 选路经常「选一条慢但跳数少的路」**——**这是它除了 15 跳上限之外的第二个硬伤。**

R3 是 0(直连),R2 听到 0 加一变成 1,R1 再听到 1 加一变成 2。 每 30 秒才传播一层,所以「网的直径」直接决定收敛时间: 直径 5 跳的网,最坏要两分半才全知道。 代码里那句 d + 1 < 16 是 RIP 的上限:超过 15 就不收了, 所以「16 跳以外」在 RIP 里等于「不可达」。 这个判断必须写在「收」这一侧,否则距离会一直涨下去(就是计数到无穷)。 还有一句 d + 1 < newd[me].get(dst, 99): get 的默认值给 99 不是随便写的—— 它必须大于 16,否则「第一次听到」会被误判成「不如现有值」。 这种「哨兵值」在实现里很常见,写错了就是「路由永远学不进来」。

30 秒实验

  1. 算一下:直径 4 跳的网,RIP 最坏要多少秒收敛?
  2. 想一下:一条千兆链路和一条 2M 链路都算 1 跳,会出什么问题?
  3. 想一下:为什么「第一轮」R2 就学到了,而 R1 要等到第二轮?

常见误区

  1. 以为「RIP 的距离和物理远近有关」。 纯跳数,隔两台路由器就是 2,哪怕它们挨着。
  2. 以为「RIP 能感知带宽」。 它的表里根本没有带宽这一项,千兆和 2M 都算一跳。
  3. 以为「换个快设备 RIP 就快了」。 30 秒是协议规定的周期,跟设备性能无关。
自测三问(点开对答案) 1. 收到通告之后的四步是什么? —— **加一、查表、比大小、记下一跳。** 2. RIP 为什么用跳数做度量? —— **1970 年代拿不到带宽也测不了延迟**,数一跳是唯一不需要测量的度量。 3. 「RIP 慢」的两层原因是什么? —— **周期 30 秒,加上要传好几跳**,两层乘起来才是真实收敛时间。

下一节看 RIPv1 缺了掩码之后出的那个大问题。

相关笔记