DIY Linux Router Part 6: WireGuard VPN

DIY Linux Router Part 6: WireGuard VPN
BLÅHAJ using protection while watching some steamy videos

The following is the sixth part of a multipart series describing how I build (software not hardware) my own Linux router from scratch, based on Debian 11.

I don't have much use for a VPN into my home network, but sometimes it's nice to be able to reach my Synology shares from my laptop while being away.

There are a lot of different VPN software solution out there, the big three one being IPsec, OpenVPN and WireGuard.

  • IPsec is very fast and widely available, but a nightmare to configure and debug.
  • OpenVPN ist much easier to configure and debug but not the fastest (Still fast enough for my 200/45 mbit/s).
  • WireGuard is the newest, it's very easy to configure and even faster than IPsec. But it currently lacks tooling around it. There is no IP address auto configure for example.

I used OpenVPN in the past and switched to WireGuard when in became available in Ubuntu 20.04 without the need for a third party repo and was even integrated natively into systemd-networkd.

You can install WireGuard with apt:

apt install wireguard

Network Topology

WireGuard is very basic, it just creates a virtual interface on a computer (peer) and connects it to one or more virtual interfaces on other computers. WireGuard does not concern itself with distributing IPs. You habe to choose the IPs for you peers manually and configure them in the WireGuard configuration.

I use 10.10.10.0/24 as my WireGuard network.

  • 10.10.10.1 - Router
  • 10.10.10.2 - Android
  • 10.10.10.3 - Surface Pro

Key Generation

WireGuard uses private and public keys for authentication. You need one key pair per peer using WireGuard. There is no server/client model, every peer and can route whatever you want through another peer. To generate a key you use the wg command:

wg genkey | tee private.key | wg pubkey > public.key

This will give you a private and public key inside these two files. Do this for every peer you have.

SCACizIYGz/DZNnBVv6kOFjM59rnuBIolmuhrfkfhGI=
private.key
UHMmuHCCOE/Z+8S+ItLsJ21u5unv2Jv1sJ94DbTFuXo=
public.key

Like OpenVPN, WireGuard also can use an additional preshared key to encapsulate all UDP packets that are send between peers. This will in theory help with post-quantum resistance, for the day when quantum computers maybe exist. This key can be generated with:

wg genpsk > psk.key

Resulting in the file:

xl2apMkGlw6dOlYgT2+u6OGc+VXk7e6bKt4vgQj+s2I=
psk.key

Router configuration

Since WireGuard is integrated into systemd-networkd, we can use .netdev and .network files to configure it. First we create a virtual WireGuard interface:

[NetDev]
Name=wg0
Kind=wireguard
Description=WireGuard tunnel wg0

[WireGuard]
ListenPort=51820
PrivateKey=SCACizIYGz/DZNnBVv6kOFjM59rnuBIolmuhrfkfhGI=

# android
[WireGuardPeer]
PublicKey=p/SfPACV5EGMMuC4a4Qeh8DX1Pgr0QKz7XRVDOZkyj4=
PresharedKey=xl2apMkGlw6dOlYgT2+u6OGc+VXk7e6bKt4vgQj+s2I=
AllowedIPs=10.10.10.2/32

# surface
[WireGuardPeer]
PublicKey=pQu33XINtxWpiO/uoU0MBy9C0IjDdoNdCPaB001UVA4=
PresharedKey=xl2apMkGlw6dOlYgT2+u6OGc+VXk7e6bKt4vgQj+s2I=
AllowedIPs=10.10.10.3/32
/etc/systemd/network/99-wg0.netdev

The WireGuard section includes the UDP port we want WireGuard to listen on (51820 being the default) and the private key of our router peer.

The WireGuardPeer section can be added multiple times and each correspond to one other peer. These sections include the public key of the peer, the optional preshared key (this can be different for each peer) and the allowed IPs. The AllowedIPs option restricts which source IPs are allowed, when coming into the WireGuard interface on the router, from the corresponding peer. The Android and Windows Client use NAT by default when routing traffic through WireGuard, so we only need to allow the internal IPs.

We then add the chosen IP address for the router and add it to the interface using a .network file.  Use /24 as a subnet mask to automatically set up a route for 10.10.10.0/24 to reach our other peers on this interface.

[Match]
Name=wg0

[Network]
Address=10.10.10.1/24
/etc/systemd/network/99-wg0.network

With these two files. we can restart systemd-networkd.service or reboot for the changes to apply. The interface should looks like this then:

Running sudo wg should look like this:

Firewall

We now need some iptables rules to open the listen port and allow forwarding of traffic coming from the WireGuard interface.

#!/bin/bash

iptables -F WIREGUARD-INPUT
iptables -X WIREGUARD-INPUT

ip6tables -F WIREGUARD-INPUT
ip6tables -X WIREGUARD-INPUT

iptables -F WIREGUARD-FORWARD
iptables -X WIREGUARD-FORWARD


iptables -N WIREGUARD-INPUT
iptables -A WIREGUARD-INPUT -p udp --dport 51820 -j ACCEPT
iptables -A MAIN-INPUT -j WIREGUARD-INPUT

ip6tables -N WIREGUARD-INPUT
ip6tables -A WIREGUARD-INPUT -p udp --dport 51820 -j ACCEPT
ip6tables -A MAIN-INPUT -j WIREGUARD-INPUT

iptables -N WIREGUARD-FORWARD
iptables -A WIREGUARD-FORWARD -s 10.10.10.0/24 -j ACCEPT
iptables -A MAIN-FORWARD -j WIREGUARD-FORWARD
/usr/local/etc/firewall/wireguard.fw

Client Configuration

The Windows and Android Client both use the default WireGuard ini config file format. To connect the clients to the router and then route the home network traffic through it, we would use the following:

[Interface]
PrivateKey = 2P2lkvs7c/FzmfPe4sxSr/hh6Uf9NfeWR46G3pcnyno=
Address = 10.10.10.2/32
DNS = 10.10.10.1

[Peer]
Endpoint = home.sherbers.de:51820
PublicKey = UHMmuHCCOE/Z+8S+ItLsJ21u5unv2Jv1sJ94DbTFuXo=
PreSharedKey = xl2apMkGlw6dOlYgT2+u6OGc+VXk7e6bKt4vgQj+s2I=
AllowedIPs = 10.10.10.0/24
AllowedIPs = 192.168.144.0/24
android internal traffic only

You need to configure an endpoint, since at least one peer needs to know where the other peer is located. Since your IP can change when using a home network connection, I recommend using a DynDNS Service. I wrote one myself: https://www.sherbers.de/running-my-own-dynamic-dns-service-with-cloudflare/

Setting the DNS options allows the clients to use the Unbound DNS server and use internal DNS names to reach resources if there are any. We can also route all traffic through the router if we want:

[Interface]
PrivateKey = 2P2lkvs7c/FzmfPe4sxSr/hh6Uf9NfeWR46G3pcnyno=
Address = 10.10.10.2/32
DNS = 10.10.10.1

[Peer]
Endpoint = nuc.nuxio.de:51820
PublicKey = UHMmuHCCOE/Z+8S+ItLsJ21u5unv2Jv1sJ94DbTFuXo=
PreSharedKey = xl2apMkGlw6dOlYgT2+u6OGc+VXk7e6bKt4vgQj+s2I=
AllowedIPs = 0.0.0.0/0
android all traffic only

Testing

You should now be able to route your clients through your router and access resources on the internal network. An easy way to test this, is to disconnect your phone from your Wi-Fi, go to any of the many "Whats my IP address" Websites and see if your IP changes to you home IP when activating the WireGuard tunnel.

Next up: Wifi