简介《RFC2328中文版》是OSPF版本2协议规范的完整中文译本面向网络工程师、运维人员及备考网络方向认证的学习者用于攻克英文RFC原文中晦涩的术语与算法描述。压缩包内仅有1个PDF文件约1.93MB属于纯文档型资料按章节编排可直接在阅读器中检索与跳转。全书从协议概述与常用术语入手依次讲解连接状态数据库的组织与计算、最短路径树生成、外部路由引入与等值多路径并深入自治系统分区、骨干区域、区域间路由与总结路由、路由器分类、存根区域及IP子网化支持后半部分涵盖LSA类型与老化、认证与安全性、路由器配置调试以及接口开销、LSA刷新间隔等性能优化参数。目录层级清晰配有连接状态数据库示例与简单区域配置实例便于对照拓扑理解SPF计算过程。目前已有432人学习适合需要系统查阅OSPFv2原始定义、做协议实现或排障时逐条核对的读者。1. OSPF 真正的难点不在最短路径而在让一群路由器对同一张图达成共识很多人翻开 RFC2328 中文版——也就是 OSPF 第二版的协议规范——是冲着「最短路径优先」这几个字去的以为核心就是跑一遍 SPF 算法。真正读进去会发现讲算法本身的篇幅只有一章剩下的全在讲洪泛、LSA 新鲜度判定、邻居状态机、区域边界这些跟「最短路」一点关系都没有全是分布式一致性问题。OSPF 要在没有中心节点、没有全局时钟的前提下让几十上百台设备对「网络长什么样」达成一致还要在链路抖动时快速收敛。这份文档值得逐节啃的原因就在这它把每一个「为什么必须这么设计」都摊开写了而不是只给结论。2. 链路状态数据库每台路由器都存一张全网地图图什么2.1 距离矢量的黑匣子与链路状态的解法早期的路由协议走的是距离矢量路线邻居告诉你「我到某个网段的开销是 n」你把这个数字加一跳再传给别人。这套机制的问题是你只知道距离不知道路径一旦网络里出现环路只能靠水平分割、毒性逆转这类补丁去事后补救而且收敛慢、故障时容易出现计数到无穷。RFC2328 绪论里专门用一小节讲了连接状态路由技术的历史核心动机就一句话把「我不知道路径」变成「我知道全部路径」。链路状态的做法反过来每台路由器只负责描述自己直连了谁、开销多少然后把这份描述洪泛到全网。所有路由器最终拿到同一份描述集合各自在本地算出一棵以自己为根的最短路径树。因为大家手里的图是一致的所以算出来的转发路径不会成环这是数学上的保证不需要任何补丁。代价也很清楚每台路由器要存下全网的拓扑内存开销随网络规模线性增长拓扑一变就要重新算树洪泛本身要占用带宽。记住这三笔账后面区域划分的所有设计动机都是从这三笔账里长出来的。2.2 Router-LSA 与 Network-LSA一张图用两种顶点拼出来具体到编码这张图里其实有两大类顶点。第一类是路由器由 Router-LSAType 1描述它的正文是一串 link 记录每条 link 说明「我这个接口连到了谁、连接类型是什么、开销多少」。链接类型分四种点对点、连接到广播网段、连接到末梢网段、以及虚拟链路。第二类是广播网段由 Network-LSAType 2描述由网段上的指定路由器DR生成正文里列出该网段上所有已经形成完全邻接的路由器。LSA 类型生成者描述对象传播范围Type 1 Router-LSA每台路由器自己的接口与邻接单个区域Type 2 Network-LSA网段上的 DR一个广播/非广播多路访问网段单个区域Type 3 Summary-LSA区域边界路由器区域外的某个网络单个区域Type 4 Summary-LSA区域边界路由器区域外的某台 ASBR单个区域Type 5 AS-external-LSAASBR自治系统之外的目的地整个 AS存根区域除外这张表里有两个细节值得停一下。Type 1 到 Type 5 并不是并列关系而是分层的前两类描述拓扑后三类描述「到达某个东西的距离」。拓扑类 LSA 参与最短路径树计算距离类 LSA 只是在算好的树上做一次代价比较。这个区分决定了后面增量更新能不能成立。2.3 伪节点广播网络为什么要虚拟出一台路由器广播型网段上如果挂了 n 台路由器全互联的话每台都要跟其余 n-1 台建立邻接邻接总数是 n(n-1)/2Router-LSA 里也要写 n(n-1) 条 link。规模稍大一点这两项都会失控。OSPF 的解法是在广播网段上虚拟出一个节点也就是由 DR 生成的 Network-LSA把它当成图里一个普通的顶点。这样一来所有路由器只需要跟 DR/BDR 建立完全邻接邻接数降到 n-1Router-LSA 里每台路由器只写一条指向该伪节点的 link伪节点再写 n 条指回去的 link总数是 2n。常见的实现思路是把这个伪节点当成一棵树的中间节点从 DR 出发的边权重是 0这样最短路径树经过伪节点时不会引入额外代价。DR 和 BDR 的选举规则在文档 9.4 节写得很死先比接口优先级优先级高者胜优先级相同或都为 0 时不能参选再比 Router ID。关键是这个选举不抢占——一旦选出来除非 DR 失效或者接口重启否则新加入一台优先级更高的路由器也不会夺权。这个设计减少了选举震荡代价是网络规划时优先级要提前配好事后补配往往不生效这是很常见的困惑点。2.4 从 LSDB 到最短路径树候选列表与等值多路径拿到一整个区域的 LSA 之后SPF 计算本身并不复杂但有个地方容易写错等值多路径。标准做法是用一个按累计开销排序的候选列表每次取出最小项并永久标记遍历它的出边时同时处理「更短」和「等长」两种情况。import heapq def spf(area_lsdb, root): 在单个区域的 LSDB 上跑一次 SPF返回每个顶点的父节点集合 dist {root: 0} parents {root: []} # 一个顶点可以有多个父节点对应等值多路径 candidate [(0, root)] # 候选列表按累计开销排序的最小堆 done set() while candidate: cost, v heapq.heappop(candidate) if v in done: # 已经被永久标记跳过重复入队 continue done.add(v) for w, edge_cost in area_lsdb[v].links: # 只遍历同一区域内的 LSA new_cost cost edge_cost if w not in dist or new_cost dist[w]: dist[w] new_cost parents[w] [v] # 发现更短路径覆盖父节点 heapq.heappush(candidate, (new_cost, w)) elif new_cost dist[w]: parents[w].append(v) # 等长路径追加父节点不覆盖 return parents这段代码有两个参数含义需要说清edge_cost来自 LSA 里的度量字段点对点链路的开销直接取自 Router-LSA经过伪节点的边开销为 0parents里保留多个父节点是因为等值多路径要保证同一份拓扑能同时算出多条下一跳。真正麻烦的是下一步——算出树之后还要把「父节点」翻译成「下一跳 IP」。RFC2328 第 16.1.1 节给了规则沿着父节点指针回溯直到找到一个与根路由器直连的节点那个节点所在网段的接口地址就是下一跳。如果中间经过伪节点下一跳是伪节点上某台路由器的接口地址这时候等值多路径的每一个父节点都要单独回溯一遍。提示很多实现里 SPF 只在区域内跑Type 3/4/5 根本不进这棵树。如果你在调试时发现某条外部路由没有出现在树上别怀疑算法先确认它是不是压根没参与计算。3. 区域与 SPF把一张大图切成小图代价落在谁头上3.1 骨干区域与 ABR区域间路由为什么要绕一圈如果整个自治系统只有一个区域所有路由器都要存全网的拓扑任何一条链路抖动都会触发全网重算。RFC2328 第 3 章给出的解法是分区把自治系统切成若干个区域拓扑信息只在区域内部洪泛跨区域只传播「距离」不传播「形状」。所有区域必须直接或间接连接到骨干区域Area 0同时属于骨干和另一个区域的路由器叫区域边界路由器ABR。规定这条硬性约束的原因是它提供了区域间无环的保证区域间的路由必须经过骨干任何一条跨区域路径在图上都必然穿过 Area 0结构上不可能成环。换句话说区域间的正确性不是靠 SPF 算出来的是靠拓扑约束「规定」出来的。这也是为什么两个非骨干区域之间不能直接连一根线就互通——那根线上的流量走的是区域间路由而区域间路由的规则要求它先绕到骨干去实际部署中这条直连要么被忽略要么就得靠虚拟链路来打补丁。路由优先级也由此确定区域内路由最优其次是区域间路由再次是外部路由。这个顺序不是凭感觉定的而是可信度的排序——区域内是精确拓扑算出来的区域间是一个距离值外部是别人告诉你的。3.2 Summary-LSA把距离矢量伪装成链路状态ABR 生成 Summary-LSA 时做的事情其实很朴素把它从别的区域学到的、或者自己算出来的目的网络连同自己到达该网络的开销打包发进目标区域。它的 Link State ID 是目的网络地址正文里带掩码和度量值仅此而已。# ABR 为某个区域生成的 Summary-LSAType 3本质就是一个距离向量 summary_lsa { ls_type: 3, # Summary-LSA描述区域间目的地 link_state_id: 10.1.0.0, # 目的网络地址不是拓扑节点 adv_router: 1.1.1.1, # 生成它的 ABR谁的 Router ID mask: 255.255.0.0, # 与 Link State ID 配合确定网段 metric: 20, # ABR 到该网络的累计开销只有一个数字 age: 0, }这里没有任何拓扑信息只有一个距离。所以严格来说OSPF 在区域内部是链路状态协议跨区域就退化成了距离矢量协议。这是一个非常清醒的折衷用「区域内精确、区域间模糊」换来 LSDB 的规模可控。模糊的代价是会算出次优路径——ABR 汇总出去的距离对下游路由器来说是一个不可分解的黑盒下游无法判断这条路径具体走哪条链路、是否绕远。区域间汇总也是在这里做的ABR 可以把多条明细 Summary-LSA 聚合成一条掩码更短的汇总减少区域间的信息量。聚合的取舍在于汇总越粗LSDB 越小但被汇总网段里某一条链路故障时区域外完全感知不到只会在 ABR 上产生一条指向空接口的丢弃路由。这条丢弃路由是防止环路用的别随手删。3.3 虚拟链路与存根区域两种缝合手段实际网络里经常出现的情况是某个区域因为历史原因没有直接连到骨干或者骨干区域本身被分成了两块。RFC2328 第 15 章给出的补丁是虚拟链路在两台 ABR 之间穿过一个「传输区域」虚构出一条属于骨干区域的点对点链路。虚拟链路有三个硬性条件缺一不可两端都必须是 ABR传输区域不能是存根区域传输区域内部必须有到达对端 ABR 的区域内路由。第三条最容易被忽略——虚拟链路只是把控制平面的邻接「假装」成骨干直连底层的报文还是要靠传输区域里的真实路由送达如果区域内路由都不通虚拟链路自然起不来。存根区域是另一个方向的取舍文档 3.6 节讲了它的动机如果某个区域只有一条出口区域内路由器没必要知道整个自治系统的外部路由干脆由 ABR 注入一条默认路由代替所有 Type 5。这样区域内 LSDB 能显著瘦身代价是区域内路由器失去了对外部路径的选择权所有出区流量都走默认路由哪怕次优也只能认了。3.4 增量更新为什么只有外部路由能局部重算拓扑变化之后要不要整棵树重算RFC2328 在 16.5 和 16.6 节给了区分对待Summary-LSA 或 AS-external-LSA 发生变化时只做增量计算不重跑 SPF只有 Router-LSA 和 Network-LSA 变化才需要重新构建最短路径树。原因还是前面那个分层这两类 LSA 不参与树的构建它们只是挂在已经算好的树上做一次「父节点开销 LSA 度量」的比较。所以一条外部路由翻动只需要遍历受影响的那部分前缀代价是线性的不需要动整张图。文档在 16.1 节末尾留了一个口子允许实现在拓扑变化只影响叶子节点时做局部重算但那是可选项不是协议强制要求不同实现之间的收敛表现差异往往就出在这里。明白了这一点很多现象就好解释了外部路由抖动通常收敛很快而区域内的链路抖动会引起整区域重算CPU 会有一个明显的尖峰。排查时如果看到 CPU 周期性冲高又回落八成是某条链路在反复 Up/Down。4. 洪泛与邻居状态机没有中心时钟怎么让全网对「谁更新」达成一致4.1 邻居状态机的八级台阶两台路由器从互相看见到完成数据库同步中间要经过八个状态。这套状态机在文档第 10 章写得极其细致每个状态、每个触发事件都有明确定义是理解 OSPF 收敛过程最值得反复读的一节。状态含义进入条件Down初始状态尚未收到 Hello接口刚启用或邻接被重置Attempt仅用于非广播多路访问网络主动向配置的邻居发 HelloInit收到了 Hello但自己不在对方邻居列表里单向可见2-Way双方都在对方的邻居列表里双向可见DR Other 之间停在这里ExStart协商主从关系比较 Router ID大者为主Exchange交换数据库摘要主从关系确定开始发 DD 包Loading请求缺失的 LSA收到 DD 摘要后发现有本地没有的Full邻接建立完成数据库一致所有 LSR 都被应答八个状态里2-Way 和 Full 的区别最容易被误解。两台非 DR 的路由器之间停留在 2-Way 是正常的因为它们不需要交换完整的数据库只要都跟 DR 建立完全邻接就够了。看到邻居列表里一片 2-Way 就以为出了故障是新手常见的误判。热备用状态下BDR 与所有路由器也保持完全邻接所以同一个广播网段上 Full 的数量是「路由器数 - 1」乘出来的固定值可以用这个数量做快速核对。4.2 DD 包的 Master/Slave 与数据库摘要交换ExStart 阶段做的事情只有一件确定谁是主、谁是从。判据是比较双方的 Router ID大的一方为主。这个协商看起来多余因为 Router ID 是静态的、直接比一下就知道结果。之所以要走一遍报文交互是因为主从关系同时确定了 DD 包的序号推进规则——必须有一方负责递增序号另一方只做回显否则双方各自维护序号同步过程会乱掉。def master_send_dd(peer): peer.dd_seq 1 # 主方每发一个新摘要序号自增 send(peer, make_dd(seqpeer.dd_seq)) def master_on_dd(peer, dd): if dd.seq ! peer.dd_seq: # 期望收到对上一次发送的回显 return drop # 序号不符丢弃由重传定时器兜底 peer.acknowledge() def slave_on_dd(peer, dd): if dd.seq ! peer.dd_seq 1: # 从方只接受严格递增的下一个 return drop peer.dd_seq dd.seq send(peer, make_dd(seqpeer.dd_seq)) # 回显同一个序号不递增序号规则的细节是主方每发一个新 DD 就加一从方的回显必须使用主方刚发的那个序号值。这个规则保证了同步过程有唯一的时间轴丢包重传时也能靠序号判断重复。DD 包里携带的是 LSA 头部20 字节不含正文。一个 DD 能塞多少条摘要取决于接口 MTU。这里埋了一个非常经典的坑ExStart 阶段的 DD 包里带了一个 MTU 字段接收方如果发现对端 MTU 比自己大会直接丢弃这个 DD 包邻居就会一直停在 ExStart反复重传日志里只有一堆 retransmit。文档在 10.6 节里明确了这项检查但没强调它有多容易踩。4.3 LSA 头部三件套与新鲜度判定洪泛的核心问题不是「怎么把 LSA 传出去」而是「怎么判断手里这份和收到的那份谁更新」。OSPF 没有中心时钟也没法依赖系统时间所以 LSA 头部设计了三样东西来共同决定新鲜度32 位序号、16 位年龄、16 位校验和。判定规则写在 13.1 节按顺序比较先比序号序号大的更新序号相同再比校验和大的更新两者都相同再看年龄——如果其中一份已经是最大年龄3600 秒另一份不是则非最大年龄的更新如果两份年龄差超过 900 秒年轻的更新否则视为同一个实例不做任何处理。MAX_AGE 3600 MAX_AGE_DIFF 900 def lsa_is_newer(new, old): RFC2328 13.1判断收到的 new 是否比库里的 old 更新 if new.seq ! old.seq: return new.seq old.seq # 序号是第一判据 if new.cksum ! old.cksum: return new.cksum old.cksum # 序号相同时用校验和打破平局 if old.age MAX_AGE and new.age ! MAX_AGE: return True # 库里的已经老化到极限 if abs(new.age - old.age) MAX_AGE_DIFF: return new.age old.age # 年龄差超过 900 秒年轻的更新 return False # 视为同一实例静默丢弃校验和参与比较这一层很多人觉得莫名其妙。原因是序号会回绕序号是有符号 32 位从 0x80000001 开始递增涨到 0x7FFFFFFF 就到顶了。这时候路由器必须提前把这条 LSA 老化到最大年龄、洪泛出去、全网删除然后再用一个很低的初始序号重新生成。在这个回绕窗口里可能出现两台路由器对同一个 LSA 用了相同序号但内容不同的情况校验和就是用来在这种边界场景下做最后裁决的。这是个典型的血泪经验点线上遇到序号回绕引发的路由震荡光看序号根本看不出问题。4.4 可靠洪泛重传、隐式确认与 MaxAgeOSPF 跑在 IP 之上本身没有可靠传输可靠性得自己实现。文档第 13 章定义了完整的洪泛流程收到一个新的 LSA加入数据库从除入接口外的所有接口发出去同时在重传列表里记一笔等确认。确认有两种。显式确认是专门的确认包收到就划掉重传项。隐式确认是指如果在重传定时器超时之前看到邻居洪泛回来的同一个 LSA 实例序号、校验和、年龄都对得上也算作确认。隐式确认在广播网段上特别重要一台路由器发出 LSU网段上多台邻居同时收到如果每台都回一个显式确认确认包的数量会翻好几倍。允许邻居用自己的洪泛报文顺带确认能把这个数量压下来。重传间隔默认 5 秒这个值在丢包率高的链路上可能需要调大但调大意味着收敛变慢是个明确的取舍。另一个容易被忽略的机制是老化。LSA 的年龄每秒加一涨到 3600 秒就认为失效。但失效不等于直接删掉——直接删会让其他路由器的数据库里残留一份过期信息。正确做法是提前把年龄置为最大值再洪泛出去让全网一起删。这就是文档 14.1 节的提前老化。同理正常情况下 LSA 每 1800 秒要刷新一次序号重新洪泛一遍防止年龄自然涨到上限。所以哪怕网络完全静止OSPF 也有周期性的洪泛流量这是设计使然不是异常。5. 避坑排查邻居卡 ExStart、路由不进表、跨区域时通时不通5.1 现象邻居长期停在 ExStart 或 Exchange这种现象最典型的画面是邻居状态显示 ExStart重传计数一直涨链路本身 ping 得通接口没报错。原因多半是两端接口 MTU 不一致。DD 包里带 MTU 字段接收方发现对端的 MTU 比自己大就直接丢弃于是主从协商永远完不成状态卡死在 ExStart。这种情况在跨厂商设备对接、或者一端配了隧道接口时特别常见因为隧道会吃掉几十字节的头部MTU 天然比物理口小。解决办法有两个方向把两端接口 MTU 配成一致这是根治或者在接口上开启忽略 MTU 检查的选项让协议跳过这项校验。后者能快速恢复但会掩盖真实的 MTU 不匹配问题后续大包转发可能还是会出问题。我一般会先用抓包工具看一眼 DD 包里的 MTU 字段很多分析软件都有 OSPF 解析插件能直接把两端的值标出来对比比逐个接口查配置快得多。5.2 现象邻居反复 Up/Down路由表跟着抖邻居在 Full 和 Down 之间来回跳每次跳变路由表都重算一遍CPU 周期性冲高。最常见的原因是两端 Hello 定时器和 Dead 定时器不一致。Hello 包本身不携带「我的 Dead 定时器是多少」这种协商机制而是各自用自己的值判断如果一端配了 10/40另一端配了 30/120短的那一端会在对端还没来得及回应之前就宣告邻居死亡。文档附录里给的默认值在广播网络上是 10 秒和 40 秒非广播多路访问网络上是 30 秒和 120 秒同一个网段上所有路由器这两项必须完全一致。第二类原因是单向连通。链路上行通、下行不通或者某个方向的访问控制列表把组播的 Hello 过滤掉了。这种情况下双方都能收到对方的 Hello 但收不到回应表现和定时器不一致几乎一样。区分方法很简单抓包看两个方向的 Hello 是否都能到达如果只有一个方向有那问题就在链路上不在协议配置上。第三类原因是链路质量丢包率高的链路上 Hello 偶尔丢失Dead 定时器一到就断这种抖动往往是间歇性的最难查。5.3 现象LSA 在数据库里路由表里没有查数据库能看到 Type 5 的外部路由但转发表里查不到对应的条目业务不通。第一个要查的是 Type 4。AS-external-LSA 只告诉你去某个外部网络的距离和出口但没告诉你「出口那台 ASBR 怎么到达」这个信息由 ABR 生成的 Type 4 Summary-LSA 提供。如果 Type 4 缺失Type 5 就会被算出来但无法安装。Type 4 缺失的常见原因是 ASBR 所在的区域没有跟骨干连通或者 ABR 那边的汇总把 Type 4 过滤掉了。第二个要查的是转发地址。如果 AS-external-LSA 里带了非零的转发地址OSPF 会要求这个地址本身在路由表里可达否则这条外部路由不生效。这个字段本来是为了避免流量绕到 ASBR 再折返但在多出口场景下很容易出现转发地址不可达的情况。第三个要查的是优先级比较同一目的地的路由可能同时被别的协议学到管理距离的设置会让 OSPF 算出来的路由被丢弃这时候数据库里一切正常只有路由表里没有。5.4 现象跨区域路由时通时不通区域内部的明细路由一直正常跨区域的汇总路由时有时无过一会儿又自己好了。根源通常在 ABR 身上。ABR 只为「自己所在且与骨干相连」的区域生成 Summary-LSA如果某台 ABR 的骨干接口因为链路故障掉线它就不再为其他区域产生 Type 3跨区域路由会立刻消失等接口恢复又冒出来。表现出来就是间歇性断连而且故障点往往不在业务路径上非常迷惑。还有一种情况是等价多路径引起的。ABR 到某个目的网络存在多条等价路径但只有其中一条经过骨干这时候 ABR 只会把经过骨干的那条汇总出去另外一条不会反映在 Summary-LSA 里跨区域流量看起来「只走了一半带宽」。这类问题没法靠 SPF 自动解决只能靠调整接口开销或者重新规划区域来规避。5.5 现象开启认证之后邻居完全起不来配置了认证之后邻居状态停在 Init 或者干脆一直是 Down日志里什么都没有。这是 OSPF 认证最不友好的地方认证失败时协议直接静默丢弃报文不产生任何告警。所以第一件事是确认两端的认证类型和密钥完全一致包括大小写和长度。第二件事是确认配置层级——认证是逐接口或逐区域生效的不是全局开关只在一端配置、或者在错误的接口上配置都会导致单向通过、单向失败。排查方法是用抓包工具看报文里是否带了认证字段、类型字段是什么值直接对比两端的实际报文比对着配置逐行核对要快。如果认证类型是加密认证还要注意密钥本身不会被明文传输抓包只能看到类型和摘要无法反推密钥这时候只能靠两端配置一致性来保证。6. 设计取舍OSPF 把复杂度放在了哪里把这份文档从头翻到尾会发现 OSPF 的设计哲学其实很清晰它把复杂度从「算法」转移到了「一致性维护」和「分层」上。SPF 本身是一个教科书级的算法几十行代码就能实现真正撑起这份文档几百页篇幅的是围绕「怎么让所有路由器手里的图保持一致」展开的一整套机制。第一层取舍是用洪泛换无环。每台路由器都存全图、都独立算树代价是内存和带宽随规模线性甚至超线性增长收益是转发路径不可能成环、收敛速度只取决于洪泛速度而不是跳数。这个取舍在网络规模不大时非常划算规模一大就必须靠区域来兜底。第二层取舍是用区域换规模。跨区域退化成本文还有配套的精品资源点击获取