只做一件事:邻居报的距离加一,比自己的小就换。 死穴是「计数到无穷」:没路的时候两个路由器互相骗。
7.1.3 讲了「邻居说的加一」,这一节把它写成算法并指出它的病。 这是 RIP 的内核,也是 7.4.4 那些补丁要修的东西。
一、算法本身只有四行
把这个算法写成伪代码,真的只有四行:
对每个邻居 n: 对 n 表里的每个目的地 d: 如果「n 到 d 的距离」加「我到 n 的代价」小于「我到 d 的距离」: 就换成新的,并把下一跳记成 n。
这四行就是 Bellman-Ford。它不需要知道全网拓扑,只需要邻居的那张表。 所以它的内存占用很小—— 一台路由器只需要存「到各处的距离」和「下一跳是谁」,不需要存「这张网长什么样」。 这个特点在 1980 年代是巨大优势:那时候内存按 KB 算,存不下拓扑图。 RIP 能跑在只有几 KB 内存的设备上,这就是它当年流行的原因。
二、收敛过程:一跳一跳传
算法的另一个特征是「只信邻居」,所以一条新路由要靠「一轮一轮地传」才能传遍全网。 每一轮,信息只能前进一跳。 这就带来一个直接的结论:收敛时间等于「跳数乘每跳的周期」。 RIP 的周期是 30 秒,所以直径 5 跳的网最坏要 150 秒。 而在这 150 秒里,有一部分路由器还不知道新路由,它们的包会走错方向甚至被丢。 「30 秒」这个数字不是随便定的:太短了报文多,太长了收敛慢。 当年的链路大多是 9.6 kbps 到 64 kbps,30 秒是一个「带宽能承受、收敛能接受」的折中。 今天带宽涨了几万倍,但 RIP 的周期没变——因为改周期会破坏兼容性,而 RIP 已经不需要再优化了。
三、死穴:两个人都以为对方还有路
现在把 C 后面的网段拔掉。 C 立刻知道(距离置成无穷),但 B 还记着「经 C 到那里只要 1」。 B 丢掉经 C 的那条,改成经 A——可 A 的那条本来也是经 B 学来的。 于是两个人都以为对方还有路。 接下来就是死循环:A 说「我这儿 2」,B 算出来 3;B 说「我这儿 3」,A 算出来 4…… 距离一轮涨一跳,一直涨到「无穷大」为止。 这个现象有个专门的名字,叫计数到无穷(count to infinity)。 注意它的根因不是「实现有 bug」,是「信息不足」: A 只知道「B 说它到那儿要 2」,它不知道「B 的 2 是经我学来的」。 如果 A 知道这一点,它就不会采信—— 这正是 7.4.4 的「水平分割」要做的事。 所以那个补丁不是打补丁,是「补上一条本来该有的信息」。
四、为什么还要用它
既然有死穴,为什么 RIP 还在用? 因为在它的适用范围内,这个死穴不致命。 RIP 把 16 定义为无穷大,所以最多骗 15 轮就停。 如果网的直径只有 3 到 4 跳,15 轮的等待可以忍受—— 而且这段时间里只有「去那个网段」的流量受影响,别的照常。 再加上水平分割、触发更新这些补丁,实际收敛通常在几轮之内。 所以「RIP 能不能用」的判断标准是三条: 网直径不超过 15 跳、对收敛速度要求不高、设备只支持 RIP。 三条都满足,RIP 是最省事的选择——配置只有几行,排错看一张表就行。 三条有一条不满足,就该上 OSPF。
五、动手 Demo:距离向量算法与它的死穴
先把正常收敛跑一遍,再把 C 后面的网段拔掉,看计数到无穷怎么发生。 注意第二张表里两列交替上涨的节奏。
INF = 16
a, b = 2, 3 # 断开之后 A、B 各自的估计
for rnd in range(1, 30):
print('第 %2d 轮 A=%2d B=%2d' % (rnd, a, b))
if a >= INF and b >= INF:
break
a, b = min(b + 1, INF), min(a + 1, INF)
实跑输出:
--------------------------------------------------------------------
距离向量算法与它的死穴
--------------------------------------------------------------------
**距离向量(Bellman-Ford)只做一件事**:**把邻居报的距离加一,比自己的小就换。**
**简单到可以手算,但有一个致命的死穴。**
**先看正常情况**:**拓扑是 A—B—C,C 后面挂着 10.0.0.0/8。**
轮次 A B C
-----------------------------------------
第 1 轮 — 距离 1 距离 0
第 2 轮 距离 2 距离 1 距离 0
第 3 轮 距离 2 距离 1 距离 0
**两轮之内全网都知道了**,这没问题。**第三轮没有变化,说明已经稳定。**
**现在把 C 后面那个网段拔掉。**
**C 立刻知道(距离置成无穷),但 B 还记着「经 C 到那里只要 1」。**
**B 丢掉经 C 的那条,改成经 A。**
**可 A 的那条本来也是经 B 学来的。**
**于是两个人都以为对方还有路。**
轮次 A 以为的距离 B 以为的距离
-------------------------------------------
第 1 轮 2 3
第 2 轮 4 3
第 3 轮 4 5
第 4 轮 6 5
第 5 轮 6 7
第 6 轮 8 7
第 7 轮 8 9
第 8 轮 10 9
第 9 轮 10 11
第 10 轮 12 11
第 11 轮 12 13
第 12 轮 14 13
第 13 轮 14 15
第 14 轮 16 15
第 15 轮 16 16
**这就是「计数到无穷」**:**明明已经没路了,两个路由器却互相把距离越抬越高。**
**RIP 把 16 定义为「无穷大」**,
**所以最多抬到 16 就停**——**代价是「这十几轮里,包一直往一个已经不通的方向送」。**
**这个死穴决定了距离向量的适用边界**:
**网小、层级浅、收敛慢点无所谓的地方能用(比如 RIP);网大、路径深的地方必须换算法**——
**这就是 7.3.2 的链路状态要解决的问题。**
一共 15 轮才把距离抬到 16,这期间所有发往那个网段的包,都还在往一个已经不通的方向送。 注意两列是「交替涨」的,不是一起涨: 因为每一轮里,A 用的是 B 上一轮的值,B 用的是 A 上一轮的值。 这种「你等我、我等你」的交替,正是「只信邻居」的必然结果—— 如果有一台能拿到全局信息,一眼就知道「那条路断了」,根本不会有这 15 轮。 16 这个上限是 RIP 定的,不是算法本身的要求: 如果不定上限,这个循环会一直涨下去(理论上到无穷)。 定成 16 相当于「给骗局设一个终止条件」——代价是「16 跳以外的网段,RIP 认为不可达」。
30 秒实验
- 算一下:直径 5 跳的网,RIP 最坏要多少秒收敛?
- 想一下:为什么两列是交替上涨,而不是一起上涨?
- 想一下:A 要是知道「B 的 2 是经我学来的」,它会怎么做?
常见误区
- 以为「计数到无穷是实现 bug」。 它是信息不足的必然结果,所有距离向量协议都有。
- 以为「距离向量要存拓扑图」。 它只存距离和下一跳,所以省内存,也因此看不见全局。
- 以为「RIP 已经被淘汰了」。 小网里它还在用,配置简单、排错容易。
自测三问(点开对答案)
1. 距离向量算法的核心四步是什么? —— **邻居的距离加一跳,比自己的小就换,并记下下一跳。** 2. 为什么会出现计数到无穷? —— **只记结论不记来源**,邻居说「还有路」就信,而那条路其实是从自己这儿学去的。 3. RIP 为什么把 16 定义成无穷大? —— **给这个循环设一个终止条件**,代价是 16 跳以外认为不可达。下一节看另一种答法:告诉邻居「原料」,自己拼图。
- ☐ (本节完,下一节 7.3.2 链路状态算法)