从链路状态计算 OSPF 区域内路由
FRRouting 的 OSPF 守护进程用 Dijkstra 计算区域内最短路径,并为路由表生成下一跳。
工程问题#
路由器收到链路状态通告后,需要根据区域内的网络拓扑与链路代价,计算到各目标的最低代价路径。结果不仅是距离,还要能确定下一跳,并处理相同代价的多条路径。
实际实现#
ospfd 将区域内路由器与中转网络作为顶点,以链路状态数据库提供邻接关系和非负代价。ospf_spf_calculate 反复选择代价最小的候选顶点,ospf_spf_next 更新邻居的距离和下一跳。frr-10.2 用跳表维护候选优先队列:发现更低代价时先删除,尝试更新合法下一跳与距离后重新插入,相同代价则继续计算可用的下一跳。
处理流程#
- SPF 调度任务按区域建立新的路由计算结果,以本机的 Router-LSA 作为根。
- 检查当前顶点的链路状态通告,计算到邻居的累计代价,过滤失效或缺少反向连接的链路。
- 对尚未确定的顶点更新代价与下一跳;更低代价重新排序,相同代价处理等价路径。
- 取出最小代价候选顶点加入最短路径树,并登记中转网络与路由器;候选耗尽后再补入末梢网络路由。
源码片段#
固定版本 · 原文摘录66static int vertex_cmp(const struct vertex *v1, const struct vertex *v2)67{68 if (v1->distance != v2->distance)69 return v1->distance - v2->distance;7071 if (v1->type != v2->type) {72 switch (v1->type) {73 case OSPF_VERTEX_NETWORK:74 return -1;75 case OSPF_VERTEX_ROUTER:76 return 1;77 }78 }79 return 0;80}81DECLARE_SKIPLIST_NONUNIQ(vertex_pqueue, struct vertex, pqi, vertex_cmp);候选顶点先按累计代价排序,代价相同时将网络顶点排在路由器顶点之前。DECLARE_SKIPLIST_NONUNIQ 明确表明这个版本用跳表维护优先队列;文件中的旧 heap 注释不能作为二叉堆实现的依据。
1749 for (;;) {1750 /* RFC2328 16.1. (2). */1751 ospf_spf_next(v, area, &candidate);17521753 /* RFC2328 16.1. (3). */1754 v = vertex_pqueue_pop(&candidate);1755 if (!v)1756 /* No more vertices left. */1757 break;17581759 v->lsa_p->stat = LSA_SPF_IN_SPFTREE;17601761 ospf_vertex_add_parent(v);每轮先检查当前顶点的邻边,再弹出累计代价最小的候选顶点,将其标记为已经加入最短路径树。候选队列为空时结束;主循环后续把可达网络和路由器写入相应的新路由表。
1499 else if (w->distance == distance) {1500 /*1501 * Found an equal-cost path to W.1502 * Calculate nexthop of to W from V.1503 */1504 ospf_nexthop_calculation(area, v, w, l,1505 distance, lsa_pos);1506 }1507 else {1508 /*1509 * Found a lower-cost path to W.1510 * nexthop_calculation is conditional, if it1511 * finds valid nexthop it will call1512 * spf_add_parents, which will flush the old1513 * parents.1514 */1515 vertex_pqueue_del(candidate, w);1516 ospf_nexthop_calculation(area, v, w, l,1517 distance, lsa_pos);1518 vertex_pqueue_add(candidate, w);1519 }相同代价的路径交给下一跳计算处理;发现更低代价时,先从候选队列移除顶点,尝试计算合法下一跳并更新距离,最后重新入队。只有找到合法下一跳才会修改距离和父节点;重新入队使候选集按更新后的实际代价排序。
1862 /* Execute SPF for each area including backbone, see RFC 2328 16.1. */1863 monotime(&spf_start_time);1864 new_table = route_table_init(); /* routing table */1865 new_rtrs = route_table_init(); /* ABR/ASBR routing table */18661867 /* If we have opaque enabled then track all router reachability */1868 if (CHECK_FLAG(ospf->opaque, OPAQUE_OPERATION_READY_BIT))1869 all_rtrs = route_table_init();18701871 ospf_spf_calculate_areas(ospf, new_table, all_rtrs, new_rtrs);1872 spf_time = monotime_since(&spf_start_time, NULL);18731874 ospf_vl_shut_unapproved(ospf);18751876 /* Calculate inter-area routes, see RFC 2328 16.2. */1877 monotime(&start_time);1878 ospf_ia_routing(ospf, new_table, new_rtrs);SPF 调度任务建立新的路由表,逐个区域执行 SPF,再计算区域间路由。这个调用方说明 Dijkstra 参与的是控制面的路由计算;实际报文转发时的目的地址查表属于另一条路径。
四段原文分别展示候选队列、Dijkstra 主循环、候选更新和真实调度调用方;LSA 校验、下一跳继承、末梢网络与路由安装请结合完整源文件阅读。
收益与取舍#
工程分析带来的收益
- 一次单源计算得到本路由器到区域内各目标的最短路径,无需逐个目标重新搜索。
- 距离计算与下一跳维护结合,结果可以用于建立路由表,并支持等价多路径。
付出的代价
- 链路状态数据库、候选队列与父节点信息占用内存;拓扑变化还会触发重新计算,FRR 用 SPF 定时与退避合并频繁事件。
- 经典 Dijkstra 的非负权条件在这里由 OSPF 链路代价满足。完整处理成本还包含 LSA 查找、链路有效性检查和多下一跳维护,不能直接把教材复杂度当成整个协议的开销。
可比较的选择
线性扫描候选顶点仍可执行 Dijkstra,但每轮寻找最小代价顶点要扫描候选集合;此版本以跳表维持有序候选集,增加节点与索引维护成本。
距离向量路由通过邻居交换距离和下一跳,可减少对完整区域拓扑的依赖;协议状态、收敛与防环方式不同,不能直接替换 OSPF 内部的 SPF 计算函数。
版本与适用范围#
依据 FRRouting frr-10.2 的 OSPFv2 区域内 SPF。这里描述控制面如何计算路由与下一跳,不是数据面收到报文后的最长前缀查找;区域间路由、外部路由与 TI-LFA 还各有额外处理。候选队列在此版本是跳表,不能据旧注释写成二叉堆。
参考来源#
源码、官方文档与原始论文ospfd/ospf_spf.c · vertex_cmp / ospf_spf_next / ospf_spf_calculate / ospf_spf_calculate_schedule_worker
相关实现与比较#
1 个相关案例Redis 有序集合既要按成员查分数,也要按分数和名次遍历;较大集合通过两种索引服务这些访问方式。