Raspberry PiのNode-REDでDHCPパケットを捕捉する

ラボなどの利用者数を調べるのに、同一ネットワーク内にいるデバイスが知れたらと思いました。定期的にPingをしても良いのですが、ネットワークに負荷をかけてしまうし、スマホなどだとスリープするときにWifiを切断してしまうようで、タイミングが悪いと応答を返してくれなかったりします。

そこで、Wifiにつないだ時のDHCPのパケットを捕捉すれば、ネットワークにつないだ瞬間がわかるのではないかと思いました。DHCPのパケットはUDPのブロードキャストで飛んでくるので、捕捉できると考えました。

今回はNode-REDを使います。Node-REDには標準でUDPのノードがあります。

## DHCPのパケット

http://www.picfun.com/lan09a.html あたりを見ると、DHCPのパケット構成がわかります。
DHCPサーバーはポートUDPポート67、DHCPクライアントはUDPポート68を使うようです。

## フローの作成

WindowsのNode-RED DesktoでUDPノードを使ってこんなフローを作ってみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/d61e0579-c85e-34bf-f64e-24e047a82969.png)

```json:フロー
[{"id":"8bfbec67.2f728","type":"udp in","z":"4e3881be.18f2e","name":"","iface":"","port":"67","ipv":"udp4","multicast":"false","group":"255.255.255.255","datatype":"buffer","x":630,"y":500,"wires":[["97287e61.cb0a8"]]},{"id":"97287e61.cb0a8","type":"debug","z":"4e3881be.18f2e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":770,"y":500,"wires":[]},{"id":"94c4df6f.98dd1","type":"udp in","z":"4e3881be.18f2e","name":"","iface":"","port":"68","ipv":"udp4","multicast":"false","group":"255.255.255.255","datatype":"buffer","x":630,"y":540,"wires":[["2c62d86f.117228"]]},{"id":"2c62d86f.117228","type":"debug","z":"4e3881be.18f2e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":770,"y":540,"wires":[]}]
```

手元のスマホをWifiのOFF→ONにすると、パケットが捕捉できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/27d54d22-25f9-2330-9f88-404e960b95ab.png)

28バイト目から6バイトがクライアントのMACアドレスになります。9c:5c:f9はSony Mobile CommunicationのOUI(Organizationally Unique Identifier:メーカーID)です。XPeriaなので正しいですね。これでMACアドレスを知ることができたので、Wifiに接続したデバイスを知ることができました。

## Raspberry Pi での実行

上記のフローをそのままRaspberry PiのNode-REDにもっていったのですが、うまくいきませんでした。
デプロイするとこんなエラーが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/7cdb09e6-15c7-0953-0138-cbd121d9f366.png)

どうやらUnix系OSでは1024未満のポートにアクセスするためには管理者権限が必要のようです。Node-REDは一般ユーザーの権限で起動しているので、管理者権限はありません。`sudo node-red`とかやれば管理者権限で起動できるのでしょうが、いろいろ調べたところセキュリティ上リスクがあるようなのでやりたくありません。

## ポートフォワーディングをする

調べたところによると、1024以下のポートに来たデータを、ユーザー権限でもアクセスできるポートに転送（ポートフォワーディング）することで1024以下のポートに来たパケットを捕捉することができるようです。
今回はUDPの67を10067に、68を10068に転送してみます。

### ufwの使用

Raspberry Piにもとから入っているiptablesというのを使えばポートフォワーディングできるらしいですが、ufwを使ったほうが簡単という情報を得たのでufwを使ってみることにしました。

まずufwをインストールします。

```bash
$ sudo apt install ufw
```

ufwをインストールすると、ファイヤーウォール機能が働いてしまい、すべてのポートが遮断されてしまっているので、ひとまずすべてのポートを開放します。（そもそもufwが入っていなかったときはすべてのポートが開放されていたので…。気になる人は必要なポートだけ開放してください。）

```bash
$ sudo ufw default ALLOW
```

次にポートフォワーディングの設定をします。`/etc/ufw/after.rules`が設定ファイルです。（before.rulesも設定ファイルなのですが、after.rulesの方に書く理由はよくわかりませんでした）

```bash
$ sudo nano /etc/ufw/after.rules 
```

以下を追加します。

```
*nat
:PREROUTING ACCEPT [0:0]
-A PREROUTING -p udp --dport 67 -j REDIRECT --to-port 10067
-A PREROUTING -p udp --dport 68 -j REDIRECT --to-port 10068
COMMIT
```

保存したら、ufwを有効化します。

```
$ sudo ufw enable
```

## フローの修正

ポート67でなく10067を、ポート68でなく10068を受信するようにフローを修正します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/35a6396d-1d5b-afe3-b0da-212cbe0da707.png)

```json:フロー
[{"id":"c5639bc.2283768","type":"udp in","z":"4e3881be.18f2e","name":"","iface":"","port":"10068","ipv":"udp4","multicast":"false","group":"255.255.255.255","datatype":"buffer","x":120,"y":380,"wires":[["3f9dd1de.322f9e"]]},{"id":"3f9dd1de.322f9e","type":"debug","z":"4e3881be.18f2e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":270,"y":380,"wires":[]},{"id":"4601ef74.d89b7","type":"udp in","z":"4e3881be.18f2e","name":"","iface":"","port":"10067","ipv":"udp4","multicast":"false","group":"255.255.255.255","datatype":"buffer","x":120,"y":340,"wires":[["9995f01d.0e956"]]},{"id":"9995f01d.0e956","type":"debug","z":"4e3881be.18f2e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":270,"y":340,"wires":[]}]
```

これをデプロイし、手元のスマホのwifiをOFF/ONしてみるとDHCPのパケットが捕捉できると思います。