一次 WSL2 SSH 远程访问的完整排障过程
起点:一个看似简单的需求
故事开始于一个很常见的需求:我想在 WSL2 的 Ubuntu 环境里启动 SSH 服务,让局域网里的 Mac 能够远程连接进来。这听起来应该两分钟就能搞定了——装个 openssh-server,改改配置,启动服务,完事。
但接下来的一个小时里,我经历了从"连接超时"到"连接被拒绝",再到"连接建立后立即断开"的完整循环,最终发现问题的根源是一个看似无关的 VPN 软件。
一:创建启动脚本
我首先创建了一个自动化脚本 setup-ssh.sh,它会检查 openssh-server 是否安装,配置 SSH 允许密码认证和公钥认证,然后启动服务。脚本看起来很完美,逻辑也很清晰:检查安装 → 配置文件 → 启动服务 → 显示连接信息。
运行脚本后,一切看起来都很顺利,直到最后一——SSH 服务启动失败了。
第一个障碍:端口被占用
错误信息很明确:Bind to port 22 on 0.0.0.0 failed: Address already in use。22 端口已经被占用了。
我开始排查是什么占用了 22 端口。用 ss -tlnp | grep :22 检查,发现端口确实在监听,但奇怪的是,用 ps aux | grep sshd 却找不到 sshd 进程。
这时候我意识到,可能是 systemd 的 ssh.socket 的问题:一般来说,Ubuntu 的 SSH 服务有两种启动方式:ssh.service(传统的守护进程模式)和 ssh.socket(按需激活模式)。如果 ssh.socket 已经在监听 22 端口,那么 ssh.service 再启动就会失败。
果然,systemctl status ssh.socket 显示它正在运行并监听 22 端口。
解决方案:换个端口
既然 22 端口有冲突,最简单的办法就是让 sshd 监听其他端口。我决定用 2222。修改 /etc/ssh/sshd_config,把 Port 改成 2222,同时禁用 ssh.socket:
sudo systemctl disable --now ssh.socket
sudo mkdir -p /run/sshd
sudo systemctl restart ssh
这次终于成功了。ss -tlnp | grep :2222 显示 sshd 正在监听 2222 端口,进程也在运行。
二:Windows 端口转发
WSL2 不像 WSL1 那样直接共享 Windows 的网络栈,它运行在一个虚拟网络里,有自己的内部 IP(通常是 172.x.x.x 或类似地址)。局域网里的其他设备无法直接访问这个 IP。
要让外部设备能连接,需要在 Windows 上做端口转发。Windows 提供了 netsh interface portproxy 命令来实现这个功能。
我在 Windows PowerShell(管理员模式)里运行:
$wslIP = (wsl hostname -I).Trim()
netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=2222 connectaddress=$wslIP
理论上,这样就能把 Windows 的 2222 端口转发到 WSL 的 2222 端口了。
三:第一次连接尝试——超时
我在 Mac 上尝试连接:
ssh -p 2222 pwn@10.0.0.1
结果:Connection timed out。
超时通常意味着网络不通或者防火墙阻止了连接。我检查了 Windows 防火墙,添加了允许 2222 端口入站的规则:
New-NetFirewallRule -DisplayName "WSL2 SSH" -Direction Inbound -LocalPort 2222 -Protocol TCP -Action Allow
再试,还是超时。
这时候我意识到一个问题:10.0.0.1 是什么 IP?用 Get-NetIPAddress 查看,发现 10.0.0.1 是一个叫 cfw-tap 的虚拟网卡地址,不是 Windows 的真实局域网 IP。
Windows 的真实局域网 IP 是 10.138.138.98(WiFi)和 10.138.170.117(有线)。
四:用正确的 IP——连接建立但立即断开
我换成正确的 IP 再试:
ssh -vvv -p 2222 pwn@10.138.138.98
这次有进展了!调试输出显示 Connection established,说明 TCP 连接成功了。但紧接着,连接就卡在了 Local version string SSH-2.0-OpenSSH_10.2,然后报错:
kex_exchange_identification: read: Connection reset by peer
这个错误很诡异。TCP 连接建立了,说明网络是通的,但 SSH 握手阶段就被重置了。这通常意味着流量到达了目标主机,但没有到达正确的 SSH 服务。
五:排查端口转发
我开始怀疑是 Windows 的端口转发配置有问题。检查转发规则:
netsh interface portproxy show all
输出显示:
侦听 ipv4: 连接到 ipv4:
地址 端口 地址 端口
0.0.0.0 2222 26.243.2.236 2222
26.243.2.236 是什么?这个我一直没有注意到,依旧以为是端口的问题,做了很久的尝试
然后我再次检查 Windows 的网卡列表的时候,发现这是 Radmin VPN 的虚拟网卡地址。
问题找到了!当我运行 wsl hostname -I 时,它返回的是所有网卡的 IP 地址,而第一个恰好是 Radmin VPN 的地址。端口转发被设置成了转发到 VPN 网卡,而不是 WSL 的真实网络接口。
六:尝试修正转发地址
我尝试获取 WSL 的 eth0 网卡 IP(这才是真正的 WSL 网络接口):
$wslIP = wsl -e sh -lc "ip -4 addr show eth0 | grep -o 'inet [0-9.]*' | head -n1"
$wslIP = ($wslIP -replace 'inet ', '').Trim()
但结果还是 26.243.2.236。这说明 Radmin VPN 不仅创建了虚拟网卡,还影响了 WSL 的网络配置。
七:改用 localhost 转发
既然直接转发到 WSL IP 有问题,我决定试试转发到 127.0.0.1。理论上,Windows 的 localhost 应该能访问到 WSL 的服务(WSL2 有 localhost 转发机制)。
netsh interface portproxy delete v4tov4 listenport=2222 listenaddress=0.0.0.0
netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=2222 connectaddress=127.0.0.1
测试正确的端口后,发现 Windows 本机确实能连到 WSL 的 2222 端口。
八:换端口再试
为了彻底避开可能的端口冲突,我决定把对外端口改成 8022:
netsh interface portproxy delete v4tov4 listenport=2222 listenaddress=0.0.0.0
netsh interface portproxy add v4tov4 listenport=8022 listenaddress=0.0.0.0 connectport=2222 connectaddress=26.243.2.236
New-NetFirewallRule -DisplayName "WSL2-SSH-8022" -Direction Inbound -LocalPort 8022 -Protocol TCP -Action Allow
从 Mac 连接:
ssh -vvv -p 8022 pwn@10.138.138.98
结果还是一样:Connection established 后立即 Connection reset by peer。
九:验证 WSL 内部服务
这时候我开始怀疑是不是 WSL 里的 sshd 本身有问题。我在 WSL 内部测试本地连接:
ssh -p 2222 pwn@127.0.0.1
成功了!SSH 握手完成,只是在认证阶段失败(因为本地没有配置密钥)。这证明 sshd 服务本身是正常的,问题确实出在转发链路上。
十:深入检查转发配置
我再次检查 Windows 的端口转发配置,发现无论怎么修改,connectaddress 总是指向 26.243.2.236。即使我明确指定了其他地址,下次查看时又变回了这个 VPN 地址。
这时候我开始意识到,Radmin VPN 可能不仅仅是添加了一个虚拟网卡那么简单,它可能还修改了系统的路由表或者网络优先级,导致所有到 WSL 的流量都被引导到了 VPN 网卡。
十一:关键转折点
我决定做一个实验:暂时关闭 Radmin VPN,看看会发生什么。
关闭 VPN 后,我重新设置端口转发:
$wslIP = (wsl hostname -I).Trim().Split(' ')[0]
netsh interface portproxy delete v4tov4 listenport=8022 listenaddress=0.0.0.0
netsh interface portproxy add v4tov4 listenport=8022 listenaddress=0.0.0.0 connectport=2222 connectaddress=$wslIP
这次,netsh interface portproxy show all 显示的转发目标终于不是 26.243.2.236 了。
从 Mac 再次尝试连接:
ssh -p 8022 pwn@10.138.138.98
终于成功了!SSH 提示输入密码,输入后顺利登录到 WSL 环境。
解决:VPN 是罪魁祸首
整个问题的根源终于清晰了:
Radmin VPN 在系统中创建了一个虚拟网卡(26.243.2.236),这个网卡不仅出现在网络接口列表中,还影响了系统的路由决策。当 Windows 的端口转发尝试连接到 WSL 时,流量被错误地路由到了 VPN 网卡,而不是 WSL 的真实网络接口。
这就解释了为什么会出现那些奇怪的现象:
- TCP 连接能建立:因为 Windows 确实在监听 8022 端口,Mac 能连到 Windows。
- SSH 握手失败:因为流量被转发到了错误的目标(VPN 网卡),没有到达 WSL 里的 sshd。
- Connection reset:VPN 网卡收到了不属于它的流量,直接重置了连接。
公钥认证配置
问题解决后,我继续配置公钥认证以实现免密登录。在 WSL 中准备好 SSH 目录:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
然后把 Mac 上的公钥(~/.ssh/id_rsa.pub)内容添加到 WSL 的 authorized_keys 文件中。之后就可以直接免密登录了。
总结
问题的三个层次
- 服务层(WSL 内部)
ssh.socket和ssh.service的端口冲突-
解决方案:禁用 socket 模式,使用独立端口
-
转发层(Windows)
netsh portproxy的配置正确性- 防火墙规则是否放行
-
解决方案:确保转发目标地址正确
-
路由层(网络环境)
- VPN 虚拟网卡对路由的影响
- 多网卡环境下的地址选择
- 解决方案:识别并排除干扰因素
诊断方法
从内到外,逐层验证:
- 先确认服务本身是否正常(WSL 内部测试)
- 再确认转发配置是否正确(Windows 配置检查)
- 最后排查网络环境因素(VPN、防火墙等)
如果跳过任何一层直接猜测,很容易走入死胡同。
经验教训
-
VPN 软件的隐藏影响:VPN 不仅创建虚拟网卡,还会影响系统路由。在排查网络问题时,要考虑所有可能影响网络栈的软件。
-
WSL2 的网络模型:WSL2 使用虚拟化网络,不是简单的网络共享。理解这一点对于配置网络服务至关重要。
-
systemd 的服务管理:现代 Linux 发行版中,同一个服务可能有多种启动方式(service vs socket)。要了解它们的区别和潜在冲突。
-
逐层诊断的重要性:不要一开始就假设问题在某一层。从最底层(服务本身)开始验证,逐步向上排查。
-
详细日志的价值:
ssh -vvv的详细输出能够精确定位问题发生在握手的哪个阶段,这对诊断至关重要。
后记
这次排障花了一个多小时,还挺有意思的,Radmin是当时为了玩mc下载的,结果这个时候坑我一把,属实难蚌
以后看到 Connection established 后立即 reset 的时候,可能说明问题不在网络连通性,而在数据流的路由路径上。
遇到 WSL SSH 连接问题时,按这个顺序检查:
- WSL 内 sshd 是否运行?(
ps -ef | grep sshd) - WSL 内端口是否监听?(
ss -tlnp | grep :<port>) - WSL 内本地连接是否成功?(
ssh -p <port> user@127.0.0.1) - Windows portproxy 规则是否存在?(
netsh interface portproxy show all) - Windows 防火墙是否放行?(检查入站规则)
- Windows 是否监听对外端口?(
netstat -ano | findstr :<port>) - 是否有 VPN 或虚拟网卡干扰?(检查网卡列表和路由表)
- 客户端能否连接到 Windows IP?(
telnet <ip> <port>)
每一步都通过后再进入下一步,这样能最快定位问题所在。