文章

WSL 2 网络深入解析 - 架构原理、VPN 适配与故障排查

深入剖析 WSL 2 网络架构(NAT/Mirrored 模式),详解 VPN 环境下的代理配置、DNS 解析链路,以及系统化的故障排查方法论

WSL 2 网络深入解析 - 架构原理、VPN 适配与故障排查

在 Windows 上做开发,WSL 2 几乎是标配。但一旦涉及到 VPN、代理、公司内网这些网络场景,WSL 2 的网络问题就成了开发者最头疼的痛点之一。本文将从底层架构讲起,帮你彻底搞懂 WSL 2 的网络是怎么工作的,出了问题该往哪里查。


一、WSL 2 网络架构全景

1.1 先理解本质:WSL 2 是一台虚拟机

与 WSL 1(系统调用翻译层)不同,WSL 2 运行着一个真正的 Linux 内核,跑在轻量级 Hyper-V 虚拟机里面。这意味着它有自己独立的网络栈:独立的网卡、独立的 IP 地址、独立的路由表。

理解了这一点,后面的所有问题都好解释了——Windows 和 WSL 2 之间的通信,本质上是两台机器之间的通信

1.2 NAT 模式(默认)

这是 WSL 2 开箱即用的网络模式,数据流如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────┐
│  Windows Host                                       │
│                                                     │
│  ┌──────────────────┐    ┌────────────────────────┐ │
│  │  物理/Wi-Fi 网卡  │    │  Hyper-V Virtual Switch│ │
│  │  (如 192.168.1.x) │    │  vEthernet (WSL)      │ │
│  └────────┬─────────┘    │  172.x.x.1 (网关)      │ │
│           │              └──────────┬─────────────┘ │
│           │                NAT      │               │
│           └─────────────────────────┘               │
│                                                     │
└─────────────────────────────────────────────────────┘
                              │
                    ┌─────────┴──────────┐
                    │  WSL 2 Linux VM    │
                    │  eth0: 172.x.x.y   │
                    │  网关: 172.x.x.1   │
                    └────────────────────┘

关键特征:

  • WSL 2 拿到的是一个私有 IP(通常 172.x.x.x 段),每次重启可能会变
  • Windows 宿主的 IP 就是 WSL 的默认网关
  • WSL 访问外网需要通过 NAT 转换
  • Windows 上的 localhost 可以直接访问 WSL 中监听的端口(自动端口转发)
  • 但 WSL 访问 Windows 上的服务需要用宿主机 IP,不能直接用 localhost

查看 WSL 2 的 IP 和网关:

1
2
3
4
5
6
7
8
# 查看 WSL 自己的 IP
ip addr show eth0

# 查看默认网关(即 Windows 宿主机在 WSL 网络中的 IP)
ip route show | grep -i default | awk '{ print $3}'

# 或者直接读 resolv.conf(NAT 模式下 nameserver 就是网关)
cat /etc/resolv.conf

1.3 Mirrored 模式(推荐)

从 WSL 2.0+ 开始(Windows 11 22H2 及以上),微软引入了镜像网络模式。这是解决 VPN 兼容性问题的官方方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──────────────────────────────────────────┐
│  Windows Host                            │
│                                          │
│  ┌──────────────────┐                    │
│  │  物理/Wi-Fi 网卡  │                    │
│  │  192.168.1.100   │   ← 同一个 IP      │
│  └────────┬─────────┘                    │
│           │  镜像                         │
│  ┌────────┴─────────┐                    │
│  │  WSL 2 Linux VM  │                    │
│  │  192.168.1.100   │   ← 共享宿主 IP    │
│  └──────────────────┘                    │
│                                          │
└──────────────────────────────────────────┘

关键特征:

  • WSL 2 和 Windows 共享同一个 IP 地址
  • Windows 和 WSL 之间可以直接用 localhost / 127.0.0.1 互相访问
  • VPN 兼容性大幅提升——因为 WSL 直接复用了 Windows 的网络接口
  • 支持 IPv6
  • 支持从局域网直接访问 WSL 中的服务
  • 支持组播(Multicast)

如何开启:%UserProfile%\.wslconfig 中配置:

1
2
[wsl2]
networkingMode=mirrored

1.4 两种模式对比

特性NAT 模式(默认)Mirrored 模式
WSL IP独立私有 IP (172.x.x.x)与 Windows 共享
localhost 互通Windows→WSL ✅ / WSL→Windows ❌双向 ✅
VPN 兼容性差(常见断网)好(官方推荐)
IPv6
局域网直连需要 portproxy✅ 直接可达
每次重启 IP 变化否(共享宿主 IP)
系统要求所有 WSL 2 版本Windows 11 22H2 + WSL 2.0+

二、DNS 解析链路

DNS 是 WSL 2 网络问题的重灾区,尤其是连了 VPN 之后。搞清楚 DNS 的链路,90% 的 “上不了网” 问题就迎刃而解。

2.1 默认行为:自动生成 resolv.conf

WSL 2 启动时会自动生成 /etc/resolv.conf,行为取决于网络模式:

NAT 模式下:

1
2
3
$ cat /etc/resolv.conf
# This file was automatically generated by WSL.
nameserver 172.28.48.1   # 指向 Hyper-V 虚拟交换机网关

DNS 请求链路:WSL 应用172.28.48.1(WSL NAT 网关)→ Windows DNS 代理 → 上游 DNS 服务器

Mirrored + DNS Tunneling 模式下:

1
2
$ cat /etc/resolv.conf
nameserver 10.255.255.254   # DNS Tunneling 虚拟地址

DNS 请求链路:WSL 应用 → DNS Tunneling 虚拟化层 → 直接使用 Windows 的 DNS 解析结果

2.2 DNS Tunneling(推荐开启)

DNS Tunneling 是 WSL 2.0+ 引入的特性,默认已开启。它不再通过网络包转发 DNS 请求,而是通过虚拟化通道直接把 DNS 查询转交给 Windows 处理。

为什么这很重要? 当 VPN 连接时,VPN 客户端通常会修改 Windows 的 DNS 服务器配置,并可能拦截或重定向 DNS 流量。NAT 模式下的 DNS 包需要经过虚拟网络栈,而 VPN 的路由规则可能不认识这些来自虚拟交换机的 DNS 包——于是就断了。DNS Tunneling 绕过了网络层,直接复用 Windows 的 DNS 解析结果。

1
2
[wsl2]
dnsTunneling=true   # 默认已开启,确认没被关掉

2.3 手动接管 DNS

如果自动 DNS 始终有问题(比如某些顽固的企业 VPN),可以手动接管:

第一步:禁止自动生成 resolv.conf

在 WSL 内编辑 /etc/wsl.conf

1
2
[network]
generateResolvConf = false

第二步:手动创建 resolv.conf

1
2
3
4
5
6
7
8
9
10
11
12
# 删除旧的符号链接
sudo rm /etc/resolv.conf

# 写入自定义 DNS
sudo tee /etc/resolv.conf << 'EOF'
nameserver 8.8.8.8
nameserver 1.1.1.1
nameserver 223.5.5.5
EOF

# 防止被覆盖
sudo chattr +i /etc/resolv.conf

第三步:重启 WSL

1
wsl --shutdown

⚠️ 注意:手动 DNS 在连接企业 VPN 时可能无法解析内网域名。如果需要同时访问内网,需要把企业 DNS 服务器加到 resolv.conf 的第一行。通过 ipconfig /all 在 Windows 侧查看 VPN 适配器的 DNS 服务器地址。


三、VPN 与 WSL 2 的恩怨情仇

3.1 为什么连了 VPN 就断网?

这是 WSL 2 社区被问得最多的问题。根本原因有三个:

原因 1:IP 地址段冲突

WSL 2 的 NAT 网络默认使用 172.16.0.0/12 范围的地址。很多企业 VPN(尤其是 Cisco AnyConnect)也使用这个范围。当两者的子网重叠时,路由表就混乱了——VPN 的路由规则会覆盖掉去往 WSL 虚拟交换机的路由。

原因 2:Interface Metric 优先级

VPN 连接后会创建一个虚拟网卡,Windows 根据 Interface Metric(接口度量值)决定流量走哪个网卡。VPN 网卡通常设置了很低的 Metric(高优先级),导致所有流量都走 VPN 通道,WSL 的虚拟交换机流量被拦截。

原因 3:DNS 劫持

VPN 连接后会把 Windows 的 DNS 服务器改成企业内部的 DNS。WSL 的 DNS 请求(在 NAT 模式下通过网络包转发)可能被 VPN 的防火墙规则阻挡,或者被路由到了错误的 DNS 服务器。

3.2 终极解决方案:Mirrored + DNS Tunneling + AutoProxy

对于 Windows 11 22H2+ 的用户,这是微软官方推荐的方案,一劳永逸:

%UserProfile%\.wslconfig 中写入:

1
2
3
4
5
[wsl2]
networkingMode=mirrored
dnsTunneling=true
firewall=true
autoProxy=true

然后重启 WSL:

1
wsl --shutdown

各配置项的作用:

配置项作用
networkingMode=mirroredWSL 共享 Windows 的网络接口,VPN 对 WSL 透明
dnsTunneling=trueDNS 走虚拟化通道,不受 VPN 路由表影响
autoProxy=true自动继承 Windows 的代理设置(含 VPN 场景)
firewall=true启用 Hyper-V 防火墙,可受 Windows 防火墙规则管控

3.3 旧系统方案:修改 WSL 子网

如果你的 Windows 版本不支持 Mirrored 模式,可以通过修改注册表避免 IP 段冲突:

1
2
3
4
5
6
7
8
# 以管理员身份运行
# 先关闭 WSL
wsl --shutdown

# 修改注册表中的 WSL NAT 网段
# 路径:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss
# NatGatewayIpAddress: 改为不冲突的网关,如 192.168.240.1
# NatNetwork: 改为不冲突的网段,如 192.168.240.0/24

也可以使用链路本地地址(Link-Local)来彻底避免冲突:

1
2
NatGatewayIpAddress = 169.254.214.1
NatNetwork = 169.254.214.0/24

这个方案来自社区(mikegerber 的 Gist),使用链路本地地址段几乎不可能和任何 VPN 冲突。

3.4 企业 VPN(Cisco AnyConnect)的特殊处理

Cisco AnyConnect 是最棘手的 VPN 之一,因为它会主动监控和恢复路由表更改。社区验证的方案:

方案 A:修改 Interface Metric(临时方案)

1
2
# 以管理员身份运行 PowerShell
Get-NetAdapter | Where-Object {$_.InterfaceDescription -Match "Cisco AnyConnect"} | Set-NetIPInterface -InterfaceMetric 6000

配合 Windows 任务计划实现自动化:创建一个在 VPN 连接事件触发时自动执行的任务(Event ID 2039)。

⚠️ 注意:修改 Interface Metric 会改变 Windows 整体的路由优先级,可能导致其他网络行为异常。优先使用修改子网的方案。

方案 B:使用 wsl-vpnkit

wsl-vpnkit 是一个社区项目,通过在 WSL 内运行一个用户态网络栈来绕过 VPN 的路由限制:

1
2
3
4
5
# 安装
wsl --import wsl-vpnkit --version 2 $env:USERPROFILE\wsl-vpnkit wsl-vpnkit.tar.gz

# 在需要网络的 WSL 发行版中启动
wsl.exe -d wsl-vpnkit --cd /app service wsl-vpnkit start

四、代理配置实战

在国内开发环境中,代理(Clash、v2rayN 等)是刚需。WSL 2 中配置代理有几种方式。

4.1 方式一:autoProxy(最省心)

如果已经开启了 Mirrored 模式,只需确认 autoProxy=true

1
2
3
[wsl2]
networkingMode=mirrored
autoProxy=true

这样 WSL 2 会自动读取 Windows 的系统代理设置。如果你的 Clash/v2rayN 设置了系统代理,WSL 里的程序就能直接用。

4.2 方式二:手动设置环境变量

~/.bashrc~/.zshrc 中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取 Windows 宿主机 IP(NAT 模式下需要)
# Mirrored 模式下直接用 127.0.0.1
export WINDOWS_HOST=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')

# 设置代理(以 Clash 默认端口 7890 为例)
export HTTP_PROXY="http://${WINDOWS_HOST}:7890"
export HTTPS_PROXY="http://${WINDOWS_HOST}:7890"
export ALL_PROXY="socks5://${WINDOWS_HOST}:7890"
export NO_PROXY="localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"

# 小写版本(部分工具只认小写)
export http_proxy="$HTTP_PROXY"
export https_proxy="$HTTPS_PROXY"
export all_proxy="$ALL_PROXY"
export no_proxy="$NO_PROXY"

Mirrored 模式下更简单:

1
2
3
export HTTP_PROXY="http://127.0.0.1:7890"
export HTTPS_PROXY="http://127.0.0.1:7890"
export ALL_PROXY="socks5://127.0.0.1:7890"

⚠️ 重要前提:代理软件(Clash 等)必须开启 “允许局域网连接”(Allow LAN),否则 WSL 作为另一台机器无法连上代理端口。

4.3 方式三:按工具单独配置

有些工具不读环境变量,需要单独配置:

Git:

1
2
3
4
5
6
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890

# 取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy

APT:

1
2
3
4
sudo tee /etc/apt/apt.conf.d/proxy.conf << 'EOF'
Acquire::http::Proxy "http://127.0.0.1:7890";
Acquire::https::Proxy "http://127.0.0.1:7890";
EOF

npm / yarn:

1
2
npm config set proxy http://127.0.0.1:7890
npm config set https-proxy http://127.0.0.1:7890

pip:

1
2
3
4
5
6
7
8
pip install --proxy http://127.0.0.1:7890 <package>

# 或者永久配置
mkdir -p ~/.config/pip
cat > ~/.config/pip/pip.conf << 'EOF'
[global]
proxy = http://127.0.0.1:7890
EOF

4.4 快捷开关脚本

每次手动设置太麻烦,推荐在 shell 配置中加入快捷函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 添加到 ~/.bashrc 或 ~/.zshrc

proxy_on() {
    local port=${1:-7890}
    local host="127.0.0.1"   # Mirrored 模式
    # NAT 模式改为:local host=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')

    export HTTP_PROXY="http://${host}:${port}"
    export HTTPS_PROXY="http://${host}:${port}"
    export ALL_PROXY="socks5://${host}:${port}"
    export NO_PROXY="localhost,127.0.0.1,::1"
    export http_proxy="$HTTP_PROXY"
    export https_proxy="$HTTPS_PROXY"
    export all_proxy="$ALL_PROXY"
    export no_proxy="$NO_PROXY"
    echo "🌐 代理已开启 → ${host}:${port}"
}

proxy_off() {
    unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY
    unset http_proxy https_proxy all_proxy no_proxy
    echo "🚫 代理已关闭"
}

proxy_test() {
    echo "--- 代理环境变量 ---"
    echo "HTTP_PROXY=$HTTP_PROXY"
    echo "HTTPS_PROXY=$HTTPS_PROXY"
    echo ""
    echo "--- 连通性测试 ---"
    curl -s -o /dev/null -w "Google: HTTP %{http_code} (%{time_total}s)\n" https://www.google.com || echo "Google: FAILED"
    curl -s -o /dev/null -w "GitHub: HTTP %{http_code} (%{time_total}s)\n" https://github.com || echo "GitHub: FAILED"
    echo ""
    echo "--- DNS 测试 ---"
    nslookup google.com 2>&1 | head -5
}

五、系统化故障排查

遇到 WSL 网络问题时,不要盲目搜索解决方案。按照以下流程系统排查,3 分钟定位问题。

5.1 排查流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
WSL 无法上网
   │
   ├─ Step 1: ping IP 地址 (8.8.8.8)
   │     ├─ 成功 → DNS 问题 → 跳到 Step 3
   │     └─ 失败 → 网络连通性问题 → Step 2
   │
   ├─ Step 2: 检查路由和网卡
   │     ├─ ip addr → 有没有 eth0 和 IP?
   │     ├─ ip route → 默认路由指向哪里?
   │     └─ ping 网关 → 网关通不通?
   │           ├─ 网关不通 → WSL 网络栈问题 → wsl --shutdown 重启
   │           └─ 网关通但外网不通 → NAT/路由问题 → 检查 VPN 和 Interface Metric
   │
   ├─ Step 3: DNS 排查
   │     ├─ cat /etc/resolv.conf → nameserver 是什么?
   │     ├─ nslookup google.com <nameserver> → 这个 DNS 能用吗?
   │     └─ nslookup google.com 8.8.8.8 → 换公共 DNS 行不行?
   │           ├─ 公共 DNS 可以 → resolv.conf 配置问题
   │           └─ 公共 DNS 也不行 → DNS 流量被拦截 → 开启 dnsTunneling
   │
   └─ Step 4: 代理排查
         ├─ curl -v https://google.com → 看握手过程
         ├─ echo $HTTP_PROXY → 代理变量设了吗?
         └─ curl -x http://proxy:port https://google.com → 手动指定代理试试

5.2 常用排查命令速查

WSL 侧(Linux)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 网卡信息
ip addr show

# 路由表
ip route show

# 测试 IP 连通性
ping -c 3 8.8.8.8

# 测试 DNS 解析
nslookup google.com
dig google.com

# 查看 DNS 配置
cat /etc/resolv.conf

# 查看 WSL 网络配置
cat /etc/wsl.conf

# 跟踪路由(需安装 traceroute)
traceroute 8.8.8.8

# 测试特定端口
nc -zv 127.0.0.1 7890    # 测试代理端口

# 查看 WSL 版本
wsl.exe --version

Windows 侧(PowerShell)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 所有网卡信息(含 VPN 的 DNS 服务器)
ipconfig /all

# 路由表
route print

# WSL 状态
wsl --list --verbose

# 查看网卡 Interface Metric
Get-NetIPInterface | Sort-Object InterfaceMetric

# 查看 Hyper-V 虚拟交换机
Get-VMSwitch

# 端口转发规则
netsh interface portproxy show v4tov4

# 重启 WSL
wsl --shutdown

# 查看 .wslconfig
type $env:USERPROFILE\.wslconfig

# 检查 Windows 系统代理
netsh winhttp show proxy

5.3 常见问题速查表

现象可能原因解决方案
WSL 完全无网络VPN 路由冲突开启 Mirrored 模式或修改 WSL 子网
能 ping IP 但无法解析域名DNS 配置错误检查 resolv.conf 或开启 dnsTunneling
VPN 连接后 WSL 断网IP 段冲突 / Metric 问题见 3.2 / 3.3 节方案
代理设了但不生效代理软件未允许 LAN开启 “Allow LAN”;Mirrored 模式用 127.0.0.1
WSL 能上外网但进不了公司内网WSL DNS 没指向企业 DNS手动添加企业 DNS 到 resolv.conf
apt update 超时代理未正确传递给 apt配置 /etc/apt/apt.conf.d/proxy.conf
git clone 卡住Git 不读环境变量 / SSH 代理未配git config 设代理或配置 SSH ProxyCommand
WSL 重启后 IP 变了NAT 模式正常行为切 Mirrored 模式或脚本动态获取

六、推荐配置方案

6.1 最佳实践(Windows 11 22H2+)

.wslconfig(位于 %UserProfile%\C:\Users\你的用户名\):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[wsl2]
# 镜像网络 - 与 Windows 共享网络栈
networkingMode=mirrored

# DNS 隧道 - 绕过网络层直接复用 Windows DNS
dnsTunneling=true

# 自动继承 Windows 代理设置
autoProxy=true

# Hyper-V 防火墙
firewall=true

# 内存限制(可选)
memory=8GB

[experimental]
# 自动回收缓存内存
autoMemoryReclaim=gradual

# 稀疏 VHD,减少磁盘占用
sparseVhd=true

# DNS 最佳努力解析
bestEffortDnsParsing=true

/etc/wsl.conf(在 WSL 发行版内部):

1
2
3
4
5
6
7
8
[boot]
systemd=true

[user]
default=你的用户名

[network]
generateResolvConf=true   # Mirrored + dnsTunneling 模式下保持 true

6.2 代理软件侧配置(以 Clash Verge 为例)

  1. 开启 “允许局域网连接”(Allow LAN / 局域网代理)
  2. 开启 “系统代理”(System Proxy)——这样 autoProxy=true 才能生效
  3. TUN 模式(可选):如果只开系统代理不够,可以开 TUN 模式接管所有流量

6.3 验证清单

配置完成后,逐项验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 确认网络模式
# 如果输出的 IP 和 Windows 的 ipconfig 一致,说明 Mirrored 生效
ip addr show

# 2. 确认 DNS
nslookup google.com
nslookup github.com

# 3. 确认外网连通性
curl -s -o /dev/null -w "%{http_code}" https://www.google.com

# 4. 确认代理生效
curl -s https://api.ipify.org  # 看出口 IP 是不是代理节点

# 5. 确认内网可达(如果有 VPN)
ping 你的内网服务器IP
nslookup 内网域名

七、附录

7.1 重要配置文件位置

文件位置作用
.wslconfig%UserProfile%\.wslconfig (Windows)WSL 全局配置(网络模式、内存等)
wsl.conf/etc/wsl.conf (WSL 内部)每个发行版的本地配置
resolv.conf/etc/resolv.conf (WSL 内部)DNS 服务器配置
hosts/etc/hosts (WSL 内部)主机名映射

7.2 参考资料

本文由作者按照 CC BY 4.0 进行授权