microsoft / microsoft/WSL

Mirrored mode: 127.0.0.1 policy-routing breaks DNS services and nftables compatibility

Open
#14,063 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature network
Dominant language
C++
Stars
33.7k
Forks
1.8k
Avg merge
3d 17h
Merged PRs (30d)
116

Description

### Windows Version

Microsoft Windows [Version 10.0.26200.7171]

### WSL Version

2.6.2.0

### Are you using WSL 1 or WSL 2?

- [x] WSL 2
- [ ] WSL 1

### Kernel Version

6.6.87.2-microsoft-standard-WSL2

### Distro Version

24.04

### Other Software

C:\Users\\.wslconfig:
```
[wsl2]
networkingMode=Mirrored
memory=30GB
swap=30GB
dnsProxy=false
dnsTunneling=false
autoProxy=false
firewall=false

[experimental]
hostAddressLoopback=false
ignoredPorts=53
sparseVhd=true
```

mosdns version: v5.3.3-0-g025823c
/etc/mosdns/config.yaml :
```
log:
level: info

plugins:
- tag: forward_udp
type: forward
args:
upstreams:
- addr: 1.1.1.1:53
- addr: 8.8.8.8:53

- tag: main
type: sequence
args:
- exec: forward_udp

servers:
- tag: udp53_server
type: udp_server
args:
listen: 127.0.0.1:53
entry: main

- tag: tcp53_server
type: tcp_server
args:
listen: 127.0.0.1:53
entry: main
```
Note: I also observed similar behavior when listening on 0.0.0.0:53. The core issue is that 127.0.0.1 traffic is routed away from lo to loopback0 under mirrored mode until overriding policy routing.

### Repro Steps

1. Start mosdns with the config above and confirm it is listening:

sudo ss -tulnp | grep -E ':53\b|:5353\b'

Actual output (mosdns is listening on :53 TCP+UDP):

udp UNCONN 0 0 *:53 *:* users:(("mosdns",pid=239,fd=8))
tcp LISTEN 0 4096 *:53 *:* users:(("mosdns",pid=239,fd=9))
tcp LISTEN 0 4096 *:5353 *:* users:(("mosdns",pid=239,fd=10))

2. Query mosdns on localhost:

nc -zv 127.0.0.1 53
dig @127.0.0.1 -p 53 +tcp baidu.com

Actual result: connection refused even though mosdns is listening

nc: connect to 127.0.0.1 port 53 (tcp) failed: Connection refused

;; Connection to 127.0.0.1#53(127.0.0.1) for baidu.com failed: connection refused.
;; no servers could be reached

Root Cause

1) Traffic capture shows SYN/RST on loopback0 (not delivered to local listener)

Run:

sudo tcpdump -ni any 'tcp port 53 or udp port 53'

Then retry nc/dig.

Actual tcpdump output:

loopback0 Out IP 127.0.0.1.46742 > 127.0.0.1.53: Flags [S], seq 1386235810, win 64240, options [mss 1460,sackOK,TS val 2814819829 ecr 0,nop,wscale 7], length 0
loopback0 In IP 127.0.0.1.53 > 127.0.0.1.46742: Flags [R.], seq 0, ack 1386235811, win 0, length 0

This indicates localhost traffic is going through loopback0 and is being reset, instead of reaching the mosdns socket in the distro.

2) Routing decision for 127.0.0.1 uses table 127 via loopback0

ip route get 127.0.0.1

Actual output:

127.0.0.1 via 169.254.73.152 dev loopback0 table 127 src 127.0.0.1 uid 1000 cache

3) Policy routing rules force TCP/UDP lookups into table 127/128 with high priority

ip rule show

Actual output (top part):

0: from all iif eth0 ipproto tcp lookup local
0: from all iif eth0 ipproto udp lookup local
0: from all iif eth1 ipproto tcp lookup local
0: from all iif eth1 ipproto udp lookup local
0: from all iif loopback0 ipproto tcp lookup local
0: from all iif loopback0 ipproto udp lookup local
1: from all ipproto tcp lookup 127
1: from all ipproto udp lookup 127
1: from all ipproto tcp lookup 128
1: from all ipproto udp lookup 128
2: from all lookup local
32766: from all lookup main
32767: from all lookup default

4) table 127 explicitly routes 127.0.0.1 via loopback0

ip route show table 127

Actual output:

127.0.0.1 via 169.254.73.152 dev loopback0 proto kernel src 127.0.0.1 onlink

Together, these explain why mosdns never receives the connection: 127.0.0.1 is policy-routed away from lo/local table into loopback0 and then immediately reset.

Workaround

Add higher-priority rules forcing 127/8 to use the local table, then flush route cache:

sudo ip rule add pref 0 to 127.0.0.0/8 lookup local
sudo ip rule add pref 0 to 127.0.0.0/8 ipproto tcp lookup local
sudo ip rule add pref 0 to 127.0.0.0/8 ipproto udp lookup local
sudo ip route flush cache

After applying workaround:

ip route get 127.0.0.1
nc -zv 127.0.0.1 53
dig @127.0.0.1 -p 53 +tcp baidu.com

Actual output after workaround (working):

local 127.0.0.1 dev lo table local src 127.0.0.1 uid 1000 cache

Connection to 127.0.0.1 53 port [tcp/domain] succeeded!

And DNS query succeeds:

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; SERVER: 127.0.0.1#53(127.0.0.1) (TCP)

it is not persistent across WSL restarts unless re-applied (e.g. systemd oneshot service).

Questions
1. Is routing 127.0.0.1 via loopback0/table 127 intended in mirrored mode?
2. If not intended, can WSL avoid forcing ipproto tcp/udp lookup 127/128 for 127/8 destinations, or provide a supported option to preserve standard localhost semantics inside the distro?

### Expected Behavior

If a service inside the distro is listening on 127.0.0.1:53 (or *:53), connecting to 127.0.0.1:53 from within the same distro should reach the local socket via lo and succeed.

### Actual Behavior

Under mirrored networking, 127.0.0.1 is routed via loopback0 (table 127). TCP connections get an immediate RST and clients see connection refused, despite an active listener inside the same WSL distro and network namespace.

### Diagnostic Logs

_No response_

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the failure with the shown .wslconfig settings, then inspect the mirrored-mode networking setup using ip rule show, ip route show table 127, and ip route get 127.0.0.1. Done means localhost traffic to 127.0.0.1:53 reaches the distro's local socket without requiring persistent manual ip rule overrides, while preserving mirrored-mode behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux
Domain
networking, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.