How to Yggdrasil with Mullvad
How to Yggdrasil with MullvadMost of the time I use Mullvad as my ISP. I think Mullvad is more trustworthy than my local ISP. I configure it through NetworkManager using a simple WireGuard config. It works fine, but I miss some cool features like Post-quantum encryption, DITA, or Obfuscation, like on my phone.
So I thought, why not write this in my NixOS config:
services.mullvad-vpn.enable = true;
And… it works! With all features! But then I tried to access my server using a Yggdrasil address, and I saw this error:
ssh: connect to host 2**:****:****:****:****:****:****:**** port ***: Connection refusedPing is also not reachable:
> ping 2**:****:****:****:****:****:****:****
PING 2**:****:****:****:****:****:****:**** (2**:****:****:****:****:****:****:****) 56 data bytes
From 2**:****:****:****:****:****:****:**** icmp_seq=1 Destination unreachable: Port unreachable
From 2**:****:****:****:****:****:****:**** icmp_seq=2 Destination unreachable: Port unreachableAt first I thought my server was down again, but no – all services were available. So what was wrong? I allowed the local network for Mullvad and… wait… Yggdrasil uses 200::/7 and 300::/7 addresses, which are not public IPv6, but also not a local subnet.
And this is the problem. Mullvad can’t allow specific masks. Yes, you can use mullvad tunnel set allowed-ips, but it uses an include list, not an exclude list as I want. I searched and found a few issues about that (Bug: Kill switch applies to non-global unicast IPv6 ranges, How to use yggdrasil with vpn in same time?), but no solution. However, I found one useful clue – split tunneling.
For per-application split tunneling, Mullvad marks packets for exclusion:
This is achieved by having rules that check if traffic is marked with a connection tracking mark (0x00000f41) to get through the firewall and a meta mark (0x6d6f6c65) to route the traffic outside the tunnel.
So I needed to mark all outgoing Yggdrasil connections. Fortunately, I use nft as firewall, so I just added this rule:
table ip6 yggdrasil-mullvad {
# Mark all outgoing connections.
chain output {
type filter hook output priority 0; policy accept;
# Use ip ranges
ip6 saddr {200::/7,300::/7} ip6 daddr {200::/7,300::/7} ct mark set 0x00000f41 meta mark set 0x6d6f6c65
# Or specific interface
oifname "tun0" ct mark set 0x00000f41 meta mark set 0x6d6f6c65
}
}For NixOS users:
networking.nftables.enable = true;
networking.nftables.tables.yggdrasil = {
enable = true;
name = "yggdrasil-mullvad";
family = "ip6";
content = ''
chain output {
type filter hook output priority 0; policy accept;
ip6 saddr {200::/7,300::/7} ip6 daddr {200::/7,300::/7} ct mark set 0x00000f41 meta mark set 0x6d6f6c65
}
'';
};
And done! Now I can use these cool technologies at the same time.
P.S. Mullvad App now supports DNS on localhost, and I can still use my Unbound + dnscrypt solution.