B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
3.3 KiB
| id | repo | file | line | status | severity | complexity | pattern |
|---|---|---|---|---|---|---|---|
| frrouting-0002 | FRRouting/frr | ospfd/ospf_spf.c | 275 | unpatched | HIGH | O(V²) worst case — V = vertices in OSPF area; triggered on every topology change | listnode_lookup on children list inside Dijkstra main loop |
Description
ospf_vertex_add_parent() is called for every vertex added to the SPF tree (inside
the Dijkstra main loop at ospf_spf_calculate:1760). For each vertex v, it checks
whether v is already in its parent's children list before adding it:
/* ospf_spf.c:264 */
static void ospf_vertex_add_parent(struct vertex *v)
{
for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) {
/* No need to add two links from the same parent. */
if (listnode_lookup(vp->parent->children, v) == NULL) // O(|children|)
listnode_add(vp->parent->children, v);
}
}
listnode_lookup() is a linear scan over a singly-linked list (lib/linklist.c).
vp->parent->children grows as the SPF tree is built; for the root vertex it
reaches size = number of directly connected neighbors.
The Dijkstra loop (ospf_spf_calculate:1748–1772) processes V vertices. For each:
- P ECMP parents (typically 1, max ~64)
- Per parent:
listnode_lookupoverparent->children— O(|children| at that moment)
Worst case (star/hub-and-spoke topology, V routers all connected to one hub):
- The hub's children list grows to size V
- Each of V vertices calls
listnode_lookupon that list - Total: O(V²)
Call context
ospf_spf_calculate() is called on every OSPF topology change (LSA received,
neighbor state change, interface up/down). The scheduling is at
ospf_spf_calculate_schedule() with configurable throttle timers
(ospf_spf_calculate_schedule_worker:1844), but during convergence storms a large
flat area will run this O(V²) loop repeatedly.
A flat enterprise OSPF area with 500 routers (common in large campus/DC deployments) has ~125,000 comparisons per SPF run vs. ~500 for the O(V) fix.
Fix
Replace struct list *children in struct vertex with a hash set for O(1) membership.
FRRouting's lib/hash.h provides a hash table. Alternative: since OSPF vertex IDs
are IPv4 addresses (4 bytes), a bitset indexed by some stable ID would give O(1)
in O(V/64) space.
Minimal fix: parallel struct hash *children_set alongside the list (for compatibility
with code that iterates children as a list), used only for the duplicate check:
/* Before */
if (listnode_lookup(vp->parent->children, v) == NULL)
listnode_add(vp->parent->children, v);
/* After — O(1) duplicate check */
if (!hash_lookup(vp->parent->children_set, v)) {
hash_get(vp->parent->children_set, v, hash_alloc_intern);
listnode_add(vp->parent->children, v);
}
Or simpler: use SET_FLAG on the vertex's flags field to mark "already added as
child of this parent" since each vertex is processed exactly once in Dijkstra.
Relationship to frrouting-0001
frrouting-0001 (already patched) fixed listnode_lookup × 5 in ospf_ti_lfa.c
(the TI-LFA post-convergence fast-reroute calculator). This defect is in the primary
SPF Dijkstra core — more fundamental, higher blast radius.
Work items
- patch
- unit test (SPF ops count: V=100, V=200, V=400 on hub-spoke topology)
- integration test (OSPF convergence time before/after on large lab topology)