# PPPoE high availability Proven in labsim. **Not applied to production.** ## What it does One consumer ISP account, two routers. The 10 gig lease is bound to a cloned MAC (`f0:9f:c2:12:9b:4f`, the retired USG's) and the Vodafone line to a single credential, so neither may be live on both boxes. The WAN follows VRRP mastership — but the two halves use different control planes, and that is the whole design: | | plane | why | |---|---|---| | `bond0.53` (10 gig) | VyOS **config** (`disable`) | only config can move a MAC | | `pppoe0` (Vodafone) | **systemd** unit gate | see below | ## Why PPPoE cannot live on the config plane `interfaces_pppoe.py` treats `disable` and `delete` identically: both **unlink `/etc/ppp/peers/pppoe0`**, call `PPPoEIf.remove()` (withdrawing the FRR default route) and stop the unit. That path is pppd's own options file (`ExecStart=/usr/sbin/pppd call %I`), so the resting state destroyed exactly what the promotion path needed. `ppp@pppoe0` then restart-looped against the missing file — 47 restarts observed, zero sessions at the access concentrator — and never tripped systemd's limiter, because `RestartSec=5s` against the default 10s/5-burst window is only two restarts per interval. It also made op-mode `connect interface pppoe0` unusable (it refuses without the peers file), and put every failover behind a priority-322 commit where one unrelated invalid node fails the whole thing. ## The gate `pppoe0` is configured identically and **enabled on both** routers, so the peers file always exists. Dialling is gated by `/etc/systemd/system/ppp@pppoe0.service.d/10-vrrp-wan-gate.conf`: ```ini ConditionPathExists=/run/vrrp-wan/may-dial ConditionPathExists=/etc/ppp/peers/pppoe0 StartLimitIntervalSec=600 StartLimitBurst=6 ``` `/run` is tmpfs, so the gate is shut at boot and neither box can dial before VRRP has decided. **This is load-bearing, not a nicety:** with the node enabled, `interfaces_pppoe.py` restarts ppp on *every* commit touching the pppoe subtree when the daemon is not running — so the backup actively tries to dial whenever anything commits (`pulumi up`, a hand commit, the boot-time config load). The gate is the only thing making that a no-op, which is why `vrrp-wan-reconcile` **refuses to bless a box whose drop-in is missing**: `/etc` is per-image, so a VyOS upgrade silently removes the protection, and failing closed turns that into "PPPoE never dials" rather than "both routers dial". `may-dial` is a **lease, not a flag**. `ConditionPathExists` is evaluated at start only — it can prevent a dial, never revoke one. `vrrp-wan-reconcile` renews it every 30s; `vrrp-wan-guard` runs every 5s and only ever revokes, on either "I do not hold the VIP" or "the lease is stale". ## Measured in labsim | | | |---|---| | Clean failover (`force-fault`) | `pppoe0` moves in **26s**, reproducible | | 10 gig down → PPPoE | route falls to `pppoe0`; LAN back online in **5s** | | Stale lease | guard hangs up within ~5s | | Missing peers file | `NRestarts=0` — no loop | | Invariant | AC never showed two `simdsl` sessions | ## Deploying (not yet done) 1. `sudo /config/vyos-known-good save` on both. 2. `migration/vrrp-wan-install --vip 192.168.1.1 --host vyos@10.0.1.253` then the same for `.252`. Then `--check` on both. **No config change yet** — verify nothing dials. 3. Confirm `/config/wan-secrets` is present and identical on both. 4. **vyos002 first** (the non-master), on its own commit — `interfaces pppoe` is priority 322 and one bad node fails everything: `delete interfaces pppoe pppoe0 disable`, `commit-confirm 10`. 5. Verify vyos002 did **not** dial — check on the wire, not from state: `sudo tcpdump -i bond0.51 -nn pppoed` should show no PADI. Then `confirm`; `save`. 6. vyos001: nothing to change; it already has `pppoe0` enabled. 7. Confirm `vif 53 disable` is in **both** `config.boot`s. 8. Add the drop-in re-install to the VyOS image-upgrade runbook. **Rollback**, from either box: `set interfaces pppoe pppoe0 disable` on both and `rm /run/vrrp-wan/may-dial`. That restores today's behaviour exactly. ## What the sim cannot prove - **Vodafone's `session-control`.** The sim's accel-ppp defaults to `replace`, so a new auth kills the old session immediately. A real BRAS may `deny` and hold the session for its own dead-peer timer. The matrix runs all three modes to bracket the risk; it cannot tell you which one you will meet, and account rate-limiting or lockout on repeated dials has no sim analogue at all. The flap damper (6 dials / 600s → 15 min hold-off) exists for that. - **Whether Vodafone honours our LCP Terminate / PADT** on a graceful stop. - **The cloned-MAC lease** — whether the 10 gig ISP re-issues `87.192.101.48` to `f0:9f:c2:12:9b:4f` arriving on a different switch port. That risk belongs to `bond0.53`, not PPPoE, and is the largest untested item in the failover. - **Real dial time and MTU/MSS under load.** PPPoE was proven on the *USG*; VyOS dialling Vodafone has never been done. - **Timing under load.** The sim routers are idle 2-vCPU VMs; commit latency on the VP2440s under kea + BGP + conntrack will be worse, and commit latency is the dominant term in the `bond0.53` half of a failover. ## A model hazard to fix before applying The imported baseline records the **running** state, not the safe one: vyos001 has no `vif 53 disable` (it is master), vyos002 does. So an apply performed while vyos002 held the VIP would enable vyos001's WAN as well, putting the cloned MAC on both boxes. An override must assert `vif 53 disable` on **both** routers, with the reconciler re-enabling whichever holds the VIP. Note the consequence: an apply then briefly disables the current master's 10 gig until the reconciler restores it (≤30s).