LoginSignup
3
3

More than 1 year has passed since last update.

Yet Another WSLに外部から接続できない問題

Last updated at Posted at 2022-04-07

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とブリッジの設定

基本的な流れ

ポートフォワードが起動時に実施するために、以下の対応を行っている

  1. タスクスケジューラーからポートフォワードスクリプトを実行する
  2. ~/.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の設定を含めたスクリプトを公開してくれていた(大変参考になりました)。

以下のスクリプトは、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";

それでも接続できない

全て正しく設定したはずなのに、それでも通信ができない場合がある。
投げ出したい気持ちになるのだが、冷静に考えると以下の要因が考えられる。

  • 送信元の問題
  1. 接続するIPアドレスかポートが間違っている(DHCP環境下ではよくある問題)
  2. 送信元のPCからパケットが出ていない
    (送信元のWindows Defender Firewallのブロックなど)
  • 受信側の問題
  1. Windows Defender FirewallでDROPしている
  2. 上記portproxy が適切に動いておらず、WSL2のサービスに到達していない
  3. サーバーが立ち上がっていないか、狙った設定になっていない

送信の問題は、色々と調べられると思うので割愛する。
受信側の問題は、なかなか厄介だが以下の切り分けが出来る。

  • 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でルールが追加されたと推測している(セキュリティ的には、理解できないでもないが、これをやられるとつらい)。

perlのブロック設定
image.png

”操作”でソートし、ブロックを抜き出したもの
image.png

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アドレスで動いていると考えるとあっているのだろうか

地道に確認するしかないな。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3