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.
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:
- two WG interfaces;
- one peer for wg1;
- one peer for each road warrior device, for wg0.
[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.