I noticed that WSL looses network connection when I turn on VPN on the host. In my case, the issue happend for Cisco AnyConnect VPN, but from the solution, it seems, the same may happen with any other VPN service.

There are plenty of open threads related to the issue:

1. Lower VPN network adapter priority

Windows implements a prioritization mechanism for network devices, and we want the VPN device to appear with lower priority than WSL routes, which by default are configured with metric value 5256. This will fix the network connectivity but not DNS.

NOTE: the metric resets on reconnect, so do this change on each reconnect to VPN. You can call this Powershell command from inside WSL, as part of the DNS script from Step 2. It may be practical if you own admin rights for your windows user.

2. Configuring DNS

VPN usually routes traffic via internal DNS services. If this is the case for you, the DNS servers in WSL should be updated and it doesn’t happen automatically.

A powershell called from inside WSL can be used to extract the new DNS config:

    /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command '
    $ErrorActionPreference="SilentlyContinue"
    Get-NetAdapter -InterfaceDescription "Cisco AnyConnect*" | ?{ $_.Status -eq "Up" } | Get-DnsClientServerAddress | Select -ExpandProperty ServerAddresses
    Get-NetAdapter | ?{-not ($_.InterfaceDescription -like "Cisco AnyConnect*") -and ($_.Status -eq "Up") } | Get-DnsClientServerAddress | Select -ExpandProperty ServerAddresses
    ' | \
            awk '{print "nameserver", $1}' | \
            tr -d '\r'

    /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command '
    $ErrorActionPreference="SilentlyContinue"
    Get-DnsClientGlobalSetting | Select-Object -ExpandProperty SuffixSearchList
    ' | \
            awk '{print "search", $1}' | \
            tr -d '\r'

DNS servers, i.e. /etc/resolv.conf, should be changed when we switch the VPN on and off. To avoid implementing a complex logic of parsing resolv.conf, I’d suggest a solution based on resolvconf tool.

Prevent WSL from overwriting /etc/resolv.conf

WSL forcefully restores the symlink from /mnt/wsl/resolv.conf to /etc/resolv.conf. We want to disable this behavior, since we want resolv.conf to go through resolvconf utility.

Edit your wsl config: /etc/wsl.conf. Add these lines:

[network]
generateResolvConf = false

Now restart WSL for changes to get into action:

# powershell
wsl --shutdown

Configuring DNS with Resolconf

Resolvconf will use /etc/wsl/resolv.conf as base config, but will prepend custom DNS records which we will dynamically extract from the host.

First, install it:

apt install resolvconf

Resolvconf maintains its copy of resolvconf, which should be symlinked from /etc/resolv.conf:

mv /etc/resolv.conf /etc/resolv.conf.bak
ln -s /run/resolvconf/resolv.conf /etc/resolv.conf

WSL mounts a custom resolv.conf pushed from the host. We want to specify it as a base config for resolvconf:

ln -sf /mnt/wsl/resolv.conf /etc/resolvconf/resolv.conf.d/original.resolvconf
ln -sf /mnt/wsl/resolv.conf /etc/resolvconf/resolv.conf.d/base

Update head of resolv.conf based on the current host configuration. You will have to run this script each time you connect/disconnect from VPN:

#!/bin/bash
set -x

function get_dns_config() {
    /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command '
    $ErrorActionPreference="SilentlyContinue"
    Get-NetAdapter -InterfaceDescription "Cisco AnyConnect*" | ?{ $_.Status -eq "Up" } | Get-DnsClientServerAddress | Select -ExpandProperty ServerAddresses
    Get-NetAdapter | ?{-not ($_.InterfaceDescription -like "Cisco AnyConnect*") -and ($_.Status -eq "Up") } | Get-DnsClientServerAddress | Select -ExpandProperty ServerAddresses
    ' | \
            awk '{print "nameserver", $1}' | \
            tr -d '\r'

    /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command '
    $ErrorActionPreference="SilentlyContinue"
    Get-DnsClientGlobalSetting | Select-Object -ExpandProperty SuffixSearchList
    ' | \
            awk '{print "search", $1}' | \
            tr -d '\r'
}

get_dns_config > /etc/resolvconf/resolv.conf.d/head
resolvconf -u

Since we plan to run it quite frequently, lets store this script to /opt/bin/reset-vpn.sh. (Remember to make it executable with chmod +x /opt/bin/reset-vpn.sh)

This should work now, but will fail upon restart, because resolvconf should be called on system start.

Startup script for resolvconf

Systemd is not enabled by default in WSL, so standard systemctl enable resolvconf won’t work.

It is possible to enable systemd for WSL ( Microsoft, StackExchange ). There is, however, a simpler way to add boot commands. Add the following to /etc/wsl.conf:

[boot]
command = resolvconf --enable-updates; /opt/bin/reset-vpn.sh;

Now you are all set. Briefly:

  • Try restarting WSL with wsl --shutdown and check that /etc/resolv.conf is updated.
  • Then start the VPN and call /opt/bin/reset-vpn.sh.
  • Remember to increase the network adapter metric if you didn’t add this command to reset-vpn.sh.
  • You should be able to ping google.com!

Cheers!