かつて山市良と呼ばれたおじさんのブログ

メモ. WSL2へのポートプロキシ設定に“ ”で苦労した話

2024年04月23日配信
2024年04月23日更新
執筆者:山内 和朗

 このブログは、月曜、木曜更新を基本としてスタートしました。定例スケジュールは1つのテーマがしばらく続く形になっています。この定例スケジュールとは別に、重大なトラブル情報や、ちょっとした思い付きやアイデア、最近気になることなど、タイムリーに共有できるように、今回のように「メモ」として、不定期に差し込んでいくことにします。

 今回は、公式ドキュメントからのコピペが期待通りに動かずに、数時間も苦労してしまった話です。Windows Subsystem for Linux(WSL 2)上のディストリビューションへのLANからのアクセスを可能にするために、ポートプロキシ(ポート転送)を構成したのですが...

 

評価によく使うであろう周辺環境を、WSL 2で作っておく

 

 現在、自分専用のラボ環境をAzure IaaSおよび物理サーバーに構築しようとしている最中です(詳しいことはこのブログの定例スケジュールにて来月に)。ラボ環境の基盤はWindows Server 2022のHyper-Vで用意し、Hyper-Vの仮想ネットワークでクローズドな評価環境を準備する予定です。評価や検証時に、本番環境に影響してはいけないので、電子メール送受信環境など、クローズドな環境内に予め用意しようと考えています。

 1つの実現案として思いついたのは、Windows Subsystem for Linux(WSL 2)の活用です。WSL 2は本物のLinuxカーネルが動くシェル環境(一部GUIに対応)であり、Windows 10/11の機能だと思われがちですが、Windows Server 2022でも利用可能です(ただし、デスクトップエクスペリエンスが必要、Server Coreではうまくいかないと思います)。

 WSL 2のLinux環境で、必要な環境(例えば、postfixとdovecotの電子メール環境《smtp、pop3、imap》)を用意しておくのです。WSL 2にはエクスポート/インポート機能があるので、スタンドアロンサーバーにWSL 2の利用環境を用意すれば、構成済みの環境をインポートするだけですばやく導入できると考えました(画面1)。WSL 2の既定のネットワーク構成では、ディストリビューションはWindowsで管理されるNATネットワークに接続され、localhost(127.0.0.1)によるホストからのアクセスが可能です。

 

画面1

画面1 Ubuntu.tarはpostfixとdovecotを構成済みのものをエクスポートしたもの。スタンドアロンサーバーにインポートするだけで、クローズドなメール環境が利用可能になる予定(メール環境の設定や動作は検証中)


 NATネットワークは外部から遮断されているため、そのままでは外部から接続することはできません(画面2)。外部からの接続を可能にする方法の1つに、Windowsのポートプロキシを使用したポート転送があります。

画面2
画面2 WSL 2側のポートがWindows側のIPアドレスでLISTENされることはない

 

公式ドキュメントからコピペしたのにどうして機能しないの?

 

 NATネットワークに接続されたWSL 2のディストリビューションには、Windows(WSL 2)によってIPアドレスが動的に割り当てられます。このディストリビューションの特定のTCPポートへのアクセス(例えば、今回の場合はSMTPポート「25」)を許可するには、netshコマンドで構成可能なポートプロキシを使用できます。その方法は以下の公式ドキュメントで説明されています。

WSL を使用したネットワーク アプリケーションへのアクセス|ローカル エリア ネットワーク (LAN) からの WSL 2 ディストリビューションへのアクセス
https://learn.microsoft.com/ja-jp/windows/wsl/networking#accessing-a-wsl-2-distribution-from-your-local-area-network-lan

 最低限必要なことは、ポート番号とディストリビューションに動的に割り当てられたIPアドレスです。そのIPアドレスはディストリビューション側でhostname -Iを実行すれば分かりますし、Windows側からもwsl hostname -I(またはwsl -e hostname -Iまたはwsl -d <Distribution> -e hostname -I)を実行して取得することもできます。


 これらの情報を取得したら、次のようにnetshコマンドを実行することでポートプロキシを構成できます(画面3)。

C:\> netsh interface portproxy add v4tov4 listenport=25 listenaddress=0.0.0.0 connectport=25 connectaddress=<WSL側IPアドレス>

画面3
画面3 netshコマンドによるポートプロキシの設定。動的に変わるIPアドレスを毎回確認して入力するのは面倒

 公式ドキュメントを見ると、PowerShellで実行するのであれば、次のように記述することができるようです。WSL 2側のIPアドレスは起動する度に動的に変わるため、このように記述できるのは、実にありがたいことです。なお、WSL 2ではhostname -IはIPv4アドレスのみを返しますが、WSL 1 は IPv4とIPv6の両方のIPアドレスが返ってくる場合があるので、このようには記述できない可能性があります。
PS C:\> netsh interface portproxy add v4tov4 listenport=25 listenaddress=0.0.0.0 connectport=25 connectaddress=(wsl hostname -I)


 しかし、ここから数時間の苦悩が始まりました。

 

 WSL 2側のIPアドレスを直接入力した場合は(この他にWindowsファイアウォールでの受信の規則の許可設定も必要)、期待通りにWSL 2側へポート転送されるのですが、=(wsl hostname -I)の方のコマンドラインはポートプロキシの設定はされるものの、ポート転送される気配がありません。しかも、netsh interface portproxy show v4tov4で両者の設定を比べてみても、全く同じプロキシ設定のように見るのです(画面4)。

 

画面4

画面4 2つの設定はまったく同じように見えるが、下のコマンドラインの設定のほうは全くポート転送してくれない(別のPCで再現)

 数時間試行錯誤しましたが、うまくいきません。パラメーターの順番を変えたりして、実行されるコマンドラインを変数に入れて確認してみたところ、ようやく不自然な空白を見つけました。(wsl hostname -I)が返すIPアドレスの最後に、空白が1つ付いていたのです。おそらく、複数のIPアドレスを返すことがあるのを想定しての空白なのでしょう。Trim()メソッドで末尾(および先頭)の空白を取り除くことで期待通りに動くようになりました(画面5)。ちなみに、最初は日本語環境の問題かとも思いましたが、英語環境でも同様に空白が付いていました。つまり、公式ドキュメントのコマンドラインでは期待どおりに設定できないのです。

画面5
画面5 wsl -e hostname -Iが返すIPアドレスには最後に余計な空白が付いていた

 というわけで、次のようなPowerShellスクリプトコードを実行すれば、WSL 2側のポート25へのポート転送を開始し、Enterの入力で終了し、設定のクリーンアップができるだろうと思います。なお、WSL 2は現在、IPv6に正式には対応していませんが、WSL 2がIPv6に正式対応したときには、返り値から前方にあるIPv4アドレスだけを取り出すコードが追加で必要になると思います。

$wslip = (wsl -e hostname -I).Trim()
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=25 connectaddress=$wslip connectport=25
New-NetFirewallRule -Name "MyWSLRule1" -DisplayName "Allow Inbound 25" -Protocol TCP -LocalPort 25 -Action Allow
Write-Host "Port forwarding to WSL 2 in progress..."
Write-Host "Press Enter to exit"
cmd /c Pause | Out-Null
netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=25
Remove-NetFirewallRule -Name "MyWSLRule1"

blog_subscribe

blog_comment

最新記事