diff --git a/_posts/2026-06-07-Route64-IPv6-Setup.md b/_posts/2026-06-07-Route64-IPv6-Setup.md new file mode 100644 index 000000000..0b858bfe3 --- /dev/null +++ b/_posts/2026-06-07-Route64-IPv6-Setup.md @@ -0,0 +1,504 @@ +--- +layout: post +title: "IPv6 via Route64 Tunnelbroker on Ubuntu Server" +lang: "en" +author: "pljones" +heading: "Setting up IPv6 connectivity using Route64's Wireguard tunnelbroker" +--- + +> Copyright (C) 2026 Peter L Jones +> +> This document is licesed under the Creative Commons Attribution-ShareAlike 4.0 International License. +> To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/). + +This is a complete guide to setting up IPv6 connectivity using Route64's Wireguard tunnelbroker, routing it to your LAN, and making it work correctly for all devices including Windows and Android. + +The initial set up here was guided by Claude AI. Testing was done with the help of @ann0see and @softins. +@dtinth has "field tested" the guide to set up a similar Route64 tunnel for the Asia Directory. + + + +If you need help with anything, first read the [Troubleshooting](#troubleshooting) section. + +## Example Address Summary + +These addresses are placeholders. You will need to replace these with your own values. +It might be easier to take a copy of the document and do global replaces once you have the real values. + +| Address | Purpose | +|---------|---------| +| `10.0.0.123` | Your server's LAN IPv4 address | +| `10.0.0.1` | Your router's LAN IPv4 address | +| `198.51.100.154` | Your router's current external IPv4 address | +| `2001:db8:f06:af::1/64` | Route64 tunnel endpoint (their end) | +| `2001:db8:f06:af::2/64` | Your tunnel endpoint (wg0) | +| `2001:db8:1400:af00::1/56` | Your server's public IPv6 address (en0) | +| `2001:db8:1400:af01::1/64` | LAN gateway / radvd source (en0) | +| `2001:db8:1400:af01::/64` | LAN client prefix (autoconfigured via SLAAC) | +| `2001:db8:1400:af02::/64` … `77ff::/64` | Available for future use (Docker, DMZ, etc.) | + +## Prerequisites + +- Ubuntu 22.04+ server with a wired Ethernet connection to your router +- A domestic IPv4 router/modem (no IPv6 support required) +- Root/sudo access +- `systemd-networkd` as the network manager (check: `systemctl is-active systemd-networkd`) +- `nftables` for firewall (check: `sudo nft list ruleset`) +- `wg-quick` / `wireguard-tools` installed: `sudo apt install wireguard-tools` + +Your network topology will look like this: + +``` +Internet + ↓ IPv4 +DSL Router (IPv4 only) + ↓ Ethernet +Ubuntu Server (fs-peter) + ├── en0 — LAN interface + └── wg0 — Wireguard tunnel to Route64 +``` + +--- + +## 1. Register with Route64 + +> [!NOTE] +> If Route64 does not have a hub near you, you should find an alternative tunnelbroker supplier. +> Adding the tunnel introduces latency, so choose a provider with a hub close to you for best performance. + +1. Go to [https://route64.org](https://route64.org) and create an account. +2. Verify your email address. +3. Note your public IPv4 address (e.g. `198.51.100.154`) — you'll need it when creating the tunnel. + Check it with: `curl -s https://api4.ipify.org` + +--- + +## 2. Create a Tunnelbroker + +1. Log into Route64 and navigate to **Tunnelbroker → Add Tunnelbroker**. +2. Choose **Wireguard4** as the tunnel type. +3. Select the hub closest to you (e.g. `lon1.uk` for London). +4. Enter your public IPv4 address as **Your Endpoint**. +5. Submit — Route64 will allocate: + - A `/64` transport network (e.g. `2001:db8:f06:af::/64`) + - Their end: `::1` + - Your end: `::2` + - A `/56` routed prefix (e.g. `2001:db8:1400:af00::/56`) + - Their WAN endpoint IP and port + - A Wireguard public key + +6. Download or copy the Wireguard config from the tunnel detail page. + +--- + +## 3. Check Your Router/Modem + +The following presumes you currently have _no existing IPv6 usage on your LAN_ +and no external IPv6 connectivity (otherwise you would not need the tunnel). + +Before proceeding, check your router admin page for anything that could interfere: + +- **IPv6 settings** — if IPv6 is enabled on the router, **disable it**. The router sending + Router Advertisements (RAs) to the LAN will conflict with your server's RAs later. + Even with IPv6 "disabled", check it isn't sending RAs: + ```bash + sudo tcpdump -i en0 -v "icmp6 and dst ff02::1" 2>&1 | grep "router advertisement" | head -5 + ``` + If you see RAs from anything other than your server, disable IPv6 on the router. + +- **Firewall** — ensure the router passes through UDP on the Wireguard port + (check the Route64 tunnel detail page for the endpoint port, e.g. `20048`). + Most domestic routers do this automatically for outbound-initiated connections. + +--- + +## 4. Configure the Wireguard Tunnel + +Create the Wireguard config file. Replace values with those from your Route64 tunnel page: + +```bash +sudo nano /etc/wireguard/wg0.conf +``` + +```ini +[Interface] +PrivateKey = +Address = 2001:db8:f06:af::2/64 + +[Peer] +PublicKey = +AllowedIPs = ::/1, 8000::/1 +Endpoint = 185.121.24.12:20048 +PersistentKeepalive = 15 +``` + +Notes: +- `Address` is your `::2` transport address with `/64` +- `AllowedIPs = ::/1, 8000::/1` covers all IPv6 (split into two halves to avoid + conflicting with the default route) +- `PersistentKeepalive = 15` keeps the tunnel alive through NAT + +Enable and start: +```bash +sudo systemctl enable wg-quick@wg0 +sudo systemctl start wg-quick@wg0 +``` + +Verify the tunnel is up: +```bash +ip -6 addr show dev wg0 +ping6 2001:db8:f06:af::1 +``` + +You should get ping replies from Route64's end of the tunnel (`::1`) within ~10ms. + +--- + +## 5. Assign Your IPv6 Addresses + +You need two addresses from your `/56` pool: + +- `2001:db8:1400:af00::1/56` — your server's public-facing address +- `2001:db8:1400:af01::1/64` — your LAN gateway address (for routing to clients) + +Edit your netplan config (adjust filename to match yours): + +```bash +sudo nano /etc/netplan/01_en0.yaml +``` + +```yaml +network: + version: 2 + renderer: networkd + ethernets: + en0: + ipv6-privacy: false + addresses: + - 10.0.0.123/24 + - 2001:db8:1400:af00::1/56 + - 2001:db8:1400:af01::1/64 + nameservers: + search: [lan] + addresses: [10.0.0.1] + routes: + - to: default + via: 10.0.0.1 +``` + +Apply: +```bash +sudo netplan apply +``` + +Verify: +```bash +ip -6 addr show dev en0 +``` + +You should see both `af00::1` and `af01::1` addresses. + +--- + +## 6. Add the Routing Config for wg0 + +Create a systemd-networkd config for the wg0 interface to make routes persistent +(wg-quick sets these but they can be lost on systemd-networkd restarts): + +```bash +sudo nano /etc/systemd/network/wg0.network +``` + +```ini +[Match] +Name=wg0 + +[Network] +Address=2001:db8:f06:af::2/64 + +[Route] +Destination=2001:db8:1400:af00::/56 +Metric=100 + +[Route] +Destination=::/1 +Scope=global +Metric=512 +PreferredSource=2001:db8:1400:af00::1 + +[Route] +Destination=8000::/1 +Scope=global +Metric=512 +PreferredSource=2001:db8:1400:af00::1 +``` + +The `PreferredSource` setting is critical — without it, reply packets from services on the +server use the tunnel transport address (`2001:db8:f06:af::2`) rather than your public +address (`2001:db8:1400:af00::1`). Remote clients will discard replies that come from an +unexpected source address. + +```bash +sudo systemctl restart systemd-networkd +``` + +Verify routes: +```bash +ip -6 route show | grep -E "::/1|8000::|af00" +``` + +You should see all three routes via `wg0`. + +--- + +## 7. Suppress SLAAC Address on the Server + +The server will autoconfigure an EUI-64 address from the RA it sends to the LAN. +This causes source address selection problems (services reply from the wrong address). +Suppress it with a systemd-networkd drop-in: + +```bash +sudo mkdir -p /etc/systemd/network/10-netplan-en0.network.d +sudo tee /etc/systemd/network/10-netplan-en0.network.d/no-slaac.conf << 'EOF' +[Network] +IPv6AcceptRA=no + +[IPv6AcceptRA] +UseAutonomousPrefix=no +EOF +``` + +```bash +sudo systemctl restart systemd-networkd +``` + +Verify only static addresses remain: +```bash +ip -6 addr show dev en0 +``` + +You should see only `af00::1`, `af01::1`, and the `fe80::` link-local — no dynamic addresses. + +--- + +## 8. Enable IPv6 Forwarding + +```bash +sudo tee /etc/sysctl.d/99-ipv6-forward.conf << 'EOF' +net.ipv6.conf.all.forwarding=1 +net.ipv6.conf.en0.forwarding=1 +EOF + +sudo sysctl -w net.ipv6.conf.all.forwarding=1 +``` + +--- + +## 9. Configure nftables + +Add IPv6 firewall rules. Adjust the sets and ports to match your services. +It is probably worth working out what IPv4 ports you have open and start with the same set for IPv6. +You may also want to add counters. These can be useful for debugging and monitoring. +This example includes Jamulus as a template for UDP services and a number of TCP services. + +```bash +# Create sets +sudo nft add set ip6 filter TcpOpen '{ type inet_service; elements = { 80, 443 } }' +sudo nft add set ip6 filter TcpLocal '{ type inet_service; flags interval; elements = { 22, 139, 445, 1714-1764, 5357-5358, 8022 } }' +sudo nft add set ip6 filter JamulusServers '{ type inet_service; elements = { 22124 } }' + +# Create INPUT chain with drop policy +sudo nft add chain ip6 filter INPUT '{ type filter hook input priority filter; policy drop; }' +sudo nft add rule ip6 filter INPUT ct state established,related accept +sudo nft add rule ip6 filter INPUT ct state invalid drop +sudo nft add rule ip6 filter INPUT iif lo accept +sudo nft add rule ip6 filter INPUT meta l4proto ipv6-icmp accept +sudo nft add rule ip6 filter INPUT tcp dport @TcpOpen accept +sudo nft add rule ip6 filter INPUT udp dport @JamulusServers accept +``` + +Tighten the FORWARD chain (get existing rule handles first): +```bash +sudo nft -a list chain ip6 filter FORWARD +# Note the handle numbers of the existing Docker jump rules, then: +sudo nft delete rule ip6 filter FORWARD handle +sudo nft delete rule ip6 filter FORWARD handle + +sudo nft add rule ip6 filter FORWARD ct state established,related accept +sudo nft add rule ip6 filter FORWARD ct state invalid drop +sudo nft add rule ip6 filter FORWARD iifname "en0" oifname "wg0" accept +sudo nft add rule ip6 filter FORWARD iifname "wg0" oifname "en0" accept +sudo nft add rule ip6 filter FORWARD counter jump DOCKER-USER +sudo nft add rule ip6 filter FORWARD counter jump DOCKER-FORWARD +sudo nft chain ip6 filter FORWARD '{ policy drop; }' +``` + +**Important:** Use `iifname`/`oifname` (string match) rather than `iif`/`oif` (index match) +for wg0 rules — the interface must exist at rule load time for `iif`, but wg0 may not be +up when nftables starts at boot. + +Save the ruleset: +```bash +sudo nft list ruleset | sudo tee /etc/nftables.conf +sudo systemctl enable nftables +``` +You probably want to get familiar with the content of `/etc/nftables.conf`. +I find it vastly easier to edit the file and reload the service than to use `nft` commands for ongoing maintenance. +In fact, I keep a copy under version control then copy it over the original when I commit. +So long as I avoid completely locking myself out with a bad ruleset, this works well for me. +(Remember to check the ruleset with `sudo nft -c list ruleset` before applying, to catch syntax errors and +spot severe breakage.) + +--- + +## 10. Set Up radvd for LAN Clients + +Install radvd to advertise your IPv6 prefix to LAN clients via SLAAC: + +```bash +sudo apt install radvd +sudo nano /etc/radvd.conf +``` + +``` +interface en0 { + AdvSendAdvert on; + AdvManagedFlag off; + AdvOtherConfigFlag off; + MinRtrAdvInterval 5; + MaxRtrAdvInterval 10; + AdvDefaultLifetime 1800; + prefix 2001:db8:1400:af01::/64 { + AdvOnLink on; + AdvAutonomous on; + AdvRouterAddr on; + }; + RDNSS 2606:4700:4700::1111 2001:4860:4860::8888 { + AdvRDNSSLifetime 600; + }; +}; +``` + +Notes: +- `AdvDefaultLifetime 1800` — critical for Android; too short a lifetime causes Android + to not set a default IPv6 route +- `MinRtrAdvInterval 5` / `MaxRtrAdvInterval 10` — send RAs frequently enough that + clients don't lose their default route between advertisements +- `RDNSS` — provides IPv6 DNS servers; without this Android won't use IPv6 for name resolution + +```bash +sudo systemctl enable radvd +sudo systemctl start radvd +``` + +--- + +## 11. Test External Connectivity + +**Test your server's public address:** + +```bash +ping6 2001:db8:1400:af00::1 # from the server itself +``` + +Then use the HE.net looking glass at [https://lg.he.net/](https://lg.he.net/): +- Select a node (e.g. London) +- Choose **ping** +- Enter `2001:db8:1400:af00::1` + +You should get 0% packet loss replies. + +**Test LAN client routing (Windows):** + +```cmd +ping -6 ipv6.google.com +``` + +Should reply with ~10ms to a Google IPv6 address. + +**Test LAN client routing (Android):** + +In a network tool app, check that the WiFi connection shows: +- An address starting `2001:db8:1400:af01:` +- A route `::/0 -> fe80:: wlan0` + +Then ping `2606:4700:4700::1111` — should reply successfully. + +**Test via test-ipv6.com:** + +Visit [https://test-ipv6.com](https://test-ipv6.com) from any LAN client. +It should show your IPv6 address as `2001:db8:1400:af01:...` and score 10/10. + +--- + +## 12. Verify Everything Survives Reboot + +```bash +sudo reboot +``` + +After reboot check: + +```bash +# Tunnel up +ping6 2001:db8:f06:af::1 + +# Addresses present +ip -6 addr show dev en0 | grep -E "af00|af01" + +# Routes present +ip -6 route show | grep -E "::/1|8000::|af00" + +# radvd running +systemctl is-active radvd + +# nftables loaded +sudo nft list chain ip6 filter INPUT | grep policy +``` + +All should be healthy without any manual intervention. + +--- + +## Troubleshooting + +### Useful sites +- Route64 support: [https://route64.org/support](https://route64.org/support) +- HE.net looking glass: [https://lg.he.net/](https://lg.he.net) +- IPv6 test: [https://test-ipv6.com](https://test-ipv6.com) + +### BGP not needed +Route64 uses static routing — your `/56` is routed to your tunnel IP automatically. +Do not install FRR or configure BGP. + +### Tunnel up but no external IPv6 +Check the default routes are present: +```bash +ip -6 route show | grep -E "::/1|8000::" +``` +If missing, restart wg-quick: +```bash +sudo systemctl restart wg-quick@wg0 +``` + +### LAN clients have IPv6 address but no internet +Check IPv6 forwarding is enabled: +```bash +sysctl net.ipv6.conf.all.forwarding +``` +Check the default routes are present (see above). +Check the FORWARD chain allows `en0 → wg0`. + +### Android not getting default IPv6 route +- Ensure `AdvDefaultLifetime` is at least 1800 in radvd.conf +- Ensure the router/modem is NOT sending Router Advertisements + (check with `sudo tcpdump -i en0 -v "icmp6 and dst ff02::1"`) +- Toggle WiFi off/on on the phone after changing radvd config + +### Services replying from wrong IPv6 address +The server is autoconfiguring a SLAAC address and using it as the source. +Apply the no-slaac drop-in (Step 7) and verify only static addresses remain. + +### nftables fails to load at boot with "Interface does not exist" +Use `iifname`/`oifname` instead of `iif`/`oif` for wg0 rules — the string +form doesn't require the interface to exist at load time.