wsl2でsshuttle - winでssh先に簡易VPNを張る

# wsshuttle
WSL2 から手軽に sshuttle を行う [wsshuttle](https://github.com/yabeenico/wsshuttle) を作ったので、お試しください。

```bash:インストール
sudo apt install -y ipcalc
pip3 install sshuttle # apt によってインストールされるものはバージョンが古くて動かない可能性がある
sudo wget https://raw.githubusercontent.com/yabeenico/wsshuttle/main/wsshuttle -O /usr/local/bin/wsshuttle
sudo chmod +x /usr/local/bin/wsshuttle
```

```bash:実行例
# ルーティングテーブルを変更するため、実行する WSL2 のターミナルに Windows の admin 権限が必要。
wsshuttle -r ssh-server -x 157.0.0.0/8 0/0
```

```bash:dry-run
wsshuttle -r ssh-server -x 157.0.0.0/8 0/0 --dry
route.exe delete 157.0.0.0 mask 255.0.0.0
route.exe delete 3.3.3.3   mask 255.255.255.255
route.exe delete 0.0.0.0   mask 128.0.0.0
route.exe delete 128.0.0.0 mask 128.0.0.0
route.exe add    157.0.0.0 mask 255.0.0.0       192.168.3.1    metric 1 if 7
route.exe add    3.3.3.3   mask 255.255.255.255 192.168.3.1    metric 1 if 7
route.exe add    0.0.0.0   mask 128.0.0.0       172.18.187.223 metric 1 if 46
route.exe add    128.0.0.0 mask 128.0.0.0       172.18.187.223 metric 1 if 46
sshuttle -l 0.0.0.0:0 -x 3.3.3.3 -r ssh-server -x 157.0.0.0/8 0/0
route.exe delete 157.0.0.0 mask 255.0.0.0
route.exe delete 3.3.3.3   mask 255.255.255.255
route.exe delete 0.0.0.0   mask 128.0.0.0
route.exe delete 128.0.0.0 mask 128.0.0.
```

# 要約
外出先 win から 自宅 NW に VPNを張りたい。
コマンドは管理者権限を持った wsl2 で実行。

- インストール

```
sudo apt install -y sshuttle ipcalc
```

- env: 環境変数を設定

```
export IF_WIN_MAC=XX-XX-XX-XX-XX-XX # 手動で設定: win の 物理 NIC の MAC アドレス
export HOME_NW=192.168.3.0/24 # 手動で設定: 自宅の NW (LAN)
export HOME_GIP=$(ssh ssh-server curl inet-ip.info 2>/dev/null) # もしくは手動で設定

export HOME_NW_ADDR=$(ipcalc $HOME_NW | grep ^Address | awk '{print $2}')
export HOME_NW_MASK=$(ipcalc $HOME_NW | grep ^Netmask | awk '{print $2}')
export IF_WIN_IDX=$(route.exe print | grep -i "$(echo $IF_WIN_MAC | perl -pe 's/-/ /g')" | awk -v FS=. '{print $1}' | sed 's/ //g')
export IF_WIN_IP=$(arp.exe -a | grep ^Interface | grep $(printf %x $IF_WIN_IDX) | awk '{print $2}')
export VIF_WIN_IP=$(grep -o '172.*' /etc/resolv.conf)
export VIF_WIN_IDX=$(arp.exe -a | grep ^Interface | grep $VIF_WIN_IP | awk '{print $4}' | perl -pe '$_=hex')
export VIF_WSL2_IP=$(hostname -I)
```

```
$ env | egrep 'IF_W|HOME_' | sort
HOME_GIP=30.30.30.30
HOME_NW=192.168.3.0/24
HOME_NW_ADDR=192.168.3.0
HOME_NW_MASK=255.255.255.0
IF_WIN_IDX=21
IF_WIN_IP=10.0.0.2
IF_WIN_MAC=XX-XX-XX-XX-XX-XX
VIF_WIN_IDX=33
VIF_WIN_IP=172.21.48.1
VIF_WSL2_IP=172.21.49.46
```

- lan: 宛先が 192.168.3.0/24 のパケットのみをトンネリング。

```
route.exe add $HOME_NW_ADDR mask $HOME_NW_MASK $VIF_WSL2_IP metric 1 if $VIF_WIN_IDX
sshuttle -r ssh-server -l 0 $HOME_NW
```

- global: 全てのパケットをトンネリング。

```
route.exe delete 0.0.0.0
route.exe add $HOME_GIP mask 255.255.255.255 $IF_WIN_IP metric 1 if $IF_WIN_IDX
route.exe add 0.0.0.0 mask 0.0.0.0 $VIF_WSL2_IP metric 2 if $VIF_WIN_IDX
sshuttle -r user@$HOME_GIP:22 -l 0 0/0 -x $HOME_GIP # ssh 接続先は適宜設定 (ドメインの名前解決は不可)
```

- default: VPN を解除。

```
route.exe delete 0.0.0.0
route.exe add 0.0.0.0 mask 0.0.0.0 $IF_WIN_IP metric 1 if $IF_WIN_IDX
```

# 目的

外出先 win から自宅の NW (192.168.3.0/24) に簡易 VPN を張る (画像を参照)。
以下、lan と global の2パターンについて解説する。

- lan: 宛先が 192.168.3.0/24 のパケットのみをトンネリング。
  -  外出先 win から `curl.exe 192.168.3.2` を叩いて結果を得る。 (or ブラウザで `192.168.3.2` を開く)
- global: 全てのパケットをトンネリング。
  - 外出先 win から `curl.exe inet-ip.info` (グローバル IP 取得) を叩いて `30.30.30.30` の結果を得る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101023/f5b97ccd-510d-91a3-55bf-ffd208bbee96.png)

# 手順
1. 図の `routing`: `route.exe` を利用し、 win が送信するパケットを wsl2 を迂回するように、 win のルーティングテーブルを設定する。 (wsl2 のシェルを管理者権限で実行する必要あり)
2. 図の `shuttle`: `sshuttle` を利用し、 wsl2 から ssh 接続可能な NW に対して簡易 VPN を張る。

# 解説
- wsl2 は、図に示すとおり、ゲスト OS とホスト OS が仮想 NW で接続される。
  - VIF_WIN、VIF_WSL2 は、その仮想 NWに接続された仮想 NIC。
  - IF_WIN は、win の物理 NIC。
- `route.exe add 192.168.3.0 mask 255.255.255.0 172.21.49.46 metric 1 if 33`
  - 宛先が `192.168.3.0/24` のパケットを、NIC インデックス == `33` の NIC から `172.21.49.46` に向けて転送。
  - metric は優先度。小さいほど優先度が高い。
- `sshuttle -r ssh-server -l 0 192.168.3.0/24 -x 192.168.3.100`
  - 宛先が `192.168.3.0/24` のパケットを ssh-server 経由でトンネリング。
  - ただし、宛先が `192.168.3.100` のパケットはトンネリングしない。
  - `-l 0` は `-l 0.0.0.0/0.0.0.0` の略で、全ての送信元のパケットを許可。

# 実用的なスクリプト
 要旨に記述したコマンドに初期化処理や後始末を付け加えたもの。

## check.sh
VPN が機能しているかを確認する、

- グローバル IP の確認
  - 20.20.20.20: 外出先
  - 30.30.30.30: 自宅
- web-server (192.168.3.2) への疎通確認

``` check.sh
#!/bin/bash

curl     inet-ip.info
curl     -sS --connect-timeout 1 192.168.3.2 | head -n 1

curl.exe inet-ip.info
curl.exe -sS --connect-timeout 1 192.168.3.2 | head -n 1
```

## env.sh
``` env.sh
#!/bin/bash

export IF_WIN_MAC=XX-XX-XX-XX-XX-XX # 手動で設定: win の 物理 NIC の MAC アドレス
export HOME_NW=192.168.3.0/24 # 手動で設定: 自宅の NW (LAN)
export HOME_GIP=$(ssh ssh-server curl inet-ip.info 2>/dev/null) # もしくは手動で設定

export HOME_NW_ADDR=$(ipcalc $HOME_NW | grep ^Address | awk '{print $2}')
export HOME_NW_MASK=$(ipcalc $HOME_NW | grep ^Netmask | awk '{print $2}')

export IF_WIN_IDX=$(route.exe print | grep -i "$(echo $IF_WIN_MAC | perl -pe 's/-/ /g')" | awk -v FS=. '{print $1}' | sed 's/ //g')
export IF_WIN_IP=$(arp.exe -a | grep ^Interface | grep $(printf %x $IF_WIN_IDX) | awk '{print $2}')

export VIF_WIN_IP=$(grep -o '172.*' /etc/resolv.conf)
export VIF_WIN_IDX=$(arp.exe -a | grep ^Interface | grep $VIF_WIN_IP | awk '{print $4}' | perl -pe '$_=hex')
#export VIF_WIN_IP=$(arp -a | awk -v FS='[()]' '{print $2}' | sort | head -n 1)

export VIF_WSL2_IP=$(hostname -I)
```

```
$ source env.sh
$ env | egrep 'IF_W|HOME_' | sort
HOME_GIP=30.30.30.30
HOME_NW=192.168.3.0/24
HOME_NW_ADDR=192.168.3.0
HOME_NW_MASK=255.255.255.0
IF_WIN_IDX=21
IF_WIN_IP=10.0.0.2
IF_WIN_MAC=XX-XX-XX-XX-XX-XX
VIF_WIN_IDX=33
VIF_WIN_IP=172.21.48.1
VIF_WSL2_IP=172.21.49.46
```

## default.sh
VPNを解除。

``` default.sh
#!/bin/bash

source env.sh
# set -x

route.exe delete $HOME_GIP
route.exe delete $HOME_NW_ADDR

route.exe delete 0.0.0.0
route.exe add 0.0.0.0 mask 0.0.0.0 $IF_WIN_IP metric 1 if $IF_WIN_IDX
```

```
$ ./default.sh # set -x をアンコメントして実行した結果
+ route.exe delete 30.30.30.30
The route deletion failed: Element not found.

+ route.exe delete 192.168.3.0
The route deletion failed: Element not found.

+ route.exe delete 0.0.0.0
 OK!

+ route.exe add 0.0.0.0 mask 0.0.0.0 172.28.100.51 metric 1 if 21
 OK!
```

```
$ ./check.sh
20.20.20.20
curl: (28) Connection timed out after 1001 milliseconds
20.20.20.20
curl: (28) Connection timed out after 1000 milliseconds
```

グローバル IP が外出先 (20.20.20.20) かつ、web-server とも疎通していない。

## lan.sh
宛先が `HOME_NW == 192.168.3.0/24` のパケットのみをトンネリング。

``` lan.sh
#!/bin/bash

source env.sh
set -x

./default.sh &>/dev/null

route.exe add $HOME_NW_ADDR mask $HOME_NW_MASK $VIF_WSL2_IP metric 1 if $VIF_WIN_IDX
sshuttle -r ssh-server -l 0 $HOME_NW

./default.sh &>/dev/null
```

```
$ ./lan.sh
+ ./default.shl
+ route.exe add 192.168.3.0 mask 255.255.255.0 172.21.49.46 metric 1 if 33
 OK!
+ sshuttle -r ssh-server -l 0 192.168.3.0/24
client: Connected.
^Cclient:
client: Keyboard interrupt: exiting.
+ ./default.sh
```

```
$ ./check.sh # sshuttle 実行中に別のシェルで実行
20.20.20.20
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
20.20.20.20
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
```

グローバル IP は外出先 (20.20.20.20) だが、web-serverとは疎通している。

## global.sh
全てのパケットをトンネリング。

``` global.sh
#!/bin/bash

source env.sh
set -x

./default.sh &>/dev/null

route.exe delete 0.0.0.0

route.exe add $HOME_GIP mask 255.255.255.255 $IF_WIN_IP metric 1 if $IF_WIN_IDX
# 宛先が HOME_GIP (30.30.30.30) のパケットは例外として、物理 NIC (IF_WIN_IDX == 21) から送信する。

route.exe add 0.0.0.0 mask 0.0.0.0 $VIF_WSL2_IP metric 2 if $VIF_WIN_IDX
# 上記例外以外の全てのパケットは win の 仮想 NIC (VIF_WIN_IDX == 33) 経由で WSL2 の 仮想 NIC (VIF_WSL2_IP == 172.21.49.46) に転送。

sshuttle -r user@$HOME_GIP:22 -l 0 0/0 -x $HOME_GIP
# 全てのパケットをトンネリング。
# ただし、宛先が HOME_GIP (30.30.30.30) のパケットを除く。
# HOME_GIP をトンネリング対象から除外しないと、HOME_GIP に ssh 接続できなくなる。
# また、名前解決の問い合わせパケットもやり取りできなくなるため、ssh の接続先は IP アドレス直打ちにする。

./default.sh &>/dev/null
```

```
$ ./global.sh
+ ./default.sh
+ route.exe delete 0.0.0.0
 OK!
+ route.exe add 30.30.30.30 mask 255.255.255.255 172.28.100.51 metric 1 if 21
 OK!
+ route.exe add 0.0.0.0 mask 0.0.0.0 172.21.49.46 metric 2 if 33
 OK!
+ sshuttle -r user@30.30.30.30:22 -l 0 0/0 -x 30.30.30.30
client: Connected.
^Cclient:
client: Keyboard interrupt: exiting.
+ ./default.sh
```

```
$ ./check.sh # sshuttle 実行中に別のシェルで実行
30.30.30.30
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
30.30.30.30
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
```

グローバル IP が自宅 (30.30.30.30) かつ、web-server とも疎通している。