ラズパイとusb-proxyを使ったUSBプロトコルアナライザ

USB バス上の通信を調査することができる USB プロトコルアナライザは、専用品は安い物でも 10 万円を超えるような高価な機器であり、趣味の開発で導入するには躊躇いを覚えます。

そこで、何処のご家庭にもある Raspberry Pi 4 Model B に usb-proxy を組み合わせることで、簡易的な USB プロトコルアナライザを作ります。

# 仕組み
USB Host と USB Device の間に Raspberry Pi を接続し、usb-proxy というソフトウェアを用いて USB パケットを中継させます。
さらに、usbmon モジュールを用いて Raspberry Pi と USB Device の間の通信をキャプチャすることで、USB プロトコルアナライザとして利用できるようになります。

ザックリと図にすると次のようになります。
```
[USB Host]  <---> [usb-proxy on Raspberry Pi] <---> [USB Device] 
                                                ↑
                                 usbmon でここの通信をキャプチャする
```

# 必要な物

- Raspberry Pi 4 Model B
- USB Power Splitter

Raspberry Pi は、USB Host へ接続するための OTG 対応 USB ポートと、USB Device を接続するための USB ポートの合計 2 ポートが使える必要があります。この条件を満たす Raspberry Pi は、記事執筆時点では Raspberry Pi 4 Model B だけです。

USB Power Splitter は、USB の通信線と電源線を分離するためのアダプタです。
USB Host から Raspberry Pi へ給電する場合は不要ですが、USB Host からの給電では電力が不足する場合や、USB Host と Raspberry Pi の電源を分けたい場合など、外部電源から Raspberry Pi へ給電する場合に必要になります。
私は自作しましたが、既製品であれば Amazon.co.jp でも購入できる「[BUFFALO AC-DC5PSC2](https://www.buffalo.jp/product/detail/ac-dc5psc2.html)」がオススメです。付属の AC アダプタの定格電流は 2A とすこし心許ない気もしますが、それほど負荷も掛からないので足りると思います。

また、消費電力の大きな USB Device を接続する場合は、セルフパワーの USB ハブを用意しても良いでしょう。

# 手順
今回使用した環境は以下の通りです。
- Raspberry Pi 4 Model B
- OS: Raspberry Pi OS Lite (64bit)
    - Release date: April 4th 2022
    - Debian version: 11 (bullseye)

## 1. OTG ポートを利用するための設定をする
OTG 対応ポートを利用するため、ドライバを読み込むよう設定します。
既に設定済みの場合は，この手順はスキップしてください。
```shell
echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
echo "dwc2" | sudo tee -a /etc/modules
sudo reboot
```

## 2. raw-gadget モジュールのインストールと読み込み
usb-proxy は、ユーザー空間で USB Gadget を実装するための低レベルインターフェイスである [USB Raw Gadget](https://www.kernel.org/doc/html/latest/usb/raw-gadget.html) を使用します。
Raspberry Pi OS のカーネルでは raw-gadget は無効化されているため、以下の通りに raw-gadget モジュールをコンパイルして読み込ませます。

```shell
# 必要なパッケージのインストール
sudo apt install build-essential linux-headers git

# raw-gadget のソースコードの取得してコンパイルする
git clone https://github.com/xairy/raw-gadget.git
cd raw-gadget/raw-gadget
make

# raw-gadget モジュールの読み込み
sudo ./insmod.sh
```

[README.md](https://github.com/xairy/raw-gadget/blob/master/raw_gadget/README.md) には、必要に応じて最新のソースコードを取得するため `./update.sh` を実行するよう記載されていますが、私が試した際にはインターフェイス変更によりコンパイルできなくなったため実行しませんでした。

## 3. usb-proxy のコンパイルと実行

```shell
# 必要なパッケージのインストール
sudo apt install libusb-1.0-0-dev libjsoncpp-dev

# usb-proxy のソースコードの取得してコンパイルする
git clone https://github.com/AristoChen/usb-proxy.git
cd usb-proxy
make
```

`lsusb` を実行して、中継させる USB Device の Vendor ID と Product ID を調べます。
```shell-session
$ lsusb
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 0590:001a Omron Corp. ERROR Device Unconfigured
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```
今回はオムロン製の ME56PS2 という PS2 専用 USB モデムの通信をキャプチャします。
`lsusb` の結果から、 Vendor ID は `0590` 、 Product ID は `001a` だと分かります。
また、バス番号は後で使うので記録をしておいてください。

次に、`usb-proxy` を実行して USB Host と USB Device の通信を中継させます。
中継させる USB Device は `--vendor_id` と `--product_id` の2つのオプションで指定します。`lsusb` で調べた ID をそれぞれ指定してください。
また、USB Host が接続される UDC (USB Device Controller) を指定する必要があります。 `--device` と `--driver` の2つのオプションがありますが、Raspberry Pi 4 Model Bの場合はどちらも `fe980000.usb` を指定します。

```shell
sudo ./usb-proxy --device=fe980000.usb --driver=fe980000.usb \
                 --vendor_id=0590 --product_id=001a
```

## 4. usbmon と tcpdump による USB パケットキャプチャ
まず、usbmon モジュールを読み込みます。
```shell
sudo modprobe usbmon
```

usbmon からパケットを直接キャプチャすることもできますが、今回は tcpdump を使います。
tcpdump は標準ではインストールされていないため、インストールします。
```shell
sudo apt install tcpdump
```

`tcpdump` コマンドでキャプチャする際には、`-i` オプションでキャプチャするインターフェイスを指定します。`lsusb` で確認した USB のバス番号が 1 であるので、`-i usbmon1` のように指定します。
`-X` オプションを指定すると、キャプチャしたパケットを 16 進数と ASCII 文字列で表示されます。通信内容を簡単に確認したい場合に便利です。
```shell-session
$ sudo tcpdump -i usbmon1 -X
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on usbmon1, link-type USB_LINUX_MMAPPED (USB with padded Linux header), snapshot length 245824 bytes
19:50:09.327416 USB CONTROL SUBMIT to 1:4:0
19:50:09.329079 USB CONTROL COMPLETE from 1:4:0
        0x0000:  1201 1001 0000 0008 9005 1a00 0101 0102  ................
        0x0010:  0301                                     ..
(...中略...)
19:51:35.478765 USB BULK SUBMIT to 1:4:2
        0x0000:  2141 5453 3338 3d30 0d                   !ATS38=0.
19:51:35.478837 USB BULK COMPLETE from 1:4:2
19:51:35.478951 USB BULK COMPLETE from 1:4:2
        0x0000:  3100                                     1.
19:51:35.479029 USB BULK SUBMIT to 1:4:2
19:51:35.479282 USB BULK COMPLETE from 1:4:2
        0x0000:  3160                                     1`
19:51:35.479390 USB BULK SUBMIT to 1:4:2
19:51:35.520979 USB BULK COMPLETE from 1:4:2
        0x0000:  3160 0d0a 4f4b 0d0a                      1`..OK..
19:51:35.521087 USB BULK SUBMIT to 1:4:2
19:51:35.528675 USB BULK SUBMIT to 1:4:2
        0x0000:  3d41 5444 5430 3132 3334 3536 3738 390d  =ATDT0123456789.
19:51:35.528759 USB BULK COMPLETE from 1:4:2
19:51:35.528825 USB BULK COMPLETE from 1:4:2
        0x0000:  3100                                     1.
```
今回は USB モデムの通信をキャプチャしているため、 AT コマンドをやりとしている様子が確認できます。

また、`-w` オプションでキャプチャしたパケットを pcap 形式で保存できます。
```shell-session
$ sudo tcpdump -i usbmon1 -w usb.pcap
tcpdump: listening on usbmon1, link-type USB_LINUX_MMAPPED (USB with padded Linux header), snapshot length 245824 bytes
^C1327 packets captured
1327 packets received by filter
0 packets dropped by kernel
```

保存した pcap ファイルは Wireshark を使って調査することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2321966/4bf51fbb-a38a-eefa-8ae5-f4a2d0379b76.png)

# 参考文献
- [xairy/raw-gadget: USB Raw Gadget — low-level interface for the Linux USB Gadget subsystem](https://github.com/xairy/raw-gadget)
- [AristoChen/usb-proxy: A USB proxy based on raw-gadget and libusb](https://github.com/AristoChen/usb-proxy)
- [USB Raw Gadget &mdash; The Linux Kernel  documentation](https://www.kernel.org/doc/html/latest/usb/raw-gadget.html)
- [usbmon - おなかすいたWiki！](https://wiki.onakasuita.org/pukiwiki/?usbmon)