WireGuard VPN Road Warrior Setup

WireGuard is a relatively new open-source software for creating VPN tunnels on the IP layer using state of the art cryptography. I attended a self-organized session by the creator and developer Jason Donenfeld at the 34c3 who explained how WireGuard works and how it can be used. I was quite impressed by it’s simplicity and gave it a try. It worked more or less out of the box. Now I created a more advanced setup for accessing my home network.

In this blog post, I will describe how you can use it to remotely access your home or corporate network from any external network as a so-called road warrior.

WireGuard VPN Software

Here is a good talk from the WireGuard developer Jason Donenfeld explaining what WireGuard can do and how it works: https://www.youtube.com/watch?v=eYztYCbV_8U:

Some key features from this talk:

  • WireGuard is a VPN solution (alternative/replacement for e.g. OpenVPN or IPsec).
  • The WireGuard setup and configuration is kept very simple.
  • It implements a layer 3 tunneling protocol for IPv4 and IPv6.
  • It’s written in  ~ 4k single lines of codes.
  • The encapsulated IP packets are inside UDP packets.
  • It uses state-of-the-art cryptography (only strong algorithms like Curve25519, ChaCha20, Poly1305 or BLAKE2 are supported and no other ciphers can be configured).
  • WireGuard runs in the Linux kernel (but there are also userspace implementations).
  • It can be managed using normal Linux networking tools like ip, iptables, …
  • Authentication is done using private/public keys, similar to SSH keys.
  • Clients can perform roaming, like in mosh (https://mosh.org/).
  • WireGuard does not respond to unauthenticated packages, so it is not possible to know if a server is running WireGuard if the sender is not authorized.
  • It provides perfect forward secrecy.
  • It does not disclose any identity because the public keys are never transmitted in cleartext over the internet.
  • The key exchange (ECDH) takes only 1 round trip time.
  • WireGuard is fast because it runs in the kernel space and because the used cryptographic algorithms are also very fast.

More infos, a whitepaper, setup instructions or demos can be found on the project website: https://www.wireguard.com/.

At some point, WireGuard will be integrated directly into the Linux kernel. Linus Torvalds said “it’s a work of art” and hopes it will be merged soon into the kernel:  https://lists.openwall.net/netdev/2018/08/02/124.

Road Warrior Scenario

A road warrior is a person that uses a mobile client (e.g. notebook or mobile phone) to connect to their corporate or home network.

Features of this setup:

  • The internal IPv4 and IPv6 intrastructure can be accessed from everywhere via IPv4 and IPv6.
  • The WireGuard VPN client can be installed and used on Linux and mobile phones like Android.
  • Either all traffic (default route) or only the traffic desired for the internal network can be routed through the VPN (split tunneling). This can be configured on the client.
  • If a road warrior does not have an IPv6 connection, this can be provided through the VPN tunnel.
  • The VPN server can also be behind a NAT router, because WireGuard works over UDP.

Note: If the road warrior establishes a VPN connection with the mobile phone and uses the mobile phone as a WiFi hotspot for another device (like a notebook), the traffic from the WiFi hotspot is not routed through the VPN. I’m not sure why this is the case but this is maybe a limitation of the OS on the mobile phone.

WireGuard Software Installation

Install WireGuard according to the installation instructions (https://www.wireguard.com/install/).

Debian

Adding the WireGuard repo and install the wireguard package:

$ echo "deb http://deb.debian.org/debian/ unstable main" | sudo tee /etc/apt/sources.list.d/unstable-wireguard.list
$ printf 'Package: *\nPin: release a=unstable\nPin-Priority: 150\n' | sudo tee /etc/apt/preferences.d/limit-unstable
$ sudo apt update
$ sudo apt install wireguard

Raspberry Pi

On a Raspberry Pi, you have to compile it manually according to these installation instructions: https://github.com/adrianmihalko/raspberrypiwireguard. However, you don’t need to install the kernel headers via rpi-soruce as mentioned. The following commands are enough for the installation on a Raspberry Pi:

$ sudo apt-get install libmnl-dev build-essential git
$ git clone https://git.zx2c4.com/WireGuard
$ cd WireGuard/
$ cd src/
$ make
$ sudo make install

Arch Linux

Installing two wireguard packages from the official repositories and the linux-headers package (this is needed because the Wireguard module is installed as a DKMS module):

$ sudo pacman -S wireguard-dkms wireguard-tools linux-headers

Android

Install the WireGuard app from the play store: https://play.google.com/store/apps/details?id=com.wireguard.android&hl=en. This application implements WireGuard in the userspace. Therefore, the phone does not have to be rooted in order to use WireGuard.

Installation Test

You can test if the kernel module wireguard is loaded:

$ lsmod | grep -i wireguard
wireguard             147456  0
ip6_udp_tunnel         16384  1 wireguard
udp_tunnel             16384  1 wireguard
ipv6                  434176  33 nf_conntrack_ipv6,nf_nat_masquerade_ipv6,nf_defrag_ipv6,wireguard,nf_nat_ipv6

Server Key Generation

To ensure that all the files have the correct permissions (only readable and writeable by the file owner, which in this case is the user root), the umask has to be set to 077:

# umask 077

The configuration is performed in the /etc/wireguard directory.  Generate a private and public key for the server:

# cd /etc/wireguard
# wg genkey | tee server-private.key | wg pubkey > server-public.key
# ls -l server-private.key server-public.key 
-rw------- 1 root root 45 Sep 23 17:26 server-private.key
-rw------- 1 root root 45 Sep 23 17:26 server-public.key

Example server keys:

# cat server-private.key server-public.key
mNt0Gx2Af/OCkT9FchX3nybsaXUAerglnuMnSud4z1k=
3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=

Client Key Generation

Generate a private and public key for every client. Note: These keys can also completely be generated on the client.

Notebook:

# cd /etc/wireguard
# wg genkey | tee notebook-private.key | wg pubkey > notebook-public.key
# ls -l notebook-* 
-rw------- 1 root root 45 Sep 23 17:30 notebook-private.key
-rw------- 1 root root 45 Sep 23 17:30 notebook-public.key

Example keys:

# cat notebook-private.key notebook-public.key 
OPTN5qb4FFulEBFLlrmC1sFTawT6AmdhsAbigpCMemw=
UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=

The same for the mobile phone:

# wg genkey | tee mobile-private.key | wg pubkey > mobile-public.key
# ls -l mobile*
-rw------- 1 root root 45 Sep 23 17:31 mobile-private.key
-rw------- 1 root root 45 Sep 23 17:31 mobile-public.key
# cat mobile-private.key mobile-public.key
kKcKPZoC4gULXOmpDi54sAy5tQvnFEn6J8yXC8OxukY=
1xy8XRtUQT/9AwYWlEXsWCezNjfiFjXaBy40UUtAWBo=

Server Configuration

WireGuard

Create a new configuration file for the server in /etc/wireguard/wg0.conf. The filename specifies the name of the VPN network interface. In this case, the new network interface will be named wg0.

# cd /etc/wireguard 
# ls -l wg0.conf 
-rw------- 1 root root 713 Sep 23 17:33 wg0.conf
# cat wg0.conf 
[Interface]
Address = 10.23.5.1/24, fc00:23:5::1/64
ListenPort = 1500
PrivateKey = mNt0Gx2Af/OCkT9FchX3nybsaXUAerglnuMnSud4z1k=
PreUp = iptables -t nat -A POSTROUTING -s 10.23.5.0/24  -o enxb827eb7dc89a -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s fc00:23:5::/64 -o enxb827eb7dc89a -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.23.5.0/24  -o enxb827eb7dc89a -j MASQUERADE; ip6tables -t nat -D POSTROUTING -s fc00:23:5::/64 -o enxb827eb7dc89a -j MASQUERADE

# Notebook
[Peer]
PublicKey = UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=
AllowedIPs = 10.23.5.2/32, fc00:23:5::2/128

# Mobile
[Peer]
PublicKey = 1xy8XRtUQT/9AwYWlEXsWCezNjfiFjXaBy40UUtAWBo=
AllowedIPs = 10.23.5.3/32, fc00:23:5::3/128

Configuration explanation Interface section:

  • Address: The server gets the IPv4 address 10.23.5.1/24 and IPv6 address fc00:23:5::1/64 assigned.
  • ListenPort: The server will listen for UDP packets on the port 1500.
  • PrivateKey: This is the private key of the server generated earlier.
  • PreUp: An iptables/ip6tables rule in the nat chain is added before the interface is created for performing NAT between the VPN clients and the destination. The interface (-o enxb827eb7dc89a) has to be adjusted according to the name of the outgoing interface on the VPN server.
  • PreDown: When the VPN server is stopped, the NAT rules will be removed.

Configuration explanation Peer section:

  • PublicKey: The public key of the clients (generated before)
  • AllowedIPs: This is the most complicated configuration option that confused me a bit in the beginning. This option always includes only IP addresses or networks that are available on the remote site. It’s not an IP address/network outside the tunnel (so no configuration from which public IP address a client is allowed to connect) but only addresses/networks which are transported inside the tunnel! In a road warrior scenario, where the client does not provide a whole network to the server, the netmask is always /32 on IPv4 or /128 on IPv6. Packets on the VPN server with this destination IP addresses are sent to this specified peer. This peer is also only allowed to send packages from this source IP address to the VPN server. It’s also important to know that there are no peers with the same AllowedIPs addresses/networks inside the same configuration file. If this would be the case, the server would not know to which peer the server has to send packages matching multiple peers with the same network configured.

IP Forwarding

IP forwarding has to be enabled on both IPv4 and IPv6. Creating the configuration file  /etc/sysctl.d/wireguard.conf:

# ls -l /etc/sysctl.d/wireguard.conf 
-rw------- 1 root root 53 Sep 25 22:23 /etc/sysctl.d/wireguard.conf
# cat /etc/sysctl.d/wireguard.conf 
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Loading the configuration:

# sysctl -p /etc/sysctl.d/wireguard.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1

Client Configuration

Routing all Traffic (Default Route)

Configuration file which will route all traffic through the VPN:

# cd /etc/wireguard
# ls -l client-notebook_dgw.conf 
-rw------- 1 root root 245 Sep 23 20:34 client-notebook_dgw.conf
# cat client-notebook_dgw.conf 
[Interface]
PrivateKey = OPTN5qb4FFulEBFLlrmC1sFTawT6AmdhsAbigpCMemw=
Address = 10.23.5.2/24, fc00:23:5::2/64
DNS = 8.8.8.8

[Peer]
PublicKey = 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
Endpoint = wg.example.com:1500
AllowedIPs = 0.0.0.0/0, ::/0

Configuration explanation Interface section:

  • Address: The client gets the IPv4 address 10.23.5.2/24 and IPv6 address fc00:23:5::2/64 assigned.
  • PrivateKey: This is the private key of the client.
  • DNS: This DNS server is used on the client. This could be a DNS server in the remote network.

Configuration explanation Peer section:

  • PublicKey: This is the public key of the VPN server.
  • Endpoint: This is the hostname of the VPN server.
  • AllowedIPs: All traffic matching these networks is sent through the VPN tunnel. In this case, 0.0.0.0/0 and ::/0 means, that all IPv4 and all IPv6 traffic is routed through the VPN.

Split Tunneling

Configuration file which will route only the traffic for the VPN (10.23.5.0/24 and fc00:23:5::/64) and for the remote network (192.168.1.0/24) through the VPN.

# cd /etc/wireguard
# ls -l client-mobile_splittunnel.conf 
-rw------- 1 root root 355 Sep 23 20:39 client-mobile_splittunnel.conf
# cat client-mobile_splittunnel.conf
[Interface]
PrivateKey = kKcKPZoC4gULXOmpDi54sAy5tQvnFEn6J8yXC8OxukY=
Address = 10.23.5.3/24, fc00:23:5::3/64
DNS = 8.8.8.8

[Peer]
PublicKey = 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
Endpoint = wg.example.com:1500
AllowedIPs = 10.23.5.0/24, fc00:23:5::/64, 192.168.1.0/24, 2001:db8:23:5::/64

The only difference is the AllowedIPs directive, which creates a split tunneling VPN setup. Only the traffic for the provided networks is routed through the VPN.

Considerations when using NAT or stateful Firewalls

If the server is behind a NAT or a stateful firewall and the client does not send any traffic to the server for a certain time, the NAT router/firewall will remove the host state from the connection table. When now a server sends a packet to the client, the client would not be able to receive this packet anymore, because the NAT router/firewall does not know what to do with this packet. To fix this issue, the PersistentKeepalive option can be used to periodically send an empty authenticated packet to the server to keep the connection open. WireGuard proposes a value of 25 seconds that would work with a wide variety of firewalls. (Thanks Ramesh for the comment on that.)

So, if the server is behind a NAT or stateful firewall, the following option should be added in the Peer section of the client configuration:

PersistentKeepalive = 25

Server Usage

Start/Stop Manually

Starting the VPN server manually:

# wg-quick up wg0
[#] iptables -t nat -A POSTROUTING -s 10.23.5.0/24 -o enxb827eb7dc89a -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s fc00:23:5::/64 -o enxb827eb7dc89a -j MASQUERADE
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip address add 10.23.5.1/24 dev wg0
[#] ip address add fc00:23:5::1/64 dev wg0
[#] ip link set mtu 1420 dev wg0
[#] ip link set wg0 up
[#] ip route add fc00:23:5::/64 dev wg0

Stopping the service again:

# wg-quick down wg0
[#] ip link delete dev wg0
[#] iptables -t nat -D POSTROUTING -s 10.23.5.0/24 -o enxb827eb7dc89a -j MASQUERADE; ip6tables -t nat -D POSTROUTING -s fc00:23:5::/64 -o enxb827eb7dc89a -j MASQUERADE

Start/Stop using systemd

Starting the service:

# systemctl start wg-quick@wg0

Showing details about the service:

# systemctl status wg-quick@wg0
● wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0
Loaded: loaded (/lib/systemd/system/wg-quick@.service; enabled; vendor preset: en
Active: active (exited) since Sun 2018-09-23 20:48:10 CEST; 9s ago
Docs: man:wg-quick(8)
man:wg(8)
https://www.wireguard.com/
https://www.wireguard.com/quickstart/
https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8
Process: 18609 ExecStart=/usr/bin/wg-quick up wg0 (code=exited, status=0/SUCCESS)
Main PID: 18609 (code=exited, status=0/SUCCESS)

Sep 23 20:48:09 wgpi systemd[1]: Starting WireGuard via wg-quick(8) for wg0...
Sep 23 20:48:09 wgpi wg-quick[18609]: [#] iptables -t nat -A POSTROUTING -s 10.23.5.
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip link add wg0 type wireguard
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] wg setconf wg0 /dev/fd/63
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip address add 10.23.5.1/24 dev wg0
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip address add fc00:23:5::1/64 dev wg0
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip link set mtu 1420 dev wg0
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip link set wg0 up
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip route add fc00:23:5::/64 dev wg0
Sep 23 20:48:10 wgpi systemd[1]: Started WireGuard via wg-quick(8) for wg0.

Stopping the service again:

# systemctl stop wg-quick@wg0

The service is now stopped:

# systemctl status wg-quick@wg0
● wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0
Loaded: loaded (/lib/systemd/system/wg-quick@.service; enabled; vendor preset: en
Active: inactive (dead) since Sun 2018-09-23 20:48:25 CEST; 37s ago
Docs: man:wg-quick(8)
man:wg(8)
https://www.wireguard.com/
https://www.wireguard.com/quickstart/
https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8
Process: 18667 ExecStop=/usr/bin/wg-quick down wg0 (code=exited, status=0/SUCCESS)
Process: 18609 ExecStart=/usr/bin/wg-quick up wg0 (code=exited, status=0/SUCCESS)
Main PID: 18609 (code=exited, status=0/SUCCESS)

Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip address add 10.23.5.1/24 dev wg0
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip address add fc00:23:5::1/64 dev wg0
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip link set mtu 1420 dev wg0
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip link set wg0 up
Sep 23 20:48:10 wgpi wg-quick[18609]: [#] ip route add fc00:23:5::/64 dev wg0
Sep 23 20:48:10 wgpi systemd[1]: Started WireGuard via wg-quick(8) for wg0.
Sep 23 20:48:25 wgpi systemd[1]: Stopping WireGuard via wg-quick(8) for wg0...
Sep 23 20:48:25 wgpi wg-quick[18667]: [#] ip link delete dev wg0
Sep 23 20:48:25 wgpi wg-quick[18667]: [#] iptables -t nat -D POSTROUTING -s 10.23.5.
Sep 23 20:48:25 wgpi systemd[1]: Stopped WireGuard via wg-quick(8) for wg0.

Automatically start the service when the system is started:

# systemctl enable wg-quick@wg0

Disable the service on startup again:

# systemctl disable wg-quick@wg0

Verifying

A new network interface wg0 is created when the service is started:

# ip a l wg0
26: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none 
inet 10.23.5.1/24 scope global wg0
valid_lft forever preferred_lft forever
inet6 fc00:23:5::1/64 scope global 
valid_lft forever preferred_lft forever

The route is sent according to the AllowedIPs directive:

# ip route
default via 192.168.1.1 dev enxb827eb7dc89a src 192.168.1.10 metric 202 
10.23.5.0/24 dev wg0 proto kernel scope link src 10.23.5.1 
192.168.1.0/24 dev enxb827eb7dc89a proto kernel scope link src 192.168.1.10 metric 202

Showing the current configuration:

# wg show
interface: wg0
public key: 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
private key: (hidden)
listening port: 1500

peer: UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=
allowed ips: 10.23.5.2/32, fc00:23:5::2/32
peer: 1xy8XRtUQT/9AwYWlEXsWCezNjfiFjXaBy40UUtAWBo=
allowed ips: 10.23.5.3/32, fc00:23:5::3/32

More data are shown if the clients are connected:

# wg show
interface: wg0
public key: 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
private key: (hidden)
listening port: 1500

peer: 1xy8XRtUQT/9AwYWlEXsWCezNjfiFjXaBy40UUtAWBo=
endpoint: 178.197.42.137:18822
allowed ips: 10.23.5.3/32, fc00:23:5::3/128
latest handshake: 4 seconds ago
transfer: 788 B received, 732 B sent

peer: UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=
endpoint: 178.197.42.137:18821
allowed ips: 10.23.5.2/32, fc00:23:5::2/128
latest handshake: 16 seconds ago
transfer: 6.61 KiB received, 6.07 KiB sent

Showing the detailed interface configuration:

# wg showconf wg0
[Interface]
ListenPort = 1500
PrivateKey = mNt0Gx2Af/OCkT9FchX3nybsaXUAerglnuMnSud4z1k=

[Peer]
PublicKey = UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=

[Peer]
PublicKey = 1xy8XRtUQT/9AwYWlEXsWCezNjfiFjXaBy40UUtAWBo=
AllowedIPs = 10.23.5.0/24, fc00:23:5::/64

Client Usage

Copying the client configuration file to /etc/wireguard:

# cd /etc/wireguard 
# ls -l wg0.conf 
-rw------- 1 root root 245 Sep 23 21:17 wg0.conf
# cat wg0.conf
[Interface]
PrivateKey = OPTN5qb4FFulEBFLlrmC1sFTawT6AmdhsAbigpCMemw=
Address = 10.23.5.2/24, fc00:23:5::2/64
DNS = 8.8.8.8

[Peer]
PublicKey = 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
Endpoint = wg.example.com:1500
AllowedIPs = 0.0.0.0/0, ::/0

Starting the service in the same way as on the server:

# wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip address add 10.23.5.2/24 dev wg0
[#] ip address add fc00:23:5::2/64 dev wg0
[#] ip link set mtu 1420 dev wg0
[#] ip link set wg0 up
[#] resolvconf -a tun.wg0 -m 0 -x
[#] wg set wg0 fwmark 51820
[#] ip -6 route add ::/0 dev wg0 table 51820
[#] ip -6 rule add not fwmark 51820 table 51820
[#] ip -6 rule add table main suppress_prefixlength 0
[#] 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

A new network interface was created:

# ip a l wg0
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1
link/none 
inet 10.23.5.2/24 scope global wg0
valid_lft forever preferred_lft forever
inet6 fc00:23:5::2/64 scope global 
valid_lft forever preferred_lft forever

The VPN works:

# ping 10.23.5.1
PING 10.23.5.1 (10.23.5.1) 56(84) bytes of data.
64 bytes from 10.23.5.1: icmp_seq=1 ttl=64 time=284 ms
64 bytes from 10.23.5.1: icmp_seq=2 ttl=64 time=63.3 ms
64 bytes from 10.23.5.1: icmp_seq=3 ttl=64 time=225 ms
64 bytes from 10.23.5.1: icmp_seq=4 ttl=64 time=150 ms
^C
--- 10.23.5.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 63.345/180.886/284.496/82.871 ms

Showing the connection:

# wg show
interface: wg0
public key: UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=
private key: (hidden)
listening port: 52682
fwmark: 0xca6c

peer: 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
endpoint: 178.194.23.5:1500
allowed ips: 0.0.0.0/0, ::/0
latest handshake: 5 seconds ago
transfer: 604 B received, 660 B sent

Because the AllowedIPs directive is configured to 0.0.0.0/0 and ::/0, all traffic is routed through the VPN:

# curl -L motd.ch/ip.php
178.194.23.5

Both IPv4 and IPv6 works through the tunnel:

Stoppping the VPN:

# wg-quick down wg0
[#] ip -4 rule delete table 51820
[#] ip -4 rule delete table main suppress_prefixlength 0
[#] ip -6 rule delete table 51820
[#] ip -6 rule delete table main suppress_prefixlength 0
[#] ip link delete dev wg0
[#] resolvconf -d tun.wg0

Starting via a systemd service:

# systemctl start wg-quick@wg0

Stopping via systemd:

# systemctl stop wg-quick@wg0

Status:

# systemctl status wg-quick@wg0
● wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0
Loaded: loaded (/lib/systemd/system/wg-quick@.service; disabled; vendor prese
Active: inactive (dead)
Docs: man:wg-quick(8)
man:wg(8)
https://www.wireguard.com/
https://www.wireguard.com/quickstart/
https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8

Sep 23 21:54:35 dimmbar wg-quick[1629]: [#] ip -4 rule add table main suppress_p
Sep 23 21:54:35 dimmbar systemd[1]: Started WireGuard via wg-quick(8) for wg0.
Sep 23 21:54:46 dimmbar systemd[1]: Stopping WireGuard via wg-quick(8) for wg0..
Sep 23 21:54:47 dimmbar wg-quick[1732]: [#] ip -4 rule delete table 51820
Sep 23 21:54:47 dimmbar wg-quick[1732]: [#] ip -4 rule delete table main suppres
Sep 23 21:54:47 dimmbar wg-quick[1732]: [#] ip -6 rule delete table 51820
Sep 23 21:54:47 dimmbar wg-quick[1732]: [#] ip -6 rule delete table main suppres
Sep 23 21:54:47 dimmbar wg-quick[1732]: [#] ip link delete dev wg0
Sep 23 21:54:47 dimmbar wg-quick[1732]: [#] resolvconf -d tun.wg0
Sep 23 21:54:47 dimmbar systemd[1]: Stopped WireGuard via wg-quick(8) for wg0.

Mobile Client Usage

Generating a QR code for the mobile client:

# qrencode -t ansiutf8 < client-mobile_splittunnel.conf

Adding a new VPN connection by selecting Create from QR code:

Scanning the QR code:

Enabling the VPN:

A new network interface was created with the configured IP addresses:

Accessing the remote network:

It’s also possible to reach other VPN clients (the firewall does not prevent that):

Because split tunneling is used, the normal network traffic does not go through the VPN box:

Debugging

Wireshark

Wireshark has a dissector for WireGuard:

More infos on how to decrypt data within Wireshark by providing key logs can be fond here: https://github.com/Lekensteyn/wireguard-dissector.

References

33 thoughts on “WireGuard VPN Road Warrior Setup”

  1. Very good tutorial/article. Thanks.

    I’m wondering how I can allow all the peers to talk to one another through the server?

    • Hi Ken

      Thanks for the feedback.

      If you follow my setup, this is allowed by default. In the 2nd last screenshot, you can see how the phone pings the notebook via the Wireguard tunnel.

      Because this is no configuration option on Wireguard itself, this has to be done using other system tools. If you want to block it, you can use the iptables firewall and block this type of traffic. If you want to allow it but it does not work, you may have an iptables rule in place that prevents this access.

      Cheers,
      Emanuel

  2. you may want to include a single line before people create the wg0.conf & run “umask 077”. else you’ll get a warning that the conf is available to all..

  3. Hi Emanuel

    another option is persistent-keepalives.. this is quite useful if the client is behind a NAT or firewall which is quite often the case. here’s the bit from WireGuard. I added this on my peer config file

    NAT and Firewall Traversal Persistence
    By default, WireGuard tries to be as silent as possible when not being used; it is not a chatty protocol. For the most part, it only transmits data when a peer wishes to send packets. When it’s not being asked to send packets, it stops sending packets until it is asked again. In the majority of configurations, this works well. However, when a peer is behind NAT or a firewall, it might wish to be able to receive incoming packets even when it is not sending any packets. Because NAT and stateful firewalls keep track of “connections”, if a peer behind NAT or a firewall wishes to receive incoming packets, he must keep the NAT/firewall mapping valid, by periodically sending keepalive packets. This is called persistent keepalives. When this option is enabled, a keepalive packet is sent to the server endpoint once every interval seconds. A sensible interval that works with a wide variety of firewalls is 25 seconds. Setting it to 0 turns the feature off, which is the default, since most users will not need this, and it makes WireGuard slightly more chatty. This feature may be specified by adding the PersistentKeepalive = field to a peer in the configuration file, or setting persistent-keepalive at the command line. If you don’t need this feature, don’t enable it. But if you’re behind NAT or a firewall and you want to receive incoming connections long after network traffic has gone silent, this option will keep the “connection” open in the eyes of NAT.

    • Hi Ramesh

      Thanks!

      I added a new section “Considerations when using NAT or stateful Firewalls” that covers that topic. It’s indeed a good idea.

      Regards,
      Emanuel

  4. Hello,

    is there a way to become log? I have follow the instruction here the services run but when I work with ping I become only

    ping 10.23.5.1
    PING 10.23.5.1 (10.23.5.1) 56(84) bytes of data.
    From 10.23.5.2 icmp_seq=1 Destination Host Unreachable
    ping: sendmsg: Der notwendige Schlüssel ist nicht verfügbar (The necessary key is not available)
    From 10.23.5.2 icmp_seq=2 Destination Host Unreachable
    ping: sendmsg: Der notwendige Schlüssel ist nicht verfügbar
    From 10.23.5.2 icmp_seq=3 Destination Host Unreachable
    ping: sendmsg: Der notwendige Schlüssel ist nicht verfügbar
    From 10.23.5.2 icmp_seq=4 Destination Host Unreachable
    ping: sendmsg: Der notwendige Schlüssel ist nicht verfügbar

    I have now checked all files and the keys are correct in the files. Thanks for advice.

    • Hello

      Unfortunately, as far as I know, there is no logging facility for WireGuard. The only thing in the kernel logfile is a copyright notice when the module is loaded:

      $ dmesg | grep wireguard
      [ 7635.602503] wireguard: WireGuard 0.0.20181218 loaded. See www.wireguard.com for information.
      [ 7635.602504] wireguard: Copyright (C) 2015-2018 Jason A. Donenfeld . All Rights Reserved.
      

      First, you should check if a new WireGuard interface is created and if the key new interface has the correct ip address assigned:

      # ip a l wg0
      4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1
      link/none 
      inet 10.23.5.2/24 scope global wg0
      valid_lft forever preferred_lft forever
      inet6 fc00:23:5::2/64 scope global 
      valid_lft forever preferred_lft forever

      You can also debug it using the wg command, so you can see if the tunnel is up:

      # wg show
      interface: wg0
      public key: 3oPm1WH3RXv+8zCEQ7onRAYJWAvuHKo/1OfIiTE5LDc=
      private key: (hidden)
      listening port: 1500
      
      peer: UGyBshzPfAH0U4QAgGJHe07LfUz4RcHA9PhUlUC4cCA=
      allowed ips: 10.23.5.2/32, fc00:23:5::2/32
      peer: 1xy8XRtUQT/9AwYWlEXsWCezNjfiFjXaBy40UUtAWBo=
      allowed ips: 10.23.5.3/32, fc00:23:5::3/32

      I hope this helps.

      Cheers,
      Emanuel

  5. Thanks a lot for the tutorial! Got it working without much trouble. I can’t however get it to work with a local DNS server (running on the same machine as the Wireguard server). It works like a charm if I do “DNS = 8.8.8.8”, but if I change it to “DNS = 10.23.5.1” or “DNS = 192.168.1.10” (or my equivalent) the DNS queries seems to get lost somewhere. I assume I’m maybe missing some iptables magic?

    • Hi

      Thanks for the feedback.

      After you bring up the VPN:

      – Is the DNS server configured on the client? You must have resolvconf installed for that. Otherwise, this will not work correctly. You can check it if the nameserver appears in the file /etc/resolv.conf.

      If not, you must install resolvconf or set the DNS server manually using the PostUp and PostDown configuration directive.

      – Can you ping the local DNS server?

      – Can you manually perform DNS queries?

      Example:

      # dig google.com @10.23.5.1
      

      If you cannot reach the DNS server, this might be a firewall issue. Make sure that either your default netfilter/iptables policy is ACCEPT or you explicitly allow incomming DNS requests.

      You can also debug the behavior using tcpdump on the VPN server if you filter for DNS traffic:

      # tcpdump -i any port domain
      
      • Thanks for a quick reply. Not quite sure what you mean by the first portion of your reply. The client is an Android device so I’m not sure I’ve got access to its resolve.conf (or if it even has one). Requests seems to hit the Wireguard server just fine. Doing “dig google.com @10.23.5.1” from the client results in “IP 10.23.5.2.40957 > 10.23.5.1.domain: 23061+ [1au] A? google.com. (51)” being printed by “tcpdump -i wg0 port 53” on the server. No traffic flowing in the other direction though. Anyway, I don’t mean to take up your time with this, as I’m sure there is some aspects I haven’t quite understood myself. I’ll dig around some more. 🙂

        • 👍

          Ah, you’re right. On an Android phone you don’t have to install anything more than the WireGuard app from the play store. The resolvconf package I meant would be on a Linux client.

          So this means the client can reach the DNS server correctly but does not get an answer. Therefore, it looks more like a DNS server misconfiguration.

          Is the nameserver listening on all interfaces or maybe only on the physical ethernet (eth0 or something like that) device?

          Command on the server (run as root / using sudo to see the process name):

          # ss -ltupn | grep 53
          udp    UNCONN     0      0            10.23.5.1:53                    *:*      users:(("named",pid=10971,fd=514))
          udp    UNCONN     0      0              127.0.0.1:53                    *:*      users:(("named",pid=10971,fd=513))
          udp    UNCONN     0      0                     :::53                   :::*      users:(("named",pid=10971,fd=512))
          tcp    LISTEN     0      10           10.23.5.1:53                    *:*      users:(("named",pid=10971,fd=22))
          tcp    LISTEN     0      10             127.0.0.1:53                    *:*      users:(("named",pid=10971,fd=21))
          tcp    LISTEN     0      10                    :::53                   :::*      users:(("named",pid=10971,fd=20))
          

          Either 0.0.0.0 (all interfaces together) or the 10.23.5.1 interface itself should be listed there.

          If this is not the case, look in the documentation of your nameserver how to configure it that the server listens on all interfaces.

          You can try to debug by performing some dig commands by explicitly specifying the listening address.

          # dig google.com @127.0.0.1
          # dig google.com @10.23.5.1
          

          If this does work, it is most probbably a firewall issue.

          Good luck and if you fixed it, let me know what the problem was 😉

          • Wow, you were absolutely right, the DNS server was only listening on the physical NIC (kind of feel like an idiot now). I’m running the Wireguard server on a Raspberry Pi with pi-hole (which acts as a DNS server). For people who run into the same problem with the same setup, the following command did the trick for me: “pihole -a -i all”. This tells the pi-hole DNS server to listen on all interfaces.

            Again, thanks for your time man. I really appreciate it!

          • You don’t have to feel like an idiot, that’s a typical error if you don’t do such specific things very often. And when you learned it the hard way (like now), you’ll (hopefully) never forget it. So it’s even better you made this mistake so you learned something!

            I’m happy that I could help you. Have fun! 🙂

  6. I barely got Wireguard working tonight with 1 IP and just found this, amazing!
    A proper scenario covering most use-cases and such sweet detail that it makes the Arch linux Wireguard wiki look out of touch, impressive indeed!

    I have a Q – what changes (if any) are needed if the WG Road Warrior notebook in your scenario above, was replaced with a linux router serving 2 LANs, (192.168.1.0/24 & 192.168.10.1/24) and we wished to route both LANs through the WG VPN Server?

    • Hi Strykar

      Thanks for the feedback. I’m happy this tutorial helped you!

      Regarding your question:

      It depends if the Linux router preforms NAT or not. If the router performs NAT, then it’s not neccessary to change the configuration, because your two networks are “hidden” behind the NAT.

      If the router does not perform NAT, the WireGuard configuration on the router does not have to be changed, since all traffic is sent to the WireGuard server (because of the AllowedIPs = 0.0.0.0/0, ::/0 configuration). But on the WireGuard server itself, the AllowedIPs configuration has to be changed in order to accept and send packets to these two networks:

      AllowedIPs = 10.23.5.2/32, fc00:23:5::2/128, 192.168.1.0/24, 192.168.10.1/24
      

      Note: I have not tested this configuration, but it should work. But it would be interesting to try it out.

      Best regards,
      Emanuel

      • That was it, cheers mate! Can’t recommend this tutorial enough to understand the innards of WireGuard.

      • A few notes on MTU..
        My ISP suggests an MTU of 1448, so my correct MTU on the WG interface on OpenWRT would be (1448 – 18) bytes for the VPN overhead.
        I host the VPN server with Google, and apparently, GCE has an MTU of 1460 bytes.
        This caused a weird issue where everything but Google related pages Gmail/Search/Cloud Console would time out till the MTU was corrected. My thanks to the helpful folks on #wireguard@Freenode for helping me nail this.

        To quote from – https://lists.zx2c4.com/pipermail/wireguard/2017-December/002201.html
        The overhead of WireGuard breaks down as follows:

        – 20-byte IPv4 header or 40 byte IPv6 header
        – 8-byte UDP header
        – 4-byte type
        – 4-byte key index
        – 8-byte nonce
        – N-byte encrypted data
        – 16-byte authentication tag

        So, if you assume 1500 byte ethernet frames, the worst case (IPv6)
        winds up being 1500-(40+8+4+4+8+16), leaving N=1420 bytes. However, if
        you know ahead of time that you’re going to be using IPv4 exclusively,
        then you could get away with N=1440 bytes.

  7. I setup wireguard but it’s being blocked at McDonald’s – assuming they have blocked ports and that’s why. It works at work and tims for example on their public wifi. I have tried setting up wireguard on a few different ports, nothing works. Thoughts on evading port blocking on wifi hot spots?

    • Hi

      Your client must be able to connect to the port where WireGuard accepts connections. It looks like McDonald’s is blocking your port. As a first try, I would try port 53 (which is used for DNS) and 500 (which is used for the key exchange in IPSec VPNs, IKE). If the port is not open, you cannot directly connect to the outside. Another technique to build up a tunnel to an external server would be to use a DNS tunnel. That works in most environments but is very very slow. It does always work if your client can lookup arbitrary DNS records from the network using the provided DNS server.

      Cheers,
      Emanuel

  8. Hey, I haven’t been able to get this working on my Android. The client appears fine if I run wg show and I can ping it, but nothing resolves on the client. Here’s some output from tcpdump:

    gleapis.l.google.com. A 172.217.3.170 (122)
    23:32:16.874390 IP (tos 0xc0, ttl 64, id 27333, offset 0, flags [none], proto ICMP (1), length 178)
    10.23.5.2 > 10.23.5.1: ICMP 10.23.5.2 udp port 13052 unreachable, length 158
    IP (tos 0x0, ttl 64, id 20654, offset 0, flags [none], proto UDP (17), length 150)
    10.23.5.1.53 > 10.23.5.2.13052: 6658 4/0/0 android.googleapis.com. CNAME googleapis.l.google.com., googleapis.l.google.com. A 172.217.3.202, googleapis.l.google.com. A 172.217.14.202, googleapis.l.google.com. A 172.217.3.170 (122)
    23:32:21.882438 IP (tos 0x0, ttl 64, id 5159, offset 0, flags [DF], proto UDP (17), length 68)
    10.23.5.2.13052 > 50.69.237.97.53: 6658+ A? android.googleapis.com. (40)
    23:32:21.884263 IP (tos 0x0, ttl 64, id 22019, offset 0, flags [none], proto UDP (17), length 150)
    10.23.5.1.53 > 10.23.5.2.13052: 6658 4/0/0 android.googleapis.com. CNAME googleapis.l.google.com., googleapis.l.google.com. A 172.217.3.202, googleapis.l.google.com. A 172.217.14.202, googleapis.l.google.com. A 172.217.3.170 (122)
    23:32:21.926039 IP (tos 0xc0, ttl 64, id 27718, offset 0, flags [none], proto ICMP (1), length 178)
    10.23.5.2 > 10.23.5.1: ICMP 10.23.5.2 udp port 13052 unreachable, length 158
    IP (tos 0x0, ttl 64, id 22019, offset 0, flags [none], proto UDP (17), length 150)
    10.23.5.1.53 > 10.23.5.2.13052: 6658 4/0/0 android.googleapis.com. CNAME googleapis.l.google.com., googleapis.l.google.com. A 172.217.3.202, googleapis.l.google.com. A 172.217.14.202, googleapis.l.google.com. A 172.217.3.170 (122)
    23:33:02.861112 IP (tos 0x0, ttl 1, id 12546, offset 0, flags [DF], proto UDP (17), length 197)
    10.23.5.1.54400 > 239.255.255.250.1900: UDP, length 169

  9. Hey Emanuel, very helpful article. I have managed to configure the Wireguard to work but unfortunately, it only shows that I have IPv4 address, but no IPv6 address even though I think that my configuration is OK. When I go to https://ipv6-test.com/ it shows to me that only the IPv4 connection as green and the IPv6 is not reachable. I can ping the IPv6 address of the server but not the Google DNS IPv6. I have even opened a thread on https://unix.stackexchange.com/questions/539768/wireguard-ipv6-connectivity-not-working with a description of my problem and also a copy of my configuration files. It would be great if you can take a look at it and eventually suggest how to fix my connectivity issue problem.

    • Hi George

      Nice that you found the problem.

      If anyone runs in the same problem and lands here, here is your answer from StackExchange:

      Apparently the VM has a separate interface for the IPv4 and another one for the IPv6 connectivity, so I did: ping6 ipv6.google.com -I eth0/1/2 and found out that only on the eth2 there is IPv6 connectivity. So I have changed the iptables rules for IPv6 to -s fc00:xxx:xxx::/64 -o eth2 on the server and restarted the Wireguard sudo systemctl stop wg-quick@wg0 && sudo systemctl start wg-quick@wg0 on the server and on the client afterward, I had a working IPv6 connectivity.

      Grz,
      Emanuel

Leave a Reply to Ken Cancel reply