Add an interoperability lab with FRR, BIRD and Routinator

scripts/interop/lab.sh runs the daemon against FRR, BIRD and Routinator,
each in a network namespace on one exchange LAN, and checks what every
side ends up with: sessions and routes over IPv4 and IPv6 with their
attributes, four-octet AS numbers, ADD-PATH, RFC 9234 roles and Only to
Customer, RTR version 2 and invalid routes, route refresh, session resets,
killed peers, a cut wire, the daemon stopped and killed, a table of a
million routes and a restart under it, role mismatch and the prefix limit.
All of it passes against FRR 10.5.1, BIRD 2.18 and Routinator 0.15.2.

scripts/interop/real-rpki.sh checks the RTR client against a Routinator
with the real RPKI: 713,258 IPv4 and 187,479 IPv6 VRPs and 3,379 ASPAs,
the same numbers on both sides.

What the lab found, fixed here:

- A daemon restarted with its routes in the kernel removed them all at
  once, its table being empty until the peers had sent theirs again, and
  then put them back: no forwarding meanwhile. Routes of an earlier run
  are now kept for `stale_seconds` (120 by default) and only those nobody
  announced again are removed. A restart under a million routes takes
  1.1 s to be in step, with nothing removed.
- FRR ends a session it loses to a collision with a state machine error
  without a subcode, which was shown as "error code 5 subcode 0".

CI gets a job that runs the lab.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-02 17:44:49 +02:00
1 parent 1de75fc610
commit 321af51e4b
16 files changed
+1024 -17

No files matched your search

+8 -1
View File
@@ -184,6 +184,10 @@ impl Config {
table: or(kernel.table, defaults.table),
metric: or(kernel.metric, defaults.metric),
nexthop_objects: !kernel.without_nexthop_objects,
stale_time: match kernel.stale_seconds {
0 => defaults.stale_time,
seconds => Duration::from_secs(seconds.into()),
},
}
}),
rpki,
@@ -746,11 +750,13 @@ mod tests {
table: 254,
metric: 20,
nexthop_objects: true,
stale_time: Duration::from_secs(120),
})
);
assert_eq!(
parse(&format!(
"{BASE}kernel {{ table: 1000 metric: 150 without_nexthop_objects: true }}"
"{BASE}kernel {{ table: 1000 metric: 150 without_nexthop_objects: true \
stale_seconds: 30 }}"
))
.unwrap()
.kernel,
@@ -758,6 +764,7 @@ mod tests {
table: 1000,
metric: 150,
nexthop_objects: false,
stale_time: Duration::from_secs(30),
})
);
}