Wireguard is a new-age VPN protocol that is fast and secure. Here’s how to seamlessly integrate this as a Debian-based AppVM in your QubesOS R4 network stack using Mullvad VPN as the service provider.

Table of Contents

TemplateVM

Install required packages

In the TemplateVM, start by installing wireguard-tools and other prequisites.

We include cURL to retrieve files from Mullvad repositories and openresolv to fix problems with the inferior Debian version of resolvconf.

user@d9-template:~$
1
2
3
4
5
6
7
8
9
10
# Change to superuser
sudo su -

# Activate unstable sources for Apt
echo "deb http://deb.debian.org/debian/ unstable main" > /etc/apt/sources.list.d/unstable-wireguard.list
printf 'Package: *\nPin: release a=unstable\nPin-Priority: 150\n' > /etc/apt/preferences.d/limit-unstable
apt update

# Install
apt install wireguard-tools curl openresolv

Dom0

Create ‘sys-vpn’ AppVM

In dom0, create a new AppVM aka ProxyVM to provide the VPN network interface. This will be a Debian-based HVM (in-VM kernel) AppVM that will be used to compile and host the wireguard.ko kernel module.

We use a HVM-style AppVM because we need to install build-related packages (linux-headers etc) to successfully compile the module. It’s not possible to successfully compile the module with the dom0 kernel.

user@dom0:~$
1
2
# Create HVM AppVM named 'sys-vpn'
qvm-create --class AppVM --template d9-template --label green --property virt_mode=hvm --property kernel="" --property provides_network=True sys-vpn

AppVM ‘sys-vpn’

Compile and install wireguard.ko kernel module

user@sys-vpn:~$
1
2
3
4
5
6
7
8
9
10
# Install required packages
sudo apt-get install libmnl-dev libelf-dev build-essential pkg-config linux-headers-$(uname -r)

# Download the Wireguard source
wget https://git.zx2c4.com/WireGuard/snapshot/WireGuard-0.0.20180420.tar.xz

# Compile
tar xvf ./WireGuard-0.0.20180420.tar.xz
cd ./WireGuard-0.0.20180420/src
make

Set up the module loader

user@sys-vpn:~$
1
2
3
4
5
6
7
8
9
10
11
12
13
# Move the compiled kernel module from the previous step to /rw/config
sudo mv ./wireguard.ko /rw/config/

# Add kernel modules to startup
echo 'modprobe udp_tunnel' | sudo tee --append /rw/config/rc.local
echo 'modprobe ip6_udp_tunnel' | sudo tee --append /rw/config/rc.local
echo 'insmod /rw/config/wireguard.ko' | sudo tee --append /rw/config/rc.local

# Make the file executable
sudo chmod +x /rw/config/rc.local

# Test it
sudo /rw/config/rc.local

Restart the AppVM using these commands in dom0:

user@dom0:~$
1
2
qvm-shutdown sys-vpn
qvm-start sys-vpn

Set up the DNS and anti-leak firewall rules

Once the sys-vpn AppVM has restarted, download or create the a startup hook file /rw/config/qubes-ip-change-hook

#!/bin/sh
ns=$(cat /etc/resolv.conf | grep -v '^#' | grep nameserver | awk '{print $2}')
NS1=$(echo ${ns} | cut -d " " -f 1)  # 10.139.1.1
NS2=$(echo ${ns} | cut -d " " -f 2)  # 10.139.1.2

NS_MULLVAD_PRIVATE=10.8.0.1
NS_MULLVAD_PUBLIC=193.138.219.228

iptables -F OUTPUT
iptables -I FORWARD -o eth0 -j DROP
iptables -I FORWARD -i eth0 -j DROP
iptables -F PR-QBS -t nat
iptables -A PR-QBS -t nat -d $NS1 -p udp --dport 53 -j DNAT --to $NS_MULLVAD_PUBLIC 
iptables -A PR-QBS -t nat -d $NS1 -p tcp --dport 53 -j DNAT --to $NS_MULLVAD_PUBLIC 
iptables -A PR-QBS -t nat -d $NS2 -p udp --dport 53 -j DNAT --to $NS_MULLVAD_PUBLIC
iptables -A PR-QBS -t nat -d $NS2 -p tcp --dport 53 -j DNAT --to $NS_MULLVAD_PUBLIC 
user@sys-vpn:~$
1
2
# Make it executable
sudo chmod +x /rw/config/qubes-ip-change-hook

Install Mullvad VPN config files

The Mullvad VPN config files need to be installed in the TemplateVM, but can only be downloaded using an AppVM. Download them then copy them across to the TemplateVM.

NB! Do this before activating the above sys-vpn AppVM hook script!

user@sys-vpn:~$
1
2
3
4
5
6
7
8
# Download the installation script and make it executable
curl -LO https://mullvad.net/media/files/mullvad-wg.sh && chmod +x ./mullvad-wg.sh

# Install the config files (these will only exist ephemerally in the AppVM)
./mullvad-wg.sh

# Copy the files to the TemplateVM ('d9-template')
qvm-copy-to-vm d9-template /etc/wireguard

TemplateVM (a second time)

Copy Mullvad VPN files to /etc/wireguard

user@d9-template:~$
1
2
# Copy the files
sudo mv ~/QubesIncoming/sys-vpn/wireguard /etc/

To pass this update to dependant AppVMs, shut down the TemplateVM and restart the AppVM using these commands in dom0:

user@dom0:~$
1
2
3
4
5
6
# Shut down the TemplateVM
qvm-shutdown d9-template

# Restart the AppVM
qvm-shutdown sys-vpn
qvm-start sys-vpn

All done!

Finalise

Test it!

All Wireguard commands need to be executed in the sys-vpn AppVM.

user@sys-vpn:~$
1
2
3
4
5
# Manually activate the DNS hook
sudo /rw/config/qubes-ip-change-hook

# Use wg-quick to activate a connection ('mullvad-se1')
sudo wg-quick up mullvad-se1

Make it permanent

Add the activation command to /rw/config/rc.local to connect at VM startup. There’s no need to include the hook script, as this will be executed automatically by QubesOS.

user@sys-vpn:~$
1
echo 'wg-quick up mullvad-se1' | sudo tee --append /rw/config/rc.local

Diagnostics and Wireguard Commands

Is my Mullvad connection active?

Check this using these commands in the sys-vpn AppVM:

user@sys-vpn:~$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Check Wireguard status
sudo wg show

#> interface: mullvad-ca1
#>   public key: Tbzsvqmx0kXLtb+2EZnb46gaY3jdLrVIVeJRYR22QTE=
#>   private key: (hidden)
#>   listening port: 52272
#>   fwmark: 0xca6c
#>
#> peer: BQVUyn4F0NlbALK0ksCi4pY9d6/adMaXPdtjKgoL41E=
#>   endpoint: 198.144.156.48:51820
#>   allowed ips: 0.0.0.0/0, ::/0
#>   latest handshake: 2 minutes, 8 seconds ago
#>   transfer: 109.45 MiB received, 5.44 MiB sent

If results like the above are returned, you’re connected! If you’re not connected, the command will present no output.

Change to a different exit node

user@sys-vpn:~$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Shut down existing connections
sudo wg-quick down mullvad-se1
#
#> [#] 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 mullvad-se1
#> [#] resolvconf -d mullvad-se1
#> Too few arguments.
#> Too few arguments.

# Connect to your chosen connection point (in this case, mullvad-se1)
sudo wg-quick up mullvad-se1
#
#> [#] ip link add mullvad-se1 type wireguard
#> [#] wg setconf mullvad-se1 /dev/fd/63
#> [#] ip address add 10.99.11.145/32 dev mullvad-se1
#> [#] ip address add fc00:bbbb:bbbb:bb01::b91/128 dev mullvad-se1
#> [#] ip link set mtu 1420 dev mullvad-se1
#> [#] ip link set mullvad-se1 up
#> [#] resolvconf -a mullvad-se1 -m 0 -x
#> Too few arguments.
#> Too few arguments.
#> [#] wg set mullvad-se1 fwmark 51820
#> [#] ip -6 route add ::/0 dev mullvad-se1 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 mullvad-se1 table 51820
#> [#] ip -4 rule add not fwmark 51820 table 51820
#> [#] ip -4 rule add table main suppress_prefixlength 0

Other networking diagnostics

See this post: Linux Network Diagnostics Cheetsheet

References and Credits

Thanks to the following publishers and resources, without whose articles I wouldn’t have been able to complete my own setup nor this guide.