WireGuard behind DS-Lite - wg-easy on TrueNAS, reachable over IPv6

Contents

Preface

The first part covered why only IPv6 gets into my home network from outside (DS-Lite), and the small script that keeps vpn.knng.de pointed at my NAS.
This part is about what sits behind that name: WireGuard.

The FRITZ!Box can do WireGuard itself, but its upload was too slow for me.
So WireGuard runs on the NAS instead, as the wg-easy app on TrueNAS.


The setup at a glance

Setting Value
Endpoint vpn.knng.de, AAAA record only
Port UDP 51820
Tunnel split tunnel, only 192.168.1.0/24 goes through it
DNS for clients Pi-hole, 192.168.1.15
MTU 1280
Persistent keepalive 25 seconds

Opening the FRITZ!Box

With IPv6 there’s no NAT, the NAS has its own public address.
The FRITZ!Box only has to let UDP 51820 through its firewall to that one device, which is a normal port sharing for the NAS:

FRITZ!Box port sharing for the NAS, UDP 51820

That’s the only port that’s open from the outside.


wg-easy on TrueNAS

wg-easy comes straight from the TrueNAS app catalog.
Most of the interesting settings are in the web UI under Admin Panel -> Config:

wg-easy config: host, port, allowed IPs, DNS, MTU and keepalive

  • Host is vpn.knng.de. That’s what ends up as the endpoint in every client config, so it has to resolve from anywhere, which is what the AAAA record from the first part is for.
  • Allowed IPs is only 192.168.1.0/24. Traffic to my home network goes through the tunnel, everything else on the phone or laptop goes out directly like it normally would.
  • DNS is Pi-hole on the NAS. That’s the nice part: the local DNS records from the first part work on the road too, so my internal *.knng.de names resolve to the NAS, with a valid certificate.

The interface settings are under Admin Panel -> Interface:

wg-easy interface: MTU, port and device

Note
The MTU is set in two places: once under Config -> Advanced for the client configs, and once under Interface for the server side. Both need the same value.

Finding the right MTU

Instead of guessing the MTU, I measured it.
WireGuard over IPv6 adds 80 bytes to every packet (40 for the IPv6 header, 8 for UDP, 32 for WireGuard itself). The usual default of 1420 assumes a clean 1500 byte path, and mobile networks often don’t give you that.

So I tested it:

  1. Start an iperf3 server on the NAS
  2. Set an MTU in both places in wg-easy and restart the interface
  3. Connect the iPhone through the tunnel and run a test against the NAS with an iperf3 app from the App Store
  4. Watch for dropouts, then try the next value

1280 was the most consistent one.
It’s also the smallest MTU IPv6 allows, so even with the WireGuard overhead on top, the packets leave plenty of headroom for smaller paths along the way.

Persistent Keepalive is set to 25 seconds, the interval the WireGuard documentation uses as its example for peers behind NAT. It keeps the connection alive when nothing is being sent.


Why not MyFRITZ!?

My first attempt was the FRITZ!Box’s own MyFRITZ! address instead of my own domain.
The FRITZ!Box has one (<id>.myfritz.net), and devices can get their own name below it, like truenas.<id>.myfritz.net.

Except the device name didn’t resolve:

for h in truenas.<id>.myfritz.net <id>.myfritz.net; do echo "== $h"; host -t AAAA $h 2>&1 | head -3; done
echo '== NAS GUA'; ip -6 addr show scope global | grep 'inet6 2a00'
== truenas.<id>.myfritz.net
Host truenas.<id>.myfritz.net not found: 3(NXDOMAIN)
== <id>.myfritz.net
<id>.myfritz.net has IPv6 address 2a00:10:22bd:2300::1
== NAS GUA
    inet6 2a00:10:22bd:2301:4a21:bff:fe62:1e72/64 scope global dynamic mngtmpaddr proto kernel_ra

The router’s name worked, but it points at the FRITZ!Box itself (...:2300::1), not at the NAS in ...:2301::/64. WireGuard would have been knocking on the wrong door.

It took me a while to figure out why the device name was missing:
the FRITZ!Box only creates truenas.<id>.myfritz.net while that device has an active MyFRITZ! sharing.
A normal port sharing, like the one for WireGuard, isn’t enough.

A MyFRITZ! sharing publishes a service on a device, with a scheme and a port.
To test it, I pointed one at the TrueNAS web UI:

MyFRITZ! sharing for the TrueNAS UI, with the MyFRITZ! address of the device

With the sharing enabled, truenas.<id>.myfritz.net resolved to the NAS.
After disabling it, the name went back to NXDOMAIN.

Note
DNS answers are cached for a while, so after enabling or disabling a sharing it can take a bit until the name resolves, or stops resolving.

So MyFRITZ! is a viable way too: create a MyFRITZ! sharing for the NAS under Internet -> Permit Access -> Port Sharing, and use <device>.<id>.myfritz.net as the host in wg-easy.
Just point that sharing at something you don’t mind being reachable from the internet, not at an admin panel like my test did…

I still went with my own domain:

  • MyFRITZ! ties the endpoint to AVM. If I ever replace the router, every client config needs a new host, while vpn.knng.de stays the same.
  • The name depends on a sharing that has nothing to do with WireGuard itself.
  • A small script and a domain I control is simply more homelab. :D

The catch: IPv6 only

The endpoint only has an IPv6 address, that’s the whole point of DS-Lite.
On a network without IPv6, like some hotel or guest WiFis, the tunnel simply can’t connect.

Cheers.