控制平面核心功能:路由协议(OSPF/BGP)的软件实现

各位同学,咱们今天聊聊控制平面最核心的活儿——路由协议。说白了,就是让路由器知道「路该怎么走」。我做了这么多年路由芯片,见过太多人把控制平面和数据平面混为一谈。其实你想想看,控制平面就像大脑,数据平面就像手脚。大脑想清楚怎么走,手脚才动得快。

OSPF:链路状态的世界

OSPF 是个典型的链路状态协议。它怎么工作的?每个路由器把自己知道的链路信息广播出去,最后全网所有路由器都有一张完整的网络拓扑图。嗯,这里要注意,OSPF 用的是 Dijkstra 算法算最短路径。

我在项目中遇到过一个问题:OSPF 收敛太慢。当时排查了半天,发现是邻居关系建立时 Hello 间隔配得太长。你想想看,默认 10 秒发一次 Hello,如果链路断了,要等 40 秒才能发现邻居挂了。这哪行?

OSPF 邻居状态机关键点:

  • Down → Init:收到 Hello 包
  • Init → 2-Way:双方都看到对方
  • 2-Way → ExStart:选举 DR/BDR
  • ExStart → Exchange:交换链路状态数据库
  • Exchange → Loading:请求缺失的 LSA
  • Loading → Full:数据库同步完成

我个人习惯,在芯片验证阶段会专门测邻居状态机的异常跳转。比如故意丢几个 Hello 包,看芯片能不能正确回退到 Init 状态。这招帮我抓过不少 bug。

BGP:路径向量的艺术

BGP 跟 OSPF 完全不同。它不关心链路状态,只关心「谁告诉我的」和「怎么过去的」。BGP 的路径选择,说白了就是比属性。AS Path 短的优先,Local Preference 大的优先,MED 小的优先……

我曾经踩过一个坑:BGP 路由的下一跳不可达。当时配置了 IBGP,但忘了配 next-hop-self。结果路由学过来了,但下一跳指向的是对端环回口,本地路由表里根本没有这条直连。嗯,从那以后我每次配 BGP 都会检查下一跳的可达性。

避坑指南:我曾经在调试 BGP 路由震荡时,发现是 keepalive 定时器和 hold time 不匹配。建议 keepalive 设为 hold time 的三分之一,留足余量。

路由表管理与维护

路由表是控制平面的核心数据结构。我见过不少芯片,路由表设计得乱七八糟,查表效率低得吓人。你想想看,数据平面每来一个包都要查路由表,如果查一次要几十个时钟周期,那还怎么跑线速?

路由表的结构设计

路由表通常用 Trie 树或者哈希表实现。我个人偏好用多级 Trie,因为支持最长前缀匹配。哈希表虽然查得快,但没法做 LPM。

// 简化版路由表条目结构
struct route_entry {
    uint32_t prefix;        // 网络前缀
    uint8_t  prefix_len;    // 前缀长度
    uint32_t nexthop;       // 下一跳 IP
    uint8_t  protocol;      // 协议来源 (OSPF/BGP/Static)
    uint32_t metric;        // 度量值
    uint32_t timestamp;     // 更新时间戳
};

这里有个细节:路由表条目一定要带时间戳。为什么?因为路由老化需要知道什么时候该删。我见过有些芯片设计者偷懒,不记时间戳,结果路由表越积越大,最后内存爆了。

路由优先级与选路规则

不同协议学来的路由,优先级不一样。直连路由最优先,静态路由次之,OSPF 内部路由又比 BGP 外部路由优先。具体优先级数值各家厂商略有不同,但逻辑是一样的。

路由来源默认优先级说明
直连路由0接口配置 IP 后自动生成
静态路由1手动配置
OSPF 内部10区域内路由
OSPF 外部150区域间路由
BGP20EBGP 学来的路由

我在项目中遇到过路由环路的问题。查了半天,发现是两条静态路由配反了。一条指向外网,一条指向内网,结果形成了环路。嗯,从那以后我养成了习惯:配完路由先 traceroute 验证一下。

控制平面与CPU的交互机制

控制平面跑在 CPU 上,数据平面跑在硬件转发引擎上。两者怎么配合?说白了就是「CPU 算好路由,下发给硬件」。但这里面的门道可不少。

消息通道的设计

CPU 和转发引擎之间通常有专门的通信通道。可以是 PCIe、AXI 总线,也可以是内部消息队列。我个人习惯用环形缓冲区,因为吞吐量高,延迟低。

消息通道的关键指标:

  • 带宽:至少是线速的 1/10,否则 CPU 下发路由会堵
  • 延迟:微秒级,否则路由收敛太慢
  • 可靠性:必须带 CRC 校验和重传机制

你想想看,如果 CPU 算出了一条新路由,但下发到硬件要 100 毫秒,那这 100 毫秒内所有去那个目的地的包都会被丢弃。这在生产网络里是不能接受的。

路由下发流程

CPU 算好路由后,会生成一条「路由更新消息」,通过消息通道发给转发引擎。转发引擎收到后,更新硬件路由表。这个过程必须原子操作——要么全部更新成功,要么回滚到旧状态。

我曾经遇到过一个问题:CPU 下发路由时,转发引擎正在查表。结果查到了一半更新的路由表,导致查表结果错误。嗯,后来我们加了双缓冲机制,一个表在更新,另一个表在服务,更新完再切换。

注意事项:路由下发过程中,千万不要直接修改正在被查表使用的内存区域。一定要用双缓冲或者写时复制机制。否则会出现「查表查到一半的数据」,后果很严重。

异常处理与保活机制

CPU 和转发引擎之间必须有保活机制。我见过一种设计:转发引擎每隔 1 秒发一个心跳包给 CPU,如果 CPU 连续 3 秒没收到心跳,就认为转发引擎挂了,触发复位流程。

反过来也一样。CPU 也要定期告诉转发引擎「我还活着」。如果转发引擎发现 CPU 挂了,应该进入「冻结模式」——保持当前路由表不变,不再接受新路由。这样至少能保证现有流量不断。

我个人习惯,在芯片设计阶段就会把保活机制做成硬件状态机。这样即使 CPU 软件跑飞了,硬件还能维持基本的路由转发。这招在不少项目里救过我的命。

控制平面与数据平面交互架构 控制平面(CPU) OSPF 协议栈 BGP 协议栈 路由表管理 消息封装 数据平面(转发引擎) 硬件路由表 查表引擎 消息解析 保活状态机 消息通道 路由更新 / 心跳 保活心跳(1s 间隔) 关键交互流程: 1. CPU 运行 OSPF/BGP 协议栈,计算路由表 2. CPU 将路由更新封装成消息,通过消息通道下发 3. 转发引擎解析消息,更新硬件路由表(双缓冲机制) 4. 双方通过心跳保活,异常时触发复位或冻结模式

这张图把控制平面和数据平面的交互关系画得很清楚。左边是 CPU 上跑的协议栈和路由管理,右边是硬件转发引擎。中间的消息通道是桥梁。你想想看,如果这个通道设计得不好,CPU 算得再快也没用。

好了,这一章的内容就到这里。路由协议的实现、路由表的管理、CPU 与转发引擎的交互,这三块是控制平面的核心。搞懂了这些,你就能理解路由器是怎么「思考」的了。

专注资料整理