summaryrefslogtreecommitdiff
path: root/gemfeed
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-01-17 00:05:48 +0200
committerPaul Buetow <paul@buetow.org>2026-01-17 00:05:48 +0200
commit05e5dd253f1b0c45e78980ea648994dd79276a18 (patch)
tree1e2563958efd9db6c3a1197ad7e510f88cb7c4ca /gemfeed
parent591cfb6238b3d4c03028ede842cf8de191b0b217 (diff)
Update content for gemtext
Diffstat (limited to 'gemfeed')
-rw-r--r--gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi249
-rw-r--r--gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi1
-rw-r--r--gemfeed/atom.xml279
3 files changed, 525 insertions, 4 deletions
diff --git a/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi b/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi
index d41bd722..7b0fd9fe 100644
--- a/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi
+++ b/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi
@@ -1,6 +1,6 @@
# f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network
-> Published at 2025-05-11T11:35:57+03:00, last updated Sun 11 Jan 21:33:40 EET 2026
+> Published at 2025-05-11T11:35:57+03:00, last updated Thu 15 Jan 19:30:46 EET 2026
This is the fifth blog post about my f3s series for my self-hosting demands in my home lab. f3s? The "f" stands for FreeBSD, and the "3s" stands for k3s, the Kubernetes distribution I will use on FreeBSD-based physical machines.
@@ -47,6 +47,18 @@ Let's begin...
* ⇢ ⇢ ⇢ Installing the `wg0.conf` files
* ⇢ ⇢ ⇢ Re-generating mesh and installing the `wg0.conf` files again
* ⇢ ⇢ ⇢ Setting up roaming clients
+* ⇢ ⇢ Adding IPv6 support to the mesh
+* ⇢ ⇢ ⇢ IPv6 addressing scheme
+* ⇢ ⇢ ⇢ Updating the mesh generator for IPv6
+* ⇢ ⇢ ⇢ IPv6 NAT on OpenBSD gateways
+* ⇢ ⇢ ⇢ Manual OpenBSD interface configuration
+* ⇢ ⇢ ⇢ Verifying dual-stack connectivity
+* ⇢ ⇢ ⇢ Benefits of dual-stack
+* ⇢ ⇢ Manual gateway failover for roaming clients
+* ⇢ ⇢ ⇢ Configuration files for pixel7pro (phone)
+* ⇢ ⇢ ⇢ Configuration files for earth (laptop)
+* ⇢ ⇢ ⇢ Using manual failover on Android
+* ⇢ ⇢ ⇢ Using manual failover on Linux
* ⇢ ⇢ Happy WireGuard-ing
* ⇢ ⇢ Managing Roaming Client Tunnels
* ⇢ ⇢ ⇢ Starting and stopping on earth (Fedora laptop)
@@ -175,6 +187,17 @@ paul@f0:~ % cat <<END | doas tee -a /etc/hosts
192.168.2.110 blowfish.wg0 blowfish.wg0.wan.buetow.org
192.168.2.111 fishfinger.wg0 fishfinger.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::130 f0.wg0 f0.wg0.wan.buetow.org
+fd42:beef:cafe:2::131 f1.wg0 f1.wg0.wan.buetow.org
+fd42:beef:cafe:2::132 f2.wg0 f2.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::120 r0.wg0 r0.wg0.wan.buetow.org
+fd42:beef:cafe:2::121 r1.wg0 r1.wg0.wan.buetow.org
+fd42:beef:cafe:2::122 r2.wg0 r2.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::110 blowfish.wg0 blowfish.wg0.wan.buetow.org
+fd42:beef:cafe:2::111 fishfinger.wg0 fishfinger.wg0.wan.buetow.org
END
```
@@ -219,6 +242,17 @@ We also update the `hosts` file accordingly:
192.168.2.110 blowfish.wg0 blowfish.wg0.wan.buetow.org
192.168.2.111 fishfinger.wg0 fishfinger.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::130 f0.wg0 f0.wg0.wan.buetow.org
+fd42:beef:cafe:2::131 f1.wg0 f1.wg0.wan.buetow.org
+fd42:beef:cafe:2::132 f2.wg0 f2.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::120 r0.wg0 r0.wg0.wan.buetow.org
+fd42:beef:cafe:2::121 r1.wg0 r1.wg0.wan.buetow.org
+fd42:beef:cafe:2::122 r2.wg0 r2.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::110 blowfish.wg0 blowfish.wg0.wan.buetow.org
+fd42:beef:cafe:2::111 fishfinger.wg0 fishfinger.wg0.wan.buetow.org
END
```
@@ -266,6 +300,19 @@ blowfish$ cat <<END | doas tee -a /etc/hosts
192.168.2.111 fishfinger.wg0 fishfinger.wg0.wan.buetow.org
192.168.2.200 earth.wg0 earth.wg0.wan.buetow.org
192.168.2.201 pixel7pro.wg0 pixel7pro.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::130 f0.wg0 f0.wg0.wan.buetow.org
+fd42:beef:cafe:2::131 f1.wg0 f1.wg0.wan.buetow.org
+fd42:beef:cafe:2::132 f2.wg0 f2.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::120 r0.wg0 r0.wg0.wan.buetow.org
+fd42:beef:cafe:2::121 r1.wg0 r1.wg0.wan.buetow.org
+fd42:beef:cafe:2::122 r2.wg0 r2.wg0.wan.buetow.org
+
+fd42:beef:cafe:2::110 blowfish.wg0 blowfish.wg0.wan.buetow.org
+fd42:beef:cafe:2::111 fishfinger.wg0 fishfinger.wg0.wan.buetow.org
+fd42:beef:cafe:2::200 earth.wg0 earth.wg0.wan.buetow.org
+fd42:beef:cafe:2::201 pixel7pro.wg0 pixel7pro.wg0.wan.buetow.org
END
```
@@ -466,6 +513,7 @@ hosts:
wg0:
domain: 'wg0.wan.buetow.org'
ip: '192.168.2.130'
+ ipv6: 'fd42:beef:cafe:2::130'
exclude_peers:
- earth
- pixel7pro
@@ -485,6 +533,7 @@ hosts:
wg0:
domain: 'wg0.wan.buetow.org'
ip: '192.168.2.120'
+ ipv6: 'fd42:beef:cafe:2::120'
exclude_peers:
- earth
- pixel7pro
@@ -504,6 +553,7 @@ hosts:
wg0:
domain: 'wg0.wan.buetow.org'
ip: '192.168.2.110'
+ ipv6: 'fd42:beef:cafe:2::110'
exclude_peers:
- earth
- pixel7pro
@@ -521,6 +571,7 @@ hosts:
wg0:
domain: 'wg0.wan.buetow.org'
ip: '192.168.2.111'
+ ipv6: 'fd42:beef:cafe:2::111'
exclude_peers:
- earth
- pixel7pro
@@ -529,6 +580,7 @@ hosts:
wg0:
domain: 'wg0.wan.buetow.org'
ip: '192.168.2.200'
+ ipv6: 'fd42:beef:cafe:2::200'
exclude_peers:
- f0
- f1
@@ -542,6 +594,7 @@ hosts:
wg0:
domain: 'wg0.wan.buetow.org'
ip: '192.168.2.201'
+ ipv6: 'fd42:beef:cafe:2::201'
exclude_peers:
- f0
- f1
@@ -856,6 +909,200 @@ For the laptop, manually copy the generated configuration:
The service is disabled from auto-start so the VPN is only active when manually started. This allows selective VPN usage based on need.
+## Adding IPv6 support to the mesh
+
+After setting up the IPv4-only mesh network, I decided to add dual-stack IPv6 support to enable more networking capabilities and prepare for the future. All 10 hosts (8 infrastructure + 2 roaming clients) now have both IPv4 and IPv6 addresses on their WireGuard interfaces.
+
+### IPv6 addressing scheme
+
+We use ULA (Unique Local Address) private IPv6 space, analogous to RFC1918 private IPv4 addresses:
+
+* Prefix: `fd42:beef:cafe::/48`
+* Subnet: `fd42:beef:cafe:2::/64` (wg0 interfaces)
+
+All hosts receive dual-stack addresses:
+
+```
+fd42:beef:cafe:2::110/64 - blowfish.wg0 (OpenBSD gateway)
+fd42:beef:cafe:2::111/64 - fishfinger.wg0 (OpenBSD gateway)
+fd42:beef:cafe:2::120/64 - r0.wg0 (Rocky Linux VM)
+fd42:beef:cafe:2::121/64 - r1.wg0 (Rocky Linux VM)
+fd42:beef:cafe:2::122/64 - r2.wg0 (Rocky Linux VM)
+fd42:beef:cafe:2::130/64 - f0.wg0 (FreeBSD host)
+fd42:beef:cafe:2::131/64 - f1.wg0 (FreeBSD host)
+fd42:beef:cafe:2::132/64 - f2.wg0 (FreeBSD host)
+fd42:beef:cafe:2::200/64 - earth.wg0 (roaming laptop)
+fd42:beef:cafe:2::201/64 - pixel7pro.wg0 (roaming phone)
+```
+
+### Updating the mesh generator for IPv6
+
+The mesh generator required two modifications to support dual-stack configurations:
+
+**1. Address generation (`address` method)**
+
+The generator now outputs multiple `Address` directives when IPv6 is present:
+
+```ruby
+def address
+ return '# No Address = ... for OpenBSD here' if hosts[myself]['os'] == 'OpenBSD'
+
+ ipv4 = hosts[myself]['wg0']['ip']
+ ipv6 = hosts[myself]['wg0']['ipv6']
+
+ # WireGuard supports multiple Address directives for dual-stack
+ if ipv6
+ "Address = #{ipv4}\nAddress = #{ipv6}/64"
+ else
+ "Address = #{ipv4}"
+ end
+end
+```
+
+**2. AllowedIPs generation (`peers` method)**
+
+For mesh peers, both IPv4 and IPv6 addresses are included in AllowedIPs:
+
+```ruby
+if is_roaming
+ allowed_ips = '0.0.0.0/0, ::/0'
+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
+```
+
+Roaming clients keep `AllowedIPs = 0.0.0.0/0, ::/0` to route all traffic (IPv4 and IPv6) through the VPN.
+
+### IPv6 NAT on OpenBSD gateways
+
+To allow roaming clients to access the internet via IPv6, we added NAT66 rules to the OpenBSD gateways' `pf.conf`:
+
+```
+# NAT for WireGuard clients to access internet (IPv4)
+match out on vio0 from 192.168.2.0/24 to any nat-to (vio0)
+
+# NAT66 for WireGuard clients to access internet (IPv6)
+# Uses NPTv6 (Network Prefix Translation) to translate ULA to public IPv6
+match out on vio0 inet6 from fd42:beef:cafe:2::/64 to any nat-to (vio0)
+
+# Allow all UDP traffic on WireGuard port (IPv4 and IPv6)
+pass in inet proto udp from any to any port 56709
+pass in inet6 proto udp from any to any port 56709
+```
+
+OpenBSD's PF firewall supports IPv6 NAT with the same syntax as IPv4, using NPTv6 (RFC 6296) to translate the ULA addresses to the gateway's public IPv6 address.
+
+### Manual OpenBSD interface configuration
+
+Since OpenBSD doesn't use the `Address` directive in WireGuard configs, IPv6 must be manually configured on the wg0 interfaces. On `blowfish`:
+
+```sh
+rex@blowfish:~ $ doas vi /etc/hostname.wg0
+```
+
+Add the IPv6 address (note the order - IPv6 must be configured before `up`):
+
+```
+inet 192.168.2.110 255.255.255.0 NONE
+inet6 fd42:beef:cafe:2::110 64
+up
+!/usr/local/bin/wg setconf wg0 /etc/wireguard/wg0.conf
+```
+
+**Important**: The IPv6 address must be specified before the `up` directive. This ensures the interface has both addresses configured before WireGuard peers are loaded.
+
+Apply the configuration:
+
+```sh
+rex@blowfish:~ $ doas sh /etc/netstart wg0
+rex@blowfish:~ $ ifconfig wg0 | grep inet6
+inet6 fd42:beef:cafe:2::110 prefixlen 64
+```
+
+Repeat for `fishfinger` with address `fd42:beef:cafe:2::111`.
+
+After reboot, the interface will automatically come up with both IPv4 and IPv6 addresses. WireGuard peers may take 30-60 seconds to establish handshakes after boot.
+
+### Verifying dual-stack connectivity
+
+After regenerating and deploying the configurations, both IPv4 and IPv6 work across the mesh:
+
+```sh
+# From r0 (Rocky Linux VM)
+root@r0:~ # ping -c 2 192.168.2.130 # IPv4 to f0
+64 bytes from 192.168.2.130: icmp_seq=1 ttl=64 time=2.12 ms
+64 bytes from 192.168.2.130: icmp_seq=2 ttl=64 time=0.681 ms
+
+root@r0:~ # ping6 -c 2 fd42:beef:cafe:2::130 # IPv6 to f0
+64 bytes from fd42:beef:cafe:2::130: icmp_seq=1 ttl=64 time=2.16 ms
+64 bytes from fd42:beef:cafe:2::130: icmp_seq=2 ttl=64 time=0.909 ms
+```
+
+The dual-stack configuration is backward compatible—hosts without the `ipv6` field in the YAML configuration will continue to generate IPv4-only configs.
+
+### Benefits of dual-stack
+
+Adding IPv6 to the mesh network provides:
+
+* **Future-proofing**: Ready for IPv6-only services and networks
+* **Compatibility**: Dual-stack maintains full IPv4 compatibility
+* **Learning**: Hands-on experience with IPv6 networking
+* **Flexibility**: Roaming clients can access both IPv4 and IPv6 internet resources
+
+## Manual gateway failover for roaming clients
+
+WireGuard doesn't automatically failover between multiple peers with identical `AllowedIPs` routes. When both gateways (blowfish and fishfinger) are configured with `AllowedIPs = 0.0.0.0/0, ::/0`, WireGuard uses the first peer with a recent handshake. If that gateway goes down, traffic won't automatically switch to the backup.
+
+To enable manual failover, separate configuration files have been created for roaming clients (earth laptop and pixel7pro phone), each containing only a single gateway peer.
+
+### Configuration files for pixel7pro (phone)
+
+Two separate configs in `/home/paul/git/wireguardmeshgenerator/dist/pixel7pro/etc/wireguard/`:
+
+* **wg0-blowfish.conf** - Routes all traffic through blowfish gateway (23.88.35.144)
+* **wg0-fishfinger.conf** - Routes all traffic through fishfinger gateway (46.23.94.99)
+
+### Configuration files for earth (laptop)
+
+Two separate configs in `/home/paul/git/wireguardmeshgenerator/dist/earth/etc/wireguard/`:
+
+* **wg0-blowfish.conf** - Routes all traffic through blowfish gateway
+* **wg0-fishfinger.conf** - Routes all traffic through fishfinger gateway
+
+### Using manual failover on Android
+
+On the pixel7pro phone, import both QR codes using the WireGuard app to create two separate tunnel profiles:
+
+```sh
+# Generate QR codes
+qrencode -t ansiutf8 < dist/pixel7pro/etc/wireguard/wg0-blowfish.conf
+qrencode -t ansiutf8 < dist/pixel7pro/etc/wireguard/wg0-fishfinger.conf
+```
+
+In the WireGuard app, you can then manually enable/disable each tunnel to select which gateway to use. Only enable one tunnel at a time.
+
+### Using manual failover on Linux
+
+On the earth laptop, copy both configs and use systemd to switch between them:
+
+```sh
+# Install both configurations
+sudo cp dist/earth/etc/wireguard/wg0-blowfish.conf /etc/wireguard/
+sudo cp dist/earth/etc/wireguard/wg0-fishfinger.conf /etc/wireguard/
+
+# Start with blowfish gateway
+sudo systemctl start wg-quick@wg0-blowfish.service
+
+# To switch to fishfinger gateway
+sudo systemctl stop wg-quick@wg0-blowfish.service
+sudo systemctl start wg-quick@wg0-fishfinger.service
+```
+
+This approach provides explicit control over which gateway handles roaming client traffic, useful when one gateway needs maintenance or experiences connectivity issues.
+
## Happy WireGuard-ing
All is set up now. E.g. on `f0`:
diff --git a/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi b/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi
index a136ee39..b215a560 100644
--- a/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi
+++ b/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi
@@ -879,6 +879,7 @@ Next, update `/etc/hosts` on all nodes (`f0`, `f1`, `f2`, `r0`, `r1`, `r2`) to r
```
192.168.2.138 f3s-storage-ha f3s-storage-ha.wg0 f3s-storage-ha.wg0.wan.buetow.org
+fd42:beef:cafe:2::138 f3s-storage-ha f3s-storage-ha.wg0 f3s-storage-ha.wg0.wan.buetow.org
```
This allows clients to connect to `f3s-storage-ha` regardless of which physical server is currently the MASTER.
diff --git a/gemfeed/atom.xml b/gemfeed/atom.xml
index 26147725..3f3cff03 100644
--- a/gemfeed/atom.xml
+++ b/gemfeed/atom.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
- <updated>2026-01-11T22:40:26+02:00</updated>
+ <updated>2026-01-17T00:03:44+02:00</updated>
<title>foo.zone feed</title>
<subtitle>To be in the .zone!</subtitle>
<link href="gemini://foo.zone/gemfeed/atom.xml" rel="self" />
@@ -7454,6 +7454,7 @@ ifconfig_re0_alias0=<font color="#808080">"inet vhid 1 advskew 100 pass testpass
<br />
<pre>
192.168.2.138 f3s-storage-ha f3s-storage-ha.wg0 f3s-storage-ha.wg0.wan.buetow.org
+fd42:beef:cafe:2::138 f3s-storage-ha f3s-storage-ha.wg0 f3s-storage-ha.wg0.wan.buetow.org
</pre>
<br />
<span>This allows clients to connect to <span class='inlinecode'>f3s-storage-ha</span> regardless of which physical server is currently the MASTER.</span><br />
@@ -9566,7 +9567,7 @@ Jul <font color="#000000">06</font> <font color="#000000">10</font>:<font color=
<title>f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network</title>
<link href="gemini://foo.zone/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi" />
<id>gemini://foo.zone/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi</id>
- <updated>2025-05-11T11:35:57+03:00, last updated Sun 11 Jan 21:33:40 EET 2026</updated>
+ <updated>2025-05-11T11:35:57+03:00, last updated Thu 15 Jan 19:30:46 EET 2026</updated>
<author>
<name>Paul Buetow aka snonux</name>
<email>paul@dev.buetow.org</email>
@@ -9576,7 +9577,7 @@ Jul <font color="#000000">06</font> <font color="#000000">10</font>:<font color=
<div xmlns="http://www.w3.org/1999/xhtml">
<h1 style='display: inline' id='f3s-kubernetes-with-freebsd---part-5-wireguard-mesh-network'>f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network</h1><br />
<br />
-<span class='quote'>Published at 2025-05-11T11:35:57+03:00, last updated Sun 11 Jan 21:33:40 EET 2026</span><br />
+<span class='quote'>Published at 2025-05-11T11:35:57+03:00, last updated Thu 15 Jan 19:30:46 EET 2026</span><br />
<br />
<span>This is the fifth blog post about my f3s series for my self-hosting demands in my home lab. f3s? The "f" stands for FreeBSD, and the "3s" stands for k3s, the Kubernetes distribution I will use on FreeBSD-based physical machines.</span><br />
<br />
@@ -9624,6 +9625,18 @@ Jul <font color="#000000">06</font> <font color="#000000">10</font>:<font color=
<li>⇢ ⇢ <a href='#installing-the-wg0conf-files'>Installing the <span class='inlinecode'>wg0.conf</span> files</a></li>
<li>⇢ ⇢ <a href='#re-generating-mesh-and-installing-the-wg0conf-files-again'>Re-generating mesh and installing the <span class='inlinecode'>wg0.conf</span> files again</a></li>
<li>⇢ ⇢ <a href='#setting-up-roaming-clients'>Setting up roaming clients</a></li>
+<li>⇢ <a href='#adding-ipv6-support-to-the-mesh'>Adding IPv6 support to the mesh</a></li>
+<li>⇢ ⇢ <a href='#ipv6-addressing-scheme'>IPv6 addressing scheme</a></li>
+<li>⇢ ⇢ <a href='#updating-the-mesh-generator-for-ipv6'>Updating the mesh generator for IPv6</a></li>
+<li>⇢ ⇢ <a href='#ipv6-nat-on-openbsd-gateways'>IPv6 NAT on OpenBSD gateways</a></li>
+<li>⇢ ⇢ <a href='#manual-openbsd-interface-configuration'>Manual OpenBSD interface configuration</a></li>
+<li>⇢ ⇢ <a href='#verifying-dual-stack-connectivity'>Verifying dual-stack connectivity</a></li>
+<li>⇢ ⇢ <a href='#benefits-of-dual-stack'>Benefits of dual-stack</a></li>
+<li>⇢ <a href='#manual-gateway-failover-for-roaming-clients'>Manual gateway failover for roaming clients</a></li>
+<li>⇢ ⇢ <a href='#configuration-files-for-pixel7pro-phone'>Configuration files for pixel7pro (phone)</a></li>
+<li>⇢ ⇢ <a href='#configuration-files-for-earth-laptop'>Configuration files for earth (laptop)</a></li>
+<li>⇢ ⇢ <a href='#using-manual-failover-on-android'>Using manual failover on Android</a></li>
+<li>⇢ ⇢ <a href='#using-manual-failover-on-linux'>Using manual failover on Linux</a></li>
<li>⇢ <a href='#happy-wireguard-ing'>Happy WireGuard-ing</a></li>
<li>⇢ <a href='#managing-roaming-client-tunnels'>Managing Roaming Client Tunnels</a></li>
<li>⇢ ⇢ <a href='#starting-and-stopping-on-earth-fedora-laptop'>Starting and stopping on earth (Fedora laptop)</a></li>
@@ -9766,6 +9779,17 @@ http://www.gnu.org/software/src-highlite -->
<font color="#000000">192.168</font>.<font color="#000000">2.110</font> blowfish.wg0 blowfish.wg0.wan.buetow.org
<font color="#000000">192.168</font>.<font color="#000000">2.111</font> fishfinger.wg0 fishfinger.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">130</font> f0.wg0 f0.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">131</font> f1.wg0 f1.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">132</font> f2.wg0 f2.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">120</font> r0.wg0 r0.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">121</font> r1.wg0 r1.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">122</font> r2.wg0 r2.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">110</font> blowfish.wg0 blowfish.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">111</font> fishfinger.wg0 fishfinger.wg0.wan.buetow.org
END
</pre>
<br />
@@ -9819,6 +9843,17 @@ http://www.gnu.org/software/src-highlite -->
<font color="#000000">192.168</font>.<font color="#000000">2.110</font> blowfish.wg0 blowfish.wg0.wan.buetow.org
<font color="#000000">192.168</font>.<font color="#000000">2.111</font> fishfinger.wg0 fishfinger.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">130</font> f0.wg0 f0.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">131</font> f1.wg0 f1.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">132</font> f2.wg0 f2.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">120</font> r0.wg0 r0.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">121</font> r1.wg0 r1.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">122</font> r2.wg0 r2.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">110</font> blowfish.wg0 blowfish.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">111</font> fishfinger.wg0 fishfinger.wg0.wan.buetow.org
END
</pre>
<br />
@@ -9875,6 +9910,19 @@ http://www.gnu.org/software/src-highlite -->
<font color="#000000">192.168</font>.<font color="#000000">2.111</font> fishfinger.wg0 fishfinger.wg0.wan.buetow.org
<font color="#000000">192.168</font>.<font color="#000000">2.200</font> earth.wg0 earth.wg0.wan.buetow.org
<font color="#000000">192.168</font>.<font color="#000000">2.201</font> pixel7pro.wg0 pixel7pro.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">130</font> f0.wg0 f0.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">131</font> f1.wg0 f1.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">132</font> f2.wg0 f2.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">120</font> r0.wg0 r0.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">121</font> r1.wg0 r1.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">122</font> r2.wg0 r2.wg0.wan.buetow.org
+
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">110</font> blowfish.wg0 blowfish.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">111</font> fishfinger.wg0 fishfinger.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">200</font> earth.wg0 earth.wg0.wan.buetow.org
+fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">201</font> pixel7pro.wg0 pixel7pro.wg0.wan.buetow.org
END
</pre>
<br />
@@ -10087,6 +10135,7 @@ hosts:
wg0:
domain: &#39;wg0.wan.buetow.org&#39;
ip: &#39;192.168.2.130&#39;
+ ipv6: &#39;fd42:beef:cafe:2::130&#39;
exclude_peers:
- earth
- pixel7pro
@@ -10106,6 +10155,7 @@ hosts:
wg0:
domain: &#39;wg0.wan.buetow.org&#39;
ip: &#39;192.168.2.120&#39;
+ ipv6: &#39;fd42:beef:cafe:2::120&#39;
exclude_peers:
- earth
- pixel7pro
@@ -10125,6 +10175,7 @@ hosts:
wg0:
domain: &#39;wg0.wan.buetow.org&#39;
ip: &#39;192.168.2.110&#39;
+ ipv6: &#39;fd42:beef:cafe:2::110&#39;
exclude_peers:
- earth
- pixel7pro
@@ -10142,6 +10193,7 @@ hosts:
wg0:
domain: &#39;wg0.wan.buetow.org&#39;
ip: &#39;192.168.2.111&#39;
+ ipv6: &#39;fd42:beef:cafe:2::111&#39;
exclude_peers:
- earth
- pixel7pro
@@ -10150,6 +10202,7 @@ hosts:
wg0:
domain: &#39;wg0.wan.buetow.org&#39;
ip: &#39;192.168.2.200&#39;
+ ipv6: &#39;fd42:beef:cafe:2::200&#39;
exclude_peers:
- f0
- f1
@@ -10163,6 +10216,7 @@ hosts:
wg0:
domain: &#39;wg0.wan.buetow.org&#39;
ip: &#39;192.168.2.201&#39;
+ ipv6: &#39;fd42:beef:cafe:2::201&#39;
exclude_peers:
- f0
- f1
@@ -10502,6 +10556,225 @@ http://www.gnu.org/software/src-highlite -->
<br />
<span>The service is disabled from auto-start so the VPN is only active when manually started. This allows selective VPN usage based on need.</span><br />
<br />
+<h2 style='display: inline' id='adding-ipv6-support-to-the-mesh'>Adding IPv6 support to the mesh</h2><br />
+<br />
+<span>After setting up the IPv4-only mesh network, I decided to add dual-stack IPv6 support to enable more networking capabilities and prepare for the future. All 10 hosts (8 infrastructure + 2 roaming clients) now have both IPv4 and IPv6 addresses on their WireGuard interfaces.</span><br />
+<br />
+<h3 style='display: inline' id='ipv6-addressing-scheme'>IPv6 addressing scheme</h3><br />
+<br />
+<span>We use ULA (Unique Local Address) private IPv6 space, analogous to RFC1918 private IPv4 addresses:</span><br />
+<br />
+<ul>
+<li>Prefix: <span class='inlinecode'>fd42:beef:cafe::/48</span></li>
+<li>Subnet: <span class='inlinecode'>fd42:beef:cafe:2::/64</span> (wg0 interfaces)</li>
+</ul><br />
+<span>All hosts receive dual-stack addresses:</span><br />
+<br />
+<pre>
+fd42:beef:cafe:2::110/64 - blowfish.wg0 (OpenBSD gateway)
+fd42:beef:cafe:2::111/64 - fishfinger.wg0 (OpenBSD gateway)
+fd42:beef:cafe:2::120/64 - r0.wg0 (Rocky Linux VM)
+fd42:beef:cafe:2::121/64 - r1.wg0 (Rocky Linux VM)
+fd42:beef:cafe:2::122/64 - r2.wg0 (Rocky Linux VM)
+fd42:beef:cafe:2::130/64 - f0.wg0 (FreeBSD host)
+fd42:beef:cafe:2::131/64 - f1.wg0 (FreeBSD host)
+fd42:beef:cafe:2::132/64 - f2.wg0 (FreeBSD host)
+fd42:beef:cafe:2::200/64 - earth.wg0 (roaming laptop)
+fd42:beef:cafe:2::201/64 - pixel7pro.wg0 (roaming phone)
+</pre>
+<br />
+<h3 style='display: inline' id='updating-the-mesh-generator-for-ipv6'>Updating the mesh generator for IPv6</h3><br />
+<br />
+<span>The mesh generator required two modifications to support dual-stack configurations:</span><br />
+<br />
+<span>**1. Address generation (<span class='inlinecode'>address</span> method)**</span><br />
+<br />
+<span>The generator now outputs multiple <span class='inlinecode'>Address</span> directives when IPv6 is present:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><b><u><font color="#000000">def</font></u></b> address
+ <b><u><font color="#000000">return</font></u></b> <font color="#808080">'# No Address = ... for OpenBSD here'</font> <b><u><font color="#000000">if</font></u></b> hosts[myself][<font color="#808080">'os'</font>] == <font color="#808080">'OpenBSD'</font>
+
+ ipv4 = hosts[myself][<font color="#808080">'wg0'</font>][<font color="#808080">'ip'</font>]
+ ipv6 = hosts[myself][<font color="#808080">'wg0'</font>][<font color="#808080">'ipv6'</font>]
+
+ <i><font color="silver"># WireGuard supports multiple Address directives for dual-stack</font></i>
+ <b><u><font color="#000000">if</font></u></b> ipv6
+ <font color="#808080">"Address = #{ipv4}\nAddress = #{ipv6}/64"</font>
+ <b><u><font color="#000000">else</font></u></b>
+ <font color="#808080">"Address = #{ipv4}"</font>
+ <b><u><font color="#000000">end</font></u></b>
+<b><u><font color="#000000">end</font></u></b>
+</pre>
+<br />
+<span>**2. AllowedIPs generation (<span class='inlinecode'>peers</span> method)**</span><br />
+<br />
+<span>For mesh peers, both IPv4 and IPv6 addresses are included in AllowedIPs:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><b><u><font color="#000000">if</font></u></b> is_roaming
+ allowed_ips = <font color="#808080">'0.0.0.0/0, ::/0'</font>
+<b><u><font color="#000000">else</font></u></b>
+ <i><font color="silver"># For mesh peers, allow both IPv4 and IPv6 if present</font></i>
+ ipv4 = data[<font color="#808080">'wg0'</font>][<font color="#808080">'ip'</font>]
+ ipv6 = data[<font color="#808080">'wg0'</font>][<font color="#808080">'ipv6'</font>]
+ allowed_ips = ipv6 ? <font color="#808080">"#{ipv4}/32, #{ipv6}/128"</font> : <font color="#808080">"#{ipv4}/32"</font>
+<b><u><font color="#000000">end</font></u></b>
+</pre>
+<br />
+<span>Roaming clients keep <span class='inlinecode'>AllowedIPs = 0.0.0.0/0, ::/0</span> to route all traffic (IPv4 and IPv6) through the VPN.</span><br />
+<br />
+<h3 style='display: inline' id='ipv6-nat-on-openbsd-gateways'>IPv6 NAT on OpenBSD gateways</h3><br />
+<br />
+<span>To allow roaming clients to access the internet via IPv6, we added NAT66 rules to the OpenBSD gateways&#39; <span class='inlinecode'>pf.conf</span>:</span><br />
+<br />
+<pre>
+# NAT for WireGuard clients to access internet (IPv4)
+match out on vio0 from 192.168.2.0/24 to any nat-to (vio0)
+
+# NAT66 for WireGuard clients to access internet (IPv6)
+# Uses NPTv6 (Network Prefix Translation) to translate ULA to public IPv6
+match out on vio0 inet6 from fd42:beef:cafe:2::/64 to any nat-to (vio0)
+
+# Allow all UDP traffic on WireGuard port (IPv4 and IPv6)
+pass in inet proto udp from any to any port 56709
+pass in inet6 proto udp from any to any port 56709
+</pre>
+<br />
+<span>OpenBSD&#39;s PF firewall supports IPv6 NAT with the same syntax as IPv4, using NPTv6 (RFC 6296) to translate the ULA addresses to the gateway&#39;s public IPv6 address.</span><br />
+<br />
+<h3 style='display: inline' id='manual-openbsd-interface-configuration'>Manual OpenBSD interface configuration</h3><br />
+<br />
+<span>Since OpenBSD doesn&#39;t use the <span class='inlinecode'>Address</span> directive in WireGuard configs, IPv6 must be manually configured on the wg0 interfaces. On <span class='inlinecode'>blowfish</span>:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre>rex@blowfish:~ $ doas vi /etc/hostname.wg0
+</pre>
+<br />
+<span>Add the IPv6 address (note the order - IPv6 must be configured before <span class='inlinecode'>up</span>):</span><br />
+<br />
+<pre>
+inet 192.168.2.110 255.255.255.0 NONE
+inet6 fd42:beef:cafe:2::110 64
+up
+!/usr/local/bin/wg setconf wg0 /etc/wireguard/wg0.conf
+</pre>
+<br />
+<span>**Important**: The IPv6 address must be specified before the <span class='inlinecode'>up</span> directive. This ensures the interface has both addresses configured before WireGuard peers are loaded.</span><br />
+<br />
+<span>Apply the configuration:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre>rex@blowfish:~ $ doas sh /etc/netstart wg0
+rex@blowfish:~ $ ifconfig wg0 | grep inet6
+inet6 fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">110</font> prefixlen <font color="#000000">64</font>
+</pre>
+<br />
+<span>Repeat for <span class='inlinecode'>fishfinger</span> with address <span class='inlinecode'>fd42:beef:cafe:2::111</span>.</span><br />
+<br />
+<span>After reboot, the interface will automatically come up with both IPv4 and IPv6 addresses. WireGuard peers may take 30-60 seconds to establish handshakes after boot.</span><br />
+<br />
+<h3 style='display: inline' id='verifying-dual-stack-connectivity'>Verifying dual-stack connectivity</h3><br />
+<br />
+<span>After regenerating and deploying the configurations, both IPv4 and IPv6 work across the mesh:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><i><font color="silver"># From r0 (Rocky Linux VM)</font></i>
+root@r0:~ <i><font color="silver"># ping -c 2 192.168.2.130 # IPv4 to f0</font></i>
+<font color="#000000">64</font> bytes from <font color="#000000">192.168</font>.<font color="#000000">2.130</font>: icmp_seq=<font color="#000000">1</font> ttl=<font color="#000000">64</font> time=<font color="#000000">2.12</font> ms
+<font color="#000000">64</font> bytes from <font color="#000000">192.168</font>.<font color="#000000">2.130</font>: icmp_seq=<font color="#000000">2</font> ttl=<font color="#000000">64</font> time=<font color="#000000">0.681</font> ms
+
+root@r0:~ <i><font color="silver"># ping6 -c 2 fd42:beef:cafe:2::130 # IPv6 to f0</font></i>
+<font color="#000000">64</font> bytes from fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">130</font>: icmp_seq=<font color="#000000">1</font> ttl=<font color="#000000">64</font> time=<font color="#000000">2.16</font> ms
+<font color="#000000">64</font> bytes from fd42:beef:cafe:<font color="#000000">2</font>::<font color="#000000">130</font>: icmp_seq=<font color="#000000">2</font> ttl=<font color="#000000">64</font> time=<font color="#000000">0.909</font> ms
+</pre>
+<br />
+<span>The dual-stack configuration is backward compatible—hosts without the <span class='inlinecode'>ipv6</span> field in the YAML configuration will continue to generate IPv4-only configs.</span><br />
+<br />
+<h3 style='display: inline' id='benefits-of-dual-stack'>Benefits of dual-stack</h3><br />
+<br />
+<span>Adding IPv6 to the mesh network provides:</span><br />
+<br />
+<ul>
+<li>**Future-proofing**: Ready for IPv6-only services and networks</li>
+<li>**Compatibility**: Dual-stack maintains full IPv4 compatibility</li>
+<li>**Learning**: Hands-on experience with IPv6 networking</li>
+<li>**Flexibility**: Roaming clients can access both IPv4 and IPv6 internet resources</li>
+</ul><br />
+<h2 style='display: inline' id='manual-gateway-failover-for-roaming-clients'>Manual gateway failover for roaming clients</h2><br />
+<br />
+<span>WireGuard doesn&#39;t automatically failover between multiple peers with identical <span class='inlinecode'>AllowedIPs</span> routes. When both gateways (blowfish and fishfinger) are configured with <span class='inlinecode'>AllowedIPs = 0.0.0.0/0, ::/0</span>, WireGuard uses the first peer with a recent handshake. If that gateway goes down, traffic won&#39;t automatically switch to the backup.</span><br />
+<br />
+<span>To enable manual failover, separate configuration files have been created for roaming clients (earth laptop and pixel7pro phone), each containing only a single gateway peer.</span><br />
+<br />
+<h3 style='display: inline' id='configuration-files-for-pixel7pro-phone'>Configuration files for pixel7pro (phone)</h3><br />
+<br />
+<span>Two separate configs in <span class='inlinecode'>/home/paul/git/wireguardmeshgenerator/dist/pixel7pro/etc/wireguard/</span>:</span><br />
+<br />
+<ul>
+<li>**wg0-blowfish.conf** - Routes all traffic through blowfish gateway (23.88.35.144)</li>
+<li>**wg0-fishfinger.conf** - Routes all traffic through fishfinger gateway (46.23.94.99)</li>
+</ul><br />
+<h3 style='display: inline' id='configuration-files-for-earth-laptop'>Configuration files for earth (laptop)</h3><br />
+<br />
+<span>Two separate configs in <span class='inlinecode'>/home/paul/git/wireguardmeshgenerator/dist/earth/etc/wireguard/</span>:</span><br />
+<br />
+<ul>
+<li>**wg0-blowfish.conf** - Routes all traffic through blowfish gateway</li>
+<li>**wg0-fishfinger.conf** - Routes all traffic through fishfinger gateway</li>
+</ul><br />
+<h3 style='display: inline' id='using-manual-failover-on-android'>Using manual failover on Android</h3><br />
+<br />
+<span>On the pixel7pro phone, import both QR codes using the WireGuard app to create two separate tunnel profiles:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><i><font color="silver"># Generate QR codes</font></i>
+qrencode -t ansiutf8 &lt; dist/pixel7pro/etc/wireguard/wg<font color="#000000">0</font>-blowfish.conf
+qrencode -t ansiutf8 &lt; dist/pixel7pro/etc/wireguard/wg<font color="#000000">0</font>-fishfinger.conf
+</pre>
+<br />
+<span>In the WireGuard app, you can then manually enable/disable each tunnel to select which gateway to use. Only enable one tunnel at a time.</span><br />
+<br />
+<h3 style='display: inline' id='using-manual-failover-on-linux'>Using manual failover on Linux</h3><br />
+<br />
+<span>On the earth laptop, copy both configs and use systemd to switch between them:</span><br />
+<br />
+<!-- Generator: GNU source-highlight 3.1.9
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><i><font color="silver"># Install both configurations</font></i>
+sudo cp dist/earth/etc/wireguard/wg<font color="#000000">0</font>-blowfish.conf /etc/wireguard/
+sudo cp dist/earth/etc/wireguard/wg<font color="#000000">0</font>-fishfinger.conf /etc/wireguard/
+
+<i><font color="silver"># Start with blowfish gateway</font></i>
+sudo systemctl start wg-quick@wg0-blowfish.service
+
+<i><font color="silver"># To switch to fishfinger gateway</font></i>
+sudo systemctl stop wg-quick@wg0-blowfish.service
+sudo systemctl start wg-quick@wg0-fishfinger.service
+</pre>
+<br />
+<span>This approach provides explicit control over which gateway handles roaming client traffic, useful when one gateway needs maintenance or experiences connectivity issues.</span><br />
+<br />
<h2 style='display: inline' id='happy-wireguard-ing'>Happy WireGuard-ing</h2><br />
<br />
<span>All is set up now. E.g. on <span class='inlinecode'>f0</span>:</span><br />