Using the VPN as the default gateway
WireGuard can also be setup to route all traffic through the VPN, and not just specific remote networks. There could be many reasons for this, but mostly they are related to privacy.
Here we will assume a scenario where the local network is considered untrusted, and we want to leak as little information as possible about our behavior on the Internet. This could be the case of an airport, or a coffee shop, a conference, a hotel, or any other public network.
public untrusted ┌── wg0 10.90.90.2/24 10.90.90.1/24 network/internet │ VPN network wg0│ xxxxxx ┌──────┴─┐ ┌─┴──┐ xx xxxxx ──────┤ VPN gw │ │ ├─wlan0 xx xx eth0 └────────┘ │ │ xx x │ │ xxx xxx └────┘ xxxxxx Laptop
For this to work best, we need a system we can reach on the internet and that we control. Most commonly this can be a simple small VM in a public cloud, but a home network also works. Here we will assume it’s a brand new system that will be configured from scratch for this very specific purpose.
Let’s start the configuration by installing WireGuard and generating the keys.
On the client:
$ sudo apt install wireguard $ umask 077 $ wg genkey > wg0.key $ wg pubkey < wg0.key > wg0.pub $ sudo mv wg0.key wg0.pub /etc/wireguard
And on the gateway server:
$ sudo apt install wireguard $ umask 077 $ wg genkey > gateway0.key $ wg pubkey < gateway0.key > gateway0.pub $ sudo mv gateway0.key gateway0.pub /etc/wireguard
On the client, we will create
[Interface] PostUp = wg set %i private-key /etc/wireguard/wg0.key ListenPort = 51000 Address = 10.90.90.1/24 [Peer] PublicKey = <contents of gateway0.pub> Endpoint = <public IP of gateway server> AllowedIPs = 0.0.0.0/0
Key points here:
- We selected the
10.90.90.1/24IP address for the WireGuard interface. This can be any private IP address, as long as it doesn’t conflict with the network you are on, so double check that. If it needs changing, don’t forget to also change the IP for the WireGuard interface on the gateway server.
0.0.0.0/0, which means “all IPv4 addresses”.
- We are using
PostUpto load the private key instead of specifying it directly in the configuration file, so we don’t have to set the permissions on the config file to
The counterpart configuration on the gateway server is
/etc/wireguard/gateway0.conf with these contents:
[Interface] PostUp = wg set %i private-key /etc/wireguard/%i.key Address = 10.90.90.2/24 ListenPort = 51000 [Peer] PublicKey = <contents of wg0.pub> AllowedIPs = 10.90.90.1/32
Since we don’t know from where this remote peer will be connecting, there is no
Endpoint setting for it, and the expectation is that the peer will be the one initiating the VPN.
This finishes the WireGuard configuration on both ends, but there is one extra step we need to take on the gateway server.
Routing and masquerading
The WireGuard configuration that we did so far is enough to send the traffic from the client in the untrusted network, to the gateway server. But what about from there on? There are two extra configs we need to make on the gateway server:
- Masquerade (or apply source NAT rules) the traffic from
- Enable IPv4 forwarding so our gateway server acts as a router.
To enable routing, create
/etc/sysctl.d/70-wireguard-routing.conf with this content:
net.ipv4.ip_forward = 1
$ sudo sysctl -p /etc/sysctl.d/70-wireguard-routing.conf -w
To masquerade the traffic from the VPN, one simple rule is needed:
$ sudo iptables -t nat -A POSTROUTING -s 10.90.90.0/24 -o eth0 -j MASQUERADE
eth0 with the name of the network interface on the gateway server, if it’s different.
To have this rule persist across reboots, you can add it to
/etc/rc.local (create the file if it doesn’t exist and make it executable):
#!/bin/sh iptables -t nat -A POSTROUTING -s 10.90.90.0/24 -o eth0 -j MASQUERADE
This completes the gateway server configuration.
Let’s bring up the WireGuard interfaces on both peers.
On the gateway server:
$ sudo wg-quick up gateway0 [#] ip link add gateway0 type wireguard [#] wg setconf gateway0 /dev/fd/63 [#] ip -4 address add 10.90.90.2/24 dev gateway0 [#] ip link set mtu 1378 up dev gateway0 [#] wg set gateway0 private-key /etc/wireguard/gateway0.key
And on the client:
$ sudo wg-quick up wg0 [#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip -4 address add 10.90.90.1/24 dev wg0 [#] ip link set mtu 1420 up dev wg0 [#] wg set wg0 fwmark 51820 [#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820 [#] ip -4 rule add not fwmark 51820 table 51820 [#] ip -4 rule add table main suppress_prefixlength 0 [#] sysctl -q net.ipv4.conf.all.src_valid_mark=1 [#] nft -f /dev/fd/63 [#] wg set wg0 private-key /etc/wireguard/wg0.key
From the client you should now be able to verify that your traffic reaching out to the internet is going through the gateway server via the WireGuard VPN. For example:
$ mtr -r 126.96.36.199 Start: 2022-09-01T12:42:59+0000 HOST: laptop.lan Loss% Snt Last Avg Best Wrst StDev 1.|-- 10.90.90.2 0.0% 10 184.9 185.5 184.9 186.9 0.6 2.|-- 10.48.128.1 0.0% 10 185.6 185.8 185.2 188.3 0.9 (...) 7.|-- one.one.one.one 0.0% 10 186.2 186.3 185.9 186.6 0.2
Above, hop 1 is the
gateway0 interface on the gateway server, then
10.48.128.1 is the default gateway for that server, then come some in between hops, and the final hit is the target.
If you just look at the output of
ip route, however, it’s not immediately obvious that the WireGuard VPN is the default gateway:
$ ip route default via 192.168.122.1 dev enp1s0 proto dhcp src 192.168.122.160 metric 100 10.90.90.0/24 dev wg0 proto kernel scope link src 10.90.90.1 192.168.122.0/24 dev enp1s0 proto kernel scope link src 192.168.122.160 metric 100 192.168.122.1 dev enp1s0 proto dhcp scope link src 192.168.122.160 metric 100
That’s because WireGuard is using fwmarks and policy routing. WireGuard cannot simply set the
wg0 interface as the default gateway: that traffic needs to reach the specified endpoint on port 51000/UDP outside of the VPN tunnel.
If you want to dive deeper into how this works, check
ip rule list,
ip route list table 51820, and consult the documentation on “Linux Policy Routing”.
The traffic is now being routed through the VPN to the gateway server that you control, and from there on to the Internet at large. The local network you are in cannot see the contents of that traffic, because it’s encrypted. But you are still leaking information about the sites you access via DNS.
When the laptop got its IP address in the local (untrusted) network it is sitting in, it likely also got a pair of IPs for DNS servers to use. These might be servers from that local network, or other DNS servers from the internet like
188.8.131.52. When you access an internet site, a DNS query will be sent to those servers to discover their IP addresses. Sure, that traffic goes over the VPN, but at some point it exits the VPN, and then reaches those servers, which will then know what you are trying to access.
There are DNS leak detectors out there, and if you want a quick check you can try out https://dnsleaktest.com. It will tell you which DNS servers your connection is using, and it’s up to you if you trust them or not. You might be surprised that, even if you are in a conference network for example, using a default gateway VPN like the one described here, you are still using the DNS servers from the conference infrastructure. In a way, the DNS traffic is leaving your machine encrypted, and then coming back in clear text to the local DNS server.
There are basically two things you can do about this: select a specific DNS server to use for your VPN connection, or install your own DNS server.
Selecting a DNS server
If you can use a DNS server that you trust, or don’t mind using, that is probably the easiest solution. Many people would start with the DNS server assigned to the gateway server used for the VPN. This address can be checked by running the following command in a shell on the gateway server:
$ resolvectl status Global Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: stub Link 2 (ens2) Current Scopes: DNS Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 10.48.0.5 DNS Servers: 10.48.0.5 DNS Domain: openstacklocal Link 5 (gateway0) Current Scopes: none Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server. In the example above, it’s
Let’s change the WireGuard
wg0 interface config to use that DNS server. Edit
/etc/wireguard/wg0.conf and add a second
PostUp line with the
resolvectl command like below:
[Interface] PostUp = wg set %i private-key /etc/wireguard/wg0.key PostUp = resolvectl dns %i 10.48.0.5; resolvectl domain %i \~. ListenPort = 51000 Address = 10.90.90.1/24 [Peer] PublicKey = <contents of gateway0.pub> Endpoint = <public IP of gateway server> AllowedIPs = 0.0.0.0/0
You can run that
resolvectl command by hand if you want to avoid having to restart the WireGuard VPN:
$ sudo resolvectl dns wg0 10.48.0.5; sudo resolvectl domain wg0 \~.
Or just restart the WireGuard interface:
$ sudo wg-quick down wg0; sudo wg-quick up wg0
And if you check again for DNS leaks, this time you’ll that you are only using the DNS server you specified.
Installing your own DNS server
If you don’t want to use even the DNS server from the hosting provider where you have your gateway server, another alternative is to install your own DNS server.
There are multiple choices out there for this:
unbound are quite popular, and easy to find quick tutorials and instructions on how to do it.
Here we will proceed with
bind9, which is in the Ubuntu Main repository.
On the gateway server, install the
$ sudo apt install bind9
And that’s it for the server part.
On the client, add a
PostUp line specifying this IP (or change the line we added in the previous section):
[Interface] PostUp = wg set %i private-key /etc/wireguard/wg0.key PostUp = resolvectl dns %i 10.90.90.2; resolvectl domain %i \~. ListenPort = 51000 Address = 10.90.90.1/24 [Peer] PublicKey = <contents of gateway0.pub> Endpoint = <public IP of gateway server> AllowedIPs = 0.0.0.0/0
And restart the WireGuard interface. Now your VPN client will be using the gateway server as the DNS server.