From a565099778deff61470c698626dc5af4438ff5f7 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 20 Jan 2026 20:52:04 +0200 Subject: Add freebsd as WireGuard mesh client with gateway: false option - Add freebsd host (192.168.2.202) as roaming client connecting to both gateways - Add gateway: false option to connect to mesh without routing internet through VPN - Add /24 subnet mask for FreeBSD IPv4 addresses (required by wg-quick) - Skip hosts without ssh section during rake install - Update exclude_peers for f0, f1, f2, r0, r1, r2, earth, pixel7pro Co-Authored-By: Claude Opus 4.5 --- wireguardmeshgenerator.rb | 46 +++++++++++++++++++++++++++++++++++---------- wireguardmeshgenerator.yaml | 27 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/wireguardmeshgenerator.rb b/wireguardmeshgenerator.rb index b12f0b9..9ef2228 100644 --- a/wireguardmeshgenerator.rb +++ b/wireguardmeshgenerator.rb @@ -141,26 +141,31 @@ WireguardConfig = Struct.new(:myself, :hosts) do # For OpenBSD, it returns a placeholder comment. Otherwise, it returns the # IP address (and optionally IPv6) as that option isn't supported on OpenBSD. # Supports dual-stack: if ipv6 field is present, outputs both IPv4 and IPv6 addresses. + # FreeBSD requires subnet mask on IPv4 address for wg-quick. def address return '# No Address = ... for OpenBSD here' if hosts[myself]['os'] == 'OpenBSD' ipv4 = hosts[myself]['wg0']['ip'] ipv6 = hosts[myself]['wg0']['ipv6'] + # FreeBSD wg-quick requires subnet mask on IPv4 address + ipv4_with_mask = hosts[myself]['os'] == 'FreeBSD' ? "#{ipv4}/24" : ipv4 # WireGuard supports multiple Address directives for dual-stack if ipv6 - "Address = #{ipv4}\nAddress = #{ipv6}/64" + "Address = #{ipv4_with_mask}\nAddress = #{ipv6}/64" else - "Address = #{ipv4}" + "Address = #{ipv4_with_mask}" end end # Generates DNS configuration for roaming clients. - # Roaming clients (no 'lan' or 'internet' sections) get DNS servers configured. + # Roaming clients (no 'lan' or 'internet' sections) get DNS servers configured, + # unless gateway: false (no default route through VPN). # Uses Cloudflare (1.1.1.1) and Google (8.8.8.8) public DNS for reliability. def dns is_roaming = !hosts[myself].key?('lan') && !hosts[myself].key?('internet') - return '# No DNS configured' unless is_roaming + use_gateway = hosts[myself].fetch('gateway', true) + return '# No DNS configured' unless is_roaming && use_gateway 'DNS = 1.1.1.1, 8.8.8.8' end @@ -176,6 +181,10 @@ WireguardConfig = Struct.new(:myself, :hosts) do # Detect if current host is a roaming client (no lan or internet section). # Roaming clients need PersistentKeepalive to all peers to maintain NAT traversal. 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). + 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. @@ -194,11 +203,26 @@ 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, route all traffic through VPN (0.0.0.0/0, ::/0). + # 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). - # Dual-stack peers get both addresses in AllowedIPs. - if is_roaming + 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'] @@ -308,10 +332,12 @@ begin conf['hosts'].keys.select { options[:hosts].empty? || options[:hosts].include?(_1) } .each do |host| - # Generate Wireguard configuration for the host reload! + # Generate Wireguard configuration for the host. WireguardConfig.new(host, conf['hosts']).generate! if options[:generate] - # Install Wireguard configuration for the host. - InstallConfig.new(host, conf['hosts']).upload!.install!.reload! if options[:install] + # Install Wireguard configuration for the host (only for hosts with ssh section). + if options[:install] && conf['hosts'][host].key?('ssh') + InstallConfig.new(host, conf['hosts']).upload!.install!.reload! + end # Clean Wireguard configuration for the host. WireguardConfig.new(host, conf['hosts']).clean! if options[:clean] end diff --git a/wireguardmeshgenerator.yaml b/wireguardmeshgenerator.yaml index a39b2ed..92dbf9c 100644 --- a/wireguardmeshgenerator.yaml +++ b/wireguardmeshgenerator.yaml @@ -17,6 +17,7 @@ hosts: exclude_peers: - earth - pixel7pro + - freebsd f1: os: FreeBSD ssh: @@ -34,6 +35,7 @@ hosts: exclude_peers: - earth - pixel7pro + - freebsd f2: os: FreeBSD ssh: @@ -51,6 +53,7 @@ hosts: exclude_peers: - earth - pixel7pro + - freebsd r0: os: Linux ssh: @@ -68,6 +71,7 @@ hosts: exclude_peers: - earth - pixel7pro + - freebsd r1: os: Linux ssh: @@ -85,6 +89,7 @@ hosts: exclude_peers: - earth - pixel7pro + - freebsd r2: os: Linux ssh: @@ -102,6 +107,7 @@ hosts: exclude_peers: - earth - pixel7pro + - freebsd blowfish: os: OpenBSD ssh: @@ -146,6 +152,7 @@ hosts: - 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) @@ -163,6 +170,26 @@ hosts: - 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) + freebsd: + os: FreeBSD + wg0: + 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 + 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 -- cgit v1.2.3