Files
Rayhaan JaufeerallyandClaude Fable 5.1 321af51e4b 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>
2026-10-02 17:44:49 +02:00

71 lines
3.0 KiB
Bash
Executable File

#!/bin/bash
# The RTR client against a validator that has the real RPKI: what the
# daemon ends up with has to be what the validator says it has.
#
# The lab (lab.sh) gives Routinator made-up data, which says nothing about
# ASPAs (Routinator does not take those from a local file) nor about data
# of the Internet's size. This does, against a Routinator you run
# yourself, which takes a quarter of an hour and some gigabytes the first
# time:
#
# routinator --enable-aspa server --rtr 127.0.0.1:3324 --http 127.0.0.1:8324
# scripts/interop/real-rpki.sh # those are the defaults
# scripts/interop/real-rpki.sh 127.0.0.1 3324 http://127.0.0.1:8324
#
# The daemon is run with no peers and nothing but the validator
# configured, on a port of its own; nothing else is touched.
set -euo pipefail
cd "$(dirname "$0")/../.."
address=${1:-127.0.0.1}
port=${2:-3324}
validator=${3:-http://127.0.0.1:8324}
bgp="${CARGO_TARGET_DIR:-target}/release/bgp"
[ -x "$bgp" ] || { echo "$bgp not found: cargo build --release -p bgp" >&2; exit 1; }
work=$(mktemp -d)
trap 'kill "$pid" 2>/dev/null || true; rm -rf "$work"' EXIT
cat > "$work/config.textpb" <<END
router_id: "192.0.2.1"
local_asn: 64601
listen_endpoints { address: "127.0.0.1" port: 11790 }
rpki { caches { address: "$address" port: $port } }
http_endpoint { address: "127.0.0.1" port: 18080 }
END
"$bgp" "$work/config.textpb" > "$work/log" 2>&1 &
pid=$!
for _ in $(seq 600); do
if curl -sf http://127.0.0.1:18080/api/rpki | jq -e '.state == "synchronised"' >/dev/null 2>&1; then
break
fi
sleep 0.2
done
ours=$(curl -sf http://127.0.0.1:18080/api/rpki)
jq -e '.state == "synchronised"' <<<"$ours" >/dev/null || {
echo "FAILED: not synchronised: $ours" >&2
tail -n 5 "$work/log" >&2
exit 1
}
# What the validator says it has: distinct VRPs, and ASes with an ASPA.
theirs=$(curl -sf "$validator/json")
their_v4=$(jq '[.roas[] | select(.prefix | contains(":") | not) | {asn, prefix, maxLength}] | unique | length' <<<"$theirs")
their_v6=$(jq '[.roas[] | select(.prefix | contains(":")) | {asn, prefix, maxLength}] | unique | length' <<<"$theirs")
their_aspas=$(jq '[(.aspas // [])[] | .customer] | unique | length' <<<"$theirs")
our_v4=$(jq .vrps_ipv4 <<<"$ours")
our_v6=$(jq .vrps_ipv6 <<<"$ours")
our_aspas=$(jq .aspas <<<"$ours")
echo "RTR version $(jq .protocol_version <<<"$ours"), serial $(jq .serial <<<"$ours")"
echo "IPv4 VRPs: validator $their_v4, daemon $our_v4"
echo "IPv6 VRPs: validator $their_v6, daemon $our_v6"
echo "ASPAs: validator $their_aspas, daemon $our_aspas"
# The validator may have moved on a serial between the two looks; a
# handful apart is that, more is not.
close() { [ $(( $1 > $2 ? $1 - $2 : $2 - $1 )) -le $(( $1 / 200 + 5 )) ]; }
close "$their_v4" "$our_v4" && close "$their_v6" "$our_v6" && close "$their_aspas" "$our_aspas" \
|| { echo "FAILED: the daemon does not have what the validator has" >&2; exit 1; }
[ "$our_v4" -gt 0 ] || { echo "FAILED: no data at all" >&2; exit 1; }
echo "ok: the daemon has what the validator has"