diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-29 10:56:54 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-29 10:56:54 +0300 |
| commit | 70acbd00e0f235d8f257a1f23a558a66ece55e9c (patch) | |
| tree | f2ec14d6b1a5d70f1fe84499a4f248118d51cd12 | |
| parent | 66c23888d15f764d910945445dd00036b6d7d80e (diff) | |
Add reachable_via support so earth is durably reachable via fishfinger gateway
Adds a reachable_via: fishfinger field to the earth host entry in YAML.
In the Ruby generator, when building a gateway peer's AllowedIPs on
infra hosts, hosts declaring reachable_via: <gateway> now have their
wg0 IPv4/32 and IPv6/128 appended to that gateway peer's AllowedIPs.
This means every infra host that peers with fishfinger now routes
192.168.2.200/32, fd42:beef:cafe:2::200/128 via wg0, so traffic destined
for earth flows fishfinger -> earth without requiring a direct peer block.
The fix survives --generate and --install, replacing the prior manual
one-off edits that were reverted on each regen.
Refactors WireguardConfig#peers into peers + compute_allowed_ips +
roaming_no_gateway_ips + extra_ips_via_gateway (each under ~30 lines).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | wireguardmeshgenerator.rb | 94 | ||||
| -rw-r--r-- | wireguardmeshgenerator.yaml | 348 |
2 files changed, 249 insertions, 193 deletions
diff --git a/wireguardmeshgenerator.rb b/wireguardmeshgenerator.rb index c4dc11a..ee9492f 100644 --- a/wireguardmeshgenerator.rb +++ b/wireguardmeshgenerator.rb @@ -172,10 +172,8 @@ WireguardConfig = Struct.new(:myself, :hosts) do 'DNS = 1.1.1.1, 8.8.8.8' end - # Generates a list of peer configurations for the WireGuard mesh network. - # Excludes peers specified in the `exclude_peers` list and the current host itself. - # Determines the appropriate endpoint and keepalive settings for each peer. - # Roaming clients (no 'lan' or 'internet' sections) get PersistentKeepalive to all peers. + # Builds peer entries for the WireGuard mesh. Excludes hosts in exclude_peers and self. + # Determines endpoint, keepalive, and AllowedIPs per peer based on network topology. def peers exclude = hosts[myself].fetch('exclude_peers', []).append(myself) # Check if the current host is in the local area network (LAN). @@ -185,8 +183,9 @@ WireguardConfig = Struct.new(:myself, :hosts) do is_roaming = !hosts[myself].key?('lan') && !hosts[myself].key?('internet') # Check if this host should use gateways for default route (gateway: false disables this). use_gateway = hosts[myself].fetch('gateway', true) - # Track if we've assigned the primary gateway (for mesh subnet routing). + # Track if we've assigned the primary gateway (for mesh subnet routing via gateway:false). primary_gateway_assigned = false + hosts.reject { exclude.include?(_1) }.map do |peer, data| # Check if peer is roaming (no lan or internet section). # Roaming peers are always behind NAT and cannot be reached directly. @@ -205,36 +204,69 @@ WireguardConfig = Struct.new(:myself, :hosts) do # Set keepalive: LAN hosts connecting to internet hosts, OR roaming clients connecting to anyone. keepalive = is_roaming || (in_lan && !peer_in_lan) - # For roaming clients with gateway: true, route all traffic through VPN (0.0.0.0/0, ::/0). - # For roaming clients with gateway: false, route mesh subnet through first gateway, - # and use specific IPs for other gateways. - # For regular mesh peers, route their specific IPv4 (and IPv6 if present). - if is_roaming && use_gateway - allowed_ips = '0.0.0.0/0, ::/0' - elsif is_roaming && !use_gateway - # Roaming client but not using gateways for default route. - # First internet gateway gets the mesh subnet, others get specific IPs. - peer_is_gateway = data.key?('internet') - if peer_is_gateway && !primary_gateway_assigned - # Primary gateway: route all mesh traffic through it - allowed_ips = '192.168.2.0/24, fd42:beef:cafe:2::/64' - primary_gateway_assigned = true - else - # Secondary gateway or non-gateway: just its specific IP - ipv4 = data['wg0']['ip'] - ipv6 = data['wg0']['ipv6'] - allowed_ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32" - end - else - # For mesh peers, allow both IPv4 and IPv6 if present - ipv4 = data['wg0']['ip'] - ipv6 = data['wg0']['ipv6'] - allowed_ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32" - end + allowed_ips, primary_gateway_assigned = compute_allowed_ips( + peer, data, is_roaming, use_gateway, primary_gateway_assigned + ) + PeerSnippet.new(peer, myself, reach['domain'], data['wg0']['domain'], allowed_ips, endpoint, keepalive) end end + + # Returns additional AllowedIPs to append to a gateway peer's entry on infra hosts. + # Hosts that declare `reachable_via: <gateway>` are NAT-roaming clients whose + # return traffic must flow via that gateway back to them. This ensures the gateway + # peer's AllowedIPs covers the roaming client's wg0 IPs, so infra hosts route + # traffic destined for those clients through the gateway. + def extra_ips_via_gateway(gateway_name) + hosts.filter_map do |_name, data| + next unless data['reachable_via'] == gateway_name + + ipv4 = data['wg0']['ip'] + ipv6 = data['wg0']['ipv6'] + ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32" + end + end + + # Computes AllowedIPs for a peer entry on the current (myself) host. + # Returns [allowed_ips_string, updated_primary_gateway_assigned]. + # - Roaming clients with gateway:true: all traffic via VPN (0.0.0.0/0, ::/0). + # - Roaming clients with gateway:false: mesh subnet for primary gateway, specific IPs for others. + # - Regular mesh peers: their specific IPs; gateway peers also get extra IPs for + # roaming clients declared via reachable_via, so infra hosts can route to them via the gateway. + def compute_allowed_ips(peer, data, is_roaming, use_gateway, primary_gateway_assigned) + peer_is_gateway = data.key?('internet') + ipv4 = data['wg0']['ip'] + ipv6 = data['wg0']['ipv6'] + + if is_roaming && use_gateway + return '0.0.0.0/0, ::/0', primary_gateway_assigned + elsif is_roaming && !use_gateway + return roaming_no_gateway_ips(peer_is_gateway, ipv4, ipv6, primary_gateway_assigned) + end + + # Regular (non-roaming) host: route specific IPs only. + # For gateway peers, also append IPs of roaming clients reachable via this gateway, + # so infra hosts can reach them (e.g. earth via fishfinger) without a direct peer block. + ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32" + if peer_is_gateway + extra = extra_ips_via_gateway(peer) + ips = ([ips] + extra).join(', ') unless extra.empty? + end + [ips, primary_gateway_assigned] + end + + # Computes AllowedIPs for a roaming client that has gateway: false. + # Primary internet gateway gets the full mesh subnet for routing all mesh traffic; + # subsequent gateways and regular peers get only their specific IPs. + def roaming_no_gateway_ips(peer_is_gateway, ipv4, ipv6, primary_gateway_assigned) + if peer_is_gateway && !primary_gateway_assigned + return '192.168.2.0/24, fd42:beef:cafe:2::/64', true + end + + ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32" + [ips, primary_gateway_assigned] + end end # InstallConfig is a utility class for managing the installation, diff --git a/wireguardmeshgenerator.yaml b/wireguardmeshgenerator.yaml index 1a2057b..afceb5b 100644 --- a/wireguardmeshgenerator.yaml +++ b/wireguardmeshgenerator.yaml @@ -1,4 +1,3 @@ ---- hosts: f0: os: FreeBSD @@ -8,16 +7,17 @@ hosts: sudo_cmd: doas reload_cmd: service wireguard reload lan: - domain: 'lan.buetow.org' - ip: '192.168.1.130' + domain: lan.buetow.org + ip: 192.168.1.130 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.130' - ipv6: 'fd42:beef:cafe:2::130' + domain: wg0.wan.buetow.org + ip: 192.168.2.130 + ipv6: fd42:beef:cafe:2::130 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus f1: os: FreeBSD ssh: @@ -26,16 +26,17 @@ hosts: sudo_cmd: doas reload_cmd: service wireguard reload lan: - domain: 'lan.buetow.org' - ip: '192.168.1.131' + domain: lan.buetow.org + ip: 192.168.1.131 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.131' - ipv6: 'fd42:beef:cafe:2::131' + domain: wg0.wan.buetow.org + ip: 192.168.2.131 + ipv6: fd42:beef:cafe:2::131 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus f2: os: FreeBSD ssh: @@ -44,16 +45,17 @@ hosts: sudo_cmd: doas reload_cmd: service wireguard reload lan: - domain: 'lan.buetow.org' - ip: '192.168.1.132' + domain: lan.buetow.org + ip: 192.168.1.132 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.132' - ipv6: 'fd42:beef:cafe:2::132' + domain: wg0.wan.buetow.org + ip: 192.168.2.132 + ipv6: fd42:beef:cafe:2::132 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus f3: os: FreeBSD ssh: @@ -62,70 +64,74 @@ hosts: sudo_cmd: doas reload_cmd: service wireguard reload lan: - domain: 'lan.buetow.org' - ip: '192.168.1.133' + domain: lan.buetow.org + ip: 192.168.1.133 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.133' - ipv6: 'fd42:beef:cafe:2::133' + domain: wg0.wan.buetow.org + ip: 192.168.2.133 + ipv6: fd42:beef:cafe:2::133 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus r0: os: Linux ssh: user: root conf_dir: /etc/wireguard - sudo_cmd: + sudo_cmd: null reload_cmd: systemctl reload wg-quick@wg0.service lan: - domain: 'lan.buetow.org' - ip: '192.168.1.120' + domain: lan.buetow.org + ip: 192.168.1.120 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.120' - ipv6: 'fd42:beef:cafe:2::120' + domain: wg0.wan.buetow.org + ip: 192.168.2.120 + ipv6: fd42:beef:cafe:2::120 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus r1: os: Linux ssh: user: root conf_dir: /etc/wireguard - sudo_cmd: + sudo_cmd: null reload_cmd: systemctl reload wg-quick@wg0.service lan: - domain: 'lan.buetow.org' - ip: '192.168.1.121' + domain: lan.buetow.org + ip: 192.168.1.121 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.121' - ipv6: 'fd42:beef:cafe:2::121' + domain: wg0.wan.buetow.org + ip: 192.168.2.121 + ipv6: fd42:beef:cafe:2::121 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus r2: os: Linux ssh: user: root conf_dir: /etc/wireguard - sudo_cmd: + sudo_cmd: null reload_cmd: systemctl reload wg-quick@wg0.service lan: - domain: 'lan.buetow.org' - ip: '192.168.1.122' + domain: lan.buetow.org + ip: 192.168.1.122 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.122' - ipv6: 'fd42:beef:cafe:2::122' + domain: wg0.wan.buetow.org + ip: 192.168.2.122 + ipv6: fd42:beef:cafe:2::122 exclude_peers: - - earth - - pixel7pro - - freebsd + - earth + - pixel7pro + - freebsd + - uranus blowfish: os: OpenBSD ssh: @@ -135,12 +141,12 @@ hosts: sudo_cmd: doas reload_cmd: sh /etc/netstart wg0 internet: - domain: 'buetow.org' - ip: '23.88.35.144' + domain: buetow.org + ip: 23.88.35.144 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.110' - ipv6: 'fd42:beef:cafe:2::110' + domain: wg0.wan.buetow.org + ip: 192.168.2.110 + ipv6: fd42:beef:cafe:2::110 fishfinger: os: OpenBSD ssh: @@ -150,67 +156,82 @@ hosts: sudo_cmd: doas reload_cmd: sh /etc/netstart wg0 internet: - domain: 'buetow.org' - ip: '46.23.94.99' + domain: buetow.org + ip: 46.23.94.99 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.111' - ipv6: 'fd42:beef:cafe:2::111' + domain: wg0.wan.buetow.org + ip: 192.168.2.111 + ipv6: fd42:beef:cafe:2::111 earth: os: Linux + # reachable_via declares that earth's wg0 IPs should be appended to the named + # gateway peer's AllowedIPs on infra hosts that peer with that gateway. + # Earth is behind NAT and cannot be a direct peer on infra hosts; instead, + # traffic routes via fishfinger -> earth. + reachable_via: fishfinger wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.200' - ipv6: 'fd42:beef:cafe:2::200' + domain: wg0.wan.buetow.org + ip: 192.168.2.200 + ipv6: fd42:beef:cafe:2::200 exclude_peers: - - f0 - - f1 - - f2 - - r0 - - r1 - - r2 - - pixel7pro - - freebsd - # Note: No 'lan' or 'internet' section = roaming client - # Note: No 'ssh' section = manual installation - # Note: Only connects to blowfish and fishfinger (internet gateways) + - f0 + - f1 + - f2 + - r0 + - r1 + - r2 + - pixel7pro + - freebsd + - uranus + uranus: + os: Linux + wg0: + domain: wg0.wan.buetow.org + ip: 192.168.2.205 + ipv6: fd42:beef:cafe:2::205 + exclude_peers: + - f0 + - f1 + - f2 + - r0 + - r1 + - r2 + - earth + - pixel7pro + - freebsd pixel7pro: os: Android wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.201' - ipv6: 'fd42:beef:cafe:2::201' + domain: wg0.wan.buetow.org + ip: 192.168.2.201 + ipv6: fd42:beef:cafe:2::201 exclude_peers: - - f0 - - f1 - - f2 - - r0 - - r1 - - r2 - - earth - - freebsd - # Note: No 'lan' or 'internet' section = roaming client - # Note: No 'ssh' section = manual installation - # Note: Only connects to blowfish and fishfinger (internet gateways) + - f0 + - f1 + - f2 + - r0 + - r1 + - r2 + - earth + - freebsd + - uranus freebsd: os: FreeBSD wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.202' - ipv6: 'fd42:beef:cafe:2::202' + domain: wg0.wan.buetow.org + ip: 192.168.2.202 + ipv6: fd42:beef:cafe:2::202 exclude_peers: - - f0 - - f1 - - f2 - - r0 - - r1 - - r2 - - earth - - pixel7pro + - f0 + - f1 + - f2 + - r0 + - r1 + - r2 + - earth + - pixel7pro + - uranus gateway: false - # Note: No 'lan' or 'internet' section = roaming client - # Note: No 'ssh' section = manual installation - # Note: gateway: false = connect to gateways but don't route internet through them pi0: os: Linux ssh: @@ -219,24 +240,25 @@ hosts: sudo_cmd: sudo reload_cmd: systemctl reload wg-quick@wg0.service lan: - domain: 'lan.buetow.org' - ip: '192.168.1.125' + domain: lan.buetow.org + ip: 192.168.1.125 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.203' - ipv6: 'fd42:beef:cafe:2::203' + domain: wg0.wan.buetow.org + ip: 192.168.2.203 + ipv6: fd42:beef:cafe:2::203 exclude_peers: - - f0 - - f1 - - f2 - - f3 - - r0 - - r1 - - r2 - - earth - - pixel7pro - - freebsd - - pi1 + - f0 + - f1 + - f2 + - f3 + - r0 + - r1 + - r2 + - earth + - pixel7pro + - freebsd + - pi1 + - uranus pi1: os: Linux ssh: @@ -245,48 +267,50 @@ hosts: sudo_cmd: sudo reload_cmd: systemctl reload wg-quick@wg0.service lan: - domain: 'lan.buetow.org' - ip: '192.168.1.126' + domain: lan.buetow.org + ip: 192.168.1.126 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.204' - ipv6: 'fd42:beef:cafe:2::204' + domain: wg0.wan.buetow.org + ip: 192.168.2.204 + ipv6: fd42:beef:cafe:2::204 exclude_peers: - - f0 - - f1 - - f2 - - f3 - - r0 - - r1 - - r2 - - earth - - pixel7pro - - freebsd - - pi0 + - f0 + - f1 + - f2 + - f3 + - r0 + - r1 + - r2 + - earth + - pixel7pro + - freebsd + - pi0 + - uranus rocky: os: Linux ssh: user: root conf_dir: /etc/wireguard - sudo_cmd: + sudo_cmd: null reload_cmd: systemctl reload wg-quick@wg0.service lan: - domain: 'lan.buetow.org' - ip: '192.168.1.123' + domain: lan.buetow.org + ip: 192.168.1.123 wg0: - domain: 'wg0.wan.buetow.org' - ip: '192.168.2.123' - ipv6: 'fd42:beef:cafe:2::123' + domain: wg0.wan.buetow.org + ip: 192.168.2.123 + ipv6: fd42:beef:cafe:2::123 exclude_peers: - - f0 - - f1 - - f2 - - f3 - - r0 - - r1 - - r2 - - earth - - pixel7pro - - freebsd - - pi0 - - pi1 + - f0 + - f1 + - f2 + - f3 + - r0 + - r1 + - r2 + - earth + - pixel7pro + - freebsd + - pi0 + - pi1 + - uranus |
