快考完试了,这个学期一直在使用 WSL 在进行开发,无论是 Python/C/React 都是用 VSCode Remote WSL 进行开发的,体验非常好。
无特别说明,本文以下内容所提到的 WSL 皆指 WSL2。
这篇文章大概有以下内容:
- WSL2 中连接到主机代理
让 WSL2 里能连上 Windows 上的代理软件 - 主机访问 WSL2
这里主要是靠设置 hosts,让一个域名一直解析到 WSL2 的 ip 上。 - 局域网访问 WSL2
局域网内访问你的 Windows 主机,Windows 转发端口到 WSL2 上
还有几个前置知识:
- Windows 和 WSL2 算是在同一个局域网内,这个局域网是由 Hyper-V 创建的。
- WSL2 使用的网络适配器是 'Default Hyper-V Switch',这个适配器每次重启都会被删除重建,这就是 WSL2 为什么 IP 不固定的原因。
- WSL2 内有些微软特意做的东西:
- 向 WSL2 的 IP 发送的请求都会被转发到 Windows 的 IP 上,但是这个时灵时不灵。
WSL2 连接到主机代理#
其实这个问题我在之前那篇配置 ArchWSL 的文章里简单提了一下,大概流程如下:
- 获取 Windows 的 ip
- Windows 上的代理软件允许局域网访问
- 设置 WSL2 的代理
获取主机的 ip#
由于 WSL2 是使用 Hyper-V 虚拟机实现的,也就不能跟 Windows 共享同一个 localhost 了,而且每次重启 ip 都会变。目前在 WSL 中可以用以下两个命令来获取主机的 ip:
ip route | grep default | awk '{print $3}'
# 或者
cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'
原理可见: User Experience Changes Between WSL 1 and WSL 2
设置代理#
Windows 的 IP 都已经拿到了,比如说我的代理软件是监听在 7890 端口的,那我只要设置代理链接为 {windows_ip}:7890
即可。
如果无法连接的话,请你检查一下你 Windows 上的代理软件允许局域网访问了吗。
还是没法连接的话有可能是 Windows 防火墙的原因,我是把防火墙关了的。
感谢评论区 Xing Fang 以及 twinmegami 给的开放防火墙的命令。
命令来源:https://github.com/microsoft/WSL/issues/4585
# 直接放开 `vEthernet (WSL)` 这张网卡的防火墙
New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow
还有这篇必读的文章:
作者详细的介绍了自己是怎么在 WSL 中使用代理的。
顺着作者的思路,我也实现了我自己的一个 「一键」设置代理 的脚本:
使用网卡代理#
当然,还有一个更方便的就是使用网卡代理,不用再设置什么 HTTP_PROXY
了,直接网卡级别的代理。
我使用的是 Clash 提供的 TUN 模式。
Clash 会创建一张网卡,接管系统的流量。
这样就不用设置任何东西了,开启网卡代理后,整个系统的流量都会走网卡。
你也可以使用下面这些:
- cfw 提供的 TAP 模式
- Netch
Netch 是一款 Windows 平台的开源游戏加速工具,Netch 可以实现类似 SocksCap64 那样的进程代理,也可以实现 SSTap 那样的全局 TUN/TAP 代理,和 Shadowsocks-Windows 那样的本地 Socks5,HTTP 和系统代理
主机访问 WSL2#
WSL2 的 IP 会变,所以怎么随时随地的都能访问到 WSL2 呢?看了很多 issue,解决方案各种各样。
这个问题困扰了我好久,因为在 WSL2 中开发,有时候就启动一些 web 应用,然后 localhost 又没法访问到.. 官方说 Windows 版本更新到 18945 之后的,程序 listen 到 0.0.0.0
上之后,在 Windows 中就可以通过 localhost 访问了,而我在测试的时候发现很多时候还是不生效,也许需要看脸吧。
自动设置 hosts 关联到 WSL 的 IP#
解决方案就是在 WSL 更新 IP 的时候,自动把 ip 加到 hosts 中,用自己喜欢的一个域名解析上去。
请看:
在 GitHub 上找到了这个 issue:[WSL 2] NIC Bridge mode 🖧 (Has Workaround🔨) #4150,根据回复有了思路。
思路就是使用任务计划程序执行 powershell 脚本,来做一些事。
我们需要写一个脚本(地址)实现我们想要的功能。
脚本的功能大概是:
- 读取 WSL 和 Windows 的 IP
- 将 IP 和想设定的域名写入 Windows 的
hosts
文件中。
这样你就能用自己定义的域名来访问两个系统了,wsl2 能访问 win.local
是因为它会向主机查询 dns(因为 wsl2 默认的 nameserver 指向了 windows 主机),主机会把 hosts 中的域名直接缓存起来然后直接作为一个 dns 记录。
关键来了,我们要使用任务计划程序在 WSL
要更新 IP 的时候执行这个脚本。
具体在 WSL
要更新 IP 时运行特定脚本步骤如下:
- 将链接中的代码保存到本地文件中,文件名后缀设为
.ps1
。 - 打开事件查看器。在小娜的搜索框里搜一下就能打开了。
- 点击 Windows 日志 -> 系统。
- 找到
Hyper-V-VmSwith
事件,查看有没有内容类似Port ... (Friendly Name: ...) successfully created on switch ... (Friendly Name: WSL).
的事件。 - 右键单击该事件,选择 将任务附加到该事件。
- 操作 选择 启动程序,程序中填
powershell
,参数填-file 你的脚本地址的绝对地址
就好了。参数中加上-WindowStyle Hidden
可以让 Powershell 执行该脚本时隐藏窗口。 - 然后在任务计划程序左侧资源栏中找到:事件查看器任务 -> 你刚创建的任务,右键属性,然后勾选下面的复选框:使用最高权限运行。
看看效果:
在 WSL 中启动一个 http 服务器:
我们在 win 下请求一下:
Awesome! 成功啦
这是一个用 Go 写的小工具,会创建一个 Windows 服务,Automatically update your Windows hosts file with the WSL2 VM IP address.
让 Windows 访问到 WSL 中监听本地的应用#
来源: https://github.com/shayne/wsl2-hacks
见 README 内的 Access localhost ports from Windows 一节
上面已经做到了 Windows 下根据固定域名访问 WSL 中监听 0.0.0.0
的应用程序,那么对于 WSL 中一些默认监听 127.0.0.1
的程序,咋办呢?
监听 127.0.0.1
的图解:
所以让请求 0.0.0.0
的请求都转发到请求 127.0.0.1
上。
现在:
WSL 中执行两条命令(花括号里面的两条)就能做到,增加 iptables
的路由规则:
expose_local(){
sudo sysctl -w net.ipv4.conf.all.route_localnet=1 >/dev/null 2>&1
sudo iptables -t nat -I PREROUTING -p tcp -j DNAT --to-destination 127.0.0.1
}
Windows 局域网内其他机器访问 WSL2#
[WSL 2] NIC Bridge mode 🖧 (Has Workaround🔨) #4150
上面提到过的这个 issue 里其实就是解决的局域网访问的问题,将需要用的端口通过 Windows 代理转发到 WSL 中。
原理也类似于上面的两幅图。
如果你用了主机访问 WSL2 里的那个脚本,就可以跳过这一节了,因为那个脚本包括了这一节的内容了。
关键代码如下:
不懂的请看注释。
# 获取 Windows 和 WSL2 的 ip
$winip = bash.exe -c "ip route | grep default | awk '{print `$3}'"
$wslip = bash.exe -c "hostname -I | awk '{print `$1}'"
$found1 = $winip -match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
$found2 = $wslip -match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
if( !($found1 -and $found2) ){
# 如果没找到 wsl 的 ip, 就退出执行
echo "The Script Exited, the ip address of WSL 2 cannot be found";
exit;
}
# 你需要映射到局域网中端口
$ports=@(80,443,10000,3000,5000,27701,8080);
# 监听的 ip,这么写是可以来自局域网
$addr='0.0.0.0';
# 监听的端口,就是谁来访问自己
$ports_a = $ports -join ",";
# 移除旧的防火墙规则
iex "Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' " | Out-Null
# 允许防火墙规则通过这些端口
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Outbound -LocalPort $ports_a -Action Allow -Protocol TCP" | Out-Null
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort $ports_a -Action Allow -Protocol TCP" | Out-Null
# 使用 portproxy 让 Windows 转发端口
# https://docs.microsoft.com/en-us/windows-server/networking/technologies/netsh/netsh-interface-portproxy
for( $i = 0; $i -lt $ports.length; $i++ ){
$port = $ports[$i];
iex "netsh interface portproxy delete v4tov4 listenport=$port listenaddress=$addr" | Out-Null
iex "netsh interface portproxy add v4tov4 listenport=$port listenaddress=$addr connectport=$port connectaddress=$wslip" | Out-Null
}
代码里第 2、3 行的 `$
的意思是让 Powershell 执行脚本时不转义 $
。
如果你执行代码报错,可以试试在 `$
前面加上一个 \
:\`$
。
Powershell 语法里 @()
就是数组的意思,这个脚本遍历你设置的想暴露到局域网的端口的数组,然后用 portproxy 反代 Windows 的端口到 WSL 中。
一键设置代理#
先上效果:
而且还可以为 git 以及 ssh 同时设置代理。
代码见:
https://github.com/bytemain/dotfiles/blob/master/ubuntu_wsl/zshrc
重点见里面的 proxy
, unpro
, getIp
, proxy_git
, proxy_npm
等函数。
这样我们执行 proxy 的时候,就能一键连接到主机的代理上了。
有用的话别忘了给个 star,谢谢~