OSPF 排错排到某个阶段,一定绕不开 LSDB 这张表;而 LSDB 里躺着的,就是一条一条 LSA。很多人背过"1 类路由器、2 类网络、3 类汇总、4 类 ASBR 汇总、5 类外部、7 类 NSSA 外部"这串口诀,考试能过,真到设备前面看 LSDB 输出还是懵——因为口诀只告诉你名字,没告诉你每条 LSA 是谁生成的、在哪个范围泛洪、被谁消费、少了它会出现什么现象。链路状态通告(Link State Advertisement)这个中文叫法其实挺贴切:它是一种"通告",既不是路由,也不是拓扑本身,而是把本地视角下的链路状况"广播"出去的一份声明。所有路由器把这些声明收齐,各自跑一遍最短路径优先算法,才拼出了一张完整的网络地图。
这篇内容想做的事情很简单:把每一类 LSA 放回它真实的生成位置和消费路径上,讲清楚它为什么必须存在、它和其他类型之间是怎么咬合的,以及在真实环境里少一条、错一条会表现成什么样。适合已经能配通 OSPF 邻居、但一看到区域间或外部路由不通就开始乱猜的人,也适合需要从 LSDB 层面定位问题的网络运维人员。全程不玩概念堆砌,尽量用"谁生成、发给谁、拿来干什么"这条线索把六种主要类型串成一条链。
1. 把 LSA 放回它该在的位置:LSDB 里到底存的是什么
1.1 为什么必须用"广播式公告"而不是邻居之间口口相传
距离矢量协议的通路逻辑很好理解:我把我知道的路由表告诉邻居,邻居挑一挑加进去再传给别人。问题在于,任何一台路由器看到的都只是别人转述过的结论,而不是网络本来的样子,一旦中间某台设备的表出错,错误会沿着邻居关系一路传下去,收敛慢、还容易绕环。链路状态的做法完全换了思路:每台路由器只负责如实描述自己身上的接口和链路状态,把这份描述打包成 LSA 发出去,全区域所有路由器保存同一份完整副本,然后各自独立算路。
这两种思路的差别,用生活里的例子打个比方——前者像是问路,每个人告诉你"往那边走能到",信息经过几手之后已经变形;后者像是所有人共同维护一张地图,每个人只负责标注自己家门口那条街的实际情况,地图始终是完整的,谁都能自己规划路线。代价也很明显:每台设备都要存储整张地图,都要跑计算,所以 LSA 的数量和泛洪范围直接决定了协议的可扩展性。后面所有类型的划分,本质上都是在回答一个问题:这条信息应该让多大范围内的路由器知道。
一个区域内所有路由器的 LSDB 必须完全一致,这是 OSPF 能正常工作的前提。注意"区域内"这三个字,跨区域之后 LSDB 就不再一致了,因为区域间信息被抽象成了另一种 LSA,这个抽象的边界正是 3 类和 4 类存在的地方。理解了这个分层,后面很多"为什么这条路由在那边看不到"的疑问会自然消解。
1.2 LSA 头部那八个字段,每一个都在解决一个具体问题
每条 LSA 都以固定的 20 字节头部开头,字段不多,但每个都在处理一个实际存在的工程问题。看懂了头部,看 LSDB 输出的时候就不会只看 Metric 一列。
| 字段 | 长度 | 解决的问题 |
|---|---|---|
| LS Age | 16 位 | 这条通告有多旧,超过 3600 秒视为不可用 |
| Options | 8 位 | 本区域支持哪些扩展能力,比如是否支持外部路由 |
| LS Type | 8 位 | 这是第几类 LSA,决定正文怎么解析 |
| Link State ID | 32 位 | 这条 LSA 描述的对象标识,不同类型含义不同 |
| Advertising Router | 32 位 | 谁生成的,出问题先找它 |
| LS Sequence Number | 32 位 | 新旧判定,越大越新 |
| LS Checksum | 16 位 | 完整性校验,计算时排除 Age 字段 |
| Length | 16 位 | 整条 LSA 的字节长度 |
真正用来唯一定位一条 LSA 的是三元组:类型、Link State ID、通告路由器。这三个值合起来相同,才被认为是同一条 LSA 的不同版本。这一点在排错时极其关键——很多"我明明改了配置,对面怎么还是老样子"的情况,本质上是新生成的 LSA 序列号没有比旧的大,接收方按规则丢弃了。
序列号从 0x80000001 开始,一路线性递增到 0x7FFFFFFF,数值越大代表越新。中间如果发生回绕,设备会先把这条 LSA 提前老化到最大年龄再重新开始,避免新旧判断彻底混乱。老化机制同样值得记住:一条 LSA 生成后默认每 1800 秒刷新一次,年龄涨到 3600 秒就会被从 LSDB 中清除。所以你在设备上看到某条 LSA 的 Age 接近 3600,通常意味着生成它的那台设备已经失联,而不是这条路由本身有问题。
1.3 泛洪的可靠性靠的是三步握手,不是"发出去就完事"
LSA 能保持全区域同步,靠的是可靠的泛洪流程。发送方把 LSA 封装在更新报文里发出去,接收方收到后先比序列号:比自己手里的新就装进 LSDB 并回一个确认,比自己手里的旧就反过来把更新的那份回给对方,完全一样就只回确认。发送方在重传定时器到期前没收到确认,就会重发。这套机制导致一个现象:邻居关系刚建立到双向通信之后,会有一次集中的数据库描述报文交换,双方互相比对摘要,把对方有自己没有的 LSA 用请求报文要过来,这段过程就是邻居状态里的交换和加载两个阶段。
我见过不少人把邻居卡在加载状态当成"链路问题"去查物理层,实际上绝大多数情况是 MTU 不一致导致大尺寸的 LSA 传不过去,或者两边 LSDB 里存在序列号冲突的残留条目。前者表现为小 LSA 能同步、大 LSA 反复重传;后者表现为某一条 LSA 一直请求不下来,日志里能看到序列号相关的告警。把这两个方向分开查,比盲目重启进程有效得多。
2. Type 1 与 Type 2:区域内部那张不可替代的原始地图
2.1 Type 1 里装的不是路由,是"我连着谁"
路由器 LSA 由区域内每一台路由器各自生成,描述的是本机在这个区域内的所有链路。注意是"每个区域各生成一条",一台同时属于三个区域的多区域设备,会生成三条内容完全不同的 1 类 LSA,每条只描述自己在该区域内的接口状况。这一点是很多跨区域故障的根源:有人在一台边界设备上改了个接口开销,结果只有那一个区域的拓扑变了,其他区域根本看不到。
1 类 LSA 的正文里是一串链路描述,每条描述由链路类型、链路标识、链路数据和开销四部分组成。四种链路类型对应四种不同的连接场景:
- 点到点:链路标识填邻居的路由器标识,链路数据填本地接口地址,开销是接口开销。描述的是两台设备直连的链路。
- 传输网络:链路标识填该网段上指定路由器的接口地址,链路数据填本地接口地址。描述的是接入广播网段或非广播多路访问网段。
- 末梢网络:链路标识填网段地址,链路数据填子网掩码,开销是接口开销。描述的是只有本机接入、没有其他邻居的网段,比如一个纯服务器网段。
- 虚链路:链路标识填虚链路对端的路由器标识,用于跨越传输区域建立逻辑连接。
除了链路描述,1 类 LSA 里还有三个标志位值得单独拎出来:V 位表示本机是虚链路的端点,E 位表示本机是自治系统边界路由器,B 位表示本机是区域边界路由器。这三个位是后面 4 类和 5 类能否被正确解析的前提。举个例子,某个区域里的路由器收到一条 5 类外部 LSA,它需要先知道"谁是这个外部路由的引入者",这个信息就来自本区域 1 类 LSA 里的 E 位。如果 E 位没置起来,即便 5 类 LSA 就在 LSDB 里躺着,也不会被用来计算路由。
2.2 Type 2 的存在,是用 DR 机制换来的一次"数量级削减"
广播网段上如果不做任何优化,N 台路由器两两建立邻接关系,会有 N×(N-1)/2 条邻接,每条邻接都要同步一次数据库,LSDB 里的链路描述数量同样按平方级增长。指定路由器机制就是来解决这个问题的:同一网段上选出一台指定路由器和一台备份指定路由器,其余设备只和它们建立完全邻接,网段拓扑被压缩成"一台代表 + 若干成员"的结构。
网络 LSA 就是这台代表生成的,内容包括该网段的子网掩码,以及所有与它形成完全邻接关系的路由器标识列表。链路状态标识填的是指定路由器在该网段上的接口地址,通告路由器自然也是它。这里有两个容易忽略的细节:一是只有存在指定路由器的网段才会产生 2 类 LSA,点到点链路上没有;二是 2 类 LSA 只在生成它的区域内部泛洪,跨区域时它的信息会被边界路由器压缩成 3 类。
我在实际环境里遇到过一种情况:某台设备接口配置正常、邻居也能起来,但就是学不到同网段其他设备的路由。查下来是该接口的优先级被改成了 0,永远不参与指定路由器选举,而网段上原指定路由器又刚好重启,选举结果反复变化,导致 2 类 LSA 的生成者频繁切换,序列号一路飙升。这种问题的表象是 LSDB 里同一条 2 类 LSA 的年龄总是很新,收敛特别慢。看到这类现象,先看选举稳定性,比查链路质量更靠谱。
2.3 最短路径树展开时,1 类和 2 类是交替参与计算的
计算过程以本机为根节点,先展开自己生成的 1 类 LSA,把所有直连链路挂到树上。遇到点到点链路,对端节点就是邻居路由器,继续展开对方的 1 类;遇到传输网络链路,先挂上一个代表网段的虚拟节点,这个节点由 2 类 LSA 描述,再从 2 类 LSA 里的路由器列表向其他设备展开;遇到末梢网络链路,直接把它作为叶子节点算出前缀和开销。
这个过程解释了为什么同一区域内所有设备算出的拓扑必须一致——它们用的是同一批 1 类和 2 类 LSA。同时也解释了为什么区域内路由的可信度最高:这是唯一一段真正基于完整拓扑算出来的路径,后面的 3 类和 5 类本质上都是在这个结果上追加的抽象信息。
3. Type 3 与 Type 4:跨区域时被抽象掉的那部分拓扑
3.1 Type 3 传的是前缀加开销,不是拓扑
区域边界路由器把本区域算出来的内部路由,转成汇总 LSA 通告到其他区域。链路状态标识填目的网络地址,通告路由器是这台边界设备自己,度量值填它到达该网络的代价。接收方拿到这条 LSA 之后不做最短路径优先计算,而是直接把它当成一条挂在边界路由器节点下的叶子路由——也就是说,跨区域这一段是距离矢量式的,只是这个"矢量"被包在了链路状态协议的壳里。
这个设计是 OSPF 层次化的核心。它带来的好处很明显:其他区域不需要知道源区域内部的任何细节,一台核心区域设备不用存储几百条外部区域的链路描述,区域内的拓扑变化也不会引发全网络的重新计算。代价也很明显:3 类 LSA 的度量值不是端到端的真实开销,而是边界路由器视角下的开销,接收方无法据此还原完整路径。
举个具体场景。区域 A 内有一条路径,从边界路由器到目标网段累计开销是 30;另一条路径的开销是 50。边界路由器把较小的 30 通告出去,接收方看到的就是 30。如果区域 A 内部后来发生变化,两条路径的开销变成 40 和 45,边界路由器会重新生成 3 类 LSA,新度量值是 40。接收方只会看到数值从 30 变成 40,它不知道内部发生过什么,也不需要知道。这种"只报结果"的方式在排错时特别容易误导人——你在接收端算半天路径,算出来的和实际走的根本不是一回事。
另外要注意默认路由也是以 3 类的形式下发的,链路状态标识填全零地址加零掩码。末梢区域和完全末梢区域的默认路由都来自这里,这也是为什么末梢区域明明没有 5 类 LSA 却能访问外部网络。
3.2 Type 4 是 5 类能被解析的前置条件,缺了它外部路由就是死数据
自治系统边界路由器汇总 LSA 的存在,完全是为了解决一个定位问题:其他区域收到了 5 类外部路由,知道"这条外部网络是通过某个路由器标识引入的",但这个路由器标识在本地拓扑里找不到,因为它在另一个区域。怎么找到它?靠 4 类。
4 类由区域边界路由器生成,链路状态标识填那个自治系统边界路由器的标识,通告路由器是边界设备自己,度量值是到达该边界路由器的开销。它相当于告诉其他区域:"你们要找的那个引入者,挂在我后面,从我这里过去要花这么多代价。"接收方先在 3 类里找到通往该边界设备所在区域的路径,再用 4 类定位到具体设备,最后才能用 5 类算出完整的外部路由。
这里有个非常经典的故障模式:外部路由能学到但走不通,或者干脆学不到,查 LSDB 发现 5 类有、4 类没有。原因通常是边界设备没有正确置位 1 类 LSA 里的 E 位,或者该区域本身是末梢区域被过滤掉了 4 类和 5 类。还有一种更隐蔽的情况——不影响转发的区域过滤配置把 4 类拦了下来,5 类却放行了,结果就是 LSDB 里有外部条目但路由表里没有,非常容易误判成重分发没生效。看到 5 类存在而路由表为空,第一反应应该是查 4 类是否可达,而不是去动重分发配置。
4. Type 5 与 Type 7:外部路由进入 OSPF 的两条通道
4.1 Type 5 的 E1/E2 选择,直接决定路径是不是真的最优
自治系统外部 LSA 由引入外部路由的那台设备生成,在整个自治系统范围内泛洪,除了末梢区域和完全末梢区域之外都能收到。链路状态标识填外部目的网络地址,通告路由器是引入者,正文里除了度量值之外还有几个关键字段:度量类型、转发地址、外部路由标记。
度量类型分两种。第一类表示外部开销加上本机到引入者的内部开销,属于"累加式",路径选择更贴近真实代价;第二类只算外部开销,忽略内部距离,属于"绝对式",也是绝大多数实现的默认值。这个默认值带来的坑非常多:当同一目的网络被两台设备以第二类度量引入时,接收方只比较外部开销,完全不管自己离哪台引入者更近,结果可能就是明明有台引入者就在隔壁,流量却绕了大半个网络去另一台设备。
我在一个双出口环境里踩过这个坑。两台边界设备都引入了同一条外部路由,外部开销配置得一模一样,都是默认值。结果所有流量都往距离远的那台跑,因为第二类度量相等时会比较其他字段,最终选了个对端看着更"优"的。改成第一类度量之后,内部开销被计入,路径立刻回到了就近出口。这个改动的影响面不小——全网所有设备都会重新计算外部路由,规模大的环境要挑时间窗口做。
转发地址是另一个值得单独说的字段。填全零表示流量必须经过引入者转发;填非零地址表示流量可以直接送往那个地址,不必绕到引入者。这个设计原本是为了解决外部网络和本自治系统共用同一段广播网络时的次优路径问题,但在实际部署里经常被忽略,导致明明可以直连的流量非要绕一圈。检查方法很直接:看 5 类 LSA 的转发地址字段是不是全零,再对比外部网络的真实位置。
4.2 Type 7 是末梢区域里的"临时通行证",P 位决定它能不能出圈
末梢区域不允许 5 类 LSA 进入,但区域内的设备有时候确实需要引入外部路由,比如挂在这个区域里的某台设备连着一条外部专线。这种区域被定义为非纯末梢区域,它的解决办法就是引入第七类 LSA。
7 类的格式和 5 类非常接近,度量类型、转发地址、外部标记这些字段都有,区别在于它只在非纯末梢区域内部泛洪,不能直接出区域。区域边界路由器收到 7 类之后,需要把它转换成 5 类才能通告给其他区域。转换不是无条件的,靠的是 7 类 LSA 正文里自带的选项字段中的传播位——这一位置位才转换,清零就只在本区域可见。
这个位什么时候会被清零?最常见的情况是引入者自己配置了抑制传播,或者转发地址指向的接口不在本区域内。前一种是人为选择,后一种是配置失误。我遇到过后者:引入者把转发地址填成了区域外的一个接口地址,结果边界路由器检查后发现该地址不可达,直接放弃了转换。表现就是本区域能访问外部网络,其他区域全部不通,而 LSDB 里 5 类根本不存在。这种故障如果只看本区域,一切正常,非常容易漏掉。
转换发生时还有两个细节值得记住。一是边界路由器会成为新的通告者,也就是说转换后的 5 类 LSA 的通告路由器字段填的是边界设备自己,不再是原始引入者,这在追踪外部路由来源时容易造成误判。二是如果原始 7 类的转发地址是全零,边界路由器在转换时通常会把转发地址填成自己在区域内的接口地址,避免流量绕回原引入者。至于默认路由,非纯末梢区域的边界路由器一般会生成一条 7 类的默认路由,传播位通常置位,转换后其他区域也能收到。
5. 串起来看:一条外部路由从引入到进表的完整链路
5.1 分四个阶段追踪,每一阶段都能单独验证
光知道每种 LSA 干什么还不够,真正有用的是把它们的协作顺序理清楚。拿一个典型场景走一遍:区域 0 有一台引入外部路由的设备,区域 1 里有一台普通路由器要访问这条外部网络。
第一阶段,引入者在自己的区域内生成 5 类 LSA,同时在自己的 1 类 LSA 里把 E 位置起来,标记自己是引入者。这条 5 类被泛洪到所有允许 5 类存在的区域,区域 1 能收到。
第二阶段,区域 0 的边界路由器在区域 1 内生成 3 类 LSA,把通往引入者所在网段的路径通告进来;同时生成 4 类 LSA,把引入者的位置和到达代价通告进来。这一步是很多人忽略的关键环节,3 类和 4 类是一对搭档,缺一不可。
第三阶段,区域 1 里那台普通路由器收到这三类信息后开始算路:先查 1 类 2 类确认自己在区域内的位置,再用 3 类确定怎么到边界路由器,用 4 类确定怎么到引入者,最后套用 5 类的外部度量。三步都通了,才会往路由表里写这条外部路由。
第四阶段是度量比较。如果外部度量类型是第二类,就比较外部开销本身;如果相等,再看内部到引入者的开销;如果是第一类,直接累加。这一阶段决定了多出口环境下流量走哪边。
把这四个阶段拆开之后,排错就有了明确的分段验证思路:先确认本区域 1 类 2 类正常,再确认 3 类 4 类齐全,最后看 5 类的度量类型和转发地址。而不是一上来就盯着 5 类猛看。
5.2 区域类型本质上是一张 LSA 准入名单
不同区域类型之间的差异,说到底就是允许哪几类 LSA 进来。把这张表记住,很多配置意图就一目了然了。
| 区域类型 | 允许的 LSA 类型 | 被过滤的类型 | 默认路由来源 |
|---|---|---|---|
| 普通区域 | 1、2、3、4、5 | 7 | 无自动下发 |
| 末梢区域 | 1、2、3 | 4、5、7 | 边界设备下发 3 类默认路由 |
| 完全末梢区域 | 1、2 | 3、4、5、7 | 边界设备下发 3 类默认路由 |
| 非纯末梢区域 | 1、2、3、7 | 4、5 | 7 类或 3 类默认路由 |
| 完全非纯末梢区域 | 1、2、7 | 3、4、5 | 边界设备下发 3 类默认路由 |
这张表解释了一个高频困惑:为什么末梢区域里做外部路由引入完全不生效。答案很直接——引入产生的是 5 类,而末梢区域根本不允许 5 类存在,边界路由器收到就丢。想让引入生效,只能把区域改成非纯末梢类型,用 7 类走内部通道。
反过来,完全末梢区域里连 3 类都被过滤,只保留一条默认路由,区域内的设备 LSDB 极其干净,资源占用最低。这种设计适合那种只有终端、没有其他出口的边缘区域,但如果有任何跨区域访问的精细化需求,就会被默认路由彻底压平,不建议滥用。
5.3 三类路由同时存在时,优先级是固定的
同一个目的网络,可能同时存在区域内路由、区域间路由和外部路由,选哪条不是靠度量值大小决定的,而是先比类型。区域内最优,区域间次之,外部再次之。外部内部还要分,第一类比第二类优先。
这套顺序导致一个反直觉的现象:某条外部路由的度量值看起来远小于区域间路由,但实际生效的还是区域间那条。很多人在这种时候会怀疑配置写错了,其实完全是正常的选路规则。要改变结果只能调整区域划分或者引入范围,纯粹改度量值没用。
6. 从 LSDB 输出反推配置问题:一份实战对照
6.1 常见现象与成因对照
把前面这些内容落到排错上,最有价值的是一张现象与成因的对照表。下面这些是我在实际环境里反复遇到的组合,按出现频率排序。
| 现象 | 优先排查方向 | 具体检查点 |
|---|---|---|
| 外部路由学不到 | 4 类是否缺失 | 引入者所在区域的 1 类里 E 位是否置位;区域类型是否过滤了 4 类 |
| 外部路由条目在但表里没有 | 转发可达性 | 5 类的转发地址是否可达;4 类指向的引入者是否能算出路径 |
| 本区域通、其他区域不通 | 7 类传播位 | 7 类的传播位是否被清零;转发地址是否落在区域外 |
| 收敛极慢、LSDB 频繁刷新 | 指定路由器选举 | 2 类 LSA 生成者是否反复切换;接口优先级是否异常 |
| 同一前缀两条外部路由都学不到优选 | 度量类型 | 是否都是第二类度量导致只比外部开销 |
| 跨区域路径和预期不符 | 3 类度量含义 | 3 类度量是边界设备视角的开销,不是端到端 |
看 LSDB 的时候,建议按类型顺序扫,而不是按前缀搜。先看 1 类和 2 类确认本区域拓扑完整,再看 3 类 4 类确认边界信息齐全,最后看 5 类 7 类确认外部信息落地。这个顺序和协议本身的层次是一致的,顺着走不容易迷路。
6.2 几个我踩过、也见过别人反复踩的坑
第一个坑是把 1 类 LSA 当成全局的。一台多区域设备在每个区域各生成一条 1 类,内容互不相干。有人在一台边界设备上看到区域 0 的 1 类里没有某个接口,就断定接口配置有问题,其实那个接口属于区域 1,压根不会出现在区域 0 的 1 类里。判断方法很简单,看这条 1 类的泛洪范围,只在单个区域内有效的就是这种。
第二个坑是改接口开销之后只影响一个区域。同理,开销写在 1 类里,只影响本区域的拓扑计算。跨区域的开销由 3 类携带,是边界设备重新计算的,不会因为你改了某个内部接口的开销就自动传导过去。想让跨区域路径变化,得改边界设备上的相关配置。
第三个坑是忽略序列号的残留。设备异常重启或者进程重启之后,旧的 LSA 可能还在邻居的 LSDB 里,新生成的序列号如果没比旧的大,就会被丢弃。表现是配置明明改了,对面收到的还是老内容。处理办法是查这条 LSA 的通告者和序列号,必要时清一下对应类型的数据库强制重建。这个操作影响面不小,规模大的环境要评估。
第四个坑是把 4 类缺失当成 5 类问题。前面提过,但值得再强调一次。外部路由不通,先看 4 类在不在。如果 4 类没有而 5 类有,问题百分百在区域边界或者区域类型上,动重分发配置纯属浪费时间。
第五个坑是转发地址随手填。引入外部路由时如果配了转发地址,一定要确认这个地址在引入者所在的区域内是可达的,否则 7 类的转换会失败,5 类的解析也会出问题。我一般的做法是能不填就不填,让流量走引入者,虽然可能不是最优,但至少路径可预期、排错链路短。真要优化路径,先确认外部网络和本自治系统确实共用一个网段,再考虑填转发地址。
第六个坑是区域类型改完不验证 LSDB。把普通区域改成末梢区域之后,5 类和 4 类会被立刻清除,依赖外部路由的业务会瞬间中断。改之前应该先把区域内所有设备的外部路由依赖梳理清楚,改完之后按类型逐条核对 LSDB,确认只留下 1 类 2 类和 3 类,以及那条默认路由。我见过改完之后默认路由没下发成功,整个区域直接断网的情况,原因就是边界设备上少配了一条命令,LSDB 里干干净净什么都没有。
把这几类问题对照着看会发现一个共性:绝大多数 LSA 相关的故障,不是协议本身出了问题,而是信息在"生成—泛洪—消费"这条链路上断了一环。定位方法始终是一样的——先确定这条信息该由谁生成,再看它在不在、泛洪范围对不对,最后看消费它的前置条件齐不齐。按这个顺序走,比凭经验乱猜快得多。