Skip to content

Latest commit

 

History

History
909 lines (752 loc) · 58.1 KB

File metadata and controls

909 lines (752 loc) · 58.1 KB

WSL Networking Deep Dive

An in-depth technical guide to understanding WSL 2 networking architecture, NAT, mirrored mode, port forwarding, and multi-distro scenarios.


Table of Contents

  1. WSL 2 Network Architecture Fundamentals
  2. NAT Mode In-Depth
  3. Multiple Linux Distributions
  4. Localhost Port Forwarding Mechanism
  5. Port Conflicts and Resolution
  6. Mirrored Networking Mode
  7. Practical Scenarios and Troubleshooting

WSL 2 Network Architecture Fundamentals

The Virtual Machine Reality

WSL 2 runs a real Linux kernel inside a lightweight Hyper-V virtual machine. This has profound implications for networking:

┌─────────────────────────────────────────────────────────────────────────────┐
│                          Physical Hardware                                   │
│                     (Your PC's Network Interface)                           │
│                          Physical IP: Assigned by Router                    │
└─────────────────────────────────────────────┬───────────────────────────────┘
                                              │
┌─────────────────────────────────────────────▼───────────────────────────────┐
│                         Hyper-V Virtual Switch                               │
│                      (Default: Internal vSwitch)                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌─────────────────────────────────┐    ┌─────────────────────────────────┐│
│   │     Windows Host Partition      │    │     WSL 2 Utility VM            ││
│   │                                 │    │                                 ││
│   │  vEthernet (WSL) Adapter        │    │  eth0 Virtual Adapter           ││
│   │  IP: 172.28.16.1               │◄──►│  IP: 172.28.21.105              ││
│   │  (Gateway for WSL VM)          │    │  Gateway: 172.28.16.1           ││
│   │                                 │    │                                 ││
│   │  Physical Adapter               │    │  All distros share this VM     ││
│   │  IP: 192.168.1.100             │    │  and this single eth0           ││
│   └─────────────────────────────────┘    └─────────────────────────────────┘│
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

Key Insight: Single VM, Multiple Distros

Critical concept: All WSL 2 distributions run inside a single shared VM. There is only ONE Linux kernel, ONE eth0 adapter, and ONE IP address for all distributions.

# Run this in Ubuntu
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 172.28.21.105/20 brd 172.28.31.255

# Run this in Debian (same result!)
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 172.28.21.105/20 brd 172.28.31.255

NAT Mode In-Depth

What is NAT in the WSL Context?

NAT (Network Address Translation) is the default networking mode in WSL 2. The Windows host acts as a router/gateway for the WSL VM.

NAT Architecture Detailed

┌──────────────────────────────────────────────────────────────────────────────────┐
│                              Internet / LAN                                       │
│                          (External Network: 192.168.1.0/24)                      │
└─────────────────────────────────────────┬────────────────────────────────────────┘
                                          │
                                          │ Your router sees only Windows
                                          │ (192.168.1.100)
                                          ▼
┌──────────────────────────────────────────────────────────────────────────────────┐
│                              WINDOWS HOST                                         │
│                          External IP: 192.168.1.100                              │
│                                                                                   │
│  ┌────────────────────────────────────────────────────────────────────────────┐  │
│  │                    Windows NAT Service (WinNAT)                             │  │
│  │                                                                             │  │
│  │   ┌─────────────────────────────────────────────────────────────────────┐  │  │
│  │   │                    NAT Translation Table                             │  │  │
│  │   ├─────────────────────────────────────────────────────────────────────┤  │  │
│  │   │  Internal IP:Port    │  External IP:Port   │  Protocol │  State     │  │  │
│  │   ├──────────────────────┼─────────────────────┼───────────┼────────────┤  │  │
│  │   │  172.28.21.105:44321 │  192.168.1.100:44321│  TCP      │  ESTABLISHED│  │  │
│  │   │  172.28.21.105:53421 │  192.168.1.100:53421│  TCP      │  ESTABLISHED│  │  │
│  │   │  172.28.21.105:8080  │  192.168.1.100:8080 │  TCP      │  LISTEN    │  │  │
│  │   └─────────────────────────────────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────────────────────────────────┘  │
│                                                                                   │
│  ┌────────────────────────────────────────────────────────────────────────────┐  │
│  │                    vEthernet (WSL) - Virtual Switch                        │  │
│  │                    IP: 172.28.16.1/20 (Gateway)                            │  │
│  │                    Subnet: 172.28.16.0/20                                  │  │
│  └──────────────────────────────────────┬─────────────────────────────────────┘  │
│                                         │                                        │
│  ┌──────────────────────────────────────▼─────────────────────────────────────┐  │
│  │                         WSL 2 Virtual Machine                               │  │
│  │                         IP: 172.28.21.105/20                                │  │
│  │                         Gateway: 172.28.16.1                                │  │
│  │                         DNS: 172.28.16.1 (Windows)                          │  │
│  │                                                                             │  │
│  │   ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐            │  │
│  │   │     Ubuntu      │  │     Debian      │  │     Fedora      │            │  │
│  │   │  (User Space)   │  │  (User Space)   │  │  (User Space)   │            │  │
│  │   │                 │  │                 │  │                 │            │  │
│  │   │  nginx :80      │  │  postgres :5432 │  │  redis :6379    │            │  │
│  │   └─────────────────┘  └─────────────────┘  └─────────────────┘            │  │
│  │                                                                             │  │
│  │              All share: eth0 (172.28.21.105)                               │  │
│  └─────────────────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────────────────┘

How NAT Translation Works Step-by-Step

Outbound Connection (WSL → Internet)

Step 1: WSL application initiates connection
┌─────────────────────────────────────────────────────────────┐
│  curl https://api.github.com                                │
│  Source: 172.28.21.105:44321                                │
│  Destination: 140.82.121.5:443                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
Step 2: Packet reaches Windows NAT gateway (172.28.16.1)
┌─────────────────────────────────────────────────────────────┐
│  NAT records the translation:                               │
│  172.28.21.105:44321 ←→ 192.168.1.100:44321                │
│                                                             │
│  Rewrites source IP:                                        │
│  Source: 192.168.1.100:44321                               │
│  Destination: 140.82.121.5:443                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
Step 3: Packet sent to internet via physical adapter
┌─────────────────────────────────────────────────────────────┐
│  Router sees: 192.168.1.100:44321 → 140.82.121.5:443       │
│  (WSL's internal IP is completely hidden)                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
Step 4: Response returns to Windows
┌─────────────────────────────────────────────────────────────┐
│  Source: 140.82.121.5:443                                   │
│  Destination: 192.168.1.100:44321                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
Step 5: NAT performs reverse translation
┌─────────────────────────────────────────────────────────────┐
│  Looks up: 192.168.1.100:44321 → 172.28.21.105:44321       │
│                                                             │
│  Rewrites destination:                                      │
│  Source: 140.82.121.5:443                                   │
│  Destination: 172.28.21.105:44321                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
Step 6: Response delivered to WSL application
┌─────────────────────────────────────────────────────────────┐
│  curl receives response from api.github.com                 │
└─────────────────────────────────────────────────────────────┘

NAT Subnet Behavior

The NAT subnet changes on every WSL restart:

# Day 1
$ ip addr show eth0
inet 172.28.21.105/20

# After wsl --shutdown and restart
$ ip addr show eth0
inet 172.22.176.42/20  # Different subnet!

# Windows gateway also changes
$ cat /etc/resolv.conf
nameserver 172.22.176.1  # Matches new subnet

Why this matters:

  • Scripts that hardcode WSL IP addresses will break
  • Firewall rules need dynamic updates
  • Use hostname resolution instead of IPs

Multiple Linux Distributions

Single VM Architecture

All WSL 2 distributions share one VM instance:

┌───────────────────────────────────────────────────────────────────────────────┐
│                          WSL 2 Lightweight Utility VM                          │
│                                                                                │
│  ┌──────────────────────────────────────────────────────────────────────────┐ │
│  │                         Linux Kernel (Microsoft)                          │ │
│  │                              Single Instance                              │ │
│  └──────────────────────────────────────────────────────────────────────────┘ │
│                                                                                │
│  ┌──────────────────────────────────────────────────────────────────────────┐ │
│  │                         Network Stack (Single)                            │ │
│  │                                                                           │ │
│  │   eth0: 172.28.21.105/20                                                 │ │
│  │   All distros see the same network interface                             │ │
│  │   All distros share the same IP address                                  │ │
│  └──────────────────────────────────────────────────────────────────────────┘ │
│                                                                                │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐  ┌─────────────┐ │
│  │    Ubuntu      │  │    Debian      │  │    Fedora      │  │   Alpine    │ │
│  │                │  │                │  │                │  │             │ │
│  │  /home/user    │  │  /home/user    │  │  /home/user    │  │  /home/user │ │
│  │  (ext4 vhdx)   │  │  (ext4 vhdx)   │  │  (ext4 vhdx)   │  │  (ext4 vhdx)│ │
│  │                │  │                │  │                │  │             │ │
│  │  Port 3000     │  │  Port 5432     │  │  Port 6379     │  │  Port 8080  │ │
│  │  (Node.js)     │  │  (PostgreSQL)  │  │  (Redis)       │  │  (nginx)    │ │
│  └────────────────┘  └────────────────┘  └────────────────┘  └─────────────┘ │
│                                                                                │
│         ▲                    ▲                   ▲                  ▲          │
│         │                    │                   │                  │          │
│         └────────────────────┴───────────────────┴──────────────────┘          │
│                           All bind to eth0 (172.28.21.105)                     │
│                           All accessible via localhost on Windows              │
└───────────────────────────────────────────────────────────────────────────────┘

Port Binding Across Distros

Since all distros share one IP, port conflicts happen at the kernel level:

# Terminal 1: Ubuntu - Start nginx on port 80
ubuntu$ sudo nginx  # Binds to 0.0.0.0:80
# Success!

# Terminal 2: Debian - Try to start apache on port 80
debian$ sudo apache2
# ERROR: Address already in use (port 80)

# Why? Both distros share the same eth0 interface
# Port 80 is already bound by Ubuntu's nginx

Viewing Ports Across All Distros

# From any distro, you see ALL listening ports from ALL distros
$ ss -tlnp
State   Local Address:Port    Process
LISTEN  0.0.0.0:3000          Node.js (Ubuntu)
LISTEN  0.0.0.0:5432          PostgreSQL (Debian)
LISTEN  0.0.0.0:6379          Redis (Fedora)
LISTEN  0.0.0.0:80            nginx (Alpine)

Inter-Distro Communication

Distros can communicate via localhost since they share the network stack:

# Ubuntu is running Node.js on port 3000
# From Debian, you can access it directly:
debian$ curl http://localhost:3000
# Works! Same network namespace

# Or using the shared IP:
debian$ curl http://172.28.21.105:3000
# Also works!

Process Isolation vs Network Sharing

┌─────────────────────────────────────────────────────────────────────┐
│                        What IS Isolated                              │
├─────────────────────────────────────────────────────────────────────┤
│  ✓ Filesystem (each distro has its own ext4 virtual disk)          │
│  ✓ User accounts (separate /etc/passwd per distro)                 │
│  ✓ Installed packages (apt vs dnf vs apk)                          │
│  ✓ Process namespace (ps shows only that distro's processes)       │
│  ✓ Init system (each distro has its own init)                      │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                       What is SHARED                                 │
├─────────────────────────────────────────────────────────────────────┤
│  ✗ Linux Kernel (one kernel for all distros)                       │
│  ✗ Network interface (single eth0)                                 │
│  ✗ IP Address (one IP: 172.28.x.x)                                 │
│  ✗ Port space (port 80 can only be used by one service)            │
│  ✗ Network namespace                                                │
│  ✗ Memory (VM's allocated RAM is shared)                           │
└─────────────────────────────────────────────────────────────────────┘

Localhost Port Forwarding Mechanism

How Windows Accesses WSL Ports on Localhost

This is one of WSL 2's most elegant features. When you run a server in WSL, it's automatically accessible on localhost from Windows.

The Magic: Localhost Forwarding Service

┌──────────────────────────────────────────────────────────────────────────────┐
│                              WINDOWS HOST                                     │
│                                                                               │
│   ┌─────────────────────────────────────────────────────────────────────┐    │
│   │                    Windows Application                               │    │
│   │                    (Browser: http://localhost:3000)                  │    │
│   └───────────────────────────────────┬─────────────────────────────────┘    │
│                                       │                                       │
│                                       ▼                                       │
│   ┌─────────────────────────────────────────────────────────────────────┐    │
│   │              WSL Localhost Forwarding Service                        │    │
│   │                     (wslhost.exe)                                    │    │
│   │                                                                      │    │
│   │   Monitors:                                                          │    │
│   │   • Listens for connections to localhost:* on Windows               │    │
│   │   • Detects listening sockets inside WSL VM                         │    │
│   │   • Automatically creates port forwards                              │    │
│   │                                                                      │    │
│   │   Active Forwards:                                                   │    │
│   │   ┌────────────────────────────────────────────────────────────┐    │    │
│   │   │  localhost:3000 (Win) ──────► 172.28.21.105:3000 (WSL)    │    │    │
│   │   │  localhost:5432 (Win) ──────► 172.28.21.105:5432 (WSL)    │    │    │
│   │   │  localhost:8080 (Win) ──────► 172.28.21.105:8080 (WSL)    │    │    │
│   │   └────────────────────────────────────────────────────────────┘    │    │
│   └───────────────────────────────────┬─────────────────────────────────┘    │
│                                       │                                       │
│   ┌───────────────────────────────────▼─────────────────────────────────┐    │
│   │                    vEthernet (WSL)                                   │    │
│   │                    172.28.16.1                                       │    │
│   └───────────────────────────────────┬─────────────────────────────────┘    │
│                                       │                                       │
└───────────────────────────────────────┼───────────────────────────────────────┘
                                        │
┌───────────────────────────────────────▼───────────────────────────────────────┐
│                              WSL 2 VM                                          │
│                                                                                │
│   ┌──────────────────────────────────────────────────────────────────────┐    │
│   │                    eth0: 172.28.21.105                                │    │
│   │                                                                       │    │
│   │    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │    │
│   │    │  Node.js     │    │  PostgreSQL  │    │  nginx       │          │    │
│   │    │  :3000       │    │  :5432       │    │  :8080       │          │    │
│   │    └──────────────┘    └──────────────┘    └──────────────┘          │    │
│   └──────────────────────────────────────────────────────────────────────┘    │
└───────────────────────────────────────────────────────────────────────────────┘

Step-by-Step Flow

1. WSL Application Binds to Port
   ┌─────────────────────────────────────────────────────────────┐
   │  # In Ubuntu WSL                                            │
   │  $ node server.js                                           │
   │  Server listening on port 3000                              │
   │                                                             │
   │  Kernel: Socket bound to 0.0.0.0:3000 on eth0              │
   └─────────────────────────────────────────────────────────────┘
                              │
                              ▼
2. WSL Notifies Windows of New Listening Port
   ┌─────────────────────────────────────────────────────────────┐
   │  wslhost.exe detects new listening socket via:              │
   │  • Hyper-V socket communication                             │
   │  • Periodic polling of /proc/net/tcp                        │
   │                                                             │
   │  Registers: "Port 3000 is now listening in WSL"            │
   └─────────────────────────────────────────────────────────────┘
                              │
                              ▼
3. Windows Creates Localhost Listener
   ┌─────────────────────────────────────────────────────────────┐
   │  Windows binds to localhost:3000                            │
   │  (Only 127.0.0.1, not 0.0.0.0!)                            │
   │                                                             │
   │  $ netstat -an | findstr 3000                              │
   │  TCP    127.0.0.1:3000    0.0.0.0:0    LISTENING           │
   └─────────────────────────────────────────────────────────────┘
                              │
                              ▼
4. Browser Connects to localhost:3000
   ┌─────────────────────────────────────────────────────────────┐
   │  Browser → localhost:3000                                   │
   │         → wslhost.exe intercepts                           │
   │         → forwards to 172.28.21.105:3000                   │
   │         → Node.js receives request                          │
   │         → Response follows reverse path                     │
   └─────────────────────────────────────────────────────────────┘

Binding Address Matters

# Binding to 0.0.0.0 (all interfaces) - WORKS with localhost forwarding
$ python -m http.server 8000 --bind 0.0.0.0
# Accessible from Windows at http://localhost:8000 ✓

# Binding to 127.0.0.1 (loopback only) - DOES NOT forward
$ python -m http.server 8000 --bind 127.0.0.1
# NOT accessible from Windows! ✗
# Because the service only listens on WSL's internal loopback

Port Conflicts and Resolution

Scenario: Port Already in Use on Windows

What happens when a WSL application tries to use a port that Windows is already using?

Scenario: SQL Server running on Windows port 1433
          PostgreSQL wants to run on WSL port 1433

┌──────────────────────────────────────────────────────────────────────────────┐
│                              WINDOWS HOST                                     │
│                                                                               │
│   ┌─────────────────────────────────────────────────────────────────────┐    │
│   │                    SQL Server (Windows)                              │    │
│   │                    Bound to: 0.0.0.0:1433                           │    │
│   │                    (Occupies the port on Windows)                    │    │
│   └─────────────────────────────────────────────────────────────────────┘    │
│                                                                               │
│   ┌─────────────────────────────────────────────────────────────────────┐    │
│   │              WSL Localhost Forwarding Service                        │    │
│   │                                                                      │    │
│   │   Port 1433 forward BLOCKED!                                        │    │
│   │   Cannot bind localhost:1433 - already in use by SQL Server        │    │
│   │                                                                      │    │
│   │   Result: No automatic forwarding created for port 1433             │    │
│   └─────────────────────────────────────────────────────────────────────┘    │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────────────────────────┐
│                              WSL 2 VM                                          │
│                                                                                │
│   ┌──────────────────────────────────────────────────────────────────────┐    │
│   │                    PostgreSQL                                         │    │
│   │                    Bound to: 0.0.0.0:1433                            │    │
│   │                    (Works fine inside WSL!)                           │    │
│   │                                                                       │    │
│   │   The port binds successfully in WSL because                         │    │
│   │   WSL has its own network namespace.                                 │    │
│   │   Windows and WSL port spaces are SEPARATE.                          │    │
│   └──────────────────────────────────────────────────────────────────────┘    │
└───────────────────────────────────────────────────────────────────────────────┘

What Actually Happens

# Windows: SQL Server on port 1433
PS> netstat -an | findstr 1433
TCP    0.0.0.0:1433    0.0.0.0:0    LISTENING

# WSL: PostgreSQL on port 1433
$ sudo -u postgres psql -c "SHOW port"
 port
------
 1433
# PostgreSQL is running fine in WSL!

# But from Windows:
PS> psql -h localhost -p 1433
# Connects to SQL Server, NOT PostgreSQL!
# Because Windows localhost:1433 goes to SQL Server

Accessing the WSL Service When Port Conflicts

Option 1: Use WSL's IP directly (bypasses localhost forwarding)

# Get WSL IP
PS> wsl hostname -I
172.28.21.105

# Connect directly to WSL's PostgreSQL
PS> psql -h 172.28.21.105 -p 1433
# Now connects to PostgreSQL in WSL!

Option 2: Use different port in WSL

# Change PostgreSQL to use port 5433 instead
$ sudo sed -i 's/port = 1433/port = 5433/' /etc/postgresql/14/main/postgresql.conf
$ sudo systemctl restart postgresql

# Now accessible via localhost:5433 from Windows

Option 3: Manual port forwarding with different port

# Forward Windows port 5433 to WSL port 1433
PS> netsh interface portproxy add v4tov4 `
    listenport=5433 listenaddress=127.0.0.1 `
    connectport=1433 connectaddress=172.28.21.105

# Access PostgreSQL via localhost:5433
PS> psql -h localhost -p 5433

Port Conflict Detection

# Check what's using a port on Windows
PS> netstat -ano | findstr :3000
TCP    0.0.0.0:3000    0.0.0.0:0    LISTENING    1234

# Find the process
PS> Get-Process -Id 1234
Handles  NPM(K)    PM(K)    CPU(s)     Id  ProcessName
-------  ------    -----    ------     --  -----------
    500      50   100000     10.50   1234  node
# Check what's using a port in WSL
$ ss -tlnp | grep :3000
LISTEN  0  511  0.0.0.0:3000  *  users:(("node",pid=1234,fd=18))

$ ps aux | grep 1234
user  1234  0.5  1.0  node /home/user/app/server.js

Conflict Resolution Matrix

WSL Port Windows Port localhost Access Direct IP Access
3000 (free) Free ✅ WSL ✅ WSL
3000 (free) 3000 (used) ❌ Windows wins ✅ WSL
3000 (used) Free ✅ WSL ✅ WSL
3000 (used) 3000 (used) ❌ Windows wins ✅ WSL

Mirrored Networking Mode

What is Mirrored Mode?

Introduced in Windows 11 22H2, mirrored networking eliminates the NAT layer entirely. WSL uses the same network interfaces as Windows.

Enabling Mirrored Mode

Create or edit %USERPROFILE%\.wslconfig:

[wsl2]
networkingMode=mirrored

Then restart WSL:

wsl --shutdown
wsl

Mirrored Mode Architecture

┌──────────────────────────────────────────────────────────────────────────────┐
│                              Physical Network                                 │
│                            Router: 192.168.1.1                               │
└─────────────────────────────────────┬────────────────────────────────────────┘
                                      │
                                      │
┌─────────────────────────────────────▼────────────────────────────────────────┐
│                              WINDOWS HOST                                     │
│                                                                               │
│   ┌─────────────────────────────────────────────────────────────────────┐    │
│   │                    Physical Network Adapter                          │    │
│   │                    IP: 192.168.1.100                                 │    │
│   │                    (Visible to router)                               │    │
│   └─────────────────────────────────┬───────────────────────────────────┘    │
│                                     │                                        │
│                                     │ MIRRORED (not NAT!)                    │
│                                     │                                        │
│   ┌─────────────────────────────────▼───────────────────────────────────┐    │
│   │                         WSL 2 VM                                     │    │
│   │                                                                      │    │
│   │   ┌─────────────────────────────────────────────────────────────┐   │    │
│   │   │                    eth0 (Mirrored)                           │   │    │
│   │   │                    IP: 192.168.1.100 (SAME as Windows!)      │   │    │
│   │   │                                                              │   │    │
│   │   │   WSL sees the exact same network as Windows:               │   │    │
│   │   │   • Same IP address                                          │   │    │
│   │   │   • Same subnet                                              │   │    │
│   │   │   • Same gateway                                             │   │    │
│   │   │   • Same DNS servers                                         │   │    │
│   │   │   • Same routing table                                       │   │    │
│   │   └─────────────────────────────────────────────────────────────┘   │    │
│   │                                                                      │    │
│   │   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │    │
│   │   │  Node.js     │    │  PostgreSQL  │    │  nginx       │          │    │
│   │   │  :3000       │    │  :5432       │    │  :80         │          │    │
│   │   └──────────────┘    └──────────────┘    └──────────────┘          │    │
│   └─────────────────────────────────────────────────────────────────────┘    │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘

                    Network sees one device: 192.168.1.100
                    Both Windows and WSL answer on this IP

Key Differences: NAT vs Mirrored

Feature NAT Mode Mirrored Mode
WSL IP Address 172.x.x.x (private) Same as Windows (e.g., 192.168.1.100)
IP Changes on Restart Yes No
Localhost Forwarding Via wslhost.exe Native (same loopback)
External Access to WSL Requires port forwarding Direct (same IP)
IPv6 Support Limited Full
VPN Compatibility Often problematic Better
Multicast/mDNS Not supported Supported
Network Discovery WSL invisible WSL visible

Mirrored Mode Deep Dive

Same Loopback Interface

# In mirrored mode, localhost truly means the same thing
# in both Windows and WSL

# WSL
$ curl http://localhost:3000  # Reaches service on either Win or WSL

# Windows
PS> curl http://localhost:3000  # Reaches service on either Win or WSL

Port Binding Behavior

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Mirrored Mode Port Binding                                │
│                                                                              │
│   Scenario: Both Windows and WSL try to bind port 3000                      │
│                                                                              │
│   ┌───────────────────────────────────────────────────────────────────┐     │
│   │  Windows: IIS binds to 0.0.0.0:3000                               │     │
│   │  Result: SUCCESS                                                   │     │
│   └───────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│   ┌───────────────────────────────────────────────────────────────────┐     │
│   │  WSL: Node.js tries to bind to 0.0.0.0:3000                       │     │
│   │  Result: EADDRINUSE - Address already in use                      │     │
│   │                                                                    │     │
│   │  In mirrored mode, WSL CANNOT bind to a port Windows is using!   │     │
│   │  (Unlike NAT mode where they have separate port spaces)           │     │
│   └───────────────────────────────────────────────────────────────────┘     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

External Network Access

NAT Mode: External device cannot reach WSL directly
┌─────────────────┐        ┌─────────────────┐        ┌─────────────────┐
│  Other PC       │───X───►│    Windows      │───────►│    WSL          │
│  192.168.1.50   │        │  192.168.1.100  │        │  172.28.21.105  │
└─────────────────┘        └─────────────────┘        └─────────────────┘
                           Need port forwarding!

Mirrored Mode: External device reaches WSL directly
┌─────────────────┐        ┌─────────────────────────────────────────────┐
│  Other PC       │───────►│    Windows + WSL (same IP: 192.168.1.100)  │
│  192.168.1.50   │        │    Both answer on this address              │
└─────────────────┘        └─────────────────────────────────────────────┘
                           Direct access works!

Mirrored Mode Configuration Options

Full .wslconfig example:

[wsl2]
networkingMode=mirrored

# Firewall settings
firewall=true

# DNS settings
dnsTunneling=true

# Hyper-V firewall rules apply to WSL
autoProxy=true

# Allow WSL to use Windows proxy settings
[experimental]
# Ignore specific ports from mirroring (WSL gets separate port space for these)
ignoredPorts=3000,3001,8080

# Host address loopback - allows binding to host IP from WSL
hostAddressLoopback=true

When to Use Mirrored Mode

Use Mirrored Mode When:

  • ✅ You need WSL accessible from other devices on your network
  • ✅ You're using VPNs (better compatibility)
  • ✅ You need IPv6 support
  • ✅ You want consistent IP addresses (no more dynamic 172.x.x.x)
  • ✅ You need multicast/mDNS (Bonjour, etc.)

Stick with NAT Mode When:

  • ✅ You need separate port spaces (same port in Windows and WSL)
  • ✅ You want WSL network isolated from external devices
  • ✅ You're on Windows 10 (mirrored not available)
  • ✅ You have complex network setups that break with mirroring

Practical Scenarios and Troubleshooting

Scenario 1: Web Development with Hot Reload

# React app with webpack-dev-server
$ cd ~/projects/my-react-app
$ npm start
# Starts on port 3000

# Automatic localhost forwarding means:
# http://localhost:3000 works in Windows browser
# Hot reload WebSocket also forwards automatically

Scenario 2: Database + App on Different Distros

┌─────────────────────────────────────────────────────────────────┐
│  Ubuntu: Running Node.js API on port 3000                       │
│  Debian: Running PostgreSQL on port 5432                        │
│                                                                  │
│  Node.js connects to PostgreSQL via localhost:5432              │
│  (Works because they share the same network namespace)          │
│                                                                  │
│  Windows browser accesses API at localhost:3000                 │
│  (Automatic localhost forwarding)                               │
└─────────────────────────────────────────────────────────────────┘

Scenario 3: Exposing WSL Service to LAN (NAT Mode)

# Get current WSL IP
$wslIP = (wsl hostname -I).Trim()

# Forward external port 8080 to WSL port 3000
netsh interface portproxy add v4tov4 `
    listenport=8080 `
    listenaddress=0.0.0.0 `
    connectport=3000 `
    connectaddress=$wslIP

# Allow through Windows Firewall
New-NetFirewallRule -DisplayName "WSL Port Forward" `
    -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow

# Now other devices can access: http://192.168.1.100:8080

Important: WSL IP changes on restart. Create a script to update:

# update-wsl-forward.ps1
$wslIP = (wsl hostname -I).Trim()
netsh interface portproxy delete v4tov4 listenport=8080 listenaddress=0.0.0.0
netsh interface portproxy add v4tov4 `
    listenport=8080 listenaddress=0.0.0.0 `
    connectport=3000 connectaddress=$wslIP
Write-Host "Updated forwarding to $wslIP"

Troubleshooting Commands

# Check WSL network configuration
$ ip addr show
$ ip route show
$ cat /etc/resolv.conf

# Test connectivity to Windows host
$ ping $(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')

# Check listening ports in WSL
$ ss -tlnp

# Test if port forwarding is working
$ curl -v http://localhost:3000
# Check Windows network adapters
Get-NetAdapter | Where-Object {$_.Name -like "*WSL*"}

# Check port forwarding rules
netsh interface portproxy show all

# Check what's listening on Windows
netstat -ano | findstr LISTENING

# Reset WSL network
wsl --shutdown
Restart-Service LxssManager

# Check WSL network mode
wsl cat /proc/sys/net/ipv4/ip_forward

Common Issues and Solutions

Issue Cause Solution
localhost not working Service bound to 127.0.0.1 Bind to 0.0.0.0
Port works in WSL, not Windows Windows firewall Add firewall rule
WSL can't reach internet DNS issues Check /etc/resolv.conf
Slow network performance VPN interference Try mirrored mode
Port forward stopped working WSL IP changed Update port proxy rules

Quick Reference

Network Commands Cheat Sheet

# WSL Commands
ip addr show eth0                    # Show WSL IP
cat /etc/resolv.conf                 # Show Windows host IP (DNS)
ss -tlnp                             # Show listening ports
curl http://localhost:3000           # Test local service
ping $(hostname -I | awk '{print $1}')  # Test own network

# PowerShell Commands
wsl hostname -I                      # Get WSL IP from Windows
netstat -ano | findstr :3000         # Check port usage
netsh interface portproxy show all   # List port forwards
Get-NetAdapter *WSL*                 # Show WSL adapter

Network Mode Comparison

┌─────────────────────────────────────────────────────────────────────────┐
│                        NAT Mode (Default)                                │
├─────────────────────────────────────────────────────────────────────────┤
│  WSL IP:        172.x.x.x (dynamic, changes on restart)                │
│  Windows IP:    Your network IP (e.g., 192.168.1.100)                  │
│  Port Space:    Separate (same port can be used by both)               │
│  localhost:     Forwarded via wslhost.exe                              │
│  External:      Requires manual port forwarding                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                        Mirrored Mode                                     │
├─────────────────────────────────────────────────────────────────────────┤
│  WSL IP:        Same as Windows (e.g., 192.168.1.100)                  │
│  Windows IP:    Same as WSL                                             │
│  Port Space:    Shared (port conflict = error)                          │
│  localhost:     Native sharing (same loopback)                          │
│  External:      Direct access (same IP)                                 │
└─────────────────────────────────────────────────────────────────────────┘

Additional Resources