WSLを便利に使っているのだが、新しいPCをセットアップしたところWSL2になった事で、外部からWSLで立ち上げたサービスへの通信ができくなった。
調べてみると、この問題を解決するには、netshでportproxy 使う方法と、ブリッジモードを使う方法があるようだ。ブリッジモードは、うまくいかなかったので、netsh portproxyを使った設定を記載する。
portproxyを使う場合、いくつかのポイントがあるので、問題の切り分けの仕方をまとめた。
トラブル対応の一つとして、Windows Firewall Defenderのログについても記載した。
- Windows Firewall Defender
- 許可の設定とブロックの設定
- portproxyの設定
- サービスの問題
wslからDNSが引けない!
2022/12/01に突然、wslからDNSが引けない状況になった
こちらの記事を参考にWindows Firewallの設定を行って解決。
Windows 11, version 22H2を当てた直後なので、これが関係するかも
参考:Portproxyとブリッジの設定
基本的な流れ
ポートフォワードが起動時に実施するために、以下の対応を行っている
- タスクスケジューラーからポートフォワードスクリプトを実行する
- ~/.local/bin/start.sh からサーバープロセスを起動する
WSLのIPアドレスを確認する
portproxyを設定するためには、WSLに割り当てられているIPアドレスを知る必要がある。WSLのIPアドレスは動的に割り当てられるので、固定のIPアドレスで書くことが出来ない。
WSLで、ifconfigやhostnameを使うことで確認できるが、スクリプトで自動化するためには、PowershellからWSLのIPアドレスを確認できる必要がある。
Powershellから見る場合
> wsl hostname -I
該当するIPだけを抜き出す
> bash -c "ip route |grep 'eth0 proto'|cut -d ' ' -f9" # Unbuntu 10
> bash -c "ip route |grep 'eth0' |grep -v '/' |head -1 |cut -d ' ' -f2" # Ubuntu 20
WSL2のIPアドレスがわかれば、Powershell で設定が可能(管理者モードで実行する必要がある)
対象とするWindows側のIPアドアレスを明記する方法もあるが、"*"を設定することで、複数のインタフェースがある場合も対応できる。面倒も少ないので、ここでは"*"を指定。
$WSL_IP = xxx.xxx.xxx
$WIN_IP = "*" # 実装しているすべてのIPアドレス
$PORT = xxxx # Listenするポート
> netsh.exe interface portproxy add v4tov4 listenaddress=$WIN_IP listenport=$PORT connectaddress=$WSL_IP connectport=$PORT
> netsh.exe interface portproxy show all
> netsh.exe interface portproxy delete v4tov4 listenaddress=$WIN_IP listenport=$PORT # Delete
WSL2のportproxyを自動設定するスクリプト
ありがたいことに同じ悩みを持つ先人がWindows Defender Firewallの設定を含めたスクリプトを公開してくれていた(大変参考になりました)。
- Windows WSL2に外部から直接アクセスするための設定 rcmdnk's blog から
以下のスクリプトは、WSL2のバージョンで、"ip route"出力フォーマットが違うので、どちらのバージョンでも動くように修正したもの。すべてのパターンでうまくいくとは限らない。
このスクリプトを、タスクスケジューラで起動時に呼び出すようにする。
# Source: https://rcmdnk.com/blog/2021/03/01/computer-windows-network/
# WSLのIPアドレス取得部分を修正したもの
#
# Original work on Ubuntu 16.04.7 LTS
# % ip route
# default via 172.26.240.1 dev eth0
# 172.26.240.0/20 dev eth0 proto kernel scope link src 172.26.254.202
#
# Added: for Ubuntu 20.04.4 LTS
# % ip route
# none 172.17.64.0/20 dev eth0 proto none metric 256
# none 172.17.64.1 dev eth0 proto none metric 256
#
# 【WSL】UbuntuのOSの基本的な情報(OS名、バージョン名、アーキテクチャ、カーネルバージョン)を調べる方法
# https://mylife8.net/post-1184/
#
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators")) { Start-Process powershell.exe "-File `"$PSCommandPath`"" -Verb RunAs; exit }
$ip = bash.exe -c "ip route |grep 'eth0 proto'|cut -d ' ' -f9"
if( ! $ip ){
$ip = bash.exe -c "ip route | grep 'eth0' | grep -v '/' | head -1 | cut -d ' ' -f2" # added
if(! $ip){
echo "The Script Exited, the ip address of WSL 2 cannot be found";
exit;
}
}
# All the ports you want to forward separated by comma
$ports=@(22,3000,8080); # set ports you want to forward
$ports_a = $ports -join ",";
# Remove Firewall Exception Rules
iex "Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' ";
# Adding Exception Rules for inbound and outbound Rules
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Outbound -LocalPort $ports_a -Action Allow -Protocol TCP";
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort $ports_a -Action Allow -Protocol TCP";
for( $i = 0; $i -lt $ports.length; $i++ ){
$port = $ports[$i];
iex "netsh interface portproxy add v4tov4 listenport=$port listenaddress=* connectport=$port connectaddress=$ip";
}
# Show proxies
iex "netsh interface portproxy show v4tov4";
それでも接続できない
全て正しく設定したはずなのに、それでも通信ができない場合がある。
投げ出したい気持ちになるのだが、冷静に考えると以下の要因が考えられる。
- 送信元の問題
- 接続するIPアドレスかポートが間違っている(DHCP環境下ではよくある問題)
- 送信元のPCからパケットが出ていない
(送信元のWindows Defender Firewallのブロックなど)
- 受信側の問題
- Windows Defender FirewallでDROPしている
- 上記portproxy が適切に動いておらず、WSL2のサービスに到達していない
- サーバーが立ち上がっていないか、狙った設定になっていない
送信の問題は、色々と調べられると思うので割愛する。
受信側の問題は、なかなか厄介だが以下の切り分けが出来る。
- WSLのIPアドレスで接続できる場合は、サーバーは狙った通りに動いている
- WindowsのIPアドレスでも接続で接続できる場合は、portproxy の設定もあっている
Windows Defender FirewallでDROPされている可能性が高い
なお、netshのローカルIPアドレスを"*"の設定することは問題ないようだ。複数のIPアドレスで動いている場合で、全てのIPからの接続を受け付けたい場合は"*"が適切。
サーバーを立ち上げたPC上で実行
> telnet 172.xx.xx.xxx # WSLのIPアドレス
> telnet 192.168.1.xxx port # WindowsのIPアドレス
厄介なのが、Windows Defender FirewallでDROPしている場合。
当然、設定を確認することが基本だが、目的とした接続を適切に”許可”している場合でも接続できないことがある。このような場合は、Windows Defender Firewallのルールで”ブロック”側のルールを確認すると、思わぬ設定がある場合がある。
この記事を書いたきっかけは、WSL2で実行するperlに対する受信が全てブロックされていることが原因で、急にperlで書いたプログラムが動かないトラブルに遭遇したこと。証拠?はないが、Windows Updateの後で通信が出来なくなったので、この時のWindows Updateでルールが追加されたと推測している(セキュリティ的には、理解できないでもないが、これをやられるとつらい)。
Windows Firewall のログの確認方法
Windows Denfender FirewallでDROPされているかを確認したいときがある。Defaultでは、ログを記録しないが、DROPログ、ALLOWログを記録することができる。
設定方法やログの見方は、こちらをご参照いただきたい。Windows Firewall Defenderのプロパティからログを設定などが紹介されている。
ログを調べる際には、grepやtail -fを使いたいところだが、Windows Defender Firewallのログは、パーミッションが厳しく、WSLから見ることが出来ない。
この問題についても、紹介されている。
その一つを紹介すると、管理者モードで立ち上げたPowerShellのコンソールから、以下のスクリプトを実行することで、grepやTail -Fと同様の事が可能。
なお、出力がバッファされているようで、表示されるまでタイムラグがあるので、おまじないに-ReadCount 0を追加した。
Get-Content C:\windows\system32\LogFiles\Firewall\pfirewall.log -ReadCount 0 -Tail 0 -Wait"
Get-Content C:\windows\system32\LogFiles\Firewall\pfirewall.log -ReadCount 0 | Select-String "SEARCH STR"
tcpdump
WSL2ではtcpdumpが動くので、tcpdumpを使うことでパケットがWSLまで届いているのかを確認することが出来る。
それでも謎は多い
それでも残った謎は少なくない
- Windows Defender Firewallのルールは、チェックの順番があるようで、通るときと通らないときがある
- 動作するセッティングのPCでWSL2のIPアドレスが変わっていることがある
不思議なことにportproxyはそのまま動いているように見える。
もしかすると、サービスは立ち上げた時のIPアドレスで動いていると考えるとあっているのだろうか
地道に確認するしかないな。