Series finale · MikroTik RB5009 · multi-homed IPv6

Multi-homing IPv6 over CGNAT on RouterOS

Run the VPS and Route64 paths at the same time under one announceable /48, with BGP best-path selecting the active IPv6 default.

Overview

The series finale of the MikroTik RB5009 home-network series: run two IPv6 uplinks at once under one announceable provider-assigned /48, so a dead tunnel on one upstream pivots traffic to the other without the LAN losing routable IPv6. This post is WAN clean-slate — it replaces the WAN-side plumbing of the two equal-paths posts (VPS, Route64) with a self-contained multi-homed build, so you do not need to have built those posts first — though §3 below pulls <R64_*> placeholders from the Route64 post §2 portal walkthrough, so keep that tab open. The LAN side still assumes the VLAN foundation post — bridge, VLANs, inter-VLAN IPv4 firewall, per-VLAN DHCPv4, ULA-bound DoH resolver.

The hard prerequisite is a 32-bit ASN (<OWN_AS>) and an announceable /48 (<OWN_48>) — the assigning provider must authorize you to originate the /48 from the ASN, with a matching ROA for RPKI. The same <OWN_48> rides over both upstreams, so consistent origin matters more than which provider issues it. If you do not yet have either, follow jf.si's IPv6 ASN + BGP + WireGuard write-up for the acquisition step — this post is another flavor of the same idea (MikroTik RouterOS on the home side, Route64 as the second upstream, BFD on the VPS path) that picks up where it ends. The flavor I used was NoPkt for both the ASN and the /48 ($20 one-time + $60/year USD), and pdxnet.co.uk / getvps.net (AS 52025) for the transit VPS — $6/month including a free self-service BGP session, so <TRANSIT_AS> in the snippets below is 52025 for this flavor.

On top of the jf.si baseline (ASN + /48 + one transit VPS) you're adding the operational weight of a second BGP session and a BFD layer. That's worth weighing against what multi-homing actually buys you. It pays off if you self-host v6 services on <OWN_48> and want a public prefix that survives an upstream swap; if your two upstreams genuinely fail independently (different transits, different physical paths to the home); or if the build itself is the point — BGP, BFD, and RIR bureaucracy as a weekend project. If none of those apply — one path plus Happy Eyeballs fail-to-IPv4 already covers your failure modes invisibly — stop at the VPS path or the Route64 path and skip this post.

Every numbered section is paste-ready against a defconf RouterOS v7 box that already has the VLAN segmentation from the foundation post. Italicized notes are the rationale.

1. Topology

                     Internet (IPv4 + IPv6)
                            │
              ┌─────────────┴─────────────┐ ┌──────────────────────────┐
              │  Transit A — your VPS     │ │  Route64 PoP (WireGuard) │
              │  AS<OWN_AS>, runs BFD     │ │  AS 212895, no BFD       │
              │  iBGP to home over wg-host│ │  eBGP to home over wg-r64│
              └─────────────┬─────────────┘ └────────────┬─────────────┘
                            │                            │
                       wg-host                       wg-route64
                            │                            │
                            │ both transit your <OWN_48>│
                            │  and return ::/0 to home  │
                            │                            │
                       ┌────┴────────────────────────────┴────┐
                       │             home router              │
                       │  AS<OWN_AS>, originates <OWN_48>     │
                       │  receives ::/0 from both, best-path  │
                       │  installs VPS-learned default        │
                       └──────────────────────────────────────┘
                                       │
                          ┌────────────┴────────────┐
                          │  per-VLAN /64s from     │
                          │  <OWN_48> — same recipe │
                          │  as Per-VLAN IPv6 post  │
                          └─────────────────────────┘

Per-VLAN IPv6 from <OWN_48> is plumbed via the Per-VLAN IPv6 post once both BGP sessions are up.

2. WAN architecture

Three working pieces:

text

text

1A. VPS — runs BFD with home, announces your /48 up to transit 2 (announce-only — AS 52025 sends nothing back), and re- 3 originates its own default route to home over iBGP as 4 ::/0 with empty AS_PATH 5B. Route64 — eBGPs your /48 from home to AS 212895, advertises ::/0 6 back with AS_PATH=[212895] 7C. Home — receives ::/0 from both sessions, BGP best-path installs the 8 VPS-learned default (shorter AS_PATH), Route64-learned 9 default is the backup

Home and VPS share <OWN_AS>, so the session between them is iBGP and the VPS-side ::/0 reaches home with an empty AS_PATH. That ::/0 is the VPS's own upstream default — from the public NIC's RA or a static route, pulled into bird and re-originated locally — not a route learned over the transit session, which for AS 52025 carries nothing back to you. Route64 sits in AS 212895, so its ::/0 arrives with AS_PATH=[212895]. No local-pref or MED is set on either session, so RouterOS best-path falls through to AS_PATH length — empty beats [212895] — and the VPS default wins. The Route64 default sits in BGP as the backup. Nothing on the home router explicitly marks one path preferred — the primary/backup ordering falls out of where each AS boundary sits.

The home router is the only place that needs to know about multi-homing. The VPS and Route64 each see exactly one BGP peer (home) and don't know the other exists. Both transits accept your <OWN_48> announcement on their respective sessions and reach <OWN_48> via the WireGuard tunnel.

3. Conventions and placeholders

PlaceholderMeaning
<OWN_AS>Your 32-bit ASN. Both home and the VPS use this; the session is iBGP.
<OWN_48>Your assigned /48, written without trailing zeros, e.g. 2001:db8:eff9.
<TRANSIT_AS>The VPS provider's ASN (your eBGP peer from the VPS upstream-side). Provider tells you.
<TRANSIT_PEER_V6>Provider's IPv6 BGP peer address handed to your VPS. From the provider's BGP onboarding.
<VPS_IP>VPS public IPv4 (used as the WireGuard endpoint for the home → VPS tunnel).
<VPS_NIC>VPS public interface (ip -o link; typical enp3s0, ens3).
<VPS_GUA>The VPS's own IPv6 on its public /64 (e.g. 2001:db8::2b). Used as the BGP source toward transit.
<VPS_PUBKEY> / <HOME_PUBKEY>WireGuard public keys, one per side. Printed during §4.
<VPS_ROUTER_ID> / <HOME_ROUTER_ID>Unique 32-bit BGP router IDs written like IPv4 addresses.
<R64_LINK>The /64 Route64 carves out for the BGP transport (portal calls it the point-to-point /64). ::1 is the PoP, ::2 is your home router.
<R64_*> (other)Other Route64 placeholders defined in the Route64 post §2.
<ULA_PREFIX>Your locally-generated ULA (same one the DNS post uses).
<CLIENT_GUA>A LAN client's GUA from <OWN_48>, used as the source in §8 induced-failover pings.

The Route64 portal needs your <OWN_AS> set as the tunnel's BYO ASN — the BGP session requires it. The field is on the tunnel page next to the WireGuard config.

<OWN_48> substitution mirrors the equal-paths posts: <OWN_48>:0::1/64 on the tunnel link, <OWN_48>:1::/64 for the trusted VLAN, etc.

4. VPS — WireGuard, transit BGP, iBGP to home, BFD

Two bird2 BGP sessions on the VPS: eBGP upstream to the transit provider (carries your <OWN_48> outbound and ::/0 inbound), and iBGP downstream to home (carries <OWN_48> inbound from home and ::/0 outbound). BFD only runs on the home-side session because that's the one your <OWN_48> rides over and the one that needs sub-second failure detection.

Each WireGuard tunnel needs the peer's public key in its own config, so the keygen runs on both sides before either config is pasted. The VPS script below prints <VPS_PUBKEY> at the end; <HOME_PUBKEY> comes from running the /interface/wireguard add name=wg-host … line in §5 first and reading the auto-generated public key with /interface/wireguard print where name=wg-host. The <R64_*> keys are not generated locally — Route64 hands you <R64_PRIVKEY> and <R64_SRV_PUBKEY> on the tunnel page.

Assumes a minimal Ubuntu install. bird listens on TCP/179 on all interfaces by default; the nftables ruleset below scopes which NIC each session is reachable on — transit NIC for the upstream session, wg0 for the home session.

VPS — wg0 + bird2 (transit eBGP + home iBGP + BFD) + nftables

bash

1set -e 2apt-get update -qq 3apt-get install -y -qq wireguard bird2 nftables 4 5cat >/etc/sysctl.d/99-multi-homed.conf <<'EOF' 6net.ipv6.conf.all.forwarding = 1 7net.ipv6.conf.default.forwarding = 1 8net.ipv6.conf.<VPS_NIC>.accept_ra = 2 9EOF 10sysctl --system >/dev/null 11 12umask 077 13mkdir -p /etc/wireguard 14wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub 15VPS_PRIVKEY=$(cat /etc/wireguard/server.key) 16 17cat >/etc/wireguard/wg0.conf <<EOF 18[Interface] 19PrivateKey = ${VPS_PRIVKEY} 20Address = <OWN_48>:0::1/64, fe80::1/64 21ListenPort = 51820 22MTU = 1420 23Table = off 24 25[Peer] 26PublicKey = <HOME_PUBKEY> 27AllowedIPs = <OWN_48>:0::2/128, <OWN_48>::/48 28PersistentKeepalive = 25 29EOF 30 31systemctl enable --now wg-quick@wg0 32echo "VPS public key: $(cat /etc/wireguard/server.pub)" 33 34cat >/etc/nftables.conf <<'EOF' 35#!/usr/sbin/nft -f 36flush ruleset 37 38table inet filter { 39 chain input { 40 type filter hook input priority filter; policy drop; 41 42 ct state established,related accept 43 ct state invalid drop 44 iif lo accept 45 meta l4proto { icmp, icmpv6 } accept 46 47 tcp dport 22 accept # SSH 48 udp dport 51820 accept # WireGuard from home 49 iifname "<VPS_NIC>" tcp dport 179 accept # BGP from transit 50 iifname "wg0" tcp dport 179 accept # BGP from home, wg0 only 51 iifname "wg0" udp dport 3784 accept # BFD from home, wg0 only 52 } 53 54 chain forward { 55 type filter hook forward priority filter; policy drop; 56 57 ct state established,related accept 58 iifname "wg0" ip6 saddr <OWN_48>::/48 accept # LAN -> internet, source must be ours 59 oifname "wg0" ip6 daddr <OWN_48>::/48 accept # internet -> LAN, dest must be ours 60 } 61 62 chain output { 63 type filter hook output priority filter; policy accept; 64 } 65} 66EOF 67systemctl enable --now nftables 68 69mkdir -p /etc/bird 70cat >/etc/bird/bird.conf <<'EOF' 71log syslog all; 72router id <VPS_ROUTER_ID>; 73 74protocol device { } 75 76protocol direct { 77 ipv6; 78 interface "wg0", "<VPS_NIC>"; 79} 80 81protocol kernel kernel6 { 82 metric 32; 83 ipv6 { 84 # Pull only ::/0 from the kernel. This is what becomes locally- 85 # originated in BGP (empty AS_PATH) and what wins best-path on home. 86 import filter { 87 if net = ::/0 then accept; 88 reject; 89 }; 90 91 # Install the BGP-learned <OWN_48> (from home) into the kernel so 92 # return traffic to home actually goes via wg0. 93 export filter { 94 if net = <OWN_48>::/48 then accept; 95 reject; 96 }; 97 }; 98 learn yes; 99} 100 101# Active BFD probing toward home. Without an explicit neighbor, bird is 102# passive; after a flap both sides wait for the other and recovery stalls. 103protocol bfd { 104 interface "wg0" { 105 min rx interval 200 ms; 106 min tx interval 200 ms; 107 idle tx interval 1 s; 108 multiplier 3; 109 }; 110 neighbor <OWN_48>:0::2 dev "wg0"; 111} 112 113# iBGP downstream to home — both ends in <OWN_AS>. 114protocol bgp home { 115 local <OWN_48>:0::1 as <OWN_AS>; 116 neighbor <OWN_48>:0::2 as <OWN_AS>; 117 bfd on; 118 119 ipv6 { 120 import filter { 121 if net = <OWN_48>::/48 then accept; 122 reject; 123 }; 124 125 export filter { 126 if net = ::/0 then accept; 127 reject; 128 }; 129 130 next hop self; 131 }; 132} 133 134# eBGP upstream to transit — your <OWN_AS> peering with <TRANSIT_AS>. 135# AS 52025 hands you a peer address (<TRANSIT_PEER_V6>) that sits in a 136# different subnet from your on-link /64, so it's reached over your 137# default route and the session is multihop. `local` pins the source to 138# your on-link address (<VPS_GUA>); without it bird may source from the 139# wrong address and the peering won't come up. 140protocol bgp transit { 141 local <VPS_GUA> as <OWN_AS>; 142 neighbor <TRANSIT_PEER_V6> as <TRANSIT_AS>; 143 multihop 2; 144 145 ipv6 { 146 # AS 52025 is announce-only and sends nothing, so this imports no 147 # routes in practice. Kept defensive: the ::/0 home uses is the 148 # kernel-originated one above (empty AS_PATH), not anything learned 149 # here. Anything more-specific would be provider-routed; reject it. 150 import filter { 151 if net = ::/0 then accept; 152 reject; 153 }; 154 155 # Originate only <OWN_48> upstream. ROA must already authorize this 156 # origin or transit will drop it via RPKI. 157 export filter { 158 if net = <OWN_48>::/48 then accept; 159 reject; 160 }; 161 }; 162} 163EOF 164chown -R bird:bird /etc/bird 165 166mkdir -p /etc/systemd/system/bird.service.d 167printf '[Service]\nRestart=on-failure\nRestartSec=2s\n' \ 168 > /etc/systemd/system/bird.service.d/restart.conf 169systemctl daemon-reload 170systemctl enable --now bird

The explicit neighbor <OWN_48>:0::2 dev "wg0" line in the protocol bfd block is what turns this side into the active prober. Without it bird is passive — it answers BFD packets but never originates them. If the home side is also passive (or briefly drops out), both ends sit waiting for the other and the session never re-establishes. The config will work fine on first boot and fail to recover from the first real outage.

Two separate protocol bgp blocks because the sessions have very different shapes — iBGP to home over wg0 with BFD, eBGP to transit over the public NIC without. The transit session has no BFD by default; whether your transit provider supports it varies and is not required for the multi-homed story to work. The kernel import filter taking only ::/0 is what gives home an empty AS_PATH on the route — bird treats kernel-learned routes as locally originated, and iBGP doesn't prepend.

The forward chain's ip6 saddr <OWN_48>::/48 and ip6 daddr <OWN_48>::/48 guards are anti-spoof: the VPS will only forward outbound packets sourced from your prefix and inbound packets destined to it. Without them iifname "wg0" accept would let the home side egress with any source address, and oifname "wg0" accept would let the VPS be used as a generic router toward whatever happens to be reachable through wg0.

If you configure the public NIC's IPv6 by hand rather than via RA (accept_ra=2 above) — on VyOS, static netplan, etc. — give it the on-link /64, never the provider's full /48. A /48 on the interface installs an on-link route for all 65k /64s it covers, so the box answers its own NDP for addresses that actually live elsewhere and silently drops them — the multihop transit peer above is exactly one such address.

5. Home router — both BGP sessions, BFD on the VPS one

The configuration below is RouterOS-only. If your home router is Linux/bird2 or VyOS, take §4 as the template: it's the same bird2 session shape you need at home, with the BGP roles swapped — home originates <OWN_48> (kernel-import a static blackhole for it) and imports ::/0 from both peers; VPS originates ::/0 (already shown) and imports <OWN_48> from home. The Route64 session is plain eBGP with no BFD.

Three blocks below — origination + aggregate, VPS session, Route64 session. The first block stands up the BGP instance and the locally-originated /48 that both sessions advertise; the second and third blocks are independent and either order works.

Home router — BGP instance + originated /48 (shared by both sessions)

bash

1/ipv6/route add dst-address=<OWN_48>::/48 blackhole distance=254 \ 2 comment="aggregate-for-bgp" 3 4/ipv6/firewall/address-list add list=bgp-networks-own \ 5 address=<OWN_48>::/48 comment="/48 to both upstreams" 6 7/routing/bgp/instance add name=default-bgp as=<OWN_AS> router-id=<HOME_ROUTER_ID>

Home router — VPS path WireGuard + iBGP + BFD

bash

1/interface/wireguard add name=wg-host listen-port=51820 mtu=1420 2/interface/wireguard/peers add interface=wg-host name=vps \ 3 public-key="<VPS_PUBKEY>" \ 4 endpoint-address=<VPS_IP> endpoint-port=51820 \ 5 allowed-address=::/0 \ 6 persistent-keepalive=25s 7 8/ipv6/address add address=<OWN_48>:0::2/64 interface=wg-host advertise=no 9 10/routing/filter/rule add chain=bgp-in-vps \ 11 rule="if (dst == ::/0) { accept } reject" 12/routing/filter/rule add chain=bgp-out-vps \ 13 rule="if (dst == <OWN_48>::/48) { accept } reject" 14 15/routing/bgp/template add name=tpl-host as=<OWN_AS> use-bfd=yes 16/routing/bgp/connection add name=host-vps instance=default-bgp \ 17 remote.address=<OWN_48>:0::1 remote.as=<OWN_AS> \ 18 local.address=<OWN_48>:0::2 local.role=ibgp \ 19 templates=tpl-host afi=ipv6 use-bfd=yes \ 20 input.filter=bgp-in-vps \ 21 output.network=bgp-networks-own output.filter-chain=bgp-out-vps 22 23/routing/bfd/configuration/add interfaces=wg-host \ 24 min-rx=200ms min-tx=200ms multiplier=3 25 26# BFD's UDP/3784 packets arrive unsolicited, so the defconf 27# established,related,untracked rule doesn't cover them. The BGP 28# session itself is dialed out by home and needs no input rule. 29/ipv6/firewall/filter add chain=input action=accept protocol=udp dst-port=3784 \ 30 in-interface=wg-host comment="BFD from VPS" \ 31 place-before=[find where chain=input and comment="defconf: drop everything else not coming from LAN"] 32# place-before= pins the new accept above the defconf drop. If your 33# RouterOS version's defconf wording differs, [find ...] returns 34# nothing and the rule lands at the bottom (after the drop) — swap to 35# a numeric place-before= in that case.

Home router — Route64 path WireGuard + eBGP, no BFD

bash

1/interface/wireguard add name=wg-route64 mtu=1420 \ 2 listen-port=<R64_POP_PORT> private-key="<R64_PRIVKEY>" 3/interface/wireguard/peers add interface=wg-route64 \ 4 public-key="<R64_SRV_PUBKEY>" \ 5 endpoint-address=<R64_POP_IP> endpoint-port=<R64_POP_PORT> \ 6 allowed-address=::/0 persistent-keepalive=15s 7/ipv6/address add address=<R64_LINK>::2/64 interface=wg-route64 advertise=no 8 9/routing/filter/rule add chain=bgp-in-r64 \ 10 rule="if (dst == ::/0) { accept } reject" 11/routing/filter/rule add chain=bgp-out-r64 \ 12 rule="if (dst == <OWN_48>::/48) { accept } reject" 13 14# Route64 portal must have BYO ASN set to <OWN_AS> for this to peer. 15/routing/bgp/connection add name=host-r64 instance=default-bgp \ 16 remote.address=<R64_LINK>::1 remote.as=212895 \ 17 local.address=<R64_LINK>::2 local.role=ebgp \ 18 afi=ipv6 \ 19 input.filter=bgp-in-r64 \ 20 output.network=bgp-networks-own output.filter-chain=bgp-out-r64

No input firewall rule is needed for either WireGuard peer at base — both BGP sessions dial out from home on TCP/179, so the return traffic rides the defconf established,related,untracked rule. The only unsolicited inbound is BFD/3784, which exists only on wg-host.

The Route64 link uses Route64's <R64_LINK> /64 from the portal (the point-to-point pair, ::1/::2) as the BGP session addressing — your <OWN_48> does not appear in the Route64 transport at all; it appears in the BGP advertisement that rides over it.

6. BFD where the peer supports it

BFD is enabled where it works (AS 52025-class transits like the VPS upstream above, where bird2 on a peer you operate runs BFD) and skipped where it doesn't. Route64 does not reply to BFD — use-bfd=yes on the host-r64 connection drops the BGP session — so the home-router BGP configuration in §5 only attaches BFD to the host-vps connection.

The host-r64 connection runs plain BGP liveness — the only option, since Route64 doesn't support BFD — and that's fine for the backup-path role it occupies: the fast-failover work is done by the primary's BFD. When BFD says the VPS path is dead, RouterOS withdraws the VPS-learned ::/0 within ~1.5 s and best-path immediately switches to the Route64-learned ::/0, which is already in BGP, just not active.

Failure modeWhat detects itRecovery time
VPS WG path dead, Route64 still upBFD on wg-host~1.5 s to switch
Route64 path dead, VPS still upirrelevant — VPS is already primary; no client-visible event0
Both paths dead simultaneouslynetwatch (§7)~6–8 s to drain LAN RA
VPS path recovers, Route64 was activeBFD comes back up~10 s to reinstall

The second row is the asymmetry worth internalizing. If only Route64 fails, nothing happens at the LAN — Route64's ::/0 is the backup, not the active path, so its withdrawal is invisible to clients.

7. Last-resort fallback — netwatch + LAN RA drain

If both upstreams fail at once, neither BGP session has a route to withdraw against — there is no surviving primary to fail over to. The LAN would happily continue picking the home router as its IPv6 default even though the router can't reach anything beyond <OWN_48>. The mitigation, lifted directly from the Route64 post §6, is a netwatch that drains LAN RA so clients fall back to IPv4 by Happy Eyeballs.

Home router — netwatch drains LAN RA when both paths are gone

bash

1/tool/netwatch add name=v6health type=tcp-conn \ 2 host=2606:4700:4700::1111 port=443 \ 3 interval=5s timeout=2s comment=v6health \ 4 up-script="/ipv6/nd set [find interface=bridge] ra-lifetime=30m; /ipv6/nd set [find interface=vlan-iot] ra-lifetime=30m; /ipv6/nd set [find interface=vlan-guest] ra-lifetime=30m; :log info v6-up" \ 5 down-script="/ipv6/nd set [find interface=bridge] ra-lifetime=0s; /ipv6/nd set [find interface=vlan-iot] ra-lifetime=0s; /ipv6/nd set [find interface=vlan-guest] ra-lifetime=0s; :log info v6-down-failedtoIPv4"

The probe is a TCP-connect to Cloudflare's IPv6 anycast on 443 — it exits via whichever ::/0 is active, so a single netwatch covers both paths. It does not distinguish VPS-down from Route64-down, by design: BFD + BGP best-path already handle single-path failures invisibly, so the netwatch only needs to fire when neither path is forwarding — the one state where the LAN must drop IPv6 entirely.

RouterOS 7.22's IPv6 ICMP netwatch is broken (type=icmp stays "down" for v6 targets even when /ping works) — type=tcp-conn is the working replacement. End-to-end client failover is netwatch detection (~5–7 s at interval=5s timeout=2s) plus the ~0.6 s unsolicited RA RouterOS emits whenever ra-lifetime changes ≈ ~6–8 s, symmetric on recovery. The set [find interface=...] lines start working as soon as Per-VLAN IPv6 is applied (substitution: <GUA_LAN>=<OWN_48>:1, <GUA_IOT>=<OWN_48>:10, <GUA_GUEST>=<OWN_48>:20).

8. Verification

Home router — both sessions healthy

bash

1# Both BGP sessions established 2/routing/bgp/session/print 3# expect: host-vps established 4# host-r64 established 5 6# BFD up on the VPS session only 7/routing/bfd/session/print 8# expect: one entry, state=up, peer=<OWN_48>:0::1 9 10# Active default route 11/routing/route/print where dst-address=::/0 12# expect: two ::/0 candidates; the bgp.as-path=[] one is active 13 14# The active default's gateway is the VPS WG endpoint 15/ipv6/route/print where active and dst-address=::/0 16# expect: gateway=<OWN_48>:0::1%wg-host 17 18# netwatch healthy 19/tool/netwatch/print where comment=v6health 20 21# BFD/3784 input rule lives above the defconf drop. 22# The §5 place-before=[find ...] depends on the defconf comment string 23# in your RouterOS version; if it doesn't match, the rule lands at the 24# bottom of the chain (after the drop) and BFD silently never arrives. 25/ipv6/firewall/filter print 26# expect: the chain=input udp dst-port=3784 rule above the 27# "defconf: drop everything else not coming from LAN" rule

VPS — transit + home sessions healthy

bash

1birdc show protocols 2# expect: home (bgp) Established 3# transit (bgp) Established 4# bfd1 (bfd) Up 5 6birdc show route ::/0 all 7# expect: best path via wg0 to <OWN_48>:0::2 from home (iBGP, AS_PATH empty) 8# plus transit's ::/0 from <TRANSIT_PEER_V6> 9 10birdc show route <OWN_48>::/48 all 11# expect: via wg0 from home (learned by iBGP), exported to transit

Induced failover

Verify primary→backup pivot

bash

1# On the VPS, drop BFD packets to simulate a half-dead tunnel. 2# BFD declares down in ~700 ms; bird withdraws ::/0 from home; home's 3# best-path picks the Route64-learned ::/0. 4nft add rule inet filter input iifname wg0 udp dport 3784 drop 5 6# On home router: time it. 7/log/print where message~"BGP|BFD" 8/ipv6/route/print where active and dst-address=::/0 9# expect: gateway now <R64_LINK>::1%wg-route64 10 11ping -c 5 -I <CLIENT_GUA> 2606:4700:4700::1111 12# expect: 1 dropped packet at the switchover, rest reach via Route64 13 14# Undo it — BFD comes back, best-path returns to VPS. 15nft -a list ruleset | grep "udp dport 3784 drop" # note the handle 16nft delete rule inet filter input handle <handle>

Verify backup-only failure is invisible to the LAN

bash

1# Disable Route64 alone. Because VPS is already best-path, this should 2# produce no active-route change and no v6-down log line — Route64 3# leaves BGP, the LAN sees nothing. 4/interface/wireguard/peers/disable [find interface=wg-route64] 5/ipv6/route/print where active and dst-address=::/0 6# expect: gateway still <OWN_48>:0::1%wg-host 7/log/print where message~"v6-(up|down)" 8# expect: no new entries 9 10/interface/wireguard/peers/enable [find interface=wg-route64]

Verify both-paths-dead fallback

bash

1# Stop WireGuard on the VPS and disable Route64 on home simultaneously. 2# Within ~8 s the netwatch fires; clients drop their v6 default and 3# Happy-Eyeballs to IPv4. 4ssh vps "wg-quick down wg0" 5/interface/wireguard/peers/disable [find interface=wg-route64] 6/log/print where message~"v6-(up|down)" 7 8# Restore. 9/interface/wireguard/peers/enable [find interface=wg-route64] 10ssh vps "wg-quick up wg0"

A brief IPv6 miss at the switchover plus a 6–8 s window before clients drop to IPv4 when both paths die — instead of either a hold-time-long blackhole or a permanent LAN-thinks-it-has-v6 stall — is the whole point of this build.

After both BGP sessions are established and verified, continue to Per-VLAN IPv6 on RouterOS to plumb <OWN_48> through to every VLAN — per-VLAN GUA + ULA + RA RDNSS, IPv6 forward-chain isolation, and SLAAC anti-spoof, using the substitution <GUA_LAN>=<OWN_48>:1, <GUA_IOT>=<OWN_48>:10, <GUA_GUEST>=<OWN_48>:20.

Glossary

AcronymExpansionReference
AS / ASNAutonomous system (number)RFC 1930
BFDBidirectional Forwarding DetectionRFC 5880
BGPBorder Gateway ProtocolRFC 4271
eBGPexternal BGP (across AS boundary)RFC 4271 §3.2
iBGPinternal BGP (within one AS)RFC 4271 §3.2
LIRLocal Internet RegistryRIPE NCC
ROARoute Origin Authorization (RPKI)RFC 6482
RPKIResource Public Key InfrastructureRFC 6480

Series-wide acronyms (CGNAT, DoH, GUA, RA, RDNSS, SLAAC, ULA, VLAN, VPS, WireGuard) live in the index glossary.

References


Disclosure: the NoPkt and pdxnet.co.uk links above are affiliate links — signing up through them earns me a referral credit at no extra cost to you. I am recommending both providers because I use them on this build, not because of the credit.

Share

Comments

Comments are powered by GitHub Discussions and require a free GitHub account to post.