那台绝不能承载数据包的路由器
三种链路状态协议,三种加入网络却又故意对它毫无用处的做法
Michel Wijnberg
第二篇论证了 Osprey 不碰你的网络。这个系列是那句话之下的一层:每种协议 究竟是怎么加入的、每种协议拒绝交出什么,以及在哪些地方”做个懂礼数的客人”其实比听上去难得多。
先从最硬的那条要求开始,因为其余一切都由它推导而来。
要读一个链路状态数据库,你必须在它里面。不是在旁边,也不是去轮询:要有真实的邻接、交换真实的 LSA、在所有其他路由器的数据库里以一台路由器的身份出现。OSPF 和 IS-IS 都没有观察者模式。要么 加入,要么什么也拿不到。
而你加入的那一刻起,区域内的每台路由器都会在一张现在包含了你的图上跑最短路径。没有人问过你是否 想要这些流量。
所以要求是:
做一台完全合规的路由器。永远不要被选为路径。
这两条在设计上就彼此拉扯,而三种协议各自用不同办法解决。
OSPFv2:最大开销
RFC 2328 要求 OSPFv2 路由器为它参与的每一个区域起源一条 Router-LSA。没有”只观察”的邻接可以退守: 参与就意味着被描述。于是记录器起源了一条,把自己唯一的点到点链路以 开销 65535 广告出去, 那是 16 位度量值所能容纳的最大值。
区域里其他每一台路由器都老老实实地把那条链路算进 Dijkstra,而任何一条备选路径都会胜出。记录器 在图里、可见、合规,而且永远不会落在最短路径上,除非网络真的再无其他选择,那样的话你面临的问题 比监控要大得多。
这就是 OSPFv2 里的全部机制,而值得注意的是它不是什么。它不是一个写着”别用我”的标志位。它是 一个被设得极高的普通度量值,使得答案从正常算法里自然掉出来。合规的路由器不需要任何特殊处理, 因为根本没有什么特殊的事情在发生。
还有第二半,学会它花了一次故障的代价,而且更有意思。记录器只广告那条点到点链路,别的什么都 不广告,尤其不广告 GRE 隧道自身内层子网的存根网络。广告它,哪怕用最大开销,也会让其他每台 路由器都装上一条通往隧道前缀的路由。那条路由随后可能引导隧道自身的外层数据包,从而杀死正在承载 观测的那条邻接。监控破坏了它用来监控所必需的东西。第七篇 讲的正是我用惨痛方式发现这件事的那个版本。
最大开销能让你远离最短路径。它不能让你离开路由表,而这是两个不同的问题。
OSPFv3:同样的做法,外加一个把话挑明的比特
OSPFv3 继承了度量值这套把戏,然后又加了一样 OSPFv2 完全没有对应物的东西。
OSPFv3 在 Router-LSA 的选项字段里带有一个 R 比特(RFC 5340)。当该比特被清零时,广告方 路由器不被视为具备转发中转流量的能力。它仍然参与拓扑分发,只是没有资格成为中转节点。
那不是一个度量值,而是一个声明。所以记录器让 R 比特保持清零,而一台合规的 OSPFv3 路由器会 把它彻底排除在中转计算之外,而不是仅仅把它排在最后。
这个差别比看上去更重要。最大度量值是一句关于偏好的陈述,只是恰好具有决定性。清零的 R 比特是一句 关于能力的陈述,任何”其余路径都更糟”的拓扑都无法推翻它。
它在读取侧也闭合了一个环。Osprey 自己的 SPF 通过一个按区域的”不做中转”标志尊重同一个比特,于是 记录器无论在合规路由器那里,还是在 Osprey 自己的路径计算里,都因同一个明示的理由被排除在中转 之外。两个实现、一个比特、两边都不需要特例。
OSPFv2 根本没有地方写下这句话。它的选项字段没有对应物,所以 v2 只能得到度量值,仅此而已;这种 不对称是协议的性质,而不是投入精力多少的结果。
IS-IS:两道门和一根闩
IS-IS 拥有单项最强的机制,而在这个实现里,它周围又围了最多的双保险。
自起源的 LSP 会置位 过载比特。ISO 10589 对它的含义毫不含糊:其他路由器可以到达这个系统, 但不得计算穿过它的路径。这与 OSPFv3 的 R 比特属于同一类陈述,而且早了很多年。
然后还有两样,而它们并不是同一类东西:
| 字段 | 取值 | 它挡住了什么 |
|---|---|---|
| OL 比特 | 置位 | 被计算为中转节点 |
| ATT 比特 | 0000 | 被装成默认网关 |
| TLV 22 度量值 | 0xFFFFFF | 一切把过载比特当作建议的实现 |
ATT 那一行值得占据它的位置,因为它关的是另一道门。过载比特关乎路径计算。attached 比特关乎默认 路由:一台 L1L2 路由器置位它,以告诉一级路由器自己能够到达区域之外,而那些路由器随后会把默认路由 指向它。这不是有路径被计算着穿过记录器,而是每一个没有更优匹配的数据包都被送往它,这更糟,而且 走的是第一种机制并不覆盖的通路。
度量值那一行是纯粹的防御。一个 24 位、取值 16,777,215 的度量值,是在一个本该让它变得多余的机制 之下再做一遍 OSPFv2 那套把戏,前提假设是:某处总有某个实现把过载比特当成建议。
(该 LSP 还会清除分区修复比特,这确实无关紧要,提一句只是为了不让任何人费神琢磨它为何在那里。)
这张表就是本文论点的全部。记录器确实在数据库里,带着自己的 LSA 和自己的序列号,与其他任何 路由器一视同仁。各一条链路,没有标记,也没有什么可再分发的。安全模型并不是把它们挡在外面的 东西,而是让「在里面」这件事变得无害的东西。
标题没有主张的东西
在往下走之前值得先切开,因为如果把标题读成一句关于机器的陈述,它就说过头了。
上面这一切都没有让主机在物理上无法转发。在实验室的记录器上它恰好无法转发,因为
net.ipv4.ip_forward 是 0,IPv6 那一侧也一样。但那是一个主机设置。有人一条命令就能改掉,
而协议层的任何东西都不会察觉。
所以这条要求不是”这台机器无法转发数据包”,而是:
网络中任何地方的任何路由计算,都不能把这台机器选为中转节点。
差别在于保证住在哪里。如果记录器的无害性依赖于一个 sysctl,那么每一个部署它的运营者都只差一条
sysctl -w,就能把一台监控主机放进转发路径,而网络对此毫不知情。因为它依赖的是拓扑,所以无论
内核会不会转发,网络自身的最短路径计算都拒绝经由记录器路由。
sysctl 是一条不错的皮带。它只是这篇文章里最弱的一环,而下一节讲的正是这个梯度。
那个是默认值而非不变量的东西
三个记录器还都以 DR 选举优先级 0 运行,因此永远不可能成为某个网段的指定路由器。一个赢下 DR 选举 的记录器,会变成该网段上其他所有路由器泛洪时所依赖的对象,而那与整个前提正好相反。
优先级 0 是默认值,而我想准确说明这是哪一类保护:它是一个配置字段。代码里没有任何地方拒绝 更高的取值。把它设上去,记录器就会像任何别的路由器一样参与竞选。
这值得与本文其余部分区分开,因为它上面的一切都是代码中的常量。过载比特在起源处就是
OLBit: true,没有任何办法把它配置掉。OSPFv2 的度量值在 LSA 构造器里就是 65535。R 比特从不
被置位。这些是不变量。DR 优先级是一个默认值,而默认值属于那类安全措施:它恰好只在没人去改一个
YAML 文件的时间里存活。
让这个错误压根无从下手
于是来到我真正在意的部分。
阻止中转是一个偏好问题。阻止注入是一个正确性问题,而且糟糕得多,因为它的失效方式不是一条 次优路径,而是别人路由表里一条不对应任何真实事物的路由。
因此自起源的 IS-IS LSP 完全不携带 IP 可达性。没有 TLV 128、130、135 或 236。没有会把记录器塞进 TE 数据库的 TLV 134 流量工程 router-id。没有 TLV 222,也没有 TLV 242。
而这一点是如何被强制的,才是重点。起源例程的输入来自一个小小的配置结构体,其中含有等级、系统 ID、 区域地址、对端系统 ID、序列号、接口地址,以及一个可选的认证器。它上面没有任何一个字段能装下 一个前缀。 调用方传不进一条路由,因为根本没有地方可传。
我想把它的边界说准确,因为这里很容易主张过头。正在构造的那条 LSP 持有一个通用的 TLV 列表,例程 会往里追加。有人去改那个例程的话,是可以追加一个可达性 TLV 的;类型系统不会反对。设计所移除的 不是可能性,而是意外:没有任何配置、任何调用方、任何设置组合能引入一条路由。要注入一条,就得 去编辑一个在文档块里逐条列出了自身存在理由(那些不变量)的函数。
这个区分正是本文前后两半的全部差别,而到这里已经有四级台阶在场了。主机设置是运营者可以更改、 却始终不知道它在保护什么的东西。运行时检查是你可能忘了写的东西。默认值是你可能没注意就覆盖掉的 东西。一个压根没地方放这个错误的输入面,则是你必须刻意绕道才能绕过的东西,而那份刻意在 diff 里 看得见。
这些都不是绝对的。它们只是”意外程度”差得很远,而值得做的工程,是把一项保证沿着这份清单往下移, 而不是在最上面再加一道检查。
要点
三种协议、三套机制、一条要求,而这些机制并不可以互换。OSPFv2 只能表达”贵”。OSPFv3 和 IS-IS 能 表达”不是中转路由器”,那是一句更强也更诚实的话,而且它们用的是比这一切早了几十年的比特来表达。
有意思的工程不在于写那套排空逻辑。而在于注意到:最大开销并不能让你离开路由表;默认值不是不变量; 以及放置一条规则最安全的地方,是那个让这条规则无法被错误表达的地方。
下一篇:离开。一个重启的记录器会发现网络仍以更高的序列号持有着它先前的自己,而修复这件事的那条 消息,正是它在关闭过程中发出的那一条。 你收不回来的那条 LSA。