解决局域网无法访问 WSL2 端口的问题

背景

WSL 升级到 WSL2 架构后,网络栈也变成虚拟机的 NAT 网卡实现了。这会导致虚拟机外的设备无法直接访问在 WSL2 里监听的端口。Microsoft 为了方便开发者在 Windows 端访问 WSL2 里的端口,基于 vsock 实现了一个支持 TCP 的端口转发逻辑。

然而可能因为安全等方面的因素,微软限制这个转发出来的端口仅能在本机访问,这可就有点不太行了。虽说这个监听在本地的端口转发已经能够满足开发者的日常使用和调试等工作了,但总会在某些时候有些特殊需求,想要能在局域网等设备上访问 WSL2 里的端口。

虽然在 GitHub 上的 issue 4150 创建了快两年,有 400 条评论,但微软这家伙还是迟迟不肯修改。没辙,咱们弱小又无助的开发者只能靠自己解决问题啦。

现存的解决方案

在上面提到的 GitHub issue 中的评论里,有着各种各样的 workaround,大致包含以下几种:

  1. 通过 Windows 自带的 netsh interface portproxy 命令或其他的端口转发工具,将监听在本机的端口转发出去
  2. 在虚拟机中安装各类 VPN,实现不受限制的直接访问。
  3. 升级到 Windows 10 Pro (微软的奸计!)并安装 HyperV,以此来使用 HyperV 的管理功能将虚拟机的网卡修改成桥接模式。

这些 workaround 或简单,或复杂,但都不符合我的喜好,我于是决定自己动手尝试解决看看。

研究实现

首先咱们要知道 Windows 这边的端口监听是怎么实现的。于是打开系统自带的 Resource Monitor,在 Network 一栏的 Listening Ports 里可以看到端口是由 wslhost.exe 监听的,那么接下来就该反汇编工具上场了。本来它作为一个系统组件程序,不会有加壳等骚操作,已经是我这种萌新也可以分析的程度了。然而在用反汇编工具尝试打开它时,才发现微软还很贴心的提供了 pdb 文件,对我这样的菜鸟选手更加友好了。

经过一段时间的研究和调试,可以推断出这个 wslhost.exe 会通过 vsock 和虚拟机内通信,然后根据收到的指令完成转发端口等相关操作。而且调用的也就是普通的 Windows API,这就好办了。

于是开始找 bind 的调用代码,再往上回溯,找到设置 sockaddr 的地方,将监听的地址从 INADDR_LOOPBACK 修改成 INADDR_ANY。这程序也没有什么校验和保护机制,修改完之后直接放回原位,替换掉系统官方的版本。再次尝试监听端口,确认修改成功,已经是监听在任意 IP 上了。

优化提升

就这样用了一段时间,自己也挺满意的,不过偶尔会担心一下系统更新导致无法使用。另外其操作方式也比较复杂繁琐,无法方便的帮助到其他受到同样问题困扰的同学们,于是便开始想怎么优化提升一下。

而像上面这样静态修改的话,每次文件更新都要再次手动寻找关键位置,很是麻烦,那怎么办呢?有的同学可能已经想到了,就是用 hook API 这样动态的方式。对于我们的需求,其实只要 hook bind 方法,在传递给系统之前,将参数中的监听地址修改掉,就能解决问题了。

于是我开始了编写内存注入补丁的项目,这里我的选择是 Microsoft 官方出品的 Detours。这个库原本是被 Microsoft 当作一个产品来销售的,而现在已经开源可以免费使用了(开源大法好)。经过一番鼓捣,最终我的成果在此:https://github.com/CzBiX/WSLHostPatcher

先下载文件并解压,然后在启动 WSL2 之后再运行 WSLHostPatcher.exe 即可。也可以将其放到 .profile 里,这样就能自动运行了。

标签:none

已有 2 条评论

  1. ZHLH ZHLH

    有点好奇反汇编过程,想问一下是用什么反汇编工具的呢,pdb文件在哪里获取的?

  2. winson winson

    这个补丁太棒了

添加新评论