無線 LAN を持たない Raspberry Pi Zero を OTG 経由でインターネットに繋ぐ (Linux 編)

以下のエントリーで Raspberry Pi Zero に USB ケーブル経由で ssh 接続するようにすることができました。

https://qiita.com/tkarube/items/bf25e542a8d0cc8d027c

当然この Linux 経由で Raspberry Pi Zero がインターネットに繋がればパッケージのアップデートやインストールをしたりするのに便利です。Windows や macOS ならもっと簡単に接続できますが Linux 環境ではやはりひと手間かかります。Linux 経由でインターネット接続をしてみてその際のパケットの流れがどうなってるのかも簡単に見ていきます。

まずネットワークはこんな構成です。ノートパソコン (Linux) が無線 LAN 経由でネットに繋がっています。Raspberry Pi Zero はその Linux に usb ケーブルで接続されています。wlan0 や usb0 は ip コマンドで確認できるデバイスの名前です。IP アドレスは将来的に割り振られる予定のものなのでとりあえずは無視してください。

```
[ Router ] 10.184.1.1/24 +--+ 10.184.1.147/24 - wlan0 [ Linux ] usb0 - 10.189.99.10/24 +--+ 10.189.99.xx/24 - usb0 [ Raspberry Pi Zero ]
```

尚、前回に引き続き Kali Linux がノートパソコンにインストールされているため Debian 系の Linux に関連する手順となります。

方法は複数ありますが一番簡単だと思う NAT で接続します。Bridge を使用した方法もありますが調べてみるとどうも Wifi は Bridge の接続先として設定することが難しいようです。

# Linux の usb0 への静的 IP アドレスの設定

Linux の usb0 に 10.189.99.0/24 の静的な IP アドレスを割り振ります。割り振る IPv4 は任意のプライベートアドレスで構いませんが、ここでは 10.189.99.0/24 のネットワークを構成するため 10.189.99.10 とします。

これを設定する方法もいろいろありますが GUI のネットワーク設定画面 (nm-connection-editor) から Manual で IPv4: Address 10.189.99.10 / Netmask 24 と設定するのが簡単だと思います。

設定後 usb0 に 10.189.99.10/24 が割り振られていることを確認します。前回の流れで 169.254.x.x などのアドレスが割り振られている場合は Linux を再起動するか ```sudo ip link set usb0 down; sudo ip link set usb0 up``` と実行してあげれば新しい IP アドレスが割り振られると思います。

```
$ ip addr

2: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000
    link/ether 5a:e4:0b:21:2c:bd brd ff:ff:ff:ff:ff:ff
    inet 10.189.99.10/24 brd 10.189.99.255 scope global usb0
       valid_lft forever preferred_lft forever
    inet6 fe80::58e4:bff:fe21:2cbd/64 scope link
       valid_lft forever preferred_lft forever
```

# Linux への dnsmasq のインストール

dnsmasq は DHCP サーバーとして動作し DNS リクエストのフォワードも行います。このため、Raspbery Pi Zero は Linux から IP アドレスを取得し DNS を使用した名前解決もできるようになります。インストールは apt コマンドで行います。

```
$ sudo apt install dnsmasq
```

インストール後 /etc/dnsmasq.conf に以下のような設定を行います。dhcp-range も該当のサブネット内の任意のもので構いませんがここでは 10.189.99.11 から 10.189.99.20 の IP アドレスが Raspberry Pi が割り振られるようにします。

```
$ cat /etc/dnsmasq.conf
interface=usb0
    dhcp-range=10.189.99.11,10.189.99.20,255.255.255.0,24h
```

/etc/dnsmasq.conf の設定後 dnsmasq を起動します。

```
$ sudo systemctl enable dnsmasq.service
$ sudo service dnsmasq start
$ sudo service dnsmasq status
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/lib/systemd/system/dnsmasq.service; disabled; vendor preset: disabled)
     Active: active (running) since Wed 2021-03-31 22:25:34 EDT; 3s ago
    Process: 1632 ExecStartPre=/etc/init.d/dnsmasq checkconfig (code=exited, status=0/SUCCESS)
    Process: 1640 ExecStart=/etc/init.d/dnsmasq systemd-exec (code=exited, status=0/SUCCESS)
    Process: 1649 ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf (code=exited, status=0/SUCCESS)
   Main PID: 1648 (dnsmasq)
      Tasks: 1 (limit: 4507)
     Memory: 1.5M
        CPU: 122ms
     CGroup: /system.slice/dnsmasq.service
             └─1648 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anch>
```

今回の例では /var/log/syslog を確認すると DHCP で Raspberry Pi Zero に 10.189.99.11 が割り振られたことが確認できます。

``` /var/log/syslog
Mar 31 22:26:02 stream dnsmasq-dhcp[1648]: DHCPDISCOVER(usb0) 92:93:99:4c:64:ff
Mar 31 22:26:02 stream dnsmasq-dhcp[1648]: DHCPOFFER(usb0) 10.189.99.11 92:93:99:4c:64:ff
Mar 31 22:26:02 stream dnsmasq-dhcp[1648]: DHCPREQUEST(usb0) 10.189.99.11 92:93:99:4c:64:ff
Mar 31 22:26:02 stream dnsmasq-dhcp[1648]: DHCPACK(usb0) 10.189.99.11 92:93:99:4c:64:ff raspberrypi
```

ここまでの設定で Linux から Raspberry Pi Zero に ssh で接続ができるようになります。もしうまく行かないようなら Raspberry Pi Zero の再起動などを試してみるのが良いと思います。

```
$ ping -c 1 10.189.99.11
PING 10.189.99.11 (10.189.99.11) 56(84) bytes of data.
64 bytes from 10.189.99.11: icmp_seq=1 ttl=64 time=0.661 ms

--- 10.189.99.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.661/0.661/0.661/0.000 ms

$ ssh -l pi 10.189.99.11
The authenticity of host '10.189.99.11 (10.189.99.11)' can't be established.
ECDSA key fingerprint is SHA256:5T3If0Rs32zE0bD9d1vcrRQpe3kZvZnYu7ONp+Pxfx8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.189.99.11' (ECDSA) to the list of known hosts.
pi@10.189.99.11's password:
```

Raspberry Pi Zero からも 10.189.99.11 が割り振られデフォルトゲートウェイと DNS サーバーが Linux の IP アドレスの 10.189.99.10 となっていることが確認できます。

```
pi@raspberrypi:~ $ ip addr

2: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 92:93:99:4c:64:ff brd ff:ff:ff:ff:ff:ff
    inet 10.189.99.11/24 brd 10.189.99.255 scope global dynamic noprefixroute usb0
       valid_lft 86265sec preferred_lft 75465sec
    inet6 fe80::6522:b3a4:45:bc97/64 scope link
       valid_lft forever preferred_lft forever
pi@raspberrypi:~ $ netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         10.189.99.10    0.0.0.0         UG        0 0          0 usb0
10.189.99.0     0.0.0.0         255.255.255.0   U         0 0          0 usb0
pi@raspberrypi:~ $ cat /etc/resolv.conf
# Generated by resolvconf
nameserver 10.189.99.10
```

# NAT の設定と tcpdump を使用したパケットの確認 

次に Raspberry Pi Zero からインターネット (1.1.1.1) へのアクセスがどのように処理されるか tcpdump で見てみます。tcpdump は Linux のネットワーク通信を見るためのツールです。この時点ではまだ Raspberry Pi Zero からのインターネット接続はできません。別のターミナルを開き Linux に接続し tcpdump を実行します。

```
$ sudo tcpdump -nni any icmp
[sudo] password for test:
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
```

Raspberry Pi Zero から 1.1.1.1 に対し ping を実行してみます。この応答は返ってきません。

```
pi@raspberrypi:~ $ ping -c 1 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.

--- 1.1.1.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
```

Linux の tcpdump の実行結果に以下のような結果が表示されると思います。Linux に対し ICMP echo request が届いていますが Linux では特になんの処理もされず無視 (ドロップ) されています。

```
21:03:40.605790 usb0  In  IP 10.189.99.11 > 1.1.1.1: ICMP echo request, id 661, seq 1, length 64
```

ここで Linux 上で以下のコマンドを実行します。これは Linux からパケット転送を有効にするための Linux カーネルの設定値です。

```
$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
```

上記と同様の手順で Linux で tcpdump を実行し Raspberry Pi Zero から ping を実行します。今度は Raspberry Pi Zero から来た ICMP echo request が wlan0 経由でインターネット側に転送されているのが確認できます。

```
21:08:43.811999 usb0  In  IP 10.189.99.11 > 1.1.1.1: ICMP echo request, id 681, seq 1, length 64
21:08:43.812066 wlan0 Out IP 10.189.99.11 > 1.1.1.1: ICMP echo request, id 681, seq 1, length 64
```

このパケットは恐らく 1.1.1.1 まで到達しているはずです。しかしこの応答は返ってきません。何故なら 1.1.1.1 というホストは 10.189.99.11 というホストへどのように ICMP echo reply を返すか知らないためです。

これを解決するため Linux 上で wlan0 から来たパケットをネットワークアドレス変換 (NAT/IP MASQUERADE) し転送するための設定を行います。

```
$ sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
```

上記と同様の手順で Linux で tcpdump を実行し Raspberry Pi Zero から ping を実行します。今度は ICMP echo reply が受け取れるのが確認できます。

```
pi@raspberrypi:~ $ ping -c 1 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=13.0 ms

--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 12.979/12.979/12.979/0.000 ms
```

tcpdump を見ると Linux の wlan0 から ICMP echo request を転送する際にソース IP アドレスが 10.184.1.147 と Linux の IP アドレスに変更されています。1.1.1.1 からの応答は Linux の IP アドレスである 10.184.1.147 に返されていますが Linux はこれを Raspberry Pi Zero の IP アドレスである 10.189.99.11 に転送しています。

```
21:20:00.775683 usb0  In  IP 10.189.99.11 > 1.1.1.1: ICMP echo request, id 686, seq 1, length 64
21:20:00.775800 wlan0 Out IP 10.184.1.147 > 1.1.1.1: ICMP echo request, id 686, seq 1, length 64
21:20:00.788162 wlan0 In  IP 1.1.1.1 > 10.184.1.147: ICMP echo reply, id 686, seq 1, length 64
21:20:00.788222 usb0  Out IP 1.1.1.1 > 10.189.99.11: ICMP echo reply, id 686, seq 1, length 64
```

10.184.1.147 もプライベート IP アドレスとなりますが Linux はルーター 10.184.1.1 に転送しルーターでも同様の処理を行っています。

TCP の場合はどうなるか TCP SYN パケットを例に見てみます。まず Linux で tcpdump を以下のように実施します。

```
$ sudo tcpdump -nni any '(tcp[tcpflags] & tcp-syn)' != 0 and '(tcp[tcpflags] & tcp-ack) ==0'
```

Raspberry Pi Zero から ```http://gogole.com:80``` へアクセスします。

```
pi@raspberrypi:~ $ curl google.com
```

tcpdump を見ると宛先 (172.217.3.110.80) は変わっていませんが送信元が Raspberry Pi Zero の 10.189.99.11.60524 から Linux の 10.184.1.147.60524 に変わっていることが分かります。細かいですがこの時ポート番号は変わっていません。尚、DNS の名前解決も正常にできていることも確認できます。

```
22:12:18.919276 usb0  In  IP 10.189.99.11.60524 > 172.217.3.110.80: Flags [S], seq 2492811649, win 64240, options [mss 1460,sackOK,TS val 1432151559 ecr 0,nop,wscale 6], length 0
22:12:18.919430 wlan0 Out IP 10.184.1.147.60524 > 172.217.3.110.80: Flags [S], seq 2492811649, win 64240, options [mss 1460,sackOK,TS val 1432151559 ecr 0,nop,wscale 6], length 0
```

ここまで設定ができればパッケージのアップデートなどが Raspberry Pi Zero から可能になります。

```
pi@raspberrypi:~ $ sudo apt update
Hit:1 http://raspbian.raspberrypi.org/raspbian buster InRelease
Hit:2 http://archive.raspberrypi.org/debian buster InRelease
Reading package lists... Done
Building dependency tree
Reading state information... Done
8 packages can be upgraded. Run 'apt list --upgradable' to see them.
```

# sysctl と iptables の恒久的な設定

sysctl と iptables の設定は Linux を再起動すると消えてしまいます。再起動後も同様の設定とするにはディストリビューションによって違うと思いますが手元の Kali Linux (Debian 系) の環境ではまず sysctl は /etc/sysctl.d/99-sysctl.conf の net.ipv4.ip_forward=1 という行をコメントからはずします (先頭の # を削除する)。


``` /etc/sysctl.d/99-sysctl.conf
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
```

iptables はもう少し複雑で Debian 系だと iptables-persistent というパッケージを使った方法があります。試してはいませんが /etc/network/if-pre-up.d/ 以下にスクリプトを置いてあげる方法もあるようです。rc.local 的なものがあればそこに書いてあげるだけでも良いと思います。以下は Linux を再起動後などして iptables が設定されて居ない場合の例です。iptables の設定が既にされていて ```sudo apt install -y iptables-persistent``` 実行時に現在の設定を保存すれば iptables-save などは必要ありません。

```
$ sudo apt install -y iptables-persistent
$ sudo systemctl enable netfilter-persistent.service
$ sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
$ sudo iptables-save | sudo tee -a /etc/iptables/rules.v4
# Generated by iptables-save v1.8.7 on Sun Apr 11 21:48:26 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o wlan0 -j MASQUERADE
COMMIT
# Completed on Sun Apr 11 21:48:26 2021
```

再起動後以下のような出力が sysctl -a と iptables -t nat -L の出力の中に含まれていれば成功です。

```
$ sudo sysctl -a | grep net.ipv4.ip_forward
net.ipv4.ip_forward = 1
$ sudo iptables -t nat -L
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  anywhere             anywhere
```

# 参照

[Setup the Ethernet gadget of PI Zero with dnsmasq](https://maxammann.org/posts/2019/03/setup-g_ether-dhcp/)

[Bridging Wifi to Ethernet on Ubuntu not working](https://superuser.com/questions/597834/bridging-wifi-to-ethernet-on-ubuntu-not-working)

[How to save iptables firewall rules permanently on Linux](https://www.cyberciti.biz/faq/how-to-save-iptables-firewall-rules-permanently-on-linux/])