BGP Active-Active Dual-Homed Home Enterprise

Overview

This document covers end-to-end design, procurement, physical prerequisites, and production VyOS configuration for the Domus Digitalis home enterprise BGP network.

Table 1. Design Goals
Goal Implementation

No single point of failure

Two physical ISP circuits, two independent BGP upstreams, two VyOS nodes. Any single failure leaves at least one forwarding path intact.

Active-active forwarding

Both VyOS nodes forward live traffic simultaneously via split VRRP mastership and AS-path prepend traffic engineering.

Provider-independent IPv6

PI /48 registered under your ASN. Permanent, portable, independent of any LIR’s business continuity.

Dual-stack clients

BGP carries IPv6 PI space. NAT44 on VyOS provides IPv4 outbound for legacy devices. DNS64/NAT64 reduces IPv4 dependency for capable clients.

PE handoff survivability

BFD with per-ISP tuned timers. Aggressive BGP hold timers. conntrack-sync preserves sessions across reconvergence.

Operational hygiene

RPKI ROA, IRR objects, graceful shutdown, VRF-based WAN separation instead of policy routing tables.

Phase 0: Physical Prerequisites

Verify these before spending any money on procurement.

0.1 NIC Availability — Supermicro E300-9D

Both kvm-01 and kvm-02 have 8x RJ45 10GbE ports (eno1-eno8). Current usage:

Port State Bridge Purpose

eno7

UP (10GbE)

br-wan

ISP-A modem (existing WAN)

eno8

UP (10GbE)

br-mgmt

LAN trunk to switch

eno1-6

DOWN

6 ports available

No additional hardware required. Use eno1 for ISP-B (T-Mobile gateway).

# Verify port status on each hypervisor
for port in eno{1..8}; do
  state=$(cat /sys/class/net/$port/operstate 2>/dev/null || echo "missing")
  speed=$(cat /sys/class/net/$port/speed 2>/dev/null || echo "?")
  master=$(ip link show $port 2>/dev/null | awk -F'master ' '{print $2}' | awk '{print $1}')
  printf "%-6s  %-6s  %5sMbps  %s\n" "$port" "$state" "$speed" "${master:-[none]}"
done

0.2 T-Mobile 5G Coverage

Verify 5G Home Internet is available at your Pasadena address before ordering:

  • Coverage check: t-mobile.com/home-internet

  • Order online — no contract, no installation fee (~$50/mo)

  • Gateway device ships via mail, self-install

  • Note: T-Mobile 5G Home Internet is behind CGNAT — outbound WireGuard works fine (UDP + persistent keepalive), inbound services must route via your BGP-announced PI /48

0.3 ISP-A Modem Configuration

Your wired ISP modem must be in bridge mode or have a DMZ host setting pointing to the Supermicro WAN interface. This ensures VyOS receives the public WAN IP directly rather than double-NAT.

# Verify after bridge mode: VyOS eth0 should show public IP
show interfaces ethernet eth0
# Address should be a public IPv4, not 192.168.x.x

0.4 NAS Path Verification

Confirm your NAS path (10GBase-T 2) is not disrupted by the bridge layout changes. The NAS must remain reachable on the internal VLAN regardless of WAN state.

Physical Architecture

Last-Mile Circuits

Circuit Provider Cost Hypervisor bridge

ISP-A

Wired cable or fiber (existing)

~$50–80/mo

br-wan → eno7 (existing)

ISP-B

T-Mobile 5G Home Internet

~$50/mo, no contract

br-ispb → eno1 (new)

Why T-Mobile 5G for ISP-B

Completely separate physical infrastructure — cellular towers, different backbone, independent failure domain. A fiber cut, cable node outage, or DSLAM failure does not affect the cellular path. This is the cheapest genuine physical last-mile redundancy available in the US (~$50/mo, no contract). T-Mobile CGNAT does not impact outbound WireGuard tunnels — UDP keepalives maintain the session through NAT state.

Full Topology

   [ISP-A modem/ONT]              [T-Mobile 5G gateway]
          |                               |
       [eno7]                          [eno1]
     [br-wan]                       [br-ispb]
          |           \        /          |
          |            \      /           |
     [vyos-01]                       [vyos-02]
     eth0=WAN-A primary              eth0=LAN (SWAPPED - needs fix)
     eth1=LAN                        eth1=WAN-A
          |                               |
     VRF WAN-A (table 100)          VRF WAN-B (table 200)
     wg0 → iFog Fremont             wg0 → iFog Fremont
     wg1 → ROUTE64                  wg1 → ROUTE64
          |                               |
          +-------------+  +-------------+
                        |  |
                  [eth3 HA link]
                  10.50.99.0/30
                  conntrack-sync
                        |
               [eth2 internal trunk]
               VRRP split-master
               dual-stack downstream
Current State (Pre-Migration)

The VyOS nodes currently have only 2 interfaces each, and vyos-02 has interfaces flipped (eth0=LAN, eth1=WAN instead of eth0=WAN, eth1=LAN).

Phase 1.5 below documents the zero-downtime VRRP migration to add eth2 (ISP-B) and eth3 (HA-sync) to each VM while fixing the interface order on vyos-02.

KVM Bridge Layout

Bridge Physical interface Purpose

br-wan

eno7 → ISP-A modem (existing)

WAN-A uplink. VyOS eth0.

br-ispb

eno1 → T-Mobile gateway (new)

WAN-B uplink. VyOS eth1 (after migration).

br-mgmt

eno8 → LAN trunk to switch (existing)

Downstream VLANs. VRRP VIPs. Dual-stack.

br-ha

Internal crosslink (no physical NIC)

conntrack-sync + VRRP peer comms only.

# Verify existing bridges on hypervisor
bridge link show

# Create new br-ispb for T-Mobile gateway
sudo nmcli con add type bridge con-name br-ispb ifname br-ispb
sudo nmcli con add type ethernet con-name br-ispb-port ifname eno1 master br-ispb
sudo nmcli con up br-ispb

# Create br-ha (internal only, no physical port)
sudo nmcli con add type bridge con-name br-ha ifname br-ha
sudo nmcli con up br-ha
Table 2. Target VM Interface Order
VyOS eth Bridge Purpose

eth0

br-wan

ISP-A WAN (existing)

eth1

br-ispb

ISP-B WAN (T-Mobile)

eth2

br-mgmt

LAN trunk

eth3

br-ha

HA sync

PE Reality and Handoff Behavior

What the Upstream PE Actually Is

Neither iFog nor ROUTE64 ships hardware. There is no CPE, no Cradlepoint, no physical handoff of any kind. The PE is a software router in a datacenter connected exclusively via WireGuard tunnel over your home ISP circuits.

Upstream PE platform Notes

iFog AS34927

Linux x86, FRR or BIRD BGP daemon

Fremont PoP closest to Pasadena. 100Mbps tunnel cap. 1TB/mo fair use. No SLA. Non-commercial only.

ROUTE64

VyOS-based, BIRD BGP daemon, VPP data plane

Automated IRR/RPKI filter refresh every 3h. Self-service portal. No SLA.

[VyOS node]
    | WireGuard UDP (encrypted, over home ISP circuit)
[iFog or ROUTE64 Linux PE — datacenter x86]
    | Their upstream transit (Cogent, Telia, HE, etc.)
[Internet DFZ]

PE Handoff Scenarios

Event What happens Recovery mechanism

iFog PE internal reroute

WireGuard endpoint unreachable briefly. BGP sessions to iFog drop on both nodes.

BFD detects in <1s (wired) or <2s (cellular). BGP withdraws iFog paths. ROUTE64 sole upstream. Reconverges in 2–5s.

ROUTE64 PE failure

Mirror of above.

iFog becomes sole upstream.

ISP-A (wired) goes down

vyos1 VRF WAN-A loses default route. iFog wg0 keepalives stop. BFD triggers.

VRF WAN-B becomes active on vyos1. Tunnels re-establish via ISP-B. vyos2 unaffected.

ISP-B (T-Mobile) goes down

Mirror of above on vyos2.

VRF WAN-A fallback on vyos2.

vyos1 node failure

VRRP: vyos2 assumes all VRID mastership (~2–3s). BGP: DFZ reconverges to vyos2 paths only.

conntrack-sync external-cache on vyos2 preserves established sessions.

vyos2 node failure

Mirror of above.

conntrack-sync on vyos1 handles it.

Phase 1: Procurement

Procurement Checklist

Complete these in order. Each step unlocks the next.

Step Action Link / Source Est. Cost

1

Verify T-Mobile 5G Home Internet coverage at your address. If available, order online — ships via mail, self-install.

t-mobile.com/home-internet

~$50/mo

2

Contact Voldeta for RIPE sponsorship — request ASN + PI /48. Email or use their portal. Provide: org name, two upstream ASNs (AS34927 iFog, ROUTE64 ASN from bgp.tools), use case.

voldeta.com

~€120 first year

3

Create iFog BGPTunnel account — verify ASN once issued. Create TWO WireGuard tunnels (one per VyOS node). Select Fremont PoP. Record: endpoint, peer pubkey, link /127 addresses.

bgptunnel.com

Free

4

Create ROUTE64 account — verify ASN. Create TWO WireGuard tunnels (one per VyOS node). Record: endpoint, peer pubkey, link /127 addresses.

route64.org

Free

5

Create RIPE database objects — route6, aut-num (see 1.4). Create RPKI ROA via my.ripe.net.

my.ripe.net

Included

6

Create br-ispb and br-ha bridges on both hypervisors. Connect T-Mobile gateway to eno1 port.

See KVM Bridge Layout above

7

Complete Phase 1.5 — Add vNICs to VyOS VMs using VRRP zero-downtime migration procedure.

See Phase 1.5 below

Recommended Order

Start with Step 2 (Voldeta/RIPE) first — ASN approval takes 3–7 business days. Order T-Mobile (Step 1) in parallel. By the time T-Mobile is set up, your ASN should be ready for Steps 3–5. No additional NICs needed — your Supermicro E300-9D has 6 unused 10GbE ports (eno1–eno6).

1.1 Hardware

Your Supermicro E300-9D-8CN8TP has 8x RJ45 10GbE ports. Current usage:

  • eno7 → br-wan (ISP-A modem) — existing

  • eno8 → br-mgmt (LAN trunk) — existing

  • eno1–eno6 → available

No additional NIC purchase required. Use eno1 for T-Mobile gateway.

  1. Order T-Mobile 5G Home Internet — t-mobile.com/home-internet

1.2 RIPE Sponsoring LIR — ASN + PI /48

Recommended: voldeta.com

RIPE NCC base fees: €50/yr ASN + €50/yr PI /48. Voldeta passes these through at cost. Expected all-in: ~€120/year.

Prepare before applying
Organization name : Domus Digitalis Network
                    (individual registration accepted)
Upstream ASN 1    : AS34927  (iFog BGPTunnel)
Upstream ASN 2    : <ROUTE64 ASN — verify on bgp.tools>
Use description   : Home enterprise network, BGP multihoming,
                    non-commercial research and learning
Country           : United States
                    (RIPE sponsoring is not region-restricted)

Expected RIPE processing: 3–7 business days.

1.3 Sign Up at Both Upstreams

Provider Portal Steps

iFog BGPTunnel

bgptunnel.com

Create account. Verify ASN. Create two WireGuard tunnels — one for vyos1, one for vyos2. Select Fremont PoP. Record: endpoint IP:port, peer pubkey, /127 link addr (both ends).

ROUTE64

route64.org

Create account. Create two WireGuard tunnels — one per node. Record: endpoint IP:port, peer pubkey, /127 link addr (both ends).

You will have four tunnel parameter sets when done:

ifog-vyos1  : endpoint, peer-pubkey, my-link-addr/127, peer-addr
ifog-vyos2  : endpoint, peer-pubkey, my-link-addr/127, peer-addr
r64-vyos1   : endpoint, peer-pubkey, my-link-addr/127, peer-addr
r64-vyos2   : endpoint, peer-pubkey, my-link-addr/127, peer-addr

1.4 RIPE Database Objects

Create after resources are issued. Both upstreams enforce IRR-based prefix filters — missing objects means your prefix is silently dropped at their PE and never propagates into the DFZ.

route6 Object

route6:     {domus-prefix}
descr:      Domus Digitalis PI block
origin:     {domus-asn}
mnt-by:     DOMUS-MNT
source:     RIPE

aut-num Object

aut-num:    {domus-asn}
as-name:    DOMUS-DIGITALIS
descr:      Domus Digitalis Home Enterprise Network
mp-import:  from {ifog-asn} accept ANY
mp-import:  from {route64-asn} accept ANY
mp-export:  to {ifog-asn} announce {domus-asn}
mp-export:  to {route64-asn} announce {domus-asn}
admin-c:    <YOUR-RIPE-HANDLE>
tech-c:     <YOUR-RIPE-HANDLE>
mnt-by:     DOMUS-MNT
source:     RIPE

RPKI ROA

Create via my.ripe.net → Resource Certification → ROAs:

Prefix   : {domus-prefix}
Max-len  : 48
Origin   : {domus-asn}
# Validate ~15 minutes after creation
curl -s "https://rpki.cloudflare.com/api/v1/validity/\
{domus-asn}/{domus-prefix}" | python3 -m json.tool
# Expect: "status": "valid"

Phase 1.5: VRRP Zero-Downtime Interface Migration

This procedure adds 2 vNICs (br-ispb, br-ha) to each VyOS VM and fixes the interface order on vyos-02 — all without downtime. VRRP ensures one node is always forwarding traffic.

Current State

VM eth0 eth1

vyos-01

br-wan (WAN)

br-mgmt (LAN)

vyos-02

br-mgmt (LAN) — WRONG

br-wan (WAN) — WRONG

Both VMs have only 2 interfaces. vyos-02 has them swapped.

Target State

VM eth0 eth1 eth2 eth3

vyos-01

br-wan (ISP-A)

br-ispb (ISP-B)

br-mgmt (LAN)

br-ha (sync)

vyos-02

br-wan (ISP-A)

br-ispb (ISP-B)

br-mgmt (LAN)

br-ha (sync)

Prerequisites

Take VM Snapshots (BEFORE ANY CHANGES)

Test snapshot/revert workflow first — validate safety net before trusting it.

# On kvm-02 — test with BACKUP node (no traffic impact)
sudo virsh snapshot-create-as vyos-02 test-snapshot "Testing snapshot workflow"
# Verify snapshot exists
sudo virsh snapshot-list vyos-02
# Make a harmless change on vyos-02
ssh vyos-02 "configure; set system host-name vyos-02-TEST; commit; save"

# Verify change applied
ssh vyos-02 "show host name"
# Expected: vyos-02-TEST
# Revert snapshot
sudo virsh snapshot-revert vyos-02 test-snapshot

# Wait for VM to come back
sleep 10

# Verify change is gone
ssh vyos-02 "show host name"
# Expected: vyos-02 (original)
# Clean up test snapshot
sudo virsh snapshot-delete vyos-02 test-snapshot

# Snapshot workflow validated ✓

Now create the real snapshots:

# On kvm-01
sudo virsh snapshot-create-as vyos-01 pre-bgp-migration "Before 4-NIC migration"

# On kvm-02
sudo virsh snapshot-create-as vyos-02 pre-bgp-migration "Before 4-NIC migration"
# Verify snapshots created
sudo virsh snapshot-list vyos-01
sudo virsh snapshot-list vyos-02
Snapshot Rollback (if needed)
sudo virsh snapshot-revert vyos-01 pre-bgp-migration
sudo virsh snapshot-revert vyos-02 pre-bgp-migration
Delete Snapshots After Success

Snapshots degrade disk I/O performance. Delete within 24–48 hours after confirming migration success:

sudo virsh snapshot-delete vyos-01 pre-bgp-migration
sudo virsh snapshot-delete vyos-02 pre-bgp-migration

Export Current Firewall Config

# On vyos-01 — export full config
ssh vyos-01 "show configuration commands" > ~/vyos-01-config-$(date +%F).txt

# On vyos-02 — export full config
ssh vyos-02 "show configuration commands" > ~/vyos-02-config-$(date +%F).txt
# Extract firewall rules specifically
ssh vyos-01 "show configuration commands | grep firewall" > ~/vyos-01-firewall-$(date +%F).txt
ssh vyos-02 "show configuration commands | grep firewall" > ~/vyos-02-firewall-$(date +%F).txt
# Find all rules referencing eth1 (will need to change to eth2)
ssh vyos-01 "show configuration commands | grep eth1"
ssh vyos-02 "show configuration commands | grep eth1"
# Copy to NAS for safekeeping
scp ~/vyos-*-$(date +%F).txt nas-01:/volume1/backups/vyos/

Create KVM Bridges

# On both hypervisors — create br-ispb and br-ha bridges
sudo nmcli con add type bridge con-name br-ispb ifname br-ispb
sudo nmcli con add type ethernet con-name br-ispb-port ifname eno1 master br-ispb
sudo nmcli con up br-ispb

sudo nmcli con add type bridge con-name br-ha ifname br-ha
sudo nmcli con up br-ha

# Verify
bridge link show

Step 1: Fix vyos-02 (Currently VRRP BACKUP)

vyos-02 is BACKUP — no traffic impact when powered off.

# On kvm-02 — verify vyos-02 is BACKUP
ssh vyos-02 "show vrrp" | awk '/State/ {print $3}'
# Expected: BACKUP (all VRIDs)
# Shutdown vyos-02
sudo virsh shutdown vyos-02

# Wait for clean shutdown
watch -n1 'sudo virsh list --all | grep vyos-02'
# Remove old interfaces (wrong order)
sudo virsh detach-interface vyos-02 bridge --mac $(sudo virsh domiflist vyos-02 | awk 'NR==3 {print $5}') --persistent
sudo virsh detach-interface vyos-02 bridge --mac $(sudo virsh domiflist vyos-02 | awk 'NR==3 {print $5}') --persistent
# Attach new interfaces in correct order
sudo virsh attach-interface vyos-02 bridge br-wan --model virtio --persistent
sudo virsh attach-interface vyos-02 bridge br-ispb --model virtio --persistent
sudo virsh attach-interface vyos-02 bridge br-mgmt --model virtio --persistent
sudo virsh attach-interface vyos-02 bridge br-ha --model virtio --persistent
# Verify interface order
sudo virsh domiflist vyos-02
# Expected:
# eth0 → br-wan
# eth1 → br-ispb
# eth2 → br-mgmt
# eth3 → br-ha
# Start vyos-02
sudo virsh start vyos-02

# Verify VyOS sees interfaces
ssh vyos-02 "show interfaces"

Step 2: Reconfigure vyos-02 VyOS

# SSH to vyos-02 — reconfigure interface addresses
configure

# eth0 is now WAN (was LAN)
delete interfaces ethernet eth0 address
set interfaces ethernet eth0 description 'WAN-A-ISPA'
set interfaces ethernet eth0 address 'dhcp'

# eth1 is ISP-B (new)
set interfaces ethernet eth1 description 'WAN-B-ISPB'
set interfaces ethernet eth1 address 'dhcp'

# eth2 is LAN (was eth0)
delete interfaces ethernet eth1 address 10.50.1.3/24
set interfaces ethernet eth2 description 'INTERNAL-TRUNK'
set interfaces ethernet eth2 address '10.50.1.3/24'

# eth3 is HA sync (new)
set interfaces ethernet eth3 description 'HA-SYNC'
set interfaces ethernet eth3 address '10.50.99.2/30'

# Update VRRP interface references
set high-availability vrrp group MGMT interface 'eth2'
set high-availability vrrp group MGMT hello-source-address '10.50.1.3'

commit
save
# Verify VRRP is BACKUP
show vrrp
# All groups should show BACKUP

Step 3: Failover to vyos-02

# On vyos-01 — reduce VRRP priority to trigger failover
configure
set high-availability vrrp group MGMT priority 50
commit
# Verify vyos-02 becomes MASTER
ssh vyos-02 "show vrrp"
# Expected: MASTER

Step 4: Fix vyos-01 (Now VRRP BACKUP)

# On kvm-01
sudo virsh shutdown vyos-01

# Detach old interfaces
sudo virsh detach-interface vyos-01 bridge --mac $(sudo virsh domiflist vyos-01 | awk 'NR==3 {print $5}') --persistent
sudo virsh detach-interface vyos-01 bridge --mac $(sudo virsh domiflist vyos-01 | awk 'NR==3 {print $5}') --persistent

# Attach new interfaces in correct order
sudo virsh attach-interface vyos-01 bridge br-wan --model virtio --persistent
sudo virsh attach-interface vyos-01 bridge br-ispb --model virtio --persistent
sudo virsh attach-interface vyos-01 bridge br-mgmt --model virtio --persistent
sudo virsh attach-interface vyos-01 bridge br-ha --model virtio --persistent

# Start
sudo virsh start vyos-01

Step 5: Reconfigure vyos-01 VyOS

# SSH to vyos-01
configure

# eth2 is now LAN (was eth1)
delete interfaces ethernet eth1 address 10.50.1.2/24
set interfaces ethernet eth2 description 'INTERNAL-TRUNK'
set interfaces ethernet eth2 address '10.50.1.2/24'

# eth1 is ISP-B (new)
set interfaces ethernet eth1 description 'WAN-B-ISPB'
set interfaces ethernet eth1 address 'dhcp'

# eth3 is HA sync (new)
set interfaces ethernet eth3 description 'HA-SYNC'
set interfaces ethernet eth3 address '10.50.99.1/30'

# Update VRRP interface references
set high-availability vrrp group MGMT interface 'eth2'
set high-availability vrrp group MGMT hello-source-address '10.50.1.2'

# Restore priority
set high-availability vrrp group MGMT priority 200

commit
save

Step 6: Verify Final State

# On both nodes
show interfaces
show vrrp
ping 10.50.99.2  # From vyos-01 — HA link test
ping 10.50.99.1  # From vyos-02 — HA link test
Check vyos-01 vyos-02

VRRP role

MASTER (priority 200)

BACKUP (priority 100)

eth0

br-wan (dhcp)

br-wan (dhcp)

eth1

br-ispb (dhcp)

br-ispb (dhcp)

eth2

10.50.1.2/24

10.50.1.3/24

eth3

10.50.99.1/30

10.50.99.2/30

HA ping

Step 7: Migrate Firewall Rules

The LAN interface moved from eth1 → eth2. Update all firewall rules.

7.1 Identify Rules to Update

# Compare exported config to find eth1 references
cat ~/vyos-01-firewall-$(date +%F).txt | grep eth1

7.2 Update Zone Definitions

# On vyos-01
configure

# Update LAN zone to use eth2
delete firewall zone LAN interface eth1
set firewall zone LAN interface eth2

# Add ISP-B zone for eth1 (new WAN)
set firewall zone WAN-B interface eth1

# Add HA zone for eth3
set firewall zone HA interface eth3

7.3 Update NAT Rules

# NAT source rules — add eth1 (ISP-B) outbound masquerade
set nat source rule 20 description 'IPv4 outbound via ISP-B'
set nat source rule 20 outbound-interface name 'eth1'
set nat source rule 20 source address '10.50.0.0/16'
set nat source rule 20 translation address 'masquerade'

7.4 Add HA Sync Rules

# Allow conntrack-sync on eth3
set firewall ipv4 name HA-LOCAL default-action drop
set firewall ipv4 name HA-LOCAL rule 10 action accept
set firewall ipv4 name HA-LOCAL rule 10 description 'conntrack-sync'
set firewall ipv4 name HA-LOCAL rule 10 protocol udp
set firewall ipv4 name HA-LOCAL rule 10 destination port 3780

set firewall ipv4 name HA-LOCAL rule 20 action accept
set firewall ipv4 name HA-LOCAL rule 20 description 'VRRP multicast'
set firewall ipv4 name HA-LOCAL rule 20 protocol vrrp

set firewall ipv4 name HA-LOCAL rule 30 action accept
set firewall ipv4 name HA-LOCAL rule 30 description 'ICMP ping'
set firewall ipv4 name HA-LOCAL rule 30 protocol icmp

# Apply to eth3
set firewall interface eth3 local name HA-LOCAL

7.5 Add ISP-B WAN Rules

# eth1 (ISP-B) gets same treatment as eth0 (ISP-A)
# Copy existing WAN rules to WAN-B

set firewall ipv4 name WAN-B-LOCAL default-action drop

set firewall ipv4 name WAN-B-LOCAL rule 10 action accept
set firewall ipv4 name WAN-B-LOCAL rule 10 description 'established/related'
set firewall ipv4 name WAN-B-LOCAL rule 10 state established
set firewall ipv4 name WAN-B-LOCAL rule 10 state related

set firewall ipv4 name WAN-B-LOCAL rule 20 action accept
set firewall ipv4 name WAN-B-LOCAL rule 20 description 'WireGuard UDP'
set firewall ipv4 name WAN-B-LOCAL rule 20 protocol udp
set firewall ipv4 name WAN-B-LOCAL rule 20 destination port 51820

# Apply to eth1
set firewall interface eth1 local name WAN-B-LOCAL

7.6 Commit and Verify

commit
save

# Verify firewall zones
show firewall zone

# Verify interface assignments
show firewall interface

# Test connectivity from LAN
# (from a client, ping gateway, access internet)

7.7 Replicate to vyos-02

# SSH to vyos-02 and apply same firewall changes
ssh vyos-02
configure
# ... repeat 7.2-7.6 ...
Table 3. Firewall Migration Summary
Interface Old Role New Role / Rules

eth0

WAN (ISP-A)

No change

eth1

LAN

ISP-B WAN — add WAN-B-LOCAL ruleset

eth2

LAN — move LAN zone here

eth3

HA sync — add HA-LOCAL ruleset

Phase 2: VyOS Configuration

2.1 Interface Addressing

Interface vyos1 vyos2 Purpose

eth0 (ISP-A)

dhcp (VRF WAN-A)

dhcp (VRF WAN-A fallback)

WAN-A uplink

eth1 (ISP-B)

dhcp (VRF WAN-B fallback)

dhcp (VRF WAN-B)

WAN-B uplink

eth2 (internal)

10.50.10.11/24

10.50.10.12/24

Downstream trunk. VRRP VIPs.

eth3 (HA)

10.50.99.1/30

10.50.99.2/30

conntrack-sync + VRRP peer comms.

# vyos1 base interfaces
set interfaces ethernet eth0 description 'WAN-A-ISPA'
set interfaces ethernet eth0 vrf 'WAN-A'
set interfaces ethernet eth0 address 'dhcp'

set interfaces ethernet eth1 description 'WAN-B-ISPB'
set interfaces ethernet eth1 vrf 'WAN-B'
set interfaces ethernet eth1 address 'dhcp'

set interfaces ethernet eth2 description 'INTERNAL-TRUNK'
set interfaces ethernet eth2 address '10.50.10.11/24'

set interfaces ethernet eth3 description 'HA-SYNC'
set interfaces ethernet eth3 address '10.50.99.1/30'

2.2 VRF Configuration

VRF provides clean L3 separation between WAN paths. Unlike policy routing tables, VRF survives DHCP lease changes and is the enterprise standard for multi-homing on a single router.

# Define VRFs
set vrf name WAN-A table '100'
set vrf name WAN-B table '200'

# Assign WAN interfaces to VRFs
set interfaces ethernet eth0 vrf 'WAN-A'
set interfaces ethernet eth1 vrf 'WAN-B'

# Default routes within each VRF (populated by DHCP automatically
# when interface is in the VRF — verify with 'show vrf WAN-A route')

# Static fallback: vyos1 falls back to WAN-B if WAN-A fails
# (lower metric = preferred, higher = fallback)
set protocols static vrf WAN-A route '::/0' interface 'eth0' metric '10'
set protocols static vrf WAN-B route '::/0' interface 'eth1' metric '20'

# vyos2: WAN-B is primary (metric 10), WAN-A is fallback (metric 20)
VRF vs Policy Routing

Policy routing (set policy route …​ set table N) works but is fragile under DHCP — if your WAN IP changes, the source-address match in the policy rule may no longer match, breaking tunnel source binding silently. VRF binds the entire interface to a routing table, so DHCP lease renewal always lands in the correct table regardless of IP.

2.3 WireGuard Tunnels

Each node builds two WireGuard tunnels — one to iFog, one to ROUTE64. Tunnels are bound to their VRF via the source-address attribute, ensuring each tunnel sources from the correct WAN interface.

# Generate keypairs first (run on each node):
# run generate pki wireguard key-pair
# Submit the public-key to iFog and ROUTE64 portals

# vyos1 — iFog tunnel (wg0), sourced via VRF WAN-A
set interfaces wireguard wg0 description 'iFog-Fremont-vyos1'
set interfaces wireguard wg0 address '<ifog-v1-link-addr>/127'
set interfaces wireguard wg0 private-key '<vyos1-wg0-privkey>'
set interfaces wireguard wg0 vrf 'WAN-A'
set interfaces wireguard wg0 peer IFOG public-key '<ifog-pubkey>'
set interfaces wireguard wg0 peer IFOG endpoint '<ifog-v1-endpoint>'
set interfaces wireguard wg0 peer IFOG allowed-ips '::/0'
set interfaces wireguard wg0 peer IFOG persistent-keepalive '25'

# vyos1 — ROUTE64 tunnel (wg1), sourced via VRF WAN-B
set interfaces wireguard wg1 description 'ROUTE64-vyos1'
set interfaces wireguard wg1 address '<r64-v1-link-addr>/127'
set interfaces wireguard wg1 private-key '<vyos1-wg1-privkey>'
set interfaces wireguard wg1 vrf 'WAN-B'
set interfaces wireguard wg1 peer R64 public-key '<r64-pubkey>'
set interfaces wireguard wg1 peer R64 endpoint '<r64-v1-endpoint>'
set interfaces wireguard wg1 peer R64 allowed-ips '::/0'
set interfaces wireguard wg1 peer R64 persistent-keepalive '25'

# vyos2: mirror with vyos2 keypairs/link-addrs
# wg0 (iFog) → VRF WAN-B (vyos2 primary WAN is ISP-B)
# wg1 (ROUTE64) → VRF WAN-A
T-Mobile CGNAT and WireGuard

T-Mobile 5G Home Internet uses CGNAT — your gateway has a private IP and shares a public IP with other subscribers. Outbound WireGuard works correctly because UDP NAT state is maintained by persistent keepalives (set to 25s, under the typical 30s NAT timeout). T-Mobile occasionally reassigns the CGNAT external IP — the gateway reboot causes a brief WireGuard re-handshake delay (~15–30s) before the tunnel recovers. BFD and BGP timers are tuned to tolerate this.

2.4 BGP Configuration

Both nodes share ASyyyyyy. Each independently peers with both upstreams. BGP runs in the global routing table but sources sessions via the WireGuard tunnel link addresses.

# vyos1
set protocols bgp system-as '{domus-asn}'
set protocols bgp parameters router-id '10.50.1.11'
set protocols bgp parameters bestpath as-path multipath-relax

# iFog peer (wg0 link address)
set protocols bgp neighbor '<ifog-v1-peer-addr>' remote-as '{ifog-asn}'
set protocols bgp neighbor '<ifog-v1-peer-addr>' description 'iFog-BGPTunnel'
set protocols bgp neighbor '<ifog-v1-peer-addr>' timers holdtime '30'
set protocols bgp neighbor '<ifog-v1-peer-addr>' timers keepalive '10'
set protocols bgp neighbor '<ifog-v1-peer-addr>' bfd
set protocols bgp neighbor '<ifog-v1-peer-addr>' address-family ipv6-unicast
set protocols bgp neighbor '<ifog-v1-peer-addr>' address-family ipv6-unicast \
  route-map export 'RM-OUT-IFOG'
set protocols bgp neighbor '<ifog-v1-peer-addr>' address-family ipv6-unicast \
  route-map import 'RM-IN-IFOG'

# ROUTE64 peer (wg1 link address)
set protocols bgp neighbor '<r64-v1-peer-addr>' remote-as '{route64-asn}'
set protocols bgp neighbor '<r64-v1-peer-addr>' description 'ROUTE64'
set protocols bgp neighbor '<r64-v1-peer-addr>' timers holdtime '30'
set protocols bgp neighbor '<r64-v1-peer-addr>' timers keepalive '10'
set protocols bgp neighbor '<r64-v1-peer-addr>' bfd
set protocols bgp neighbor '<r64-v1-peer-addr>' address-family ipv6-unicast
set protocols bgp neighbor '<r64-v1-peer-addr>' address-family ipv6-unicast \
  route-map export 'RM-OUT-R64'
set protocols bgp neighbor '<r64-v1-peer-addr>' address-family ipv6-unicast \
  route-map import 'RM-IN-R64'

# Advertise PI prefix
set protocols bgp address-family ipv6-unicast network '{domus-prefix}'

# RPKI validation
set protocols rpki cache RIPE address 'rpki.ripe.net'
set protocols rpki cache RIPE port '3323'
set protocols rpki cache RIPE preference '1'

# vyos2: router-id 10.50.1.12
# Route-map local-pref values are flipped (see 2.5)

2.5 BGP Route Maps — Active-Active Traffic Split

vyos1 is the preferred inbound path via iFog. vyos2 is preferred via ROUTE64. AS-path prepending signals this preference into the DFZ.

# Prefix list — PI block exact match only
set policy prefix-list6 PL-MY-PREFIX rule 10 action 'permit'
set policy prefix-list6 PL-MY-PREFIX rule 10 prefix '{domus-prefix}'
set policy prefix-list6 PL-MY-PREFIX rule 10 le '48'

# --- vyos1 route-maps ---

# Outbound to iFog — clean advertisement (vyos1 preferred inbound via iFog)
set policy route-map RM-OUT-IFOG rule 10 action 'permit'
set policy route-map RM-OUT-IFOG rule 10 match ipv6 address \
  prefix-list 'PL-MY-PREFIX'
set policy route-map RM-OUT-IFOG rule 20 action 'deny'

# Outbound to ROUTE64 — prepend twice (signals DFZ to prefer vyos2 via ROUTE64)
set policy route-map RM-OUT-R64 rule 10 action 'permit'
set policy route-map RM-OUT-R64 rule 10 match ipv6 address \
  prefix-list 'PL-MY-PREFIX'
set policy route-map RM-OUT-R64 rule 10 set as-path-prepend \
  '{domus-asn} {domus-asn}'
set policy route-map RM-OUT-R64 rule 20 action 'deny'

# Inbound from iFog — preferred (local-pref 150)
set policy route-map RM-IN-IFOG rule 10 action 'permit'
set policy route-map RM-IN-IFOG rule 10 set local-preference '150'

# Inbound from ROUTE64 — fallback (local-pref 100)
set policy route-map RM-IN-R64 rule 10 action 'permit'
set policy route-map RM-IN-R64 rule 10 set local-preference '100'

# --- vyos2 differences ---
# RM-OUT-IFOG: add prepend (vyos2 not preferred inbound via iFog)
# RM-OUT-R64: no prepend (vyos2 preferred inbound via ROUTE64)
# RM-IN-IFOG: local-pref 100
# RM-IN-R64: local-pref 150

2.6 BFD — Per-ISP Tuned Timers

BFD timers are tuned differently per WAN path. The wired ISP-A path is stable — tight timers give fast detection. T-Mobile cellular exhibits jitter — looser timers avoid false positives causing unnecessary reconvergence.

# vyos1 — iFog peer (tunnel over ISP-A wired — tight timers)
set protocols bfd peer '<ifog-v1-peer-addr>' interval multiplier '3'
set protocols bfd peer '<ifog-v1-peer-addr>' interval receive '300'
set protocols bfd peer '<ifog-v1-peer-addr>' interval transmit '300'
# Detection window: 300ms x 3 = 900ms

# vyos1 — ROUTE64 peer (tunnel over ISP-B cellular — relaxed timers)
set protocols bfd peer '<r64-v1-peer-addr>' interval multiplier '4'
set protocols bfd peer '<r64-v1-peer-addr>' interval receive '500'
set protocols bfd peer '<r64-v1-peer-addr>' interval transmit '500'
# Detection window: 500ms x 4 = 2000ms — tolerates cellular jitter

# Link BFD to BGP neighbors
set protocols bgp neighbor '<ifog-v1-peer-addr>' bfd
set protocols bgp neighbor '<r64-v1-peer-addr>' bfd

# vyos2: mirror, but swap timer profiles
# iFog tunnel on vyos2 → ISP-B (cellular) → use 500/4
# ROUTE64 tunnel on vyos2 → ISP-A (wired) → use 300/3

2.7 BGP Graceful Shutdown

Use graceful shutdown for maintenance to signal peers cleanly before dropping sessions. This prevents black-holing traffic during planned work.

# Before any maintenance — send NOTIFICATION with Administrative Shutdown
# Peers drain traffic gracefully before session drops
set protocols bgp neighbor '<ifog-v1-peer-addr>' shutdown

# After maintenance is complete — restore
delete protocols bgp neighbor '<ifog-v1-peer-addr>' shutdown

2.8 VRRP — Split Master Downstream

# vyos1: MASTER DATA (VRID 10), BACKUP VOICE (VRID 20)

set high-availability vrrp group DATA-GW interface 'eth2'
set high-availability vrrp group DATA-GW vrid '10'
set high-availability vrrp group DATA-GW address '10.50.10.1/24'
set high-availability vrrp group DATA-GW priority '150'
set high-availability vrrp group DATA-GW hello-source-address '10.50.10.11'
set high-availability vrrp group DATA-GW peer-address '10.50.10.12'
set high-availability vrrp group DATA-GW no-preempt

set high-availability vrrp group VOICE-GW interface 'eth2'
set high-availability vrrp group VOICE-GW vrid '20'
set high-availability vrrp group VOICE-GW address '10.50.20.1/24'
set high-availability vrrp group VOICE-GW priority '100'
set high-availability vrrp group VOICE-GW hello-source-address '10.50.10.11'
set high-availability vrrp group VOICE-GW peer-address '10.50.10.12'
set high-availability vrrp group VOICE-GW no-preempt

set high-availability vrrp sync-group SYNC member 'DATA-GW'
set high-availability vrrp sync-group SYNC member 'VOICE-GW'

# vyos2: DATA-GW priority 100, VOICE-GW priority 150

2.9 conntrack-sync

# Both nodes — peer address differs only

set service conntrack-sync accept-protocol 'tcp'
set service conntrack-sync accept-protocol 'udp'
set service conntrack-sync accept-protocol 'icmp'
set service conntrack-sync failover-mechanism vrrp sync-group 'SYNC'
set service conntrack-sync interface 'eth3' peer '10.50.99.2'
set service conntrack-sync event-listen-queue-size '8'
set service conntrack-sync sync-queue-size '8'

# vyos2: peer '10.50.99.1'

2.10 Dual-Stack — NAT44 for IPv4 Clients

BGP carries your PI /48 IPv6 space only. IPv4 outbound for legacy devices uses SNAT masquerade via the active WAN interface. Clients receive both RFC1918 IPv4 (DHCP) and a routable IPv6 from your /48 (SLAAC or DHCPv6).

# NAT44 — masquerade IPv4 outbound via ISP-A WAN
set nat source rule 10 description 'IPv4 outbound via ISP-A'
set nat source rule 10 outbound-interface name 'eth0'
set nat source rule 10 source address '10.50.0.0/16'
set nat source rule 10 translation address 'masquerade'

# NAT44 — masquerade IPv4 outbound via ISP-B WAN
set nat source rule 20 description 'IPv4 outbound via ISP-B'
set nat source rule 20 outbound-interface name 'eth1'
set nat source rule 20 source address '10.50.0.0/16'
set nat source rule 20 translation address 'masquerade'

# IPv6 downstream — advertise PI /48 subnets via Router Advertisements
# Allocate a /64 per VLAN from your /48
# Example: DATA VLAN gets 2a0e:xxxx:0:10::/64
set service router-advert interface 'eth2' prefix '{domus-prefix}'
IPv6 Subnet Allocation from PI /48

Your /48 provides 65,536 /64 subnets. Allocate one /64 per VLAN:

DATA  VLAN 10 → 2a0e:xxxx:0:10::/64
VOICE VLAN 20 → 2a0e:xxxx:0:20::/64
GUEST VLAN 30 → 2a0e:xxxx:0:30::/64
IoT   VLAN 40 → 2a0e:xxxx:0:40::/64
MGMT  VLAN 50 → 2a0e:xxxx:0:50::/64

Clients use SLAAC (stateless address autoconfiguration) to self-assign addresses from the /64. No DHCPv6 required unless you want managed addressing.

Phase 3: Verification

WireGuard Tunnel Health

show interfaces wireguard wg0
show interfaces wireguard wg1
# latest-handshake should be < 30 seconds

ping6 <ifog-v1-peer-addr> interface wg0 count 5
ping6 <r64-v1-peer-addr> interface wg1 count 5

VRF Routing Tables

show vrf WAN-A route
show vrf WAN-B route
# Each should have a default route via its WAN interface

ip -6 route show vrf WAN-A
ip -6 route show vrf WAN-B

BGP Session State

show bgp summary
# Expect: 2 neighbors, both Established, non-zero prefixes received

show bgp ipv6 unicast {domus-prefix}
# Expect: 2 paths — one via iFog, one via ROUTE64

show bgp ipv6 unicast neighbors <ifog-peer> advertised-routes
# Expect: {domus-prefix} advertised outbound

RPKI Validation

show rpki prefix-table
# Your prefix should appear as valid

# External check
curl -s "https://rpki.cloudflare.com/api/v1/validity/\
{domus-asn}/{domus-prefix}" | python3 -m json.tool

VRRP, conntrack-sync, BFD

show vrrp
# vyos1: DATA-GW MASTER, VOICE-GW BACKUP
# vyos2: DATA-GW BACKUP, VOICE-GW MASTER

show conntrack-sync statistics
# internal-cache non-zero on active node
# external-cache on standby matches internal-cache count

show bfd peers
# Both peers: state Up

External Prefix Visibility

# Check DFZ propagation
# Visit bgp.tools → search your ASN
# Prefix should be visible from multiple vantage points

# Verify AS-path prepend is working
# Visit lg.he.net → traceroute/BGP lookup for your prefix
# Via iFog: AS34927 {domus-asn}
# Via ROUTE64 from vyos1: AS{r64} {domus-asn} {domus-asn}
# Via ROUTE64 from vyos2: AS{r64} {domus-asn}

Phase 4: Failover Testing

Test 1 — PE Handoff (iFog tunnel drop)

# Establish long-lived TCP session through the network first (e.g. iperf6)
# Bring down iFog WireGuard tunnel on vyos1:
sudo ip link set wg0 down

# Expected sequence:
# t+0.9s  BFD declares iFog peer down (wired timer: 300ms x 3)
# t+1s    BGP withdraws iFog paths on vyos1
# t+3-5s  DFZ reconverges — all inbound via ROUTE64
# TCP session survives — conntrack-sync preserved state on vyos2

sudo ip link set wg0 up
# BGP re-establishes, iFog paths return

Test 2 — ISP-A Physical Circuit Failure

# Disable eth0 on vyos1 (simulates cable cut)
set interfaces ethernet eth0 disable
commit

# Expected:
# VRF WAN-A loses default route on vyos1
# iFog wg0 tunnel keepalives stop → BFD triggers
# VRF WAN-B becomes active path for vyos1 tunnels
# vyos2 unaffected — already on ISP-B primary

delete interfaces ethernet eth0 disable
commit

Test 3 — Node Failure

# Power off or pause vyos1 KVM VM
virsh suspend vyos1

# Verify on vyos2:
show vrrp         # Both VRIDs should be MASTER on vyos2
show bgp summary  # Both sessions still Established (vyos2 independent)
show conntrack-sync statistics  # Sessions flowing from external-cache

virsh resume vyos1
# vyos1 re-establishes BGP, VRRP transitions back to split-master

Test 4 — T-Mobile Gateway Reboot

# Reboot T-Mobile gateway (power cycle)
# WireGuard tunnel over ISP-B will drop for ~30-60s during reboot

# Expected on vyos2:
# BFD on ROUTE64 peer (ISP-B path): 500ms x 4 = 2s detection
# BGP withdraws ROUTE64 paths on vyos2
# iFog (via ISP-A fallback VRF) becomes sole path
# After T-Mobile recovers: WireGuard re-handshakes,
# BGP re-establishes, ROUTE64 paths return

Reference

Quick Command Reference

# BGP
show bgp summary
show bgp ipv6 unicast
show bgp ipv6 unicast neighbors <addr> advertised-routes
show bgp ipv6 unicast neighbors <addr> received-routes

# Graceful shutdown (before maintenance)
set protocols bgp neighbor <peer> shutdown
delete protocols bgp neighbor <peer> shutdown

# VRRP
show vrrp
show vrrp detail

# conntrack-sync
show conntrack-sync statistics
show conntrack-sync status

# BFD
show bfd peers
show bfd peers detail

# VRF
show vrf
show vrf WAN-A route
show vrf WAN-B route

# WireGuard
show interfaces wireguard wg0
show interfaces wireguard wg1

# IPv6 routing
show ipv6 route
show ipv6 route bgp

# nftables
sudo nft list ruleset | grep -A5 ip6

# RPKI
show rpki prefix-table
show rpki cache-connection

External Tools

Tool Purpose

bgp.tools

View ASN, prefix propagation, AS paths globally. Search your ASN after prefix goes live.

lg.he.net

Hurricane Electric looking glass. Verify AS-path prepend from multiple vantage points.

rpki.cloudflare.com

Validate ROA registration and propagation status.

stat.ripe.net

RIPE routing visibility and prefix visibility stats.

bgptunnel.com portal

Manage iFog WireGuard tunnels, check tunnel state.

route64.org portal

Manage ROUTE64 tunnels, check session state.

t-mobile.com/home-internet

Coverage check before ordering ISP-B.

Annual Cost Summary

Item One-time Annual

RIPE ASN — €50 NCC fee + Voldeta margin

~€60

~€60

RIPE PI /48 IPv6 — €50 NCC fee + Voldeta margin

~€60

~€70

iFog BGPTunnel transit

Free

Free

ROUTE64 transit

Free

Free

T-Mobile 5G Home Internet

~$600

ISP-A wired circuit

existing

Total new spend

~€120 (~$130)

~$730/yr

Professional Portfolio Use

This document demonstrates production-grade network architecture skills suitable for interview portfolios and professional discussions.

Skills Demonstrated

Skill Area Evidence in This Document

BGP Operations

eBGP multihoming, route maps, local-preference, AS-path prepending, RPKI/ROA validation, IRR object creation

Traffic Engineering

Active-active inbound split via prepending, outbound preference via local-pref, per-upstream policy differentiation

High Availability

Dual physical circuits, dual BGP upstreams, dual VyOS nodes, VRRP split-master, conntrack-sync for session preservation

Fast Convergence

BFD with per-transport tuning (wired vs cellular), aggressive BGP hold timers, sub-second failure detection

Enterprise Patterns

VRF for WAN separation (not policy routing), graceful shutdown for maintenance, dual-stack with NAT44 + native IPv6

Infrastructure Planning

Hardware prerequisites, cost analysis, procurement sequencing, failure mode analysis, verification procedures

Internet Governance

RIPE NCC process, LIR sponsorship model, IRR/RPKI ecosystem understanding, provider-independent address space

Target Roles

Role Level Relevance

Mid-Level Network Engineer

Demonstrates readiness for senior responsibilities. Direct skill match.

Senior Network Engineer

Good conversation starter. Expect probing questions on design decisions.

Network Architect

Shows end-to-end thinking: physical layer through BGP policy through failure scenarios.

ISP / Transit Provider

Direct relevance. This is core ISP edge work at smaller scale.

Cloud Network Engineer

Demonstrates understanding of what cloud abstracts away. Valuable context.

Positioning in Interviews

What to say

"I designed this for my home enterprise infrastructure. The document covers procurement through implementation through failure testing. I wanted real BGP multihoming experience with actual transit providers, not just lab simulations. The architecture uses provider-independent IPv6 space from RIPE, dual physical last-mile circuits, and active-active forwarding across two VyOS nodes."

Likely Interview Questions

Question Key Points

"Why RIPE instead of ARIN?"

ARIN has multi-year waitlists, expensive for small allocations. RIPE sponsors non-EU members, faster processing, cost-effective for IPv6-only PI space.

"Why two BGP upstreams?"

Single upstream = provider lock-in, no failover path, no traffic engineering practice. Dual upstream enables active-active and real-world failure scenarios.

"What if both physical ISPs fail?"

Total outage. Acceptable risk for home infrastructure. The design documents the failure domain explicitly — shows realistic thinking.

"Why not cloud provider BGP (AWS, GCP)?"

Cloud BGP is abstracted — you don’t see the PE, don’t control timers, don’t manage IRR/RPKI. This is raw BGP with real upstream routers. Different skill set.

"Have you implemented this?"

Answer honestly: "In procurement phase" or "Running in production." Either is valid — the design work itself demonstrates the skill.

"Why T-Mobile 5G for redundancy?"

Cheapest genuine physical diversity in US consumer market. Separate infrastructure from wired (cellular towers vs cable/fiber plant). CGNAT doesn’t affect outbound WireGuard tunnels.

What Differentiates This

Most candidates discuss BGP from certification study or GNS3 labs. This document demonstrates:

  • Real design with real providers (iFog, ROUTE64, Voldeta/RIPE)

  • Real cost analysis and procurement planning

  • Real failure mode thinking with recovery mechanisms

  • Actual implementation intent, not theoretical exercise

The difference between "I studied BGP" and "I designed and will operate a multihomed AS" is significant in hiring decisions.

Appendix: Port Discovery Scripts

Quick Port Status

# Port status with bridge membership and link state
ip -br link show | awk '/^(eno|br-)/ {printf "%-12s %-10s %s\n", $1, $2, $3}'

# Which ports are in which bridges
bridge link show | awk '{print $2, "→", $NF}'

Comprehensive Port Inventory

# Full picture: port, state, speed, bridge membership
for port in eno{1..8}; do
  state=$(cat /sys/class/net/$port/operstate 2>/dev/null || echo "missing")
  speed=$(cat /sys/class/net/$port/speed 2>/dev/null || echo "?")
  master=$(ip link show $port 2>/dev/null | awk -F'master ' '{print $2}' | awk '{print $1}')
  printf "%-6s  %-6s  %5sMbps  %s\n" "$port" "$state" "$speed" "${master:-[none]}"
done