Peer at link-local addresses: fe80::1%eth0
A peer's endpoint may be a link-local IPv6 address with the interface it is on. Such a peer is the address and the interface together, wherever peers are told apart: admission, collisions, hold-downs, the refusal log, the status page. The interface is looked up by name each time it is needed, so one that appears later or is made anew is found. Our routes go out with our link-local address in both places of MP_REACH_NLRI's next hop, as FRR sends its own. A peer's link-local next hop is taken alone, twice, or after "::" (BIRD), and resolved in the kernel on the interface of the session rather than by looking fe80::/64 up, which every interface has. The lab has a new phase, linklocal: sessions with FRR (by interface, with radvd beside the daemon) and BIRD over a LAN with link-local addresses only, both families, pings across, the interface down and up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
92e013ca01
commit
fd2248feb5
38 files changed
+975
-151
No files matched your search
+171
-3
@@ -20,7 +20,7 @@
|
||||
# PHASES="basics scale" scripts/interop/lab.sh only these phases
|
||||
# SCALE_ROUTES=1000000 ... the size of the table BIRD sends
|
||||
#
|
||||
# Needs Linux, sudo, and: frr, bird2, jq, routinator, a built `bgp`
|
||||
# Needs Linux, sudo, and: frr, bird2, radvd, jq, routinator, a built `bgp`
|
||||
# (cargo build --release -p bgp). scripts/linux-vm.sh runs it from macOS;
|
||||
# scripts/interop/install.sh installs what is missing on Ubuntu.
|
||||
set -euo pipefail
|
||||
@@ -29,7 +29,7 @@ cd "$(dirname "$0")/../.."
|
||||
bgp="${CARGO_TARGET_DIR:-target}/release/bgp"
|
||||
work=/tmp/ferrix-lab
|
||||
pathspace=ferrixlab
|
||||
all_phases="basics roles rpki refresh flaps restart scale limits v6only"
|
||||
all_phases="basics roles rpki refresh flaps restart scale limits v6only linklocal"
|
||||
phases="${PHASES:-$all_phases}"
|
||||
scale_routes="${SCALE_ROUTES:-200000}"
|
||||
|
||||
@@ -59,6 +59,7 @@ down() {
|
||||
for pidfile in "$work"/dut.pid "$work"/routinator.pid; do
|
||||
[ -f "$pidfile" ] && sudo kill "$(cat "$pidfile")" 2>/dev/null || true
|
||||
done
|
||||
[ -f "$work/radvd.pid" ] && sudo kill "$(sudo cat "$work/radvd.pid")" 2>/dev/null || true
|
||||
[ -f "$work/bird.pid" ] && sudo kill "$(sudo cat "$work/bird.pid")" 2>/dev/null || true
|
||||
for daemon in bgpd zebra; do
|
||||
local pidfile="/var/run/frr/$pathspace/$daemon.pid"
|
||||
@@ -727,7 +728,8 @@ phase_limits() {
|
||||
|
||||
# The exchange LAN loses its IPv4 addresses. One session over IPv6 with
|
||||
# each router carries both families, and IPv4 routes have IPv6 next hops
|
||||
# (RFC 8950), in BGP and in the kernel. Run last: the lab stays that way.
|
||||
# (RFC 8950), in BGP and in the kernel. Run after the rest: the lab stays
|
||||
# that way.
|
||||
phase_v6only() {
|
||||
dut_down TERM
|
||||
frr_down TERM
|
||||
@@ -782,6 +784,172 @@ phase_v6only() {
|
||||
ok "an IPv4 route withdrawn over the IPv6 session goes"
|
||||
}
|
||||
|
||||
# The exchange LAN loses its global IPv6 addresses too: all that is left on
|
||||
# it are link-local ones, the same on every link a router has, which mean
|
||||
# something only together with the interface. The sessions are between
|
||||
# those, as are the next hops, for both families. Run after v6only.
|
||||
dut_config_linklocal() {
|
||||
cat > "$work/dut.textpb" <<END
|
||||
router_id: "10.9.0.1"
|
||||
local_asn: 64601
|
||||
listen_endpoints { address: "::" port: 179 }
|
||||
connect_retry_seconds: 1
|
||||
peers {
|
||||
name: "frr" endpoint { address: "fe80::2%ix0" } remote_asn: 64602
|
||||
role: PEER
|
||||
import { accept_bogons: true max_prefixes: 1000 }
|
||||
}
|
||||
peers {
|
||||
name: "bird" endpoint { address: "fe80::3%ix0" } remote_asn: 4200000003
|
||||
role: PROVIDER
|
||||
import { accept_bogons: true max_prefixes: 1000 }
|
||||
}
|
||||
originate { prefix: "10.1.0.0/24" }
|
||||
originate { prefix: "2001:db8:1::/48" }
|
||||
kernel { stale_seconds: 3 }
|
||||
http_endpoint { address: "127.0.0.1" port: 8080 }
|
||||
END
|
||||
}
|
||||
|
||||
# FRR is told the interface and not the address ("BGP unnumbered"), which
|
||||
# is how it is mostly set up for this. It learns who is on the link from
|
||||
# router advertisements, which the daemon does not send: radvd does, as it
|
||||
# would have to beside the daemon on a real router with such a neighbor.
|
||||
radvd_up() {
|
||||
cat > "$work/radvd.conf" <<END
|
||||
interface ix0 {
|
||||
AdvSendAdvert on;
|
||||
MinRtrAdvInterval 3;
|
||||
MaxRtrAdvInterval 4;
|
||||
AdvDefaultLifetime 0;
|
||||
};
|
||||
END
|
||||
in_ns dut "$(command -v radvd || echo /usr/sbin/radvd)" -C "$work/radvd.conf" -p "$work/radvd.pid" -m logfile -l "$work/radvd.log"
|
||||
}
|
||||
|
||||
frr_config_linklocal() {
|
||||
sudo tee "/etc/frr/$pathspace/frr.conf" > /dev/null <<END
|
||||
frr defaults traditional
|
||||
hostname frr
|
||||
log file $work/frr.log
|
||||
!
|
||||
router bgp 64602
|
||||
bgp router-id 10.9.0.2
|
||||
no bgp ebgp-requires-policy
|
||||
no bgp network import-check
|
||||
timers bgp 3 9
|
||||
neighbor ix0 interface remote-as 64601
|
||||
neighbor ix0 local-role peer
|
||||
neighbor ix0 timers connect 1
|
||||
neighbor ix0 capability extended-nexthop
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.2.0.0/24
|
||||
network 10.2.5.0/24
|
||||
neighbor ix0 activate
|
||||
exit-address-family
|
||||
!
|
||||
address-family ipv6 unicast
|
||||
network 2001:db8:2::/48
|
||||
neighbor ix0 activate
|
||||
exit-address-family
|
||||
!
|
||||
END
|
||||
}
|
||||
|
||||
bird_config_linklocal() {
|
||||
cat > "$work/bird.conf" <<END
|
||||
log "$work/bird.log" all;
|
||||
router id 10.9.0.3;
|
||||
protocol device {}
|
||||
protocol kernel kernel4 { ipv4 { export where source = RTS_BGP; }; }
|
||||
protocol kernel kernel6 { ipv6 { export where source = RTS_BGP; }; }
|
||||
protocol static static4 { ipv4; route 10.3.0.0/24 blackhole; }
|
||||
protocol static static6 { ipv6; route 2001:db8:3::/48 blackhole; }
|
||||
protocol bgp dut {
|
||||
local as 4200000003;
|
||||
neighbor fe80::1%ix0 as 64601;
|
||||
local role provider;
|
||||
connect retry time 1;
|
||||
# Back soon after the hold timer ran out, which the phase makes it do.
|
||||
error wait time 1, 2;
|
||||
hold time 9;
|
||||
graceful restart off;
|
||||
ipv4 { import all; export where source = RTS_STATIC; extended next hop on; };
|
||||
ipv6 { import all; export where source = RTS_STATIC; };
|
||||
}
|
||||
END
|
||||
}
|
||||
|
||||
phase_linklocal() {
|
||||
dut_down TERM
|
||||
frr_down TERM
|
||||
sudo kill "$(sudo cat "$work/bird.pid")"; sleep 0.5; sudo rm -f "$work/bird.ctl"
|
||||
local n=0 name
|
||||
for name in dut frr bird; do
|
||||
n=$((n + 1))
|
||||
in_ns "$name" ip addr del "10.9.0.$n/24" dev ix0 2>/dev/null || true
|
||||
in_ns "$name" ip -6 addr flush dev ix0
|
||||
in_ns "$name" ip -6 addr add "fe80::$n/64" dev ix0 nodad
|
||||
# The same address on another link: it must not be mistaken for
|
||||
# the one the session is on.
|
||||
in_ns "$name" ip -6 addr flush dev lan scope link
|
||||
in_ns "$name" ip -6 addr add "fe80::$n/64" dev lan nodad
|
||||
done
|
||||
in_ns dut sysctl -qw net.ipv6.conf.ix0.forwarding=1
|
||||
radvd_up
|
||||
frr_config_linklocal; frr_up
|
||||
bird_config_linklocal; bird_up
|
||||
dut_config_linklocal; dut_up
|
||||
|
||||
wait_for "two sessions between link-local addresses" established 2
|
||||
peer_is frr '.peer_addr | startswith("[fe80::2%ix0]:")' >/dev/null \
|
||||
&& peer_is bird '.peer_addr | startswith("[fe80::3%ix0]:")' >/dev/null \
|
||||
&& peer_is frr '.local_addr | startswith("[fe80::1%ix0]:")' >/dev/null \
|
||||
|| fail "the peers are at $(api /api/peers | jq -c '[.[] | {name, peer_addr, local_addr}]')"
|
||||
api /api/peers | jq -e 'all(.[]; .extended_next_hop and (.address_families | length == 2))' >/dev/null \
|
||||
|| fail "the sessions did not agree on both families: $(api /api/peers | jq -c '[.[] | {name, address_families, extended_next_hop}]')"
|
||||
ok "sessions with FRR and BIRD at fe80::2%ix0 and fe80::3%ix0, for both families"
|
||||
|
||||
# Their routes: link-local next hops, on the session's interface.
|
||||
wait_for "FRR's IPv4 route" dut_has 10.2.0.0/24 frr
|
||||
wait_for "BIRD's IPv4 route" dut_has 10.3.0.0/24 bird
|
||||
wait_for "FRR's IPv4 route in the kernel" dut_kernel -4 "10.2.0.0/24 via inet6 fe80::2 dev ix0 metric 20"
|
||||
wait_for "BIRD's IPv4 route in the kernel" dut_kernel -4 "10.3.0.0/24 via inet6 fe80::3 dev ix0 metric 20"
|
||||
wait_for "FRR's IPv6 route in the kernel" dut_kernel -6 "2001:db8:2::/48 via fe80::2 dev ix0 metric 20"
|
||||
wait_for "BIRD's IPv6 route in the kernel" dut_kernel -6 "2001:db8:3::/48 via fe80::3 dev ix0 metric 20"
|
||||
api /api/kernel | jq -e '.unresolved == {}' >/dev/null || fail "some next hops did not resolve: $(api /api/kernel)"
|
||||
ok "their routes of both families are in the kernel by way of fe80::… on ix0"
|
||||
|
||||
# Ours, at them.
|
||||
wait_for "our IPv4 route at FRR" frr_has ipv4 10.1.0.0/24
|
||||
wait_for "our IPv6 route at FRR" frr_has ipv6 2001:db8:1::/48
|
||||
wait_for "our IPv4 route at BIRD" bird_has 10.1.0.0/24
|
||||
wait_for "our IPv6 route at BIRD" bird_has 2001:db8:1::/48
|
||||
wait_for "our IPv4 route in FRR's kernel" in_ns frr ip -4 route get 10.1.0.1
|
||||
wait_for "our IPv6 route in BIRD's kernel" in_ns bird ip -6 route get 2001:db8:1::1 from 2001:db8:3::1
|
||||
ok "FRR and BIRD have our routes of both families"
|
||||
|
||||
in_ns dut ping -q -c 2 -W 2 -I 10.1.0.1 10.2.0.1 >/dev/null || fail "no IPv4 ping to FRR's lan"
|
||||
in_ns dut ping -q -c 2 -W 2 -I 10.1.0.1 10.3.0.1 >/dev/null || fail "no IPv4 ping to BIRD's lan"
|
||||
in_ns dut ping -q -c 2 -W 2 -I 2001:db8:1::1 2001:db8:2::1 >/dev/null || fail "no IPv6 ping to FRR's lan"
|
||||
in_ns dut ping -q -c 2 -W 2 -I 2001:db8:1::1 2001:db8:3::1 >/dev/null || fail "no IPv6 ping to BIRD's lan"
|
||||
in_ns frr ping -q -c 2 -W 2 -I 10.2.0.1 10.1.0.1 >/dev/null || fail "no IPv4 ping from FRR's lan"
|
||||
in_ns bird ping -q -c 2 -W 2 -I 2001:db8:3::1 2001:db8:1::1 >/dev/null || fail "no IPv6 ping from BIRD's lan"
|
||||
ok "IPv4 and IPv6 packets cross a link with only link-local addresses, both ways"
|
||||
|
||||
# The interface goes and comes back, as a tunnel does when it is
|
||||
# restarted: the sessions come back by themselves.
|
||||
in_ns dut ip link set ix0 down
|
||||
wait_for "the sessions to go" established 0
|
||||
in_ns dut ip link set ix0 up
|
||||
in_ns dut ip -6 addr flush dev ix0
|
||||
in_ns dut ip -6 addr add fe80::1/64 dev ix0 nodad
|
||||
wait_for "the sessions to come back" established 2
|
||||
wait_for "FRR's IPv4 route in the kernel again" dut_kernel -4 "10.2.0.0/24 via inet6 fe80::2 dev ix0 metric 20"
|
||||
ok "the sessions and the routes come back after the interface was down"
|
||||
}
|
||||
|
||||
# --- main -----------------------------------------------------------------------
|
||||
|
||||
case "${1:-test}" in
|
||||
|
||||
Reference in new issue
Block a user