主题
动态路由:OSPF
前十章的所有实验都用静态路由:
bash
ip route add 10.2.0.0/24 via 10.99.1.2两个站点、一条链路时这没问题。这一章讲为什么规模一上来就不行, 以及 VPP 上的动态路由是怎么工作的。
VPP 自己不跑路由协议
这是理解这一章的前提。
VPP 是纯转发面。路由协议由外部的控制面进程跑 —— netlab 的 VPP 镜像 用的是 bird(也支持 FRR),它跑在容器的 dataplane netns 里。
三段链条:
text
1. bird 跑 OSPF,学到路由,写进 netns 的内核路由表
2. linux-cp 插件的 netlink 监听器捕获这个变化
3. 路由被同步进 VPP 的 FIB,数据面按它转发这个架构决定了排错要分两段看
bash
# 控制面:协议层面对不对
docker exec <容器> ip netns exec dataplane birdc show ospf neighbors
docker exec <容器> ip netns exec dataplane birdc show route
# 数据面:路由有没有真的进转发表
docker exec <容器> vppctl show ip fib 10.2.0.0/24两边都要看。 邻接建起来了但 FIB 里没有,说明 linux-cp 那一环出了问题; FIB 里有但不通,那是转发面的事(邻接解析、feature 拦截等)。
实验拓扑
启动动态路由底座
bash
./labctl up ospftext
hA 10.1.0.11 ── r1 ══ WAN-1 198.51.100.0/30 ══ r2 ── hB 10.2.0.11
╚══ WAN-2 198.51.100.4/30 ══╝两条平行的 WAN 链路 —— 这是演示动态路由价值的最小拓扑:能学路由,也能看多路径。
netlab 的 OSPF 配置在 --no-config 下也生效
本教程所有底座都用 netlab up --no-config(为了抢在 VPP 启动前改配置)。 那 OSPF 配置从哪来?
netlab 把它生成在一个独立文件 node_files/<节点>/ospf, 并在 clab.yml 里挂载到容器的 /etc/bird/ospf.mod.conf:
yaml
binds:
- node_files/r1/bird:/etc/bird/bird.conf
- node_files/r1/ospf:/etc/bird/ospf.mod.conf主配置里有一行 include "/etc/bird/ospf.mod.conf";。 容器启动时 bird 自动加载 —— 完全不依赖 netlab 的配置部署阶段。
生成的内容也是对的:
text
protocol ospf v2 ospf_global_v2 {
area 0.0.0.0 {
interface "loop0" { stub; };
interface "eth1" { stub; type ptp; }; ← LAN 口是 stub
interface "eth2" { type ptp; }; ← WAN 口跑协议
interface "eth3" { type ptp; };
};
}LAN 口设成 stub 是标准做法:那边没有别的路由器, 发 Hello 纯属浪费,但网段本身要被通告出去。
零手工路由即互通
底座起来后直接就通了:
text
── 1) 零手工路由,站点已互通 ──
3 packets transmitted, 3 packets received, 0% packet loss
✓ hA → hB 全通
整个过程没有执行过一条 ip route add对比前十章 —— 每个隧道实验都要在两端手工配 ip route add, 少配一边就单向不通。
自动 ECMP
bash
./labctl vppctl ospf show ip fib 10.2.0.0/24text
[@0]: ipv4 via 198.51.100.2 host-eth2
[@0]: ipv4 via 198.51.100.6 host-eth3OSPF 算出两条等价路径(两条 WAN 的 cost 相同), linux-cp 把它们都同步进了 FIB,VPP 自动做 ECMP 负载分担。
静态路由要达到同样效果,得手工在每台设备上配两条等价路由, 而且链路变化时要手工调整。
一条路径失效会怎样
跑自检
bash
./labctl apply ospf 01
./labctl verify ospf 01自检在持续 ping 期间删掉一条路径的邻接:
text
已删除 198.51.100.2 的邻接(WAN-1 那一跳)
30 packets transmitted, 29 packets received, 3% packet loss
✓ 丢包 3% —— 一条路径失效,数据流基本没受影响30 个包只丢了 1 个。 因为两条路径本来就都在转发表里, 一条失效时 VPP 把对应的 bucket 标为未解析,后续包全走剩下那条 —— 不需要等 OSPF 重算,也不需要等任何定时器。
text
单链路 + OSPF 故障 → 等 dead interval(默认 40 秒)
→ 重算 SPF → 才切过去。这 40 秒全丢。
双链路 + ECMP 失效的瞬间就切。这就是生产网络宁可多花钱建两条链路的原因 —— 不只是为了带宽,更是为了这个"不用等"的性质。
前提是故障能被本地感知
上面的即时切换建立在"本地知道这条路径坏了"之上(接口 down、邻接失效)。
如果是对端静默失效或者中间某一跳黑洞,本地看什么都正常, 那就只能等 OSPF 的 dead interval。要让这种情况也做到亚秒级,需要 BFD (Bidirectional Forwarding Detection)—— VPP 有 bfd 插件, 但本教程没有覆盖它和 OSPF 的联动。
在本环境模拟链路故障有多难
这一节是踩坑记录。想演示"链路断了会怎样",三种直觉的做法全都不行:
set interface state down 会永久破坏 af_packet 的 ARP
bash
vppctl set interface state host-eth2 down
vppctl set interface state host-eth2 up # 恢复接口是 up 回来了,但邻居再也解析不了:
text
vppctl show ip fib 10.2.0.0/24
[0] [@3]: arp-ipv4: via 198.51.100.2 host-eth2 ← 永远停在这
[1] [@5]: ipv4 via 198.51.100.6 host-eth3show ip neighbors 里那个地址干脆消失了,怎么 ping 都解析不回来。 只能重建容器。
这个坑让我误判了两次:ECMP 下一条流会确定性地哈希到某个 bucket, 所以坏掉一个 bucket 的表现是"有些流通、有些流不通" —— 看起来像随机故障,实际是确定的。
容器里 ip link set down,VPP 感知不到
bash
docker exec <容器> ip link set eth2 downVPP 通过 af_packet 附着在这个口上,但它不监听 Linux 侧的链路状态。 实测 OSPF 邻居数不变、FIB 不变,什么都没发生。
ACL / policer 拦不住 OSPF
最后试了用 ACL 全 deny 来模拟"链路还在但流量过不去":
bash
vppctl set acl-plugin acl deny src 0.0.0.0/0
vppctl set acl-plugin interface host-eth2 input acl <N>OSPF 邻居纹丝不动,而且 ACL 计数是 0。
原因:OSPF 用组播 224.0.0.5,走的不是 ip4-unicast 弧。
bash
vppctl show interface features host-eth2text
ip4-multicast:
none configured ← ACL 挂在 unicast 弧上,管不到这里policer 同理,计数也是 0。
这是个有普遍意义的教训:第 9 章的 feature 顺序 讲的都是 ip4-unicast 弧。组播流量(路由协议、PIM、IGMP)走的是另一条弧, 你在 unicast 弧上挂的所有东西对它们都无效。
想过滤路由协议报文,得用别的手段(比如 OSPF 自己的认证, 或者在 ip4-multicast 弧上挂)。
所以自检最终改成直接删邻接来演示 ECMP 切换 —— 它对应的是 "故障能被本地立即感知"这一类,也是真实网络里最常见的一类。
一个假随机故障:未解析的 ECMP bucket
上面那个坑值得单独提炼,因为它在生产里也会遇到。
text
vppctl show ip fib 10.2.0.0/24
[0] [@3]: arp-ipv4: via 198.51.100.2 host-eth2 ← 未解析
[1] [@5]: ipv4 via 198.51.100.6 host-eth3 ← 正常后果是一部分流不通,另一部分完全正常。因为一条流会按五元组 确定性地哈希到某一个 bucket。
症状表现得像随机故障:
text
· ping 通了,换个源端口再 ping 又不通
· 同一个服务,有些客户端好、有些客户端坏
· 今天正常,明天不正常(源端口变了)
· 重启应用就好了(换了源端口)排查方法:show ip fib <前缀>,直接看有没有 arp-ipv4。
这类"看起来随机、实际确定"的故障,是 ECMP 环境特有的。 本教程的 reset.sh 会主动把两条路径的邻接都解析好,就是为了避免它干扰实验。
动态路由 vs 静态路由
text
┌──────────────┬────────────────────────┬──────────────────────────┐
│ │ 静态路由 │ OSPF │
├──────────────┼────────────────────────┼──────────────────────────┤
│ 加一条链路 │ 改所有相关设备 │ 自动学到 │
│ 链路故障 │ 人工切换 │ 自动收敛 │
│ 多路径 │ 手工配等价路由 │ 自动 ECMP │
│ 排错 │ 看配置 │ 看邻接 + LSA + FIB │
│ 出错风险 │ 配错一台就黑洞 │ 协议保证一致性 │
│ 需要理解的东西 │ 几乎没有 │ 区域、LSA、cost、定时器 │
└──────────────┴────────────────────────┴──────────────────────────┘代价是多了一层需要理解和排查的东西。邻接建不起来、区域设计不对、 LSA 泛洪范围过大 —— 这些都是静态路由不会有的问题。
小规模、拓扑稳定的场景,静态路由依然是对的选择。本书前十章 全部用静态路由,不是偷懒,是因为那些实验的拓扑本来就只有一两条路径。
小结
| VPP 的角色 | 纯转发面,路由协议由 bird / FRR 跑 |
| 同步链条 | bird → netns 内核路由表 → linux-cp netlink → VPP FIB |
| 排错要分两段 | birdc show ospf neighbors(控制面)+ show ip fib(数据面) |
| netlab 集成 | OSPF 配置独立成文件挂载,--no-config 下也生效 |
| 实测结果 | 零手工路由即互通;两条等价路径自动 ECMP |
| ECMP 切换 | 一条路径失效,30 个包只丢 1 个 |
| 模拟链路故障 | 本环境三种做法都不行(见上文),最后用删邻接代替 |
| 重要发现 | OSPF 组播不走 ip4-unicast 弧,ACL / policer 拦不住它 |
| 假随机故障 | 未解析的 ECMP bucket → 部分流不通,show ip fib 看 arp-ipv4 |
| 未覆盖 | BFD 联动、多区域设计、BGP |