Automatic GUA IPv6 address for Wireguard client

Just a little guide, way to automatically assign GUA IPv6 addresses to road warrior Wireguard clients.

What you need:
OpenWRT router
ISP that gives you more than a single /64 GUA IPv6 subnet.

What you get:
Remote Wireguard clients will get an automatic assignment of GUA IPv6 address.
No need to adjust Wireguard or OpenWRT configuration if ISP changes your IPv6 prefix.
Privacy IPv6 address for some clients.

Some technical details and caveats:
GUA IPv6 will get assigned using using SLAAC, so you’ll need a separate /64 network and a separate interface for every Wireguard client in order for multicast to work. Basically a P2P link for every Wireguard device.
Adjust ip6hint: unique for every Wireguard client and according to the size of IPv6 prefix you get from your ISP.
IPv4 has to be static, DHCP doesn’t work over L3 tunnels like Wireguard, not a big deal since we are behind NAT in 99% of cases anyway.
Some Android clients assign random privacy IPv6, other won’t and will use LL address device identification part.
Android doesn’t seem to get DNS from RDNSS, so need so specify it like DNS=fe80::1. Systemd-networkd on Linux gets it just fine.
No idea if it works for Windows, macOS, iOS etc. clients, don’t care a bit. Feel free to test yourself.

OpenWRT configuration:
/etc/config/network

config interface 'myphone'
	option proto 'wireguard'
	option private_key '[SERVER PRIVATE KEY]'
	option listen_port '12305'
	list addresses '192.168.5.1/24'
	list addresses 'fe80::1/64'
	option ip6assign '64'
	option ip6hint '5'

config wireguard_myphone
	option public_key '[CLIENT PUBLIC KEY]'
	list allowed_ips '0.0.0.0/0'
	list allowed_ips '::/0'

/etc/config/firewall

config zone
	option name 'lan'
	….
	list network 'lan'
	list network 'myphone'
config rule
	option name 'Wireguard myphone'
	option family 'any'
	option proto 'udp'
	option src 'wan'
	option dest_port '12305'
	option target 'ACCEPT'

/etc/config/dhcp

config dhcp 'myphone'
        option interface 'myphone'
        option dhcpv4 'disabled'
        option dhcpv6 'disabled'
        option ra 'server'
        option ra_slaac '1'
        list ra_flags 'none'

Client Wireguard configuration for Android

[Interface]
Address = 192.168.5.2/32
Address = fe80::2/64
DNS = 192.168.5.2
DNS = fe80::1
PrivateKey = [CLIENT PRIVATE KEY]

[Peer]
AllowedIPs = 0.0.0.0/0
AllowedIPs = ::/0
Endpoint = [SERVER HOSTNAME]:12305
PersistentKeepalive = 25
PublicKey = [SERVER PUBLIC KEY]

Client Wireguard configuration for Linux systemd-networkd
/etc/systemd/network/myserver.netdev

[NetDev]
Name = myserver
Kind = wireguard

[WireGuard]
PrivateKey = [CLIENT PRIVATE KEY]

[WireGuardPeer]
PublicKey = [SERVER PUBLIC KEY]
AllowedIPs = 0.0.0.0/0
AllowedIPs = ::/0
Endpoint = [SERVER HOSTNAME]:12305
PersistentKeepalive = 25

/etc/systemd/network/myserver.network

[Match]
Name=myserver

[Network]
Address=192.168.5.2/32
Address = fe80::2/64
DNS = 192.168.5.1
5 Likes

Follow up, recent version of systemd-network require both IPv6LL adress and MULTICAST flag to be set on Wireguard interface in order to receive IPv6 route advertisements.

/etc/systemd/network/myserver.network

[Match]
Name=myserver

[Link]
Multicast=yes

[Network]
Address=192.168.5.2/32
Address = fe80::2/64
DNS = 192.168.5.1
2 Likes

Windows seems to completely stop any effort to get any IP Adresses with Router Solicitation because it recognizes the LLA as "manually added" and assumes its valid.

However, I have a hacky, potentially insecure solution which enables even Windows to get a GUA and ULA by making the SLAAC calculation with a PowerShell script, appending it to the global and local prefix afterwards and then registering the generated IPs with the VPN interface.

Disclaimer: Running PostUp scripts on Windows happens at SYSTEM-Level and is potentially dangerous, thus is disabled by default. Use at your own risk!

First, you need to enable script execution by setting a DWORD at HKLM\Software\WireGuard\DangerousScriptExecution:

reg add HKLM\Software\WireGuard /v DangerousScriptExecution /t REG_DWORD /d 1 /f

Then you need a way to see your current prefix; I use DynDNS for that. We will use the generated DNS-Entry to reconstruct our current prefix.

The ULA Prefix can just be hardcoded into the script since we know that one won't change.

Afterwards create a new PowerShell script with these lines, if you have a /56 prefix you can leave it as is, if not, adjust the value of .trim() -replace ".{20}$" accordingly:

$Prefix = (Resolve-DnsName vpn.example.com | Where Type -eq AAAA | Format-Table IPAddress -HideTableHeaders | Out-String).trim() -replace ".{20}$"
# 1. Get your MAC-Address
$mac = (Get-WmiObject win32_networkadapterconfiguration -Filter "IPEnabled='True'" | select macaddress | format-table -HideTableHeaders | Out-String).trim().Replace(":", "")
# 2. insert "ff:fe" 
$mac2 = $mac.Substring(0,$_.length+6) + "FFFE" + $mac.Substring(6,$_.length+6)
# 3. get Hex value of first octet
$mac3 = "0x" + $mac2.Substring(0,2)
#4.+5. Binary conversion, flip bit at index 6
$mac3_5 = $mac3 -bxor 0x2
#6. Convert back to Hex
$mac6 = [Convert]::ToString($mac3_5,16)
#7. Replace first octet
$mac7 = $mac2.Remove(0,2).insert(0,$mac6)
#8. Restore IPv6-Notation
$mac8 = $mac7 -replace '....(?!$)','$0:'
#9. Prepend Prefix
$GUA = ($Prefix + "bc:" + $mac8)
$ULA = ("fdf3:1234:5678:9abc:" + $mac8)
# Add calculated IPs to VPN Interface
netsh interface ipv6 add address your_vpn $GUA
netsh interface ipv6 add address your_vpn $ULA

Lastly add the following line to the [Interface] block of your client config:

PostUp = powershell.exe -noprofile -executionpolicy bypass -file "C:\path\to\your\script.ps1"

You can check your config afterwards and try to ping google.com and you will see that Windows will use IPv6 as default now:

Unbekannter Adapter your_vpn:

   Verbindungsspezifisches DNS-Suffix:
   IPv6-Adresse. . . . . . . . . . . : 2xxx:xxxx:xxxx:xxbc:6649:7dff:fecf:2051
   IPv6-Adresse. . . . . . . . . . . : fdf3:1234:5678:9abc:6649:7dff:fecf:2051
   Verbindungslokale IPv6-Adresse  . : fe80::2%76
   IPv4-Adresse  . . . . . . . . . . : 10.2.0.2
   Subnetzmaske  . . . . . . . . . . : 255.255.255.255
   Standardgateway . . . . . . . . . : ::
                                       0.0.0.0
>ping google.com

Ping wird ausgeführt für google.com [2a00:1450:4001:828::200e] mit 32 Bytes Daten:

Let me quote Wireguard author Jason Donenfeld: "WireGuard does not work with LLv6 by design."

Quite some people disagree though, in fact Wireguard does work just fine when configured as P2P link, good enough to handle ff02::2 link-local all-nodes multicast address.

See intereresting discussion there https://github.com/systemd/systemd/issues/17380

Basically systemd-networkd supports SLAAC over Wireguard notwithstanding Jasons position.
And Android supports SLAAC over any interface able to move packets, because of ...reasons. Which is fine by me.

Wireguard on Windows could easily support receiving router advertisements and SLAAC, but good luck trying to persuade authors.

2 Likes