Windows上で立てたTCPサーバーにWSL2上のクライアントから接続する

# はじめに
WSL2上で立てたサーバーにWindows側から接続する場合、localhostを使う方法が一般的に紹介されています。しかし、逆の場合はWindowsファイアウォールがWSLをパブリックネットワークとみなしてしまうため、明示的に許可しないと接続する事が出来ません。安全に許可する方法について検討したため、記録として残します。

# 使用環境
OS：WIndows 10 home 2009
仮想環境：WSL2、Ubuntu 24.04.2 LTS

# 設定概要
始めに設定の要点だけ下記にまとめておきます。
+ wf.mscを起動。設定したいアプリケーションの規則を開き、接続を許可、パブリック、リモートIPアドレスに172.16.0.0/12を設定。エッジトラバーサルを"アプリケーションに従う"
+ インターフェースの種類を"ローカルエリアネットワーク"に設定。

>+ 172.16.0.0/12はクラスB プライベートIPアドレスのすべての値の範囲。
>+ WSL2 は仮想NIC上にあるため、Windows から見ると「NAT越しの別マシン」に見える。従って、エッジトラバーサルの許可をアプリケーションに従う必要がある。
>+ "アプリケーションに従う"は、アプリケーションが開いているポートの設定に応じて例外を許可する設定。
>+ I/Fを"ローカルエリアネットワーク"に設定する事で、物理的な接続のあるデバイス、及び仮想NICのみ接続を許可できます。

# WSL2のネットワーク構成
Windows、WSL間のネットワーク構成は下記のようになっており、仮想Ethernetで接続されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4085876/42e8789b-6188-43b2-9405-ae12681f2cda.png" width=50%>


ここで、Windows → WSLの通信ではlocalhostを用いて、IP指定を意識しなくても接続できるような設計になっています。localhostとは、コンピュータが自分自身を表す際の特殊なホスト名です。一方で、WSL → Windows間の通信ではlocalhostで接続できるようにはなっておらず、NAT外の別のIPからの接続として扱われてしまうため、Windowファイアウォールにおいてパブリックネットワークでの受信を許可していないとブロックされてしまうようです。
Network Address Translation (NAT)：プライベートIPアドレスとグローバルIPアドレスの変換

# TCPサーバー/クライアントの作成
Pythonを用いて、Windows上でTCPサーバー、WSL上でクライアントを立ち上げていきます。まず、WindowsのコマンドプロンプトでPythonを起動し、下記を実行します。この時、Windowsファイアウォールの接続設定の画面が出るかもしれませんが、とりあえずプライベートネットワークのみ許可しておきます。

```python:server.py
import socket

s = socket.socket()
s.bind(('0.0.0.0', 3000))
s.listen(1)
conn, addr = s.accept()
```

次に、WSL2を起動し、下記を実行してWSL2から見たWindowsのIPアドレスを取得します。
```bash:
grep nameserver /etc/resolv.conf | awk '{print $2}'
```
取得したIPアドレスに対して、WSL2上のクライアントから接続を試みます。この時、WSLからの接続はパブリックネットワークと認識され、ファイアウォールにブロックされているため、接続は出来ないと思います。

```python:client.py
import socket

s = socket.socket()
s.connect(('172.28.160.1(例)', 3000))
```

# Windowsファイアウォールの設定
次に、Windowsファイアウォールの設定で明示的に接続を許可します。パブリックの接続を完全に許可するとどかからでも接続できるようになってしまうため、プライベートネットワークのIPアドレス、かつ物理的な接続のあるLAN、又は仮想NICに限定していきます。
<b>

まず、Windowsの検索窓から"wf.msc"と入力してファイアウォールの設定画面を開き、TCPサーバー側のアプリケーションを探します。今回はpythonなので、python.exeを探し、プロファイルがパブリック、プロトコルがTCPのものを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4085876/6ef4c6d5-889c-4694-ba9d-3818d30700cd.png)

全般で"接続を許可"、スコープで"172.16.0.0/12"のみ許可をします。ここで、"172.16.0.0/12"はクラスBのプライベートIPアドレスを全て許可するという意味になります。Windowsファイアウォールのデフォルトはブロックなので、明示的に許可していない他のIPはブロックされます。
>心配な場合はWSLのIPを調べてそれのみ許可してもいいですが、WSLのIPは固定ではないため、変わってしまった場合に変更する手間が出てきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4085876/328192a9-820b-4dbf-8854-26f6fa4849f3.png" width=40%>

次に、詳細設定でインターフェースの種類を"ローカルエリアネットワーク"にします(WindowsのバージョンによってはLANの場合もあるようです)。こうすることで、WSLやLANなど、仮想NICや物理接続されたデバイスからの接続のみ許可する事ができ、意図しないwi-fiからの接続などを防ぐことができます。また、エッジトラバーサルは"アプリケーションに従う"にしておきます。
>WSLはWindows から見ると「NAT越しの別マシン」に見えるため、エッジトラバーサルの許可をアプリケーションに従う必要があります。"アプリケーションに従う"は、アプリケーションが開いているポートの設定に応じて例外を許可する設定です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4085876/99c56827-9e7a-4d6f-9419-fdb57a5d0921.png" width=70%>

設定を適用して再度接続を試みます。s.accept()の実行が完了したら接続できたということです。
>addrにはWSLのIPが格納されています。WSLのIPのみ許可したい場合はファイアウォール設定をこのアドレスに変更しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4085876/39b1d3b4-6d57-44b9-9802-9ee29a14bb15.png)


# おわりに
最後まで読んで頂きありがとうございました。この手法は、物理接続したLANからのプライベートIPによる接続を全て許可する設定ですので、安全性はLANのセキュリティに強く依存する事に留意しておく必要があります。特に、公共のネットワークに有線接続をした場合はセキュリティリスクがあります。
その他、セキュリティ上のアドバイスなどありましたら、コメント頂けるとありがたいです!