Effective WSL
Due to restrictions in many workplaces, a lot of developers are bound to using Microsoft Windows.
While Windows itself is a good and stable operating system, out of all major operating systems I would argue it is
the worst for power users and especially programmers, because it lacks a good and well integrated
shell and CLI utilities like the ones we can find on Unix based systems.
To mitigate this problem, Microsoft created WSL, now in its second major iteration with the goal to run
a fast Linux VM with good interoperability with Windows.
WSL is a solid system, but in order to utilize it to its fullest extent, we need to take a look
under the hood and examine its strengths and weaknesses.
Today I want to show you how to tweak WSL to make it much more usable, so that it can truly
become the center piece of your software development activities on Windows.
Hyper V
WSL gets managed by the Microsoft-developed Hypervisor Hyper-V. If you have ever set up a VM using VMWare, you probably already have a feeling what a Hypervisor is. A Hypervisor is a software layer that sits between an OS and the hardware and configures the hardware dedicated to the VM, which includes the amount of CPU, RAM and disk space. It also configures things like Networking and access to periphery. Essentially its job is to isolate an operating system so it can run alongside others. Generally we differentiate between two types of Hypervisors - Type 1 and Type 2.
While type 1 Hypervisors run directly on the hardware and manage every OS running on the machine, type 2 Hypervisors run as a child process of one central “Host OS”. Generally this makes Type 1 Hypervisors noticeably faster than their counterpart, because not every hardware interaction needs to be go through the Host OS.
Hyper-V is built around the concept of type 1 virtualization, which is why its performance is way better than something like VMWare VMs (Type 2) allow for. That’s also why big cloud providers like AWS also use Type 1 Hypervisors to provide their high performance VPS instances like EC2.
Windows does a lot of configuration on Hyper-V to make WSL integrate better with Windows, which may even convey
the feeling that WSl is managed by a Type-2 Hypervisor.
The important thing to understand is that WSL is a system running independent from
Windows - it acts as its own logical host with its own memory, storage, network interfaces and so on.
Knowing this makes us understand the seeming quirks of WSL much more.
Cross File System access
Cross file system access is painfully slow. This means it is highly advisable to keep all files inside the same system’s file system you want to work with. Usually we will want to work in WSL, therefore we need to put all of our source code into the WSL file system. But why is cross file system access so slow and how can we still manage to share files efficiently if we need to?
The problem is that WSL and Windows use different file systems. Windows usually uses NTFS
, while Linux
generally uses ext4
and the major problem is that these are inherently incompatible. For example
- they have an entirely different permission management methodology
- ext4 uses case-sensitive file names, while NTFS does not (in Windows you actually can’t create a file named
file.txt
andFile.txt
in the same directory in case you haven’t tried before)
When we access the files stored inside the drive of another OS, every operation has to be run through a translation
layer, which roughly means that Windows and WSL both run file servers, which serve all their drives’ contents to their
counterpart - that’s the part for the slowness. Due to the arising inefficiencies running a simple git status
on a medium
size repository can take half a minute.
Knowing this limitation, we still have easy access to both filesystems. We can access
- Windows files inside WSL by accessing the mount point
/mnt/<drive>
- Linux files from Windows at
\\wsl$\<distro_name>
Personally, I find it incredibly useful to symlink some of my Windows folders to WSL, for example downloads
.
Most of the stuff I download from the net or people send me via Teams lands in my downloads
folder.
Having a symlink to that location in place makes downloaded files better accessible for development related activities.
The symlink can be created like so:
ln -s /mnt/c/Users/Steff/Downloads /home/steff/downloads
Afterwards, one can easily reference the downloads folder by ~/downloads
.
Even though cross file system access is slow, as long as the number of files is low and not all of them have to be accessed
at the same time (looking at you git status
👀), this is not a big issue.
Networking Deep Dive
There are 2 networking modes for WSL
- The default one, which uses Windows as a NAT Gateway
mirrored
Networking mode - A newer alternative, which replicates the Windows network interfaces directly into WSL
NAT Networking Mode (the default)
Even though Windows and the WSL-OS run independent from one another, we need some way for the two systems to communicate easily. The way this is implemented is through a VLAN, in which both WSL and Windows take part. We can examine the respective network interfaces and their IP addresses in both systems.
In Windows, we can execute ipconfig /all
in Powershell to get a list of all the available network interfaces. Among the list,
we can find the interface for the WSL VLAN:
Ethernet adapter vEthernet (WSL (Hyper-V firewall)):
Connection-specific DNS Suffix:
Link-local IPv6 Address . . . . . : fe80::db54:7294:3c2e:2756%21
IPv4 Address . . . . . . . . . . : 172.24.208.1
Subnet Mask . . . . . . . . . . . : 255.255.240.0
The adapter has the IP 172.24.208.1
. The IP ends with 1, which makes this interface
the default Gateway of the associated VLAN.
Inside WSL, we can obtain the network interface list by issuing ip addr
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1492 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:88:b9:88 brd ff:ff:ff:ff:ff:ff
altname enx00155d88b988
inet 172.24.222.66/20 brd 172.24.223.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fe88:b988/64 scope link
valid_lft forever preferred_lft forever
By default, there will only be two network interfaces visible from within the WSL - The eth0
interface and a loopback interface.
We can observe that the eth0
interface has an IP out of the address range of the VLAN, for which
the Windows adapter poses as the default gateway.
This means that WSL has no other means to communicate with the internet, but to go through the
Windows host. To completely verify this observation,we can take a look at the routing table using
ip route
, which gives us the following output
default via 172.24.208.1 dev eth0 proto kernel
[...]
The important part is the first line, which starts with default via
. This line alone lets us
verify our deduction that all traffic is routed through Windows. This means that windows acts
as a NAT Gateway (that’s super important to know).
Because we already understand how Type 1 virtualization works, we know that both systems are their own independent hosts.
Now with the knowledge how networking between these hosts works, it probably does not come as a surprise
that when using localhost
in a pure Type 1 virtualized environment, it points to the host of the instance making the request,
which in our case would be WSL (and not Windows).
WSL being nicely integrated however, comes with the option localhostforwarding
, which is by default set to true.
The option configures automatic port forwarding from the Windows localhost
to the WSL. Note
however, that no matter how you configure your firewall in WSL, this port is only available
from Windows. If you want to expose your port over a real network (for testing purposes), you need
to configure this on the Windows side.
localhostforwarding
also only works in one direction. By default open Windows ports are
not forwarded to WSL. To achieve this we would again have to do manual work and configure port forwarding.
Mirrored networking mode
Since Windows 11 22H2, there is a mirrored networking mode available. In this mode, Windows network interfaces are replicated directly into WSL. The idea is that when both windows and WSL use the same network interfaces we get a seamless experience - there is no more distinction between WSL and Windows, they both resolve to the same logical host and we don’t have to think about networking anymore.
We can enable mirrored mode in the global WSL settings file, located at C:\Users\<your-user>\.wslconfig
like so:
[wsl2]
networkingMode=mirrored
Afterwards we need to make sure to restart WSL using wsl.exe --shutdown
. Upon the next boot, we can observe
how this setting has changed networking.
Let’s run ip addr
in WSL:
eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1492 qdisc mq state UP group default qlen 1000
link/ether 04:7c:16:00:55:88 brd ff:ff:ff:ff:ff:ff
altname enx047c16005588
inet 192.168.178.73/24 brd 192.168.178.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
and let’s run ipconfig /all
in Powershell:
Ethernet-Adapter Ethernet:
Connection specific DNS-Suffix : fritz.box
IPv4-Address . . . . . . . . . . : 192.168.178.73(Preferred)
Well look at this. While the VLAN interface we observed earlier still exists, the IP address
of the WSL eth0
interface is now the same as of the Windows host - amazing.
This is my personally preferred way of doing networking, because it takes away the need to think
of WSL and Windows as two separate hosts. You might be thinking why I even explained the NAT
mode in so much detail if there is a better option available. The problem is that not every
developer is using mirrored
and you have to account for that in your software. If you have
several services running in your WSL, you should not reference them using localhost, but rather
with the IP of your WSL instance to be sure. These kinds of different system configurations
make it (annoyingly) necessary to get to terms with all eventualities.
There is one more thing we need to consider when we want to make our applications reachable to other devices in the system - The Hyper-V firewall. It’s just a regular firewall really, which blocks all inbound traffic to the WSL instance in its default configuration. Based on your personal needs and security requirements we have the option
to allow inbound traffic on all ports:
Set-NetFirewallHyperVVMSetting -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -DefaultInboundAction Allow
or to allow inbound traffic on specific ports, e.g. 80:
New-NetFirewallHyperVRule -Name "MyWebServer" -DisplayName "My Web Server" -Direction Inbound -VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -Protocol TCP -LocalPorts 80
The command you decide for needs to be issued in a Powershell instance with Administrator privileges.
Calling Windows programs from WSL
Windows has done a nice job making it possible to call Windows executables from WSL. Actually the entire
Windows PATH
variable is added to the PATH
variable of the WSL instance. We can nicely observe this
by printing all WSL path entries to a new line: echo "$PATH" | tr ':' '\n'
.
It is even possible to mix Windows and Linux programs. Granted, most of the time this is not useful,
but one of the programs I reach for often is clip.exe
, which can be used to copy arbitrary data
into the Windows system clipboard. For example, we could easily copy the name of your WSL OS
to the clipboard like so:
cat /etc/os-release | grep PRETTY_NAME | clip.exe
Bonus: Installing certificates
When custom CA certificates are installed on Windows, for example the certificates from
a companies private CA of their intranet, these certificates are not registered in WSL.
This is quickly noticeable when we access REST APIs that were legitimated by those CAs.
In case one of those APIs is accessed using curl
, curl prints this dreaded error:
60: SSL certificate problem: self signed certificate in certificate chain
While we can work around that error by adding --insecure
, this is a bad practice security-wise and
we likely want to access these APIs through different programs without such flags as well.
Luckily, there is a way to export Windows certificates and then import them into WSL:
- Launch the Windows certificate manager
certmgr.msc
- Under the tab
Trusted root CAs
, you should find your companys root CA certificate among all other trusted root CAs with trustworthy names likeGoDaddy
- Right click the cert, select
all tasks
and then export. Make sure you export itDER
encoded - Convert the certificate from
CRT
toPEM
format usingsudo openssl x509 -inform der -outform pem -in <your-cert-name>.der -out <your-cert-name>.crt
- Import the certificate in
PEM
format into your Linux OSsudo cp local-ca.crt /usr/local/share/ca-certificates && sudo update-ca-certificates
Summary
WSL uses the type-1 Hypervisor Hyper-V, which means that the OS inside WSL runs with equal rights to
Windows. Even though both OSes are logically separate hosts, both systems can access their counterparts’
files through File Servers, which are nicely abstracted away, but painfully slow.
With default networking settings, both systems take part in the same VLAN and Windows acts as a NAT
gateway. We can enable mirrored
networking mode to replicate the Windows networking interfaces
into WSL to make them behave like one network node.
It is possible to call Windows programs from WSL and mix them with Unix commands.