野声

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 内には Microsoft が特に作成したものがあります:
    1. WSL2 の IPに送信されたリクエストはすべてWindows の IPに転送されますが、これは時々不安定です。

WSL2 がホストプロキシに接続#

実際、この問題については以前のArchWSL の設定に関する記事で簡単に触れました。大まかな流れは以下の通りです:

  1. Windows の IP を取得
  2. Windows 上のプロキシソフトがローカルネットワークからのアクセスを許可
  3. WSL2 のプロキシを設定

ホストの IP を取得#

WSL2 は Hyper-V 仮想マシンを使用しているため、Windows と同じ localhost を共有することはできず、再起動のたびに IP が変わります。現在、WSL 内で以下の 2 つのコマンドを使用してホストの 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 に更新された後、プログラムが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ファイルに書き込む。

これにより、定義したドメイン名を使用して 2 つのシステムにアクセスできるようになります。wsl2 がwin.localにアクセスできるのは、ホストに DNS を問い合わせるからです(WSL2 のデフォルトの nameserver が Windows ホストを指しているため)、ホストは hosts 内のドメイン名を直接キャッシュし、DNS レコードとして使用します。

重要な点は、タスクスケジューラを使用してWSLが IP を更新する際にこのスクリプトを実行することです。

具体的に **WSLが IP を更新する際に特定のスクリプトを実行する ** 手順は以下の通りです:

  1. リンクのコードをローカルファイルに保存し、ファイル名の拡張子を.ps1に設定します。
  2. イベントビューワーを開きます。Cortana の検索ボックスで検索すれば開けます。
  3. Windows ログ -> システムをクリックします。
  4. Hyper-V-VmSwitchイベントを見つけ、Port ... (Friendly Name: ...) successfully created on switch ... (Friendly Name: WSL).に似た内容のイベントがあるか確認します。
  5. そのイベントを右クリックし、そのイベントにタスクを追加を選択します。
  6. 操作プログラムの開始を選択し、プログラムpowershell引数-file あなたのスクリプトの絶対パスを入力します。引数に-WindowStyle Hiddenを追加すると、PowerShell がスクリプトを実行する際にウィンドウを隠すことができます。
  7. その後、タスクスケジューラの左側のリソースバーで、イベントビューワータスク -> あなたが作成したタスクを見つけ、右クリックしてプロパティを選択し、下のチェックボックスをチェックします:最高の権限で実行

効果を見てみましょう:

WSL で HTTP サーバーを起動します:
wsl_http_server.png

Windows でリクエストを送ります:
curl_wsl.png

素晴らしい!成功しました。

以下の小ツールを使用して、類似の機能を実現することもできます:
shayne/go-wsl2-host

これは Go で書かれた小ツールで、Windows サービスを作成し、WSL2 VM の IP アドレスで Windows の hosts ファイルを自動的に更新します。

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 内で 2 つのコマンド(波括弧内の 2 つ)を実行するだけで、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 に転送します。

原理は上記の 2 つの図と似ています。

ホストから 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 "スクリプトが終了しました。WSL 2のIPアドレスが見つかりません。";
  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

特にproxyunprogetIpproxy_gitproxy_npmなどの関数に注目してください。

これにより、プロキシを実行すると、ホストのプロキシに一鍵で接続できるようになります。

役に立ったら、スターを忘れずに、ありがとうございます~

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。