解决WSL2内部网络访问问题_续
解决WSL2内部网络访问问题
在上一篇文章《解决WSL2内环境无法通过localhost读取到Windows的localhost的问题》中,我给出了一个socat+supervisor的方案;关于这个问题,还可以进一步探索;
主要是两个方面,
一是,除了用socat转发,Linux有自带的iptables相关的功能;
二是,在有systemd的情况下,使用supervisor的必要性;
Linux的iptables功能
iptables只是一种工具集合的说法,实际上,目前的Linux在底层已经用netfilter(nft)功能替换了iptables;
为了防止网上所说的”不兼容”问题,可以先行检查: 1
lsmod | grep -E 'nf_|nft_|xt_|ip_tables|iptable_'
WSL的systemd真的拿不到1的pid吗?
上篇文章中提到“WSL由于内核设计问题,systemd拿不到为1的pid,因此systemd的很多功能是废的,因此不要用”
这个结论是错的,对于Debian Trixie on WSL,通过命令 1
ps --no-headers -o comm 1
1
systemd
systemd的功能一切正常;
由此,引出这篇文章;
方案整体构架
目前的方案是,用dummy ip与nft配合,将本地的10.254.254.254上的7890和7897全部转发到$WIN_IP:7890;
任务写进一个bash脚本中,放在/usr/local/bin/中,由systemd负责开机时拉起;
fallback方案
当宿主机为Windows11时
当宿主机为Win11这种支持mirroredWSL网络配置的Windows时,这个功能自身不必用这些方案,直接开启mirrored就能实现双向通信;
此时不建议,也无法折腾iptables,因为启用了mirrored之后,不支持这些功能;
当WSL内核不支持nft时
当WSL内核不支持nft时,可以换成两种方案: 1.
放弃内核路由,改为应用层支持,也就是socat转发; 2.
改用传统的iptables-legacy;
当WSL内核不能提供正常的systemd时
当WSL内核不能提供systemd时,可改为由bash脚本拉起supervisor,由supervisor管理相关功能;
步骤
检查nft相关功能
上面提到了,iptables功能已经被nft取代,因此,通过一些命令检查nft工作的状态很有必要;
对于WSL2,Microsoft已经在/proc/config.gz中公开了编译配置文件,可以从此处拿到针对内核情况的判断:
1 | zgrep -i 'NF_TABLES' /proc/config.gz |
此两条命令,主要是为了检查是否有把功能编译进去;
在我的Debian Trixie上试一下: 1
2
3
4
5
6
7
8
9
10
11➜ ~ zgrep -i 'NF_TABLES' /proc/config.gz
CONFIG_NF_TABLES=y
CONFIG_NF_TABLES_INET=y
CONFIG_NF_TABLES_NETDEV=y
CONFIG_NF_TABLES_IPV4=y
CONFIG_NF_TABLES_ARP=y
CONFIG_NF_TABLES_IPV6=y
CONFIG_NF_TABLES_BRIDGE is not set
➜ ~ zgrep -E 'CONFIG_NFT_(NAT|REDIRECT)' /proc/config.gz
CONFIG_NFT_NAT=y
➜ ~
结果很好,y代表功能是”内建”的,它甚至不是可加载模块的m,而是内核本体的一部分;
然后可以再仅看看REDIRECT: 1
zgrep -i 'REDIRECT' /proc/config.gz
在我的Debian 13上结果是: 1
2
3
4
5
6
7
8
9➜ ~ zgrep -i 'REDIRECT' /proc/config.gz
CONFIG_NET_REDIRECT=y
CONFIG_NF_NAT_REDIRECT=y
CONFIG_NETFILTER_XT_TARGET_REDIRECT=m
CONFIG_IP_NF_TARGET_REDIRECT=m
CONFIG_BRIDGE_EBT_REDIRECT=m
CONFIG_OVERLAY_FS_REDIRECT_DIR is not set
CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW=y
➜ ~
既然内核的基础设施没有问题,那么最大的阻塞因素就没有了,接下来安装相关的前端配置工具:
1
sudo apt update && sudo apt install nftables -y
首先,nftables上关于此配置是一串命令,因此最好不要把它们塞进systemd来处理,而是做成一个单独的脚本,由systemd拉起;
其次,由于zsh与bash的语法有一些细微差别,尤其是对于花括弧{和}的处理上,且bash是更加通用的shell,因此尽量将配置写成bash语法,并指定由bash解析脚本;
不过,在把命令写成脚本,设定由bash每次开机时解析之前,先在zsh中检测一下效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
151. 彻底清空可能存在的旧表
sudo nft delete table ip wsl_to_win 2>/dev/null || true
2. 获取当前 Windows 宿主机的 IP
WIN_IP=$(awk '/nameserver/ {print $2}' /etc/resolv.conf)
3. 创建一个名为 wsl_to_win 的 nat 表
sudo nft add table ip wsl_to_win
4. 创建 output 链(用单引号包裹,防止 zsh 报错)
sudo nft add chain ip wsl_to_win output '{ type nat hook output priority mangle; }'
5. 创建 prerouting 链(用单引号包裹)
sudo nft add chain ip wsl_to_win prerouting '{ type nat hook prerouting priority mangle; }'
6. 核心规则:DNAT 转发到 Windows 的 7897 端口
sudo nft add rule ip wsl_to_win output tcp dport 7890 dnat to $WIN_IP:7897
sudo nft add rule ip wsl_to_win output tcp dport 7897 dnat to $WIN_IP:7897
sudo nft add rule ip wsl_to_win prerouting tcp dport 7890 dnat to $WIN_IP:7897
sudo nft add rule ip wsl_to_win prerouting tcp dport 7897 dnat to $WIN_IP:78971
2
3
41. 看看变量拿到了什么
awk '/nameserver/ {print $2}' /etc/resolv.conf
2. 看看当前 nftables 实际写入的 DNAT 目标是谁
sudo nft list table ip wsl_to_win$WIN_IP;
然后检查Windows的防火墙是否拦截了请求: 1
2请将下面的 172.x.x.x 替换为第一步查到的真实 Windows IP
nc -zv 172.x.x.x 7897
如果提示没有 nc,可以运行 sudo apt install netcat-openbsd
不出意外的话,结果应该是Connection to 172.x.x.x 7897 port [tcp/*] succeeded!
既然链路都没有断,那么问题出在哪里呢?这就引出了另一个问题,nftables的安全机制:
Linux内核识别到本地环回127.0.0.1流量被路由到外部的时候,会认为这是一个非法路由,触发安全机制rp_filter和route_localnet,将包丢弃;理由也很简单,本地环回的包,凭什么路由出去,这会造成信息泄露;
检查方法是,用如下指令检测功能的开关情况: 1
2
3查看所有网卡的本地网卡路由限制状态
sysctl net.ipv4.conf.all.route_localnet
sysctl net.ipv4.conf.lo.route_localnet1
2cat /proc/sys/net/ipv4/conf/all/route_localnet
cat /proc/sys/net/ipv4/conf/lo/route_localnet
如果两个值为0,那么“果然如此”;
如何突破限制
将这两个值设为1是绝对不应该的方案,即便那能够制造便利,但思考这样的问题,“127001的包是不是应该离开本地环回?”
答案是不应该;
那么问题要怎么解决?
- 能不能伪造包地址?
答案是“不能”,只要涉及到127001到外部地址的路由,就会被内核干掉;
- 用
dummy ip;
1 | 1. 清理掉刚才的 dummy0 |
此时,尝试 1
curl -ILv https://commercial.encrypted.intranet -x http://10.254.254.254:7890
supervisor中的实现
supervisor中需要由root来执行这些指令;
考虑到在内核中转发网络数据包,本来就是root才应该做的事情,与具体的用户无关,因此这也没有造成root泄露污染;
/etc/supervisor/conf.d/wsl-proxy.conf 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[program:wsl-kernel-proxy]
; 由 root 直接执行,免去一切 sudo 和密码烦恼
command=/bin/bash -c '\
ip link delete dummy0 2>/dev/null || true && \
ip link add dummy0 type dummy && \
ip address add 10.254.254.254/32 dev dummy0 && \
ip link set dummy0 up && \
WIN_IP=$(awk '\''/nameserver/ {print $2}'\'' /etc/resolv.conf) && \
nft delete table ip wsl_secure_proxy 2>/dev/null || true && \
nft add table ip wsl_secure_proxy && \
nft add chain ip wsl_secure_proxy output { type nat hook output priority mangle\; } && \
nft add chain ip wsl_secure_proxy postrouting { type nat hook postrouting priority srcnat\; } && \
nft add rule ip wsl_secure_proxy output ip daddr 10.254.254.254 tcp dport 7890 dnat to $WIN_IP:7897 && \
nft add rule ip wsl_secure_proxy output ip daddr 10.254.254.254 tcp dport 7897 dnat to $WIN_IP:7897 && \
nft add rule ip wsl_secure_proxy postrouting ip daddr $WIN_IP tcp dport 7897 masquerade'
; 瞬间完成的单次任务
startsecs=0
autorestart=false
; 核心安全调整:使用 root 运行底层网络配置
user=root
autostart=true
redirect_stderr=true
stdout_logfile=syslog
之后将supervisord自己挂在某个开机启动的地方,比如shell脚本或者其他开机启动的进程下面让其他进程顺手拉起来就可以了;
systemd方案
新一些的WSL内核基本还原了systemd的功能,可以在/etc/wsl.conf中做出如下修改:
1
2[boot]
systemd=truesystemd.pid == 1
鉴于我的Debian Trixie
WSL已经有这项功能了,因此直接创建相关文件(注意,不要在systemd的配置文件直接写脚本,容易造成潜在问题):
先创建这件事情的承包商:
sudo nano /usr/local/bin/wsl-kernel-proxy.sh 在其中输入:
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!/bin/bash
1. 彻底清理并重建 dummy0 网卡
/usr/bin/ip link delete dummy0 2>/dev/null || true
/usr/bin/ip link add dummy0 type dummy
/usr/bin/ip address add 10.254.254.254/32 dev dummy0
/usr/bin/ip link set dummy0 up
【升级:双重保险】循环等待,直到 resolv.conf 生成并包含 nameserver(最多等5秒)
for i in {1..5}; do
WIN_IP=$(awk '/nameserver/ {print $2}' /etc/resolv.conf)
if [ ! -z "$WIN_IP" ]; then
break
fi
sleep 1
done
如果真的极度极端情况下还是没拿到 IP,直接退出,防止写入错误的空规则
if [ -z "$WIN_IP" ]; then
exit 1
fi
3. 清理旧规则,注入全新的 nftables 核心树
/usr/sbin/nft delete table ip wsl_secure_proxy 2>/dev/null || true
/usr/sbin/nft add table ip wsl_secure_proxy
/usr/sbin/nft add chain ip wsl_secure_proxy output '{ type nat hook output priority mangle; }'
/usr/sbin/nft add chain ip wsl_secure_proxy postrouting '{ type nat hook postrouting priority srcnat; }'
4. 写入规则
/usr/sbin/nft add rule ip wsl_secure_proxy output ip daddr 10.254.254.254 tcp dport 7890 dnat to $WIN_IP:7897
/usr/sbin/nft add rule ip wsl_secure_proxy output ip daddr 10.254.254.254 tcp dport 7897 dnat to $WIN_IP:7897
/usr/sbin/nft add rule ip wsl_secure_proxy postrouting ip daddr $WIN_IP tcp dport 7897 masquerade
然后是负责拉起承包商的启动脚本:
sudo nano /etc/systemd/system/wsl-kernel-proxy.service
内容为: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17[Unit]
Description=WSL2 Kernel Level Proxy Forwarding (nftables)
DefaultDependencies=no
After=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
# 5 秒强行超时锁,绝对不阻塞开机
TimeoutStartSec=5
Restart=no
# 简单纯粹:直接运行我们写好的高能脚本
ExecStart=/usr/local/bin/wsl-kernel-proxy.sh
[Install]
WantedBy=multi-user.target
注意,这个配置文件应该在/etc/systemd/system/中,而不是/etc/systemd/network/中,因为,
📂 1.
/etc/systemd/network/是用来干嘛的?这个目录不是用来存放“脚本”或“服务”的,它是现代 Linux 中 systemd-networkd(网络守护进程)的专用配置文件目录。
它的里面通常存放的是以 .network、.netdev 或 .link 结尾的静态属性配置文件。
- 比如你想给某个网卡固定一个 IP,你会在这里写一个 eth0.network 文件。
- 比如你想创建一个 VLAN 或网桥,你会在这里写一个 br0.netdev 文件。
为什么它不能满足我们的需求?
- 它不支持动态脚本:它是纯声明式的配置文件。而我们的核心痛点是每次开机必须通过 awk 去动态计算 Windows 那台电脑变来变去的 IP 变量($WIN_IP),并将它实时塞进防火墙里。这种“流式命令”是无法在静态网络配置文件里执行的。
- 它管不到 nftables:这个目录只负责网卡本身的网线通不通、IP 对不对,它没有权限也无法管理内核防火墙的规则树(NFT 路由劫持)。
📂 2. 为什么一定要放在
/etc/systemd/system/? 这个目录是整个 Linux 系统存放自定义服务、脚本和计划任务(Oneshot)的核心圣地。凡是涉及“开机我要执行一段 Bash 命令”、“开机我要拉起一个自定义服务”的需求,其对应的 .service 文件都必须安放在这里。
最后是任务本身的软链,这个我们自己不搞,让systemctl去做:
1
2
3强制重新建立开机自启的软链接
sudo systemctl disable wsl-kernel-proxy.service
sudo systemctl enable wsl-kernel-proxy.service
一切顺利的话,会提示: > Created symlink
/etc/systemd/system/multi-user.target.wants/wsl-kernel-proxy.service
→ /etc/systemd/system/wsl-kernel-proxy.service.
如果马上就要跑,可以: 1
sudo systemctl start wsl-kernel-proxy.service
否则的话,还是建议在powershell里面wsl --shutdown一下,开机后
1
2检查状态
sudo systemctl status wsl-kernel-proxy.serviceenabled和active;
如果systemd遇到了Assignment outside of section类的报错,那就需要检查/etc/systemd/system/wsl-kernel-proxy.service的内容,最简单的方案是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16sudo tee /etc/systemd/system/wsl-kernel-proxy.service << 'EOF'
[Unit]
Description=WSL2 Kernel Level Proxy Forwarding (nftables)
DefaultDependencies=no
After=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutStartSec=5
Restart=no
ExecStart=/usr/local/bin/wsl-kernel-proxy.sh
[Install]
WantedBy=multi-user.target
EOFsystemd重新加载,并再测试一遍:
1
2
3
4
5
6
7
8
91. 强行刷新 systemd 配置
sudo systemctl daemon-reload
2. 重新进行一次自启绑定(彻底洗掉之前的 disabled 状态)
sudo systemctl disable wsl-kernel-proxy.service
sudo systemctl enable wsl-kernel-proxy.service
3. 立即手动启动它
sudo systemctl start wsl-kernel-proxy.service
如果一切顺利,现在无论如何关机开机WSL,都可以正常地从10.254.254.254:7890
10.254.254.254:7897得到来自Windows的服务了;
防止IP和Port冲突
注意,supervisor和systemd方案只留一个,否则IP和Port肯定会发生冲突;
1 | sudo rm -f /etc/supervisor/conf.d/wsl-proxy.conf |
或者类似的 1
2sudo rm -f /etc/systemd/system/wsl-kernel-proxy.service
sudo systemctl daemon-reload
至此,获得了内核级别的ip:port稳定转发,且兼顾了localhost的安全性,收工!
总结systemd方案
- 分为两个空间,一部分是
nft的转发配置,另一部分是dummy的IP;两个流程,一个开机启动流程,另一个实际工作流程; - 这两个空间,可以由一个脚本实现,把脚本放在
/usr/local/bin/中;两个流程,分成脚本和开机启动配置; - 为脚本在
/etc/systemd/system/中写配置脚本; - 用
systemctl enable让其创建所需要的软链接; - 用
curl -ILv https://commercial.encrypted.intranet -x http://dummy:port来测试systemctl是否能做出符合预期的动作;
Knighthana
2026/06/11