Our net connection at home is not great: amongst its several misfeatures is a lack of IPv6. Yesterday I (at last!) got around to setting up a wireguard IPv6 VPN tunnel between my workstation and my Mythic Beasts virtual private server.
There were a few, um, learning opportunities.
incorrect ideas
I made a couple of wrong assumptions about the routing setup for my
VPS. I thought my /64 was routed to some kind of virtual ethernet,
with the gateway on $prefix::0
and my VPS on $prefix::1
.
Based on this assumption, my plan was to allocate $prefix::2
to my
workstation on the far end of the tunnel, and use some link-local
trickery to allow it to appear on the Mythic Beasts network.
I reckoned this could be done fairly simply on Linux by turning on IPv6 Neighbour Discovery Protocol proxying, and by adding a neighbour table entry for my workstation:
sysctl net.ipv6.conf.eth0.proxy_ndp=1
ip neigh add proxy $prefix::2 dev eth0
However this setup never worked.
gateway gone
At some point during my flailing experiments, I noticed that my
VPS had lost IPv6 connectivity. I bodged it by manually configuring a
default route via $prefix::0
until I could fix it properly.
Eventually I worked out the problem: I had turned on IP forwarding,
sysctl net.ipv6.conf.all.forwarding=1
This has the side-effect that Linux stops listening for router advertisements so it loses connectivity. To prevent this, I needed:
sysctl net.ipv6.conf.eth0.accept_ra=2
The default is accept_ra=1
which means, accept router advertisements
only if this machine is not being a router, i.e. if this machine does
not have IP forwarding turned on.
I needed accept_ra=2
which means, always accepr router
advertisements. But it must be set on a specific interface: setting
net.ipv6.conf.all.accept_ra=2
does not, in fact, unconditionally
accept router advertisements on “all” interfaces.
rectified routing
Eventually I worked out that my assumptions were incorrect.
Although a /64 had been reserved for me, only the bottom /127 was routed to my VPS.
And although $prefix::0
worked as a gateway address, the supported
setup is to listen for router advertisements which give my VPS two
routers on link-local adddresses.
I asked Mythic Beasts to route the whole /64 to me, but my VPS lost
connectivity because of my earlier bodged default route. (No use for a
server to send packets via $prefix::0
if $prefix::0
is one of the
server’s addresses!) After I fixed the accept_ra=2
setting, the
routing change worked as expected.
wireguard woes
The tunnel setup was not smooth sailing either.
For a while I was deeply confused about which IP addresses in wireguard’s configuration are inside the tunnel and which are outside. I was referring to the wg(8) man page , which doesn’t mention it, nor does the quick start guide. I should have read the wireguard homepage! I plan to submit some improvements to the wg(8) man page, to fix this omission, and to improve the formatting so it is less of a wall of text and easier to see which options are explained where.
Another hitch happened when I first ran ifup wg0
. Amongst other
errors, it complained fopen: File not found
. Er, which file? and who
is trying to fopen
it? Eventually I worked out wireguard was failing
to open its private key file. I have submitted a patch to wireguard to
improve its error reporting (perror(3)
should be avoided, use
err(3)
instead) so it is easier to fix this mistake in the future.
There’s one aspect of wireguard’s configuration file that I dislike: it includes secret keys inline in the clear. I strongly prefer to keep each secret in its own file separate from any non-secret configuration. I can work around this issue by generating the wireguard config from a jinja template, but that has the disadvantage that I need to decrypt and redeploy the secrets for non-secret config changes. I’ve submitted another patch to wireguard so that its config file can load secret keys from separate files, in the same way as its command line.
correct configuration
What I ended up with (eventually) is pleasingly neat. All the routing happens at the IP layer without ugly trickery.
On my VPS I have an entry in /etc/network/interfaces.d/wg0
that
starts like:
auto wg0
iface wg0 inet6 manual
Weirdly, this wireguard endpoint doesn’t need an address of its own.
The manual
method suppresses all the IPv6 autoconfiguration magic.
Create the wireguard interface:
pre-up ip link add wg0 type wireguard
pre-up wg set wg0 private-key /etc/wireguard/private
Give it a fixed endpoint; 30567 is 0x7767 which is ASCII ‘w’ and ‘g’:
pre-up wg set wg0 listen-port 30567
Tell it that my workstation exists and which IP address it is allowed to use inside the tunnel:
pre-up wg set wg0 peer $(cat /etc/wireguard/basalt.pub) \
allowed-ips $prefix::2
After the interface is up, set the hard-won sysctls (they could
probably be static settings in /etc/sysctl.conf
but it makes sense
to have all the tunnel-related stuff in one place):
post-up sysctl net.ipv6.conf.eth0.accept_ra=2
post-up sysctl net.ipv6.conf.all.forwarding=1
And finally, route packets to my workstation over its tunnel:
post-up ip -6 route add $prefix::2 dev wg0
The teardown process is slightly simpler:
pre-down ip -6 route del $prefix::2 dev wg0
pre-down sysctl net.ipv6.conf.all.forwarding=0
pre-down sysctl net.ipv6.conf.eth0.accept_ra=1
post-down ip link del wg0
My workstation’s version of /etc/network/interfaces.d/wg0
has more
addresses to care about. It has a static IPv6 address and router:
auto wg0
iface wg0 inet6 static
gateway $prefix::1
address $prefix::2
The wireguard interface is created in the same way:
pre-up ip link add wg0 type wireguard
pre-up wg set wg0 private-key /etc/wireguard/private
Tell it where to send encrypted packets, and that the whole IPv6 internet is on the other end of the tunnel:
pre-up wg set wg0 peer $(cat /etc/wireguard/shale.pub) \
endpoint 93.93.130.7:30567 \
allowed-ips ::/0
The teardown process is simply:
post-down ip link del wg0
another author
Last year Chris Siebenmann wrote about his wireguard IPv6 tunnel. Since his server network works the way I incorrectly thought mine did, he is using NDP proxying. His considerations on link-local IPv6 addresses on wireguard interfaces are useful; after some vague thinking I decided not to have link-local addresses in my tunnel, since my setup doesn’t depend on SLAAC nor NDP at either end.