一口气搞定 WSL2 的网络问题

发布于 2021/1/31, 编辑于 2024/1/20

本文介绍 wsl2 的端口转发、网络代理等问题,不会或者少量涉及 wsl2 的安装部署,因为安装教程很容易能找到。这里着重于端口转发、网络代理等容易遇到的坑

一、相关

二、关于 WSL2 的 IP

2.1. 要解决的问题

  1. 把 wsl2 的端口暴露给局域网

2.2. 端口转发

上述问题,可以通过端口转发的方式,让 windows 把 wsl2 的端口转发暴露给局域网,因此需要搞定三件事情:

  1. windows 需要能获取到子系统的模拟 ip 地址(不再需要获取子系统 ip,可以利用 localhost 来映射)
  2. windows 转发对应子系统端口
  3. 破除防火墙的限制

以上三个问题的对应方案如下:

2.2.1 获取子系统的虚拟 IP 地址(这一步已废弃,请直接看下文的 2.2.2)

============================
※ 注意,本第三节已经废弃,不再需要使用这种迂回的方式来映射子系统的 ip

※ 对于获取子系统 ip 不感兴趣的可以直接跳到文章第 2.2.2 小节端口转发

※ 本节不删除只用作存档,修改时间:2024/1/20
============================

  1. go-wsl2-host 下载该软件,这个脚本可以帮忙在 windows 系统中生成一个服务用来获得子系统的虚拟 ip 地址
  2. 在 powershell(后面的章节简写为 pwsh)中用管理员权限安装他:.\wsl2host.exe install
  3. 然后按照提示输入你当前的 windows 服务账号和密码,如果以前没有添加过服务账号,请看第 6 步的补充说明
  4. 重启电脑
  5. 检查服务有无启动成功:
    • 查看 windows 服务中有没有 wsl2host
    • 查看 hosts 文件中有没有多出一行类似于:192.168.82.59 ubuntu.wsl # managed by wsl2-host
    • 都没问题即说明这时候已经可以在 pwsh 脚本中用 ubuntu.wsl 来获取到子系统的虚拟 ip
  6. 补充说明:
    • 第三步的账号密码可以通过以下方式添加:windows 管理工具-本地安全策略-本地策略-用户分配权限-作为服务登录-添加当前登录的微软账号或本地账号(然后检查名称),然后 install 的时候用添加上去的那个账号名,例如你的微软账号可能是 [email protected],但是添加上去之后的账号名可能是 12345,所以以显示的账号(12345)为准
    • 如果你的系统上没有“本地安全策略”,那么说明你的 windows 版本如家庭版不支持这一操作。但别慌,可以用以下这段脚本下载“本地安全策略”到系统中(保存为 .bat 文件并以管理员权限打开):
      pushd "%~dp0"
      dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt
      dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientTools-Package~3*.mum >>List.txt
      for /f %%i in ('findstr /i . List.txt 2^>nul') do dism /online /norestart /add-package:"C:\Windows\servicing\Packages\%%i"
    • 家庭版有时候更新完系统后 wsl2host 服务会失灵,这时候重新 install 即可,重新 install 的时候可能会提示 keys already exist,这时候 remove 一下即可,remove 时会提示错误(不存在 wsl2host 服务),不用在意,只需要知道这时候已经移除了帐号了就行
    • 有的朋友可能会遇到死活无法安装上 wsl2host 服务的情况, 那也有个麻烦点的做法, 就是使用 .\wsl2host.exe debug 指令, 这样也可以手动给 hosts 添加上新的 ip, 这样需要每次开机都手动调下指令 (原因是每次开机宿主机 ip 都不一样), 挺麻烦的

2.2.2. 端口转发

  1. pwsh 中执行:notepad $profile 打开 pwsh 的配置文件,接下来要写两段脚本
  2. 在配置文件中添加如下代码并保存。另外下面的脚本中有 sudo 指令,这个要求你的 pwsh 要事先安装了 sudo 插件,这个是用来获取管理员权限的,请自行搜索安装方法。对于脚本里面的 192.168.10.68 就是你需要映射的端口,一般就是你的宿主机的内网 ip,自行修改,下面是代码:

    function setWslNetsh {
        param (
            $Port
        )
        sudo netsh interface portproxy add v4tov4 listenport=$Port connectaddress=localhost connectport=$Port listenaddress=192.168.10.68 protocol=tcp
        Write-Output "✔ Port($Port) now is out!"
    }
    
    function unsetWslNetsh {
        param (
            $Port
        )
        sudo netsh interface portproxy delete v4tov4 listenport=$Port listenaddress=192.168.10.68 protocol=tcp
        Write-Output "✔ Port($Port) now is not out!"
    }
    
    Set-Alias wsl-netsh-set setWslNetsh
    Set-Alias wsl-netsh-unset unsetWslNetsh
  3. 上面这段代码就是实现端口转发的主要脚本,这时候在 pwsh 中执行:wsl-netsh-set <port> 就能让 windows 把子系统的端口转发出去。例如:wsl-netsh-set 8000 就能把子系统中端口号为 8000 的进程转发出去

  4. 同样的 wsl-netsh-unset <port> 则是取消转发
  5. 另外还可以在子系统中去执行这个指令:pwsh wsl-netsh-set <port>pwsh wsl-netsh-unset <port>
  6. 原理补充:wsl2 的流量转发原理就是会把 wsl2 中的使用到的端口通过 localhost 转发的宿主机,例如你在子系统起了一个 http://localhost:8000 的服务,那么你不需要做任何事情,就可以在宿主机通过 http://localhost:8000 访问到这个服务,而这个脚本就是基于这个原理,通过 netsh 进行流量转发,把特定的 <ip>:<port> 的访问流量转发到 localhost:<port>,这样就能把子系统的端口暴露给局域网了
  7. ipv6 说明,上面的脚本是针对使用了 ipv4 的服务的,从 v4tov4 你就可以看出来,如果你子系统使用的是 ivp6 服务,那么你也可以这样来设置转发和取消转发,你可以自行调整 v4tov6 这一处,看你的宿主机是想用什么 ip 协议来访问这个子系统端口
    sudo netsh interface portproxy delete v4tov6 listenport=8000 listenaddress=192.168.10.68
    sudo netsh interface portproxy add v4tov6 listenport=8000 listenaddress=192.168.10.68 connectport=8000 connectaddress=::1
  8. 每次宿主机开机以后你会发现明明上一次开机已经进行过端口映射了,但是为什么这次开机后不生效,这是预料之中的事情,你只需要每次开机完毕后都用 wsl-netsh-unset <port> 取消转发,然后再重新设置一下转发就行了,你可以通过 netsh interface portproxy show all 看到当前有哪些进行了流量转发的地址和端口

2.2.3. 破除防火墙限制

最简单的做法其实就是在防火墙中添加出站和入站规则,添加一下需要暴露到局域网的端口即可。当然也可以把这个写成脚本,需要暴露什么端口直接执行脚本就行,同样在 pwsh 的配置文件中添加代码:

function setFWPort {
    param (
        $Port
    )
    $Port4WSL = "Port4WSL-" + $Port
    $NetFirewallRule = Get-NetFirewallRule
    if (-not $NetFirewallRule.DisplayName.Contains($Port4WSL)) {
        # sudo Remove-NetFireWallRule -DisplayName $Port4WSL
        sudo New-NetFireWallRule -DisplayName $Port4WSL -Direction Outbound -LocalPort $Port -Action Allow -Protocol TCP
        sudo New-NetFireWallRule -DisplayName $Port4WSL -Direction Inbound -LocalPort $Port -Action Allow -Protocol TCP
        Write-Output "✔ New rule for WSL(Port: $Port)!"
    }
    else {
        Write-Output "✔ Rule for WSL(Port: $Port) exists!"
    }
}

function unsetFWPort {
    param (
        $Port
    )
    $Port4WSL = "Port4WSL-" + $Port
    $NetFirewallRule = Get-NetFirewallRule
    if (-not $NetFirewallRule.DisplayName.Contains($Port4WSL)) {
        Write-Output "✔ Rule for WSL(Port: $Port) not exists!"
    }
    else {
        sudo Remove-NetFireWallRule -DisplayName $Port4WSL
        Write-Output "✔ Rule for WSL(Port: $Port) removed!"
    }
}

Set-Alias fw-port-set setFWPort
Set-Alias fw-port-unset unsetFWPort

然后就能用 fw-port-set <port> 来创建新的出入站规则,用 fw-port-unset <port> 移除规则。规则名称定为了 Port4WSL-<port>,可以自行修改

完成上述步骤,就可以把子系统的进程端口暴露到局域网了,例如我在子系统起了一个服务,端口为 8000,windows 在局域网的地址为 192.168.1.215,那么局域网下的其他设备就能通过访问 http://192.168.1.215:8000 访问我子系统下的服务

点击这里前往 Github 查看原文,交流意见~

文档信息

版权声明:自由转载 - 非商用 - 非衍生 - 保持署名(创意共享3.0许可证