快考完試了,這個學期一直在使用 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,謝謝~