.@ Tony Finch – blog

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 \
                           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.