labsim: capture BGP, dual WAN and both ISP VMs as code

The sim's routing config existed only as running state on the VMs. It was
applied by hand over SSH, so rebuilding a VM lost the rehearsal and nothing
recorded why any of it was shaped the way it was. The two ISP VMs were not
referenced anywhere in the repo at all.

sim-net-config.py generates all four roles; sim-net-apply.sh applies them over
the serial console, or diffs them against the running VMs. Verified reproducing
live state exactly before committing: primary 40/40 commands, secondary 16/16,
isp-dhcp 19/19, isp-pppoe 21/21.

Carries the reasoning that was previously nowhere: RFC 8212 needing policy in
both directions or the session carries zero prefixes; probe targets that must
not double as system name-servers; default-route-distance 210 rather than
no-default-route, which blanks new_routers and hands the default route to the
backup line; and the WI-8 bootstrap bug that pinned /32s fix.

Dropped a stale `pppoe-server interface eth0` on isp-pppoe (a NIC that does not
exist there) so a green drift check stays meaningful.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
This commit is contained in:
Michal
2026-08-22 16:26:36 +01:00
parent f41ffdd039
commit 7f551081ad
3 changed files with 393 additions and 0 deletions

59
labsim/sim-net-apply.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Apply -- or drift-check -- the labsim routing config on all four VMs.
#
# ./sim-net-apply.sh check what the VMs run vs what sim-net-config.py says
# ./sim-net-apply.sh apply push the generated config over the serial console
#
# `check` is the one you want most of the time. The whole failure mode this
# guards against is somebody (including me) fixing something on a VM over SSH
# and never writing it down, so the next rebuild silently loses it.
#
# Applied over the serial console rather than SSH because a freshly installed
# sim router holds the same addresses as its peer -- there is a window where it
# is not safely reachable over the network at all. See console-apply.py.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ACTION="${1:-check}"
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
# role : vm : address : regex selecting the subtrees this generator owns
TARGETS=(
"primary:labsim-vyos:172.31.1.252:^set (protocols (bgp|failover|static)|policy (prefix-list|route-map)|nat source rule 1[12]0|interfaces (pppoe|bonding bond0 vif 5[13]))"
"secondary:labsim-vyos2:172.31.1.253:^set (protocols bgp|policy (prefix-list|route-map))"
"isp-dhcp:labsim-isp-dhcp:192.168.122.136:^set (interfaces ethernet|nat source|service dhcp-server|firewall ipv4 forward|system host-name)"
"isp-pppoe:labsim-isp-pppoe:192.168.122.63:^set (interfaces ethernet|nat source|service pppoe-server|firewall ipv4 forward|system host-name)"
)
# Sim-only credential; these VMs hold nothing real and are not reachable from
# outside the hypervisor.
SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
-o LogLevel=ERROR -o PreferredAuthentications=password -o ConnectTimeout=5)
live() { timeout 30 sshpass -p vyos ssh "${SSH_OPTS[@]}" "vyos@$1" \
"/opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration commands" 2>/dev/null; }
norm() { sed "s/'//g" | grep -v 'hw-id\|offload' | sort -u; }
rc=0
for t in "${TARGETS[@]}"; do
IFS=: read -r role vm addr rx <<<"$t"
"$HERE/sim-net-config.py" --role "$role" >"$WORK/$role.conf" 2>/dev/null || {
printf ' %-11s GENERATE FAILED\n' "$role"; rc=1; continue; }
if [ "$ACTION" = apply ]; then
printf ' %-11s applying to %s over console...\n' "$role" "$vm"
"$HERE/console-apply.py" --vm "$vm" --config "$WORK/$role.conf" || rc=1
continue
fi
if ! live "$addr" >"$WORK/$role.live" || [ ! -s "$WORK/$role.live" ]; then
printf ' %-11s UNREACHABLE (%s)\n' "$role" "$addr"; rc=1; continue
fi
grep -E '^set ' "$WORK/$role.conf" | norm >"$WORK/$role.g"
grep -E "$rx" "$WORK/$role.live" | norm >"$WORK/$role.l"
if d="$(diff "$WORK/$role.g" "$WORK/$role.l")" && [ -z "$d" ]; then
printf ' %-11s in sync (%s commands)\n' "$role" "$(wc -l <"$WORK/$role.g")"
else
printf ' %-11s DRIFT — "<" only in code, ">" only on the VM:\n' "$role"
printf '%s\n' "$d" | sed 's/^/ /'
rc=1
fi
done
exit $rc