How to Yggdrasil with Mullvad

How to Yggdrasil with Mullvad
Posted on

Most 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 refused

Ping 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 unreachable

At 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.

Smolyakov Ivan