野声

Hey, 野声!

谁有天大力气可以拎着自己飞呀
twitter
github

WSL2 的一些網絡訪問問題

快考完試了,這個學期一直在使用 WSL 在進行開發,無論是 Python/C/React 都是用 VSCode Remote WSL 進行開發的,體驗非常好。

無特別說明,本文以下內容所提到的 WSL 皆指 WSL2。

這篇文章大概有以下內容:

  1. WSL2 中連接到主機代理
    讓 WSL2 裡能連上 Windows 上的代理軟體
  2. 主機訪問 WSL2
    這裡主要是靠設定 hosts,讓一個域名一直解析到 WSL2 的 ip 上。
  3. 局域網訪問 WSL2
    局域網內訪問你的 Windows 主機,Windows 轉發端口到 WSL2 上

還有幾個前置知識:

  1. Windows 和 WSL2 算是在同一個局域網內,這個局域網是由 Hyper-V 創建的。
  2. WSL2 使用的網路適配器是 'Default Hyper-V Switch',這個適配器每次重啟都會被刪除重建,這就是 WSL2 為什麼 IP 不固定的原因。
  3. WSL2 內有些微軟特意做的東西:
    1. WSL2 的 IP 發送的請求都會被轉發到 Windows 的 IP 上,但是這個時靈時不靈。

WSL2 連接到主機代理#

其實這個問題我在之前那篇配置 ArchWSL 的文章裡簡單提了一下,大概流程如下:

  1. 獲取 Windows 的 ip
  2. Windows 上的代理軟體允許局域網訪問
  3. 設定 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

image.png

設定代理#

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 會創建一張網卡,接管系統的流量。

image.png

這樣就不用設定任何東西了,開啟網卡代理後,整個系統的流量都會走網卡。

你也可以使用下面這些:

  • 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 腳本,來做一些事。

我們需要寫一個腳本(地址)實現我們想要的功能。

腳本的功能大概是:

  1. 讀取 WSL 和 Windows 的 IP
  2. 將 IP 和想設定的域名寫入 Windows 的 hosts 文件中。

這樣你就能用自己定義的域名來訪問兩個系統了,wsl2 能訪問 win.local 是因為它會向主機查詢 dns(因為 wsl2 預設的 nameserver 指向了 windows 主機),主機會把 hosts 中的域名直接快取起來然後直接作為一個 dns 記錄。

關鍵來了,我們要使用任務計劃程序WSL 要更新 IP 的時候執行這個腳本。

具體WSL 要更新 IP 時運行特定腳本步驟如下:

  1. 鏈接中的代碼保存到本地文件中,文件名後綴設為 .ps1
  2. 打開事件查看器。在小娜的搜索框裡搜一下就能打開了。
  3. 點擊 Windows 日誌 -> 系統
  4. 找到 Hyper-V-VmSwith 事件,查看有沒有內容類似 Port ... (Friendly Name: ...) successfully created on switch ... (Friendly Name: WSL).的事件。
  5. 右鍵單擊該事件,選擇 將任務附加到該事件
  6. 操作 选择 啟動程序程序中填 powershell參數-file 你的腳本地址的絕對地址 就好了。參數中加上 -WindowStyle Hidden 可以讓 Powershell 執行該腳本時隱藏窗口。
  7. 然後在任務計劃程序左側資源欄中找到:事件查看器任務 -> 你剛創建的任務,右鍵屬性,然後勾選下面的複選框:使用最高權限運行

看看效果:

在 WSL 中啟動一個 http 伺服器:
wsl_http_server.png

我們在 win 下請求一下:
curl_wsl.png

Awesome! 成功啦

你也可以使用下面這個小工具來實現類似的功能:
shayne/go-wsl2-host

這是一個用 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 的圖解:
image.png

所以讓請求 0.0.0.0 的請求都轉發到請求 127.0.0.1 上。

現在:
image.png

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 中。

一鍵設定代理#

先上效果:
image.png
而且還可以為 git 以及 ssh 同時設定代理。

代碼見:
https://github.com/bytemain/dotfiles/blob/master/ubuntu_wsl/zshrc

重點見裡面的 proxy, unpro, getIp, proxy_git, proxy_npm 等函數。

這樣我們執行 proxy 的時候,就能一鍵連接到主機的代理上了。

有用的話別忘了給個 star,謝謝~

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。