Mullvad VPN App 4.2 setup guide

Intro

This guide explains how to setup a a VPN with Mullvad app on Qubes OS 4.2 using a Fedora template.

Mullvad app is open source and they provide repositories for fedora / debian, there are no documentation to deploy the App, only an official page explaining how to setup WireGuard, or a community guide for Mullvad WireGuard without the App

The App supports OpenVPN and WireGuard tunnels. Bridge support is available for OpenVPN and WireGuard has obfuscation available, these features are useful if you are unable to connect due to censorship.

They seem also a legit service to use as per the trustable source https://www.privacyguides.org/en/vpn/

Setup

Qube creation

  • Create a dedicated qube for the vpn
    • Name it as you want (I will name it sys-vpn-mullvad-app)
    • Choose type “Standalone” with the template fedora-39 (or xfce flavor, minimal flavor should work too)
    • Check “provide network access to other qubes” in the Advanced settings tab
  • In the qube settings
    • Give it 800 MB of memory minimum
    • Add the service qubes-firewall

Qube configuration

  • Start the qube
  • In the qube terminal, run:
sudo dnf config-manager --add-repo https://repository.mullvad.net/rpm/stable/mullvad.repo
sudo dnf install mullvad-vpn
  • Reboot the qube

Mullvad App

Start the App with /opt/Mullvad\ VPN/mullvad-gui or add “Mullvad VPN” application in the qube menu entry that should be available after the installation process.

The Mullvad VPN app should start without issue:

  • Enter your credentials
  • Configure the App as you want

Auto start at boot can be enabled in the settings.

Fix DNS

:information_source: The App uses several custom DNS that change based on the options selected by the user, but this doesn’t propagate to the qubes behind it, resulting in long latency times for resolving hostnames. The following script forces all DNS requests to automatically go through the selected custom DNS server.

:warning: The script depends on inotify, which can be installed with the inotify-tools package.

Add this to /usr/local/bin/mullvad-dns.sh

#! /usr/bin/env bash

update_dns() {
	# mullvad_on: 0 -> off, 1 -> on
	mullvad_on=$([[ $(grep -v -c "nameserver \+10.139" /etc/resolv.conf) -gt 0 ]] && echo 1 || echo 0)

	if [[ $mullvad_on -eq 1 ]]; then

		echo "Mullvad is on"

		# get the mullvad dns ip address. First one is used if there is more than one.
		mullvad_dns_ip=$(grep "nameserver" < /etc/resolv.conf| awk '{print $2}' | head -n 1)

		# delete all the lines defined in dnat-dns
		sudo nft flush chain ip qubes dnat-dns

		# forward all dns requests to mullvad dns servers
		sudo nft add rule ip qubes dnat-dns meta l4proto { tcp, udp } ip daddr { 10.139.1.1, 10.139.1.2 } th dport 53 dnat to "$mullvad_dns_ip"

	else

		echo "Mullvad is off"

		# get qubes nameserver ip addresses
		nameserver_ips=$(grep "nameserver" < /etc/resolv.conf| awk '{print $2}')

		# delete all the lines defined in dnat-dns
		sudo nft flush chain ip qubes dnat-dns

		# add rule to forward dns requests to qubes nameservers
		for ip in $nameserver_ips; do
			sudo nft add rule ip qubes dnat-dns ip daddr "$ip" udp dport 53 dnat to "$ip"
			sudo nft add rule ip qubes dnat-dns ip daddr "$ip" tcp dport 53 dnat to "$ip"
		done

	fi
}

update_dns
# check for /etc/resolv.conf content change
inotifywait -m -q -e close_write /etc/resolv.conf | while read -r;
do
	update_dns
done

Make the script executable

sudo chmod +x /usr/local/bin/mullvad-dns.sh

And add this to run the script at boot time to /rw/config/rc.local

/usr/local/bin/mullvad-dns.sh &

Avoid issues with WireGuard

:information_source: WireGuard tunnels can trigger a MTU issue in the network, in short it could make some websites not working (like duckduckgo) because of too big packet sizes. This is a common issue with WireGuard VPNs.

Add this to /rw/config/qubes-firewall-user-script

nft add rule ip qubes custom-forward tcp flags syn / syn,rst tcp option maxseg size set rt mtu

This will automatically ensure that the qubes network packets will fit in a WireGuard network packet and will make things works©.

Killswitch configuration (easy method)

:information_source: You may want to force all qubes traffic to go through the VPN and block non-VPN traffic. Mullvad app offers a killswitch but the app could still crash and the killswitch wouldn’t be guaranteed to work.

:information_source: This easy method plays well with the App as you don’t need to configure a firewall rule for each server/port. However, if the qube gets compromised it’s possible to disable the rule, if you want more security against this threat you should use qvm-firewall.

Add the rules below in /rw/config/qubes-firewall-user-script in the qube:

# Prevent the qube to forward traffic outside of the VPN
nft add rule qubes custom-forward oifname eth0 counter drop
nft add rule ip6 qubes custom-forward oifname eth0 counter drop
5 Likes

Have you looked at this guide which uses an AppVM instead of standalone?

Nope, thanks for sharing.

If someone only uses a single instance of Mullvad app, the template increases the setup complexity, but it’s useful when you need multiple instances.

I’m curious to see the DNS tricks works, I’ve had no success with /usr/lib/qubes/qubes-setup-dnat-to-ns

Mullvad uses many different DNS addresses depending on what the user selects in the app. The default Wireguard DNS address is 10.64.0.1, but it can also be in the 100.64.0.x range if the user chooses to use the DNS filtering rules.
For OpenVPN, it’s different too. Each port gives a different subnet and the DNS is always 10.x.0.1. DNS filtering (100.64.0.x) also works with OpenVPN.

Since it’s hard to know what protocol the user will be using and if they will be using any DNS filtering rules, I would recommend adding the following script to update the DNS accordingly:

#! /usr/bin/env bash

update_dns() {
	# mullvad_on: 0 -> off, 1 -> on
	mullvad_on=$([[ $(grep -v -c "nameserver \+10.139" /etc/resolv.conf) -gt 0 ]] && echo 1 || echo 0)

	if [[ $mullvad_on -eq 1 ]]; then

		echo "Mullvad is on"

		# get the mullvad dns ip address. First one is used if there is more than one.
		mullvad_dns_ip=$(grep "nameserver" < /etc/resolv.conf| awk '{print $2}' | head -n 1)

		# delete all the lines defined in dnat-dns
		sudo nft flush chain ip qubes dnat-dns

		# forward all dns requests to mullvad dns servers
		sudo nft add rule ip qubes dnat-dns meta l4proto { tcp, udp } ip daddr { 10.139.1.1, 10.139.1.2 } th dport 53 dnat to "$mullvad_dns_ip"

	else

		echo "Mullvad is off"

		# get qubes nameserver ip addresses
		nameserver_ips=$(grep "nameserver" < /etc/resolv.conf| awk '{print $2}')

		# delete all the lines defined in dnat-dns
		sudo nft flush chain ip qubes dnat-dns

		# add rule to forward dns requests to qubes nameservers
		for ip in $nameserver_ips; do
			sudo nft add rule ip qubes dnat-dns ip daddr "$ip" udp dport 53 dnat to "$ip"
			sudo nft add rule ip qubes dnat-dns ip daddr "$ip" tcp dport 53 dnat to "$ip"
		done

	fi
}

update_dns
# check for /etc/resolv.conf content change
inotifywait -m -q -e close_write /etc/resolv.conf | while read -r;
do
	update_dns
done
1 Like

Mullvad has their own guide specifically for Qubes: Mullvad on Qubes OS 4

However it doesn’t allow to easily change VPN server on the fly

I would feel better if a killswitch was whitelist-based instead of blacklist-based like here. Maybe it would be better to drop everything by default and only allow packets on Mullvad’s interface?

It was already linked in the guide, but they don’t cover the App setup.

if you need a strong killswitch, do it with qvm-firewall that will apply the rules in the qube’Netvm, it’s way more reliable.

Thank you for this tutorial,

you forgot quotation marks in the dns fix part

New Qubes user here. I struggled for a bit and want to clarify what I had trouble with for any other less experienced users.

I was thinking the DNS script wasn’t working because Mullvad’s leak checker kept telling me I was leaking. This was not because of the script DVM wrote, which works, but was actually from a Firefox browser setting.

When you do the leak check on mullvad’s site and read the DNS leak guide, it has the answer. You have to disble “DNS over HTTPS” setting in Firefox ESR. By default it is enabled and made it so I was using a Cloudflare DNS server nearby the VPN server.

Also it wasn’t clear to me by reading this thread, but the script DVM wrote needs to be run in the sys-mullvad-vpn-app qube terminal, not the Dom0 terminal. The script needs to be run again if you change the settings in the DNS blocking inside the Mullvad app, however it seems like you don’t need to run the script again just to change the VPN server if it has the same DNS blocking settings. The script also needs to be run again after reboot.

Could someone give specific instructions on how to set up the killswitch in the qvm-firewall? I barely know what I’m doing and don’t want to break anything since I have the VPN working now.

Thank you all.

Big thanks for this guide. This is easy and fast and the gui helps a lot (edit. internet not working on browsers, but telegram app works?)

nothing happens when i put this command in console.

what did you add to that file? what do you expect when you run it?

I did this guide exactly same, same fedora version everyting.
Now the proxy vm which has mullvad gui app seems to somehow provice internet to app vm, because telegram app is working via mullvad vpn, and firefox mullvad extensions shows “connected to mullvad” status, and says the proxy is working… but the firefox can’t connect to any websites. Same with chrome and other app vms.

Wonder what is causing this?

can you try ping 9.9.9.9 and ping quad9.net in a terminal and say if both works?

If quad9.net doesn’t work, it’s likely there is a DNS issue. There is a DNS fix required in the guide.

I have the exact same problem too.
ping 9.9.9.9 in the appvm works fine but ping quad9.net does not work ?

did you do the DNS fix step?

yes i added the script with the DNS fix but still not working on the browsers in appvm

what is the content of /etc/resolv.conf in the Mullvad qube?

nameserver 100.64.0.23

I just found a typo in my guide,

I wrote

DNS=10.64.01

instead of

DNS=10.64.0.1

Can you try to change this and reboot the mullvad qube? If it still doesn’t work, try 100.64.0.23 instead of 10.64.0.1

yeah i noticed the typo earlier, i fixed that but still no go, I also changed the Ip to 100.64.0.23, but still not working ?