Nested WireGuard tunnels to my home

2021.12.07

My setup to safely (I hope) access my home network via WireGuard VPN.

TL;DR: WG from my home to a VPS, to expose another WG endpoint bypassing CGNAT.

I love the simplicity of WireGuard. It feels to me like fresh air when compared to the disorienting huge amount of OpenVPN options, or to the weapons-grade accuracy needed to set up an IKE2/IPsec tunnel. Maybe one day I’ll try iked(8) and I’ll love it, but for now I think setting up IPsec for a road warrior device is a nighmare. I’d still like to know why a W1nd0w$ PC wasn’t liking the IKE chiper suites I was offering him a couple years ago.

I simply want a tunnel between my PC and my home network, using modern cryptography. The problem is my ISP hiding my home behind CGNAT. For my road warrior PC to be able to create the tunnel, the other WG peer must be reacheable.

First of all, since I had a nice MikroTik RB750Gr3 doing nothing, it was chosen to serve my home. RouterOS 6 doesn’t support WG. At the time of writing, ROS 7.1 beta with WG support is in testing and not very stable, but I have no choice so that’s what I’m using.

Since this MT is not directly facing WAN, it acts as a router-on-a-stick. Routing and firewall rules set on my main firewall that make all this work are beyond the scope of this post, and more related to my LAN.

MikroTik RB750Gr3

Now I need a VPS to bypass CGNAT. The first way I considered was this:

    +--------+       +--------+       +--------+
    |        |       |        |       |        |
    |       ===========      ===========       |
    |       ====wg0====      ====wg1====       |
    |        |       |        |       |        |
    +--------+       +--------+       +--------+
        PC              VPS               MT

PC creates a tunnel to VPS. VPS routes packets destinated to MT into tunnel wg1. This should work, but packets are not encrypted when they are routed between wg0 and wg1.

What if I don’t trust the VPS? The next diagram shows what I ended up with.

    +--------+       +--------+       +--------+
    |        |       ===================       |
    |       ============================       |
    |       ====wg0=====================       |
    |        |       ============wg1====       |
    +--------+       +--------+       +--------+
        PC              VPS               MT

Here wg1 is used to expose another WG endpoint (on another port) of MT. When PC creates wg0, VPS doesn’t get to see the end of the tunnel, it can only see encrypted WG traffic.

To implement this, starting from MT, we have to declare:

[admin@MikroTik_hEX] > /interface/wireguard/print
Flags: X - disabled; R - running
 0  R name="wg0" mtu=1420 listen-port=60000 private-key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
      public-key="njg+gAB5Zt/NJl2U8HUA6+vTJvjOIdSLhpbw7SxpADA="

 1  R name="wg1" mtu=1420 listen-port=60001 private-key="YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
      public-key="XKm72iBBljwo3l2YAIvzO4RnbR5HitAh9UoelWjBQ3U="

[admin@MikroTik_hEX] > /interface/wireguard/peers/print
Columns: INTERFACE, PUBLIC-KEY, ENDPOINT-ADDRESS, ENDPOINT-PORT, ALLOWED-ADDRESS, PERSISTENT-KEEPALIVE
# INTERFACE  PUBLIC-KEY                                    ENDPOINT-ADDRESS  ENDPOINT-PORT  ALLOWED-ADDRESS  PERSISTENT-KEEPALIVE
0 wg1        q9ii3/DoyZgqYkzEiTdPM9FfraDtUJdKP1KItmh6c1U=  X.X.X.X                   60100  172.16.1.0/24    25s
;;; RW1
1 wg0        TvfgJvb9Zn37tJNxWU9Lyf+/TWQQy3rFZq4xBILCRwI=                                0  172.16.0.1/32
;;; RW2
2 wg0        AFSycdpkMPYm9Fj32MB4fjiOpP306omN06WMtzo0s3g=                                0  172.16.0.2/32

[admin@MikroTik_hEX] > /ip/address/print where interface=wg1
Columns: ADDRESS, NETWORK, INTERFACE
# ADDRESS          NETWORK     INTERFACE
1 172.16.1.254/24  172.16.1.0  wg1

I redacted portions of the config, but it’s quite clear. X.X.X.X is the public IP of VPS. MT initiates the tunnel to X.X.X.X:60100. Note that persistent-keepalive=25s is useful to persist the state that lets us traverse CGNAT. Otherwise, after some time of inactivity, firewalls on the path would drop the state, and the tunnel could not be recreated trying to send a packet from VPS to MT.

On the VPS: Alpine Linux, wireguard-tools, ifupdown-ng-wireguard.

# cat /etc/wireguard/wg1.conf
[Interface]
ListenPort = 60100
PrivateKey = ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ

# MT

[Peer]
PublicKey = XKm72iBBljwo3l2YAIvzO4RnbR5HitAh9UoelWjBQ3U=
AllowedIPs = 172.16.1.254/32

Note that there’s no wg0.conf since VPS is not aware of wg0.

With this config in place and the interface activated, MT should initiate the tunnel almost instantly, but for road warriors to be able to reach wg0 we have to forward packets into wg1.

On VPS, right after interface wg1 goes up, we tell Linux to forward packets. We also define a few iptables(8) dstnat rules to let the world access wg0 port.

# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
	hostname <redacted>

iface eth0 inet6 static
	address <redacted>
	gateway fe80::1
	pre-up echo 0 > /proc/sys/net/ipv6/conf/eth0/accept_ra

auto wg1
iface wg1 inet static
	address 172.16.1.100/24
	requires eth0
	use wireguard

	post-up echo 1 > /proc/sys/net/ipv4/ip_forward
	post-up echo 1 > /proc/sys/net/ipv4/conf/all/proxy_arp
	post-up iptables -t nat -A PREROUTING -p udp -i eth0 --dport 60000 -j DNAT --to-destination 172.16.1.254
	post-up iptables -t nat -A POSTROUTING -p udp -d 172.16.1.254 --dport 60000 -j MASQUERADE

	post-down iptables -t nat -D PREROUTING -p udp -i eth0 --dport 60000 -j DNAT --to-destination 1172.16.1.254
	post-down iptables -t nat -D POSTROUTING -p udp -d 172.16.1.254 --dport 60000 -j MASQUERADE
	post-down echo 0 > /proc/sys/net/ipv4/ip_forward
	post-down echo 0 > /proc/sys/net/ipv4/conf/all/proxy_arp

Now from PC our favourite WG client might be able to complete the tunnel wg0.