在 OSPF 协议中,两台路由器要成为邻居并同步链路状态数据库,必须经历几个严格的状态迁移。其中,ExStart和Exchange阶段使用 Database Description(DD)报文来交换所有 LSA 的摘要信息。这些 DD 报文直接跑在 IP 层上(协议号 89),下层没有任何像 TCP 那样的确认和重传机制。为了保证摘要信息一条不丢、顺序不乱,OSPF 引入了一种精巧的隐式确认机制。本文将对这一机制进行详细拆解。
一、为什么需要可靠的 DD 报文传输
OSPF 的链路状态数据库由几十上百条 LSA 构成。在数据库同步初期,路由器不会直接交换完整的 LSA,而是先交换 LSA 头部列表(即摘要),让对方知道自己有哪些 LSA。这个过程好比两个人核对各自的货物清单,必须先确保清单内容完整无误地送到对方手上,后续才能针对缺失条目提出请求(LSR/LSU)。
IP 网络本身是“尽力而为”的,报文可能丢失、乱序或重复。因此,OSPF 必须在协议自身层面为 DD 报文提供:
- 无丢失:每条 DD 报文要么被收到,要么被重传直到成功;
- 有序:双方必须按照相同的顺序处理这些摘要;
- 无重复:不能因为重传导致同一条摘要被处理多次。
TCP 通过序列号和 ACK 来实现这些,OSPF 没有独立的三次握手和 ACK 报文,于是它把确认信息“藏”在了 DD 报文自身的交互过程中——这便是隐式确认。
二、隐式确认的前置条件:Master/Slave 协商
在开始交换摘要之前,两台路由器需要先确立一个主从关系。这个过程在ExStart阶段完成,同样是靠空的 DD 报文(不含 LSA 摘要)来协商。
- 双方都会发送一个空的 DD 报文,里面包含自己提议的初始序列号、以及是否愿意成为 Master(通过 MS 位表示)。
- 接口 IP 地址较大的路由器会成为 Master,另一方成为 Slave。这个角色一旦确定,在整个 Exchange 阶段就不再改变。
- 最终,Master 选取自己提议的序列号作为起始值,Slave 必须认同并使用该序列号进行后续交互。
主从关系的核心作用在于:所有序列号的推进都由 Master 单方面控制,Slave 只能被动地“复制”序列号。这就为隐式确认提供了严格的参照基准。
三、隐式确认的工作过程详解
进入 Exchange 阶段后,双方开始用携带 LSA 摘要的 DD 报文交换信息。此时,序列号的变化规律直接决定了确认的完成方式。我们可以用几步把规则拆开来看。
1. 序列号的使用规则
- Master 的发送:Master 每次要发送**包含新数据(之前未发送过的 LSA 摘要)**的 DD 报文时,都会把序列号加 1。如果只是重传上一次的报文,序列号保持不变。
- Slave 的回应:Slave 收到 Master 发来的 DD 报文后,必须在自己的回应报文里原封不动地使用相同的序列号。同时,Slave 也可以在这个回应中附上自己还没有发送过的 LSA 摘要。
- 确认的判定:
- 对 Master 来说,只要收到了 Slave 返回的、序列号与自己刚发报文相同的 DD 报文,就意味着那条报文已经被成功接收。
- 对 Slave 来说,如果它发出回应后,又收到了来自 Master 的序列号已经递增了的新 DD 报文,就说明 Master 已经收到了自己的回应;因为 Master 只有在确认收到上一条 Slave 回应后,才会推进序列号并发送新内容。
2. 一个完整的交互示例
假设路由器 A 为 Master,B 为 Slave,初始序列号协商为 100。交互过程如下:
A → B:DD(seq=100,M=1,I=1)
A 发送第一个报文,包含部分 LSA 摘要,M 位设为 1 表示后面还有更多摘要。I 位在第一个报文中置 1,表示这是初始报文(仅 ExStart 后的第一个 Exchange 报文使用)。B 收到后:它知道 seq=100 的报文已到。B 准备自己的 LSA 摘要,并构建回应报文。
B → A:DD(seq=100,M=1)
这条报文同时完成三件事:- 对 A 刚才那条 seq=100 报文的隐式确认(“我收到了你的 seq=100”);
- 携带 B 自己的第一组 LSA 摘要;
- 向 A 表明 B 还想继续发送,M 位=1。
A 收到 B 的 seq=100 回应后,确认自己第一条报文已送达。现在 A 准备下一组摘要。因为要发新内容,序列号递增为 101。
A → B:DD(seq=101,M=1)
这条报文不仅发送了 A 的第二组摘要,还隐式地告诉 B:“你上次的 seq=100 回应我已经收到了,因为我把序列号变成了 101。” 如果 A 没有收到 B 的回应,它会一直重传 seq=100,而不会跳到 101。B 收到 seq=101 后,知道 A 确认了自己之前的回应,同时拿到新的摘要。B 再次构造回应:
B → A:DD(seq=101,M=1)
又一次完成对 A 的确认并发送自己的新摘要。重复此模式:Master 递增序列号,Slave 复制序列号。序列号充当了“数据编号”和“确认回执”的双重角色。
当某一方(假设 A)发送最后一个摘要时,它会将 M 位置 0。
A → B:DD(seq=200,M=0)
B 收到后,知道 A 发完了。B 仍需发送一个回应:
B → A:DD(seq=200,M=1 或 M=0,视 B 自己是否发完)
这个回应是对 A 最后一个报文的确认。A 收到后才算真正完成。如果这个回应丢失,A 会因定时器超时重传 seq=200 的报文,直至收到确认。
3. 丢包时的恢复过程
假设 Master 发出的 seq=105 报文在网络上丢失了,Slave 根本没收到。此时:
- Master 在发出 seq=105 后启动重传定时器(默认 5 秒),等待 Slave 的 seq=105 回应。
- Slave 对 seq=105 毫不知情,所以不会主动发送任何东西。
- 5 秒后定时器超时,Master 重传 seq=105 的 DD 报文(序列号仍然是 105)。
- Slave 收到这个“迟到”的 seq=105,正常处理摘要,并像往常一样回应一个 seq=105 的报文。
- Master 收到回应,确认完成,之后再将序列号推进到 106。
如果是 Slave 发出的回应丢失,Master 同样会在超时后重传原来的 seq,促使 Slave 重新生成一次回应。整个过程不需要任何额外的控制报文。
四、为什么这样设计能工作得很好
看似简单的“你发一个、我回一个、序号加一”的机制,其实隐含了三个强约束,恰好满足了可靠传输的所有需求:
自然形成停止-等待协议
Master 必须在收到当前序列号的 Slave 回应后,才能发送下一个序列号的新报文。这强制 Master 任何时候只允许有一个未确认的“在途报文”。窗口大小为 1 的停止-等待协议虽然效率不算最高,但对于 DD 报文通常只有几十个、且数据量很小的场景完全够用,而且实现极其简单,不需要复杂的滑动窗口和缓冲区管理。确认与数据合并,减少报文开销
在 Exchange 阶段,DD 报文本来就需要双向交换(双方都要把自己的摘要告诉对方)。隐式确认将 ACK 功能合并到反向数据报文中,省去了独立的 ACK 包。尤其当 LSA 数量较多时,节省的报文数量很可观。利用主从非对称性简化状态机
只有 Master 有权改变序列号,Slave 只需机械地复制。因此,双方判断是否收到新数据或确认的逻辑都非常简单:比较序列号是否等于期望值。这大幅降低了代码实现的复杂度,也使得故障排查时有非常清晰的期望值可以对照。
五、实现中需要严格注意的细节
尽管概念上简单,但实际实现和运维中仍有几个关键点容易被忽视。
序列号的严格处理
- Master 发送新摘要时,序列号必须严格 +1。任何跳变都会让 Slave 视为错误而丢弃报文。
- 重传时,序列号绝对不能变。如果变了,Slave 会认为这是一个新报文,可能导致摘要重复处理或状态混乱。
- Slave 回应的序列号必须与触发它的 Master 报文完全相等。如果 Slave 自己“发明”了一个序列号,对端会直接忽略。
重传定时器与邻居失效
- Master 每发出一条需要确认的 DD 报文,就会启动一个 RxmtInterval 定时器(常见默认值 5 秒)。
- 如果超时未收到期望的 Slave 回应,报文重传,定时器重置。
- 如果持续重传直到邻居失效定时器(RouterDeadInterval,通常 40 秒)到期仍未成功,路由器宣告邻居 Down,回到 Init 状态。
- Slave 侧通常不维护独立的重传定时器,它的“重传”是由收到一个重复序列号的 Master 报文被动触发的。因此 Slave 必须能够正确处理重复的 Master DD。
MTU 不匹配时的故障特征
DD 报文头部携带了接口 MTU 值。如果两台路由器直接相连的接口 MTU 不同,且较大的 DD 报文未被 IP 层分片或丢弃,可能导致报文被静默丢弃。此时,隐式确认表现为 Master 一直重传同一个序列号(例如 seq=100 发了多次),Slave 毫无回应。通过抓包看到大量重复的 DD 报文且序列号不推进,就应该立即检查两端的 MTU 是否匹配。
M 位结束时的最后一轮确认
当 Master 发送 M=0 的报文后,它并不会立即进入 Loading 阶段,而是必须等待 Slave 对该报文的确认。如果 Slave 自己还有摘要没发完,它可以回复 M=1 的报文并继续发;否则回复 M=0。无论如何,这最后一次交互必须完成,任何一方的丢失都会触发生重传。协议状态机中对此有明确的规定。
六、总结
OSPF 的 DD 隐式确认机制通过主从角色分配和序列号镜像规则,在没有独立确认报文的情况下,实现了 DD 摘要信息的可靠、有序交换。它实质上是一个精心设计的停止-等待协议,将确认、重传和流量控制集成到了序列号的单调整递增和镜像回传中。