Skip to content

Instantly share code, notes, and snippets.

@machuu
Last active June 16, 2024 09:49
Show Gist options
  • Save machuu/7663aa653828d81efbc2aaad6e3b1431 to your computer and use it in GitHub Desktop.
Save machuu/7663aa653828d81efbc2aaad6e3b1431 to your computer and use it in GitHub Desktop.
Workaround for WSL2 network broken on VPN

Overview

Internet connection and DNS routing are broken from WSL2 instances, when some VPNs are active.

The root cause seems to be that WSL2 and the VPN use the same IP address block, and the VPN routing clobbers WSL2's network routing.

This problem is tracked in multiple microsoft/WSL issues including, but not limited to:

Change WSL2 subnet (Preferred Solution)

Permanently changing the IP address block used by WSL2 appears to prevent the routing conflict that breaks WSL2 networking.

More details and instructions in this gist: (Thanks @mikegerber for the explanation)

Interface Metric solution

Below is my original solution of modifying the VPN interface metric each time the VPN connects

Preferably, use the WSL subnet change above for a permanent fix.

The workaround breaks down into two problems:

  1. Network connection to internet
  2. DNS in WSL2

Network connection

When the VPN connection is active, network traffic out of WSL2 is not passed to the internet.

Changing the Interface Metric 1 -> 6000 for AnyConnect VPN Adapter resolves the connection issue, but this has to be done after each time the VPN connects.

By default, the Interface Metrics for AnyConnect are:

  • IPv6: 6000
  • IPv4: 1

ping times out from WSL Shell.

Changing the Interface Metrics for AnyConnect to:

  • IPv6: 6000
  • IPv4: 6000

ping to IP Addresses succeed, but still no DNS Resolution.

DNS Resolution

When the VPN is active, the autogenerated /etc/resolv.conf does not work. The list of nameservers must be manually built to include some sane default DNS Name Servers and the DNS from the VPN.

First, disable automatically generating /etc/resolv.conf. Add the following configuration, or create the file if it doesn't exist. The path to this file is from the shell prompt of your WSL2 instance.

/etc/wsl.conf

[network]
generateResolvConf = false

Next, manually add the corportate DNS Server as the first nameserver in /etc/resolv.conf.

/etc/resolv.conf

nameserver <corporateDNS1>
nameserver <corporateDNS2>
nameserver 1.1.1.1

To get <corporateDNS> addresses, use ipconfig /all from CMD or Powershell prompt, and check the details of the VPN adapter:

Description . . . . . . . . . . . : Cisco AnyConnect Secure Mobility Client Virtual Miniport Adapter for Windows x64
Physical Address. . . . . . . . . : XX-XX-XX-XX-XX-XX
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
IPv6 Address. . . . . . . . . . . : xxxx:xxxx:xxxx:xxxx(Preferred)
Link-local IPv6 Address . . . . . : xxxx:xxxx:xxxx:xxxx(Preferred)
IPv4 Address. . . . . . . . . . . : 10.20.30.40(Preferred)
Subnet Mask . . . . . . . . . . . : 255.255.255.255
Default Gateway . . . . . . . . . : ::
                                    0.0.0.0
DHCPv6 IAID . . . . . . . . . . . :
DHCPv6 Client DUID. . . . . . . . : 
DNS Servers . . . . . . . . . . . : 123.45.67.89    <- Corporate DNS 1
                                    123.45.67.90    <- Corporate DNS 2
Primary WINS Server . . . . . . . : xxx.xx.xxx.xx
NetBIOS over Tcpip. . . . . . . . : Enabled

Automatically update Interface Metric

To automate this, I put the PS command in a script and created a Scheduled Task to run every time there is a network change.

Save the script in a file

First, create the script. I have a 'scripts' directory in my Windows user home, so I put it at:

%HOMEPATH%\scripts\UpdateAnyConnectInterfaceMetric.ps1

Get-NetAdapter | Where-Object {$_.InterfaceDescription -Match "Cisco AnyConnect"} | Set-NetIPInterface -InterfaceMetric 6000

You can save it where you want, just make sure to use that path in step 13 below.

Create the scheduled task:

  1. Open 'Task Scheduler'
  2. Click "Create Task" on Right Sidebar
  3. Name: Update Anyconnect Adapter Interface Metric for WSL2
  4. Set Security Options
    • Check box: 'Run with highest priveleges'
  5. Select 'Triggers' Tab
  6. Click 'New' at bottom of Window
  7. Open 'Begin the task' drop-down
  8. Select 'On an Event'
  9. Configure Event:
    • option 1: Trigger on any Network Change
      • Log: 'Microsoft-Windows-NetworkProfile/Operational'
      • Source: 'NetworkProfile'
      • Event ID: '10000'
    • option 2: Trigger only when AnyConnect Client successfully connects to VPN
      • Anyconnect 4.x
        • Log: 'Cisco AnyConnect Secure Mobility Client'
        • Source: 'acvpnagent'
        • Event ID: '2039'
      • Anyconnect 5.x
        • Log: 'Cisco Secure Client - AnyConnect VPN'
        • Source: 'csc_vpnagent'
        • Event ID: '2039'
  10. Click 'OK'
  11. Select 'Actions' Tab
  12. Click 'New'
  13. Configure Action:
    • Action: 'Start a Program'
    • Program/script: 'Powershell.exe'
    • Add arguments: '-ExecutionPolicy Bypass -File %HOMEPATH%\scripts\UpdateAnyConnectInterfaceMetric.ps1'
  14. Click 'OK'
  15. Select 'Conditions' Tab
  16. Uncheck box:
    • Power -> Start the task only if the computer is on AC Power
  17. Click 'OK'

When AnyConnect finishes connecting, a Powershell window pops up for a couple seconds and WSL can reach the network.

@pypycodes
Copy link

Tested OK. Flawless

@bisand
Copy link

bisand commented Nov 4, 2022

It worked perfectly! Thanks!

@luisbunuel
Copy link

Have you tried the option in the Cisco AnyConnect client "Allow local (LAN) access when using VPN (if configured)" ?
It works for me, you dont need to modify the interfaces metrics and let WSL to manage the resolv.conf file.

@573
Copy link

573 commented Nov 18, 2022

Have you tried the option in the Cisco AnyConnect client "Allow local (LAN) access when using VPN (if configured)" ? It works for me, you dont need to modify the interfaces metrics and let WSL to manage the resolv.conf file.

This works when the admins in your enterprise enabled that switch and also in certain cases internal resources those visible only to enterprise members are invisible when using the switch.

@cwsmith-160
Copy link

In DNS Resolution the /etc/resolv.conf link should be removed, a new file should be created, and then be made immutable e.g.,

sudo rm /etc/resolv.conf
sudo bash -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf'
sudo chattr +i /etc/resolv.conf

sudo bash -c 'echo "[network]" > /etc/wsl.conf'
sudo bash -c 'echo "generateResolvConf = false" >> /etc/wsl.conf'

See the following for more details
microsoft/WSL#5420

@Tarun097
Copy link

Thanks @machuu , works for me

@5ebbe
Copy link

5ebbe commented Jan 12, 2023

Wonderful! This really fixed it for me (tried alot of other things that did nothing)!

A tip I have is to add '-Window Minimized' at the beginning of the arguments. This cuts down the time the powershell screen is shown whenever the event is triggered. I understand there are other ways but they seem to be more advanced than just adding an argument.

My goal would be it never show at all or just start minimized from the beginning. Any ideas on this will be greatly appreciated.

Thanks :)

@mrudinal
Copy link

Thank you very much. I was able to configure internet and dns resolution for my wsl2 instance.
This guide worked perfectly

@TW4177
Copy link

TW4177 commented Apr 20, 2023

Great instructions. I used @regwhitton's approach proactively, and found I needed to use """ around the Cisco AnyConnect match string in the scheduled task arguments (looks like it does a layer of de-quoting on them.) Thanks!

@Pit-Storm
Copy link

You shouldn't change the Interface-Metric due to the than different routing. See the following blogpost for explanation: https://janovesk.com/wsl/2022/01/21/wsl2-and-vpn-routing.html
TLDR: If it works, it doesn't mean that it doesn't have side effects. And it's not only solving the thing that you was intended to fix.

The problem of not using the correct DNS-Server is properly explained and the suggested solution should be used from networking point of view.

For the IP-Range problem you have only the following two options:

  1. Changing the routing table (see blogpost above)
  2. Changing the subnet-range that WSL is using

How second could work, is shown in this Microsoft Q&A: https://learn.microsoft.com/en-us/answers/questions/1123820/set-wsl2-subnet

TLDR: Change SubNet of WSL NAT-Router to a different one which does not collide with your Company-VPN subnet. To do so got to regedit and edit the following entries:
Path: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss
Entries: NatGatewayIpAddress and NatNetwork
Values e.g.: 19.16.0.1 and 19.16.0.0/16

Again: Please don't change the InterfaceMetric!

@Shterneregen
Copy link

Shterneregen commented Jun 28, 2023

Thanks for the great instruction!

Base on this info I wrote script that creates windows task to automatically update metric on VPN startup (gist)

  • Create CreateScheduledTask.ps1 file with content below
$taskname="Fix VPN for WSL"
$scriptName = "UpdateAnyConnectInterfaceMetric.ps1"
$commandToUpdateMetric = "Get-NetAdapter | Where-Object {`$_.InterfaceDescription -Match 'Cisco AnyConnect'} | Set-NetIPInterface -InterfaceMetric 5500"

$profilePath = $env:USERPROFILE
$scriptPath = "$profilePath\$scriptName"

if(Test-Path -Path $scriptPath){
    Write-Output "File '$scriptPath' already exists"
} else {
    New-Item -ItemType File -Path $scriptPath -Value $commandToUpdateMetric
    Write-Output "File '$scriptPath' created successfully"
}

$triggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskEventTrigger
$trigger = New-CimInstance -CimClass $triggerClass -ClientOnly
$trigger.Subscription = 
@"
<QueryList><Query Id="0" Path="Cisco AnyConnect Secure Mobility Client"><Select Path="Cisco AnyConnect Secure Mobility Client">*[System[Provider[@Name='acvpnagent'] and EventID=2039]]</Select></Query></QueryList>
"@
$trigger.Enabled = $True 

$triggers = @()
$triggers += $trigger
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit 0

$user = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -expand UserName
$action = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-Command Get-Content '$scriptPath' | PowerShell.exe -noprofile -"

Register-ScheduledTask -TaskName $taskname -Trigger $triggers -User $user -Action $action -Settings $settings -RunLevel Highest -Force
  • Run as admin command in PowerShell (in the same folder with the script file)
Get-Content .\CreateScheduledTask.ps1 | PowerShell.exe -noprofile -
  • Restart Windows
  • Launch WSL
  • Launch VPN
  • Done! Metric automatically updated

@swe9
Copy link

swe9 commented Jul 19, 2023

TLDR: Change SubNet of WSL NAT-Router to a different one which does not collide with your Company-VPN subnet. To do so got to regedit and edit the following entries: Path: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss Entries: NatGatewayIpAddress and NatNetwork Values e.g.: 19.16.0.1 and 19.16.0.0/16

Again: Please don't change the InterfaceMetric!

I was able to use those keys to put WSL into my local network. The only problem was that WSL kept getting assigned xxx.xxx.xxx.1 which happened to be my gateway on the local network. I tried several solutions from other postings, but ultimately ended up moving the gateway to xxx.xxx.xxx.9 and having DHCP start at 10. Everything seems to work nicely after that.

@vquanguit1102
Copy link

to be honest I don't know what I am doing but it works 👍

@vaskenis
Copy link

Thanks for the proposed solution, it works for me on Win11! 👍

@riosdav1
Copy link

The only solution that worked for me, thanks! But, I had to change %USERPATH% to $HOME.

@dswhite42
Copy link

dswhite42 commented Nov 15, 2023

You shouldn't change the Interface-Metric due to the than different routing. See the following blogpost for explanation:
...
For the IP-Range problem you have only the following two options:

  1. Changing the routing table (see blogpost above)

@Pit-Storm , just a note that Cisco AnyConnect (at least the way it's configured for us) is smart enough to detect an attempt to delete a route, and will automatically reinsert it back into the routing table the moment you try.

@dswhite42
Copy link

For users of Cisco AnyConnect 5.0+ (I have 5.0.03076), the Windows Event logging naming convention has changed. So if you're using Option 2 in step 9 of the instructions above (trigger the PowerShell script only after the VPN connects), the trigger should now look like this:

  • option 2: Trigger only when AnyConnect Client successfully connects to VPN
    • Log: 'Cisco Secure Client - AnyConnect VPN'
    • Source: 'csc_vpnagent'
    • Event ID: '2039'

@machuu
Copy link
Author

machuu commented Nov 15, 2023

@dswhite42,

Thanks for the 5.x info.
That step is now updated with 4.x vs 5.x Event Log details.

@cassucena
Copy link

Thanks so much ! this workaround worked fine for me!

@GrizzlyBoer
Copy link

Hello everyone,

The command "Get-NetAdapter | Where-Object {$_.InterfaceDescription -Match "Cisco AnyConnect"} | Set-NetIPInterface -InterfaceMetric 6000" works fine when VPN is freshly started.

However, if VPN is activated before the Windows logon, it doesn't work.
So VPN -> Windows Logon -> PS-Script -> WSL does not work.
I had to stop the VPN, start it again, and then it worked with the PS script. Does anyone have an idea what could be the reason for this and whether it can be circumvented?

@storkviola
Copy link

@GrizzlyBoer I have no idea, but I can confirm your workaround works for me as well.

@ZaidaEMtzMo
Copy link

@GrizzlyBoer and @storkviola: Are you using the task scheduler? Would it work if you changed the settings of the scheduler to "Run whether user is logged on or not" in the General tab?

@s-lindenau
Copy link

s-lindenau commented Dec 21, 2023

If you're also having problems with executing unsigned files like i had, you can transform the file to a direct command.
Full example in the comment by @regwhitton
Or if you want to do it yourself, see for reference: https://superuser.com/a/1080336

I would also recommend adding the minimize window parameter, as @5ebbe suggested!

Works great now, even on corporate devices with strict policies 👍
Edit: is does however seem to lose access to specific (other) sites, so i might have to check the comments by @573 for a permanent solution.

@GrizzlyBoer
Copy link

GrizzlyBoer commented Jan 30, 2024

@ZaidaEMtzMo Sorry for the late answer, we have been quite busy the last 2 months.

I've tried using the scheduler with the "Run whether user is logged on or not" and without but the problem stays the same.
If VPN is established before Windows Login it won't work until you reconnect VPN after login again.

BUT we found something different, it seems Podman Desktop has finally caught up to Docker Desktop with an option for "user-mode networking" when you create a new podman machine. If you activate it WSL, including all running instances, has full connectivity as soon as Podman is running.

At least Podman Desktop is free to use

@mikegerber
Copy link

Again: Please don't change the InterfaceMetric!

I agree. In our setup I had to set the WSL2 network to link-local addresses. Details + commands to set the IP range here:

https://gist.github.com/mikegerber/91fcea262028e09b2fd0969193c6c260

TL&DR: WSL2 and VPN networks should not overlap, set the IP address range and not the VPN interface metric.

@machuu
Copy link
Author

machuu commented Feb 28, 2024

@mikegerber,
Thanks, this does look like a cleaner and more permanent solution.
Adding a note and link to the top of this gist.

@ZaidaEMtzMo
Copy link

ZaidaEMtzMo commented Feb 28, 2024

@mikegerber Does this solution allow WSL to work when the VPN is on AND when the VPN is off?
I used the previous solution, so I would like to confirm this before I go and undo all the changes.

@mikegerber
Copy link

@mikegerber Does this solution allow WSL to work when the VPN is on AND when the VPN is off? I used the previous solution, so I would like to confirm this before I go and undo all the changes.

Yes. You need to check your DNS configuration though, because you can't use a DNS server from the VPN. Here WSL just auto-configured my laptop as DNS server:

$ cat /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 169.254.214.1

(169.254.214.0/24 is the WSL2 NAT network I configured (see my comment above), .1 is the NAT gateway = my laptop) I would recommend first checking if you have general IPv4 connectivity using ping -n 8.8.8.8 first, then fixing DNS if it needs fixing.

@mikegerber
Copy link

mikegerber commented Mar 1, 2024

(169.254.214.0/24 is the WSL2 NAT network I configured (see my comment above), .1 is the NAT gateway = my laptop) I would recommend first checking if you have general IPv4 connectivity using ping -n 8.8.8.8 first, then fixing DNS if it needs fixing.

I've added a note about DNS in my gist. WSL2 seems to bring a DNS proxy and autoconfigures it here, but I didn't find any documentation on it + I'm not a really a Windows expert.

@micheldiemer
Copy link

Anyone coming here mightwant to attach anoter HyperV switch to the WSL machine

Here is a procedure

microsoft/WSL#4799 (comment)

microsoft/WSL#4799 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment