路由 TCP/IP 卷一 第五章:Routing Information Protocol(RIP) 总结
最古老的、且仍在使用的距离向量 IP 路由协议是 Routing Information Protocol(RIP)。它目前分为两个版本,这章讨论版本 1。下一章讨论版本 2 和 匹配 IPv6 的 RIPng。
距离向量协议基于动态规划算法。最早在 ARPANET 和 CYCLADES 中实现(好久以前)。在 1970 年代中期,Xerox 在链路层实现了一个 PARC Universal Protocol(PUP),这一协议通过 Gateway Information Protocol 路由。
之后,PUP 进化为了 Xerox 的网络系统协议栈(XNS),网关信息协议又进化成了 XNS 的路由信息协议。XNS RIP 成为了 Novell 的 IPX RIP 的前身,也是 Appletalk 对应协议的前身,当然,也是 IP RIP 的前身。
1982 年,RIP 在 Unix 系统中以 routed 这一守护进程实现,更新的版本在 routed 或者 gated 中实现。这里注意一下发音,d 单独发音,比如 “route-dee”。直到 1988 年,才有一个 RIP 的标准文件,是 RFC 1058。
基于你所读到的文献,RIP 可能被不公平地批评,或者是受到不匹配的夸赞。总的来说 RIP 协议缺乏一些其后继者具有的能力,但它也很简单,便于实现。
在 2025 年,实际生产中很少使用 RIP 了,大多数是 OSPF 或者 BGP。所以我的想法是,这章和下一章注重理解理论知识。敲一敲命令,不要陷的太深。
Operation of RIP
RIP 运作在 520 端口中,采用 UDP 作为传输层协议。RIP 有两种消息类型:请求与响应。前者是向其他路由器请求路由表信息,后者是返回这一信息。RIP 使用的度量是跳数,1 跳表示直连,16 表示不可达。
启动时,RIP 对通过所有启用 RIP 的端口对外发送请求,随后进入循环等待响应。接收到请求的邻居会返回响应消息。
当发送请求的路由器接收到响应消息后,它会处理信息,分成几种情况。如果路由是新信息,则会被插入到路由表中;如果是信息在路由表已存在,只有接收到的信息比现有信息的跳数短时才会被替换。
如果接收到的信息跳数更高且来源于路由表记录中的下一跳路由器,则路由会被标记为不可达,并限制(hold down)一段时间。在那个时间后,如果同样的邻居还在发送更高的跳数,则新的度量被接受。扩展一下,要是其他邻居对这一路由广播更大的跳数,路由器不会理会,毕竟有更好的路可以走。
RIP Timers and Stability Features
看看 RIP 的一些计时器。
启动后,路由器平均每 30 秒通过启用 RIP 的接口发送平凡响应消息。消息包含路由器完整的路由表,除了分割地平线影响的记录外。促使周期更新的计时器需要增加一些偏移量,避免同步更新。所以一个 RIP 进程的更新时间可能在 25s ~ 35s。
一些其他的计时器也被 RIP 采用。第 4 章讲,为了防止一个路由器失效形成网络黑洞,采用非法计时器表示路由表中的记录在不被更新的情况下最多能待多久。RIP 管这个叫过期计时器,或者超时。Cisco 叫它非法计时器。当一个新路由建立时,过期时钟初始化为 180s。如果 6 个更新周期都没听到这一路由的更新,则它被标记为不可达。
另一个计时器叫垃圾回收计时器,或者刷新计时器。它 240s,比过期时间长 60s。当一个路由失效时,失效的路由会被通知给邻居们,直到垃圾回收计时器失效。
下一个计时器叫限制计时器,即使 RFC 1058 并不要求。当一条路由的跳数变得更多(跳数减少不会起作用喔),这一路由会限制 180s,仍然是六个更新周期。限制计时器没有要求标记限制状态的路由不可达,RFC 1058 也没要求限制计时器的实现,这是 Cisco 自己的实现。
为什么路由的跳数会变多?之前不是说,对同一个目的地,跳数更小的下一跳才可能替换原来的路由?这样看,路由的跳数只可能小于等于当前跳数?
跳数变多的情况出现在原来的路由失效时。路由失效,度量变成 16。此时如果有其他邻居广播这一路由的目的地可由它到达,则度量会被修改。当然了,这一度量可能比原来的高,也可能比原来的低,但大概率是前者,毕竟原来的最优路由坏掉了。
另一种情况是,这条路由当前的下一跳路由器通知邻居们,这一路由现在如果从它走,需要更大的代价。此时新的跳数会写入,且跳数肯定更高。
这四个计时器可以用这一命令统一管理:
1 | |
要注意,如果一个路由器的计时器改了,其他路由器的也必须同时修改。因此,要是没有仔细考虑过,不推荐修改计时器的值。
你说,为什么必须同时修改呢?比如我就想让一个链路上的两个路由器的更新时间减慢,不改其他路由器的计时器,行吗?
分析一下,路由器的计时器只能全局修改,不能为每个端口、每条记录单独指定一个计时器。那么,想要某个链路上的两个路由器更新减慢,就意味着这两个路由器连接的所有链路的更新时间都减慢。那么,邻居的计时器都得调整,不然更新还没来呢就过期了。以此类推,邻居的邻居也要调整计时器。级联效应导致网络中所有路由器的计时器都应该修改。
想一想,路由器的更新计时器修改后,邻居们的失效计时器和限制计时器必须修改,不然会过早标记失效和解除限制。更新计时器和垃圾回收计时器不修改是不是也可以?邻居之间发的快一些又何妨?
问题是,这样会导致失效计时器远远超过更新计时器的 6 倍。如果一个链路失效了,6 个更新周期没收到消息,路由器仍然认为链路有效。这就造成收敛慢的问题。当然,要是把更新计时器减慢,收敛时间还是没改变,但是收敛周期降低了,且链路上的 RIP 流量显著减少。发的报文频率低,效果还一样,不如频率低一点,防止拥塞。
RIP 采用污染反向的分割地平线以及触发式更新。复习一下这两个概念,前者指的是从该端口学习到的路由,通过该端口广播出去时标记为不可达;后者指的是度量更新时,不等到更新计时器过期便广播更新,也不修改更新计时器。
RIP 的触发式更新稍作修改,为了避免在拓扑变化时大量的触发式更新,每两个触发式更新之间会有一段时间的随机停顿,在 1s ~ 5s 之间。
有些主机会在安静模式下实现 RIP,它们能接收 RIP 路由响应并更新路由表,但不会发出自己的路由表。
RIP Message Format
1 | |
上图展示了 RIP 消息的格式。每个 RIP 消息都会包含命令与版本号,并能携带最多 25 条路由信息。每条路由信息包含地址族标识符、通过这条路由可达的 IP 地址,以及跳数。如果一个路由器一定要发送超过 25 条记录的更新,则必须使用多条 RIP 消息。
想想为什么一定限制在 25 条以内?报文也没有 Packet Length 字段限制长度啊?
因为提出 RIP 协议时,网络硬件还没那么先进。IP 协议只是要求所有主机必须接收 576 字节的报文(RFC 791)。为了避免 IP 分片导致丢一个包整个更新失效的情况以及降低 CPU 的负担,选择了 25 这个数字。
注意到报文的起始部分有 4 字节,且每条路由有 20 个字节。这样最大的消息大小是 $4 + (25 \times 20) = 504$ 字节。要是加上 8 字节的 UDP 头部,则是 512 字节。
下面逐个讲解字段的含义。
Command 字段要么是 1,表示请求消息;要么是 2,表示响应消息。也有其他的命令类型,但它们要么废止了,要么是私有用途。
Version 是 1,表示 RIPv1。
Address Family Identifier 对 IP 来说是 2。唯一的一个异常是请求某个路由器的完整路由表,此时该字段为 0。
IP Address 是路由目的地的地址,可以是主网络号,子网,或者主机路由。后文会讲解 RIP 是如何区分这三种地址的。可以明确的是,地址的子网掩码从报文中是无法推断出来的。
Metric 是度量,在 RIP 中表示跳数,1 到 16 之间。
一些历史原因导致 RIP 的格式并没有那么美观。不过没关系,在 RIPv2 中,这些未使用的字段都被利用了。
Request Message Types
一个 RIP 请求可以请求一个完整的路由表或者某些具体的路由。
请求完整的路由表,请求部分只有包含一个路由项,地址族标识符设置为 0,地址是全 0,度量是 16。接收到这样一个请求的设备将会单播它的完整路由表到发送方,此时会遵守分割地平线或者边界汇总(后文讲解)之类的原则。
有些诊断操作需要知道某些具体路由的信息。这样的话,一个请求消息可能会包含一些待查询的路由项。接收到这样请求的设备将会逐个检查路由项中的地址是否存在于其路由表中,同时逐项构建响应消息。此时,响应只是关于路由器是否知道某一路由,不遵守分割地平线和边界汇总原则。
之前讲过,有些设备可能会静默运行 RIP。对这些设备,RFC 1058 规定,如果它们接收到了来自非 RIP 标准 520 端口的请求,必须发送一份响应。这样,诊断进程就能查询到静默主机上的路由信息。
Classful Routing
如果执行 show ip route 命令,在输出的结果中,以 R 为标志的表示 RIP 路由。值得注意的是方括号部分,例如 [120/3]。其中的 120 表示 RIP 的管理距离,3 表示度量,也就是跳数。如果对同一个目的地有多个等跳数的路由,那等代价负载均衡将会启用。
当一个报文进入到运行 RIP 协议的路由器时,路由表查找将会进行,排除表中的多种选择,直到剩下唯一一种。
首先,目的地址的网络部分会被匹配。第一步读取 A、B 或 C 类网络号定义了一个有类别的路由表查找。如果主网络号没有匹配,则包被丢掉。如果有匹配,则继续匹配该网络号下的子网部分。如果有匹配,则报文被路由,否则,丢包。
Classful Routing: Directly Connected Subnets
有类别的路由可以分成三种情况考虑:
- 如果一个目的地址是
192.168.35.3的报文进入路由器,且路由表中没有192.168.35.0的路由记录,则丢包; - 如果一个目的地址是
172.55.33.89的报文进入路由器,路由表中有172.55.0.0/24的路由记录,匹配,继续匹配子网部分,发现没有对应的子网,丢包; - 如果一个目的地址是
172.25.153.220的报文进入路由器,网络号匹配成功,且具有子网172.25.153.0的记录,转发报文给下一跳。
距离讲解 IPv4 的子网掩码已经过去了好几个章节,我们复习一下。
首先,为什么要引入子网掩码?因为单纯的 A、B、C 类地址划分(RFC 791 的 2.3 节)不够细粒度、不够现实。C 类地址的两百多个主机太少,但 B 类的六万多个又太多。
为了解决这一问题,人们提出了子网掩码,借用主机位实现更细粒度的网络划分(RFC 950)。主机位可以随意使用,对于
172.34.0.0/16这个 B 类地址,子网部分可以是任何位数,0 ~ 15 均可。注意,此时的地址仍然是有类别的,类别不等于没有子网。我们说,把某类地址进行子网划分。在申请地址时,仍然只能申请一个类别的地址。划分是局部概念,直连的网络才能通过子网路由,企业外部的路由器只能以网络块为最小单位路由,RIP 就是这样。
比如一个企业想向 IANA 申请地址,只能申请 A、B、C 的一种,不能申请 B 的某个子网。所以,一个适合企业规模的中等网络块仍然不存在,企业为了方便,还是会申请 B 类地址,再在内部进行子网划分。企业外的路由器不知道子网是如何划分的,不能把某个子网作为路由记录。
B 类地址的枯竭问题很严重。因此 RFC 1519 提出,网络块可以任意大小分配,不只是之前的三种类别;此外,路由器在发送路由信息时,会附带子网掩码,实现了子网路由的功能。
最后,说 RIP 是一个 classful routing protocol 的原因是,其发送的报文不包含子网掩码信息。需要路由器按自己接口的子网掩码推断(如果是直连),或者只记录主网络号(非直连)。
总结一下,RIP 路由器接收到一个地址后,先比对地址的主网络号部分,不考虑子网。匹配成功后,再根据子网掩码的宽度确定子网部分,比对子网。如果匹配,则路由。
对于 RIP 这样的有类别路由协议来说,它收到一个地址后,不知道其对应的子网掩码是什么。所以,它有两种处理措施。如果是直连的网络,则把自己本地接口配置的子网掩码当作网络的子网掩码;如果非直连,则只记录主网络号。
这个描述和上面引用部分反映的事情是一样的,再展开讲讲。直连等于局部,隐含的意思是在统一控制下。所以对于直连的地址,可以假设同一网络块的子网掩码是一致的,因为相信管理员能够做到这一点。但对于非直连的网络,没办法确定子网掩码的位数,只能记下某一类网络的路由。
你可能会想,万一公司很大,给总部里的每个部门都申请了一个 IP 地址,怎么路由呢?还是那样啊,这里,部门相当于是一个小组织,每个部门都认为其他部门是外部。对于自己部门内部的主机,路由器会按照子网路由;对其他部门,就只知道某类地址,无法得到更细粒度的信息。
你会想,万一部门拆拆合合,导致部门之间没那么泾渭分明,一个部门内部可能有其他部门的地址,怎么办呢?把部门之间的界限打破,给部门 A 的路由器分配一个部门 B 的 IP,这样路由器就知道所有有关部门的子网掩码啦!在 Discontiguous Subnets 这个案例研究详细讲解了这种方法。
最后,补充一下 RIP 协议是如何区分主机地址的。一个地址如果想成为主机地址,它所在的主网络号必须和路由器所在的相同。毕竟,外部地址只会保留主网络号。除此之外,其主机部分不为全 0。
广播子网信息比较常见,此时报文中目的地址的有效部分和配置的子网掩码位数是一致的。比如一个路由器从 IP 为
192.168.123.1 255.255.255.0的端口接收了目的地址是192.168.20.0的报文,注意到子网掩码为 0 的位置,目的地址也是全 0。但如果由于配置失误或者某个主机不是静默主机,对外发送自己的信息,就有可能出现主机地址。还是刚刚的端口,如果接收到来自
192.168.20.128的目的地址。注意到该 IP 的最后 8 位不是全 0,那路由器就会把这个 IP 当作主机 IP。其在路由表的子网掩码是 32。由于子网配置失误导致某个子网被当作主机 IP 的例子是本章 Troubleshooting Exercise 的第 2 题。
Classful Routing: Summarization at Boundary Routers
前边讲了,如果 RIP 路由器没有连接某个网络的接口,该怎么判断接收到的目的地址属于哪个子网呢?简单,不判断是哪个子网了,只需要记录网络号,以及指向直连了这个网络的路由器地址(肯定在同一个网络内)。注意指向二字,强调的点在于路由器中记录的是下一跳地址,不是边界路由器地址。
课本举了个例子,是在两个不同网络边界上的边界路由器。对于任何一方,它只会发送另一方的主网络号,而不会发送内部地址。这个策略叫汇总,或者子网隐藏。
比如对一个 192.168.115.0 网络内的主机,它只会发送 10.0.0.0 可从我这里抵达这一消息,不会细节到 10.10.20.64 等更具体的子网信息、主机路由。
具体的路由表长这样
1 | |
之前说过,要是有个绑定端口的路由记录 + 指定了下一跳 IP 地址,则 arp 缓存中只会涉及下一跳路由器物理地址的记录。测试了一下,确实如此。
形象的来说,边界路由器连接了两片云。一片云内部的主机知道其所在云中的子网信息,但它不知道另一片云的内部结构。它只知道,通过其所在云中的某个路由器能够到达另一片云。
之前讲了,不连续的子网会对 RIP 和 IGRP 这样有类别的路由协议造成困难。问题就在于不连续的子网会在网络边界被自动汇总。网络内部的路由器一看,有两条路到达同一个子网,便会开启负载均衡,交替通过两个途径发包。欲知后事如何,在案例研究中展开。
Classful Routing: Summary
对一个有类别的路由协议的决定特性是它在广播目的地址时,不同时伴有子网掩码。因此,一个有类别的路由协议必须首先匹配目的地址的主类别 A、B 或者 C。对于经过路由器的每个报文:
- 如果目的地址是直连主网络的成员,则在连接这个主网络的端口上配置的子网掩码会被认为是目的地址的子网掩码。因此,在一个主网络中,子网掩码必须一致;
- 如果目的地址不是直连主网络的成员,路由器只会尝试匹配目的地址的主类别部分。
Configuring RIP
和 RIP 简单的性质相符,配置 RIP 是非常简单的任务。有一个命令启用 RIP,以及一个命令指明 RIP 运行在哪个网络上。除此之外,RIP 还有一些配置命令。
Case Study: A Basic RIP Configuration
配置 RIP 需要的两个步骤是
- 用
router rip启用 RIP; - 用
network命令明确运行 RIP 的网络。
展开讲讲第二点,这里的明确二字有两个含义。一方面,明确了在哪个链路发送 RIP 消息;另一方面,明确了发送的路由表中是否含有某个网络的消息。
举个例子,
network 10.0.0.0的意思是,现在开始,路由器会把 RIP 消息通过连接了10.0.0.0这个子网的端口发送给这个子网;而且,发送的路由信息中包含10.0.0.0网络下的路由信息。如果没有第一点,
10.0.0.0这个链路上不会发送 RIP 消息;如果没有第二点,则发送的 RIP 消息中(在所有启用 RIP 的链路上)不会包含10.0.0.0这个子网的路由信息。
举个例子
1 | |
这里有一个点要强调,network 后的地址必须是 A、B 或 C 类地址,不能是某种子网。
用 debug ip rip 命令可以打开检测 RIP 报文的功能。从这里能够看出 RIP 的路由汇总特性。
Case Study: Passive Interfaces
这个案例要实现的需求是,某条链路上不进行 RIP 消息的交换。
对于案例中的一方,Floyd,是很简单的,只要在 network 中不包含这条链路即可。对另一方 Andy 有些复杂,因为 Andy 在同一个主网络号下有多个子网,这个链路只是其中一个子网。如果采用和 Floyd 一样的处理办法,则其他子网无法运行 RIP。
这样配置 Floyd 的代价是,Floyd 连接的、启用 RIP 的子网无法接收到 Floyd 和 Andy 之间的链路信息。如果想让那些子网知道这一信息,同时避免该链路上有 RIP 消息,也需要对 Floyd 使用被动接口。
为了让启用 RIP 的主网络号下的某个子网不运行 RIP,方法是 passive-interface,如
1 | |
被动接口实质上把路由器在这个接口上变成了静默主机。和静默主机一样的地方是,它会监听链路上的 RIP 响应,并以此更新路由表。和静默主机的一个区别是,路由器不会响应发往静默接口的 RIP 请求消息,
如果想达到更具体的控制,如禁止路由器从链路中学习信息更新路由表,则需要用到 13 章讲的知识,路由过滤。
想一想,为什么这样设计能够让链路禁用 RIP?禁用 RIP 的意思是链路上 RIP 不起作用。
因为对 Floyd,RIP 在这一链路根本没启用,它不会发送,也不会接收任何 RIP 消息。对 Andy,它不会发送 RIP 消息,但会接收 RIP 消息。
你说,并没有禁止 Andy 接收 RIP 消息,有没有可能导致链路上 RIP 并没有被完全禁用?有没有可能它无意间接收到了哪里的 RIP 消息?不会的,因为链路上只有 Andy 和 Floyd,且后者不会发送 RIP 消息。
这样,即使 Andy 在链路上的接收功能没有关闭,也没关系,因为不会有人发送 RIP 消息。最终达成的效果就是,RIP 好像不存在,不起作用。
passive-interface 并不是一个限于 RIP 的命令,而是所有路由协议均可使用。
Case Study: Configuring Unicast Updates
接下来是一个更棘手的配置问题。上一个例子中,要求一个链路上没有 RIP 消息的交换。这一次,要求一个链路上,有的路由器之间有 RIP 交换,有的没有。
举个例子吧,A B C 三个路由器组成一个子网,除了 A B 之间不允许有 RIP 信息交换之外,其他的组合都要交换 RIP 信息。
这下不可能通过在 network 命令中略去某个网络来达成目的了。解决的方案是这样的,对于路由器 C,因为剩下两个路由器都需要和它交换,所以它照常配置。
对于另外两个路由器,需要把接口设定为静默接口不变,这样可以防止更新被广播出去。此外,为了和 C 交换信息,需要把 C 的 IP 地址添加到邻居中,实现单播更新。
默认的更新方式是广播更新。单播更新是在被动接口(禁止更新)的基础上开了一个口子实现的。
一个配置例子是
1 | |
你可能会想,现在是限制了 A B 之间的传播了,但是,C 有没有可能作为中介,导致 A B 间接出现了路由信息的交换?答案是否定的,因为分割地平线原则的存在,C 不会把从 A B 学到的消息广播给这两个路由器。
Case Study: Discontiguous Subnets
之前就预告过了,有类别路由协议会在处理不连续子网时出现问题。仔细来看看。
画一下网络的拓扑图:
1 | |
由于汇总路由的存在,Barney 和 Ernest 都认为自己处在与 10.0.0.0 相连的边界。它们都会向 192 打头的网络中发送到达 10.0.0.0 网络的路由。
这样,Andy 就会错认为其有两条等代价的路可以到达 10.0.0.0 这个子网,所以会执行等代价负载均衡。所以现在报文就有大概 50% 的概率送到正确地点。
比如一个报文要发给 Barney 连接的 10.0.0.0 子网,Andy 就会不分青红皂白地均分给 Barney 和 Ernest_T。
解决的方法是给在 192 子网内的接口也配置上 10 子网的地址,这样那些路由器就不会认为自己处在两个不同网络的边界了。实现的方式是次级 IP。
比如 Andy 配置是
1 | |
之所以加上最后两句,是因为原来 Andy 并没有 10 子网,需要在其上激活 RIP。
路由器会把配置在一个端口上的次级 IP 和首要 IP 当成两个不同的链路,即使它们连接的是同一端口。
注意两个地方,一方面,次级 IP 也会被当成独立的数据链路,这会造成路由表很大。如果网络的带宽有限,需要注意拥塞问题;另一方面配置次级 IP 时不要忘了 secondary,否则路由器会认为是 IP 替换,这在生产网络中会造成严重后果。
Case Study: Manipulating RIP Metrics
这个例子的情景是,三个路由器组成 L 形。现在,把 L 的两个端点 A 和 B 连接起来,作为一个备用线路。问题是,A 和 B 走备用线路的跳数更少,默认 RIP 会选择备用线路作为主线路。
解决问题的方法是给度量加上偏移量。这是 offset-list 命令的格式:
1 | |
比如这串命令中最后一行的意思是,检查一下所有从 Serial0 端口进入的 RIP 消息,对那些在 access-list 中的地址,度量 + 2。
1 | |
access-list 相关的知识在书后附录 B 中有讲解。这里只需要理解它是一个过滤列表,可以进行流量的筛选。现在它的作用只是用来提供 IP 地址。
解释一下这里 access-list 的含义。它的意思是,创建一个编号为 1 的 access-list,允许发送地址为 10.30.0.0 的流量。最后一部分的 0.0.0.0 表示逆掩码,也叫通配符掩码,它和子网掩码的区别在于执行 OR 运算。置 0 的位表示需要严格匹配,置 1 表示不关心。
除了在 in 方向修改度量,也可以在 out 方向,不再赘述。
需要注意两点。第一点,到底在输入还是输出方向修改度量呢?如果在输出方向上修改,意图是链路上的所有路由器都需要修改后的值。如果链路上有的路由器需要修改后的度量,有的要原始度量,那么,哪些路由器需要修改后的度量,就在它们的输入端口上修改。
另一方面,如果在生产网络中应用了度量偏移,那一个路由器对外广播时,被修改的路由记录的度量会提高。这会让接收到这条信息的路由器标记这个路由不可达,直到限制计时器过期。
Case Study: Minimizing the Impact of Updates
要是网络中的更新流量太多,想要减少网络拥塞,以及可能产生的带宽费用,可以使用触发扩展。设置后,涉及的 RIP 流量只有初始化时的路由表交换以及路由表变化时的更新。增加更新计时器时长的方法不好,因为会减慢收敛速度。
触发扩展和触发更新不同。前者是为了解决周期广播的流量太大而提出的(RFC 2091),含义是现在路由信息不是平凡地周期发送了,而是触发后才发送;后者是为了解决网络收敛太慢的问题,网络的跳数变化(触发因素)后,不等待周期计时器过期,便广播新的路由信息。
启用的命令是 ip rip triggered。要注意的地方是,触发扩展只在串行链路中有效,而且必须在链路的双方都配置才有效。
Troubleshooting RIP
对 RIP 的排错相对简单,要么是子网掩码不一致,要么是出现了不连续的子网。
在一个高速路由器给低速路由器发送大量 RIP 信息时,有个命令十分有效。通过 output-delay delay 来指明 RIP 报文之间的间隔,可以在 8 ~ 50ms,默认是 0ms。
