IEEE 802.11acフレームキャプチャ with Raspberry Pi

# Raspberry Piでパケットキャプチャできるようにする
自身の研究で、802.11acで規定されてるCSI(Channel State Information)を取得する必要があったのだけど、Raspberry Piでパケットキャプチャできるようにするまでの流れがかなりしんどかったので、まとめておきます。まとめるつもりが、**まとめても大ボリューム**なのでめちゃくちゃ暇と興味のある人向け。使用するのは**Nexmon**という、Broadcom製のWifiチップ向けのファームウェアパッチです。

## [！注意！]
この記事は内容の検証が十分ではありません。後日加筆修正を加える可能性がありますので、下記の作業は、作業者の責任において実行してください。特にWifiチップのファームウェアパッチを適用するという作業は、ハードウェアのサポートが受けられなくなる可能性がありますので注意してください。

## 必要なもの
- Raspberry Pi 3B+/4
    - 重要なのはBroadcom製の**BCM434355c0**というWifiチップを積んでること
    - Raspbianが動作しているものに限る、liteでもdesktopでも大丈夫
    - SDの容量は8GBあれば十分っぽいけど余裕がある方が良い

## 手順
基本的にはNexmon公式の[インストラクション](https://github.com/seemoo-lab/nexmon)に従いますが、一部記述のないパッケージを入れないと先に進まないところがあったので、その補足も書いておきます。「パケットキャプチャできるようにする」過程と「CSIを取得する」過程に分けて説明します。

### パケットキャプチャの準備
無線をキャプチャするためには、デバイスのNIC(Network Interface Card)をモニターモードに変更しなければなりませんが、現状Raspberry Piにはモニターモードに移行させるオプションが実装されていません。そのため、以下の手順を踏んで、モニターモードに移行できるようにしていきます。
特に補足のないところはコマンドを上から順に実行すればOK。
1. `sudo su`
2. `apt update && apt upgrade`
3. `apt install raspberrypi-kernel-headers git libgmp3-dev gawk qpdf bison flex make`
4. `apt install libtool-bin automake texinfo`[^1]
5. `git clone https://github.com/seemoo-lab/nexmon.git`
6. `cd nexmon`で5.でクローンしたディレクトリに入る
7. `cd buildtools/isl-0.10`, `autoreconf -f -i`, `./configure`, `make`, `make install`, `ln -s /usr/local/lib/libisl.so /usr/lib/arm-linux-gnueabihf/libisl.so.10`を順に実行し[^2]、親ディレクトリ`/nexmon`に戻る
8. `cd buildtools/mpfr-3.1.4`, `autoreconf -f -i`, `./configure`, `make`, `make install`, `ln -s /usr/local/lib/libmpfr.so /usr/lib/arm-linux-gnueabihf/libmpfr.so.4`を順に実行し[^3]、親ディレクトリ`/nexmon`に戻る
9. `source setup_env.sh`, `make`を順に実行
10. `cd patches/bcm43455c0/7_45_189/nexmon/`, `make`, `make backup-firmware`, `make install-firmware`を順に実行[^4]

ここまでうまくいっていれば、モニターモードのインターフェースを追加できるようになっているはず。sudo権限で
``iw phy `iw dev wlan0 info | gawk '/wiphy/ {printf "phy" $2}'` interface add mon0 type monitor``
と実行して、モニターモードのインターフェース`mon0`が追加されていたら成功。

### 再起動後も動かすために
Nexmonは既存のファームウェアを上書きしてるわけじゃないので、このままだとラズパイを再起動すると元のWifiドライバが読み込まれて使えなくなってしまう。ので、以下の手順を踏んで、再起動後もNexmon経由にできるようにする。

1. `modinfo brcmfmac`を実行し、冒頭のfilename:以降に記述されているパス(`.../brcmfmac.ko`)をコピーする
2. `mv "<PATH>/brcmfmac.ko" "<PATH>/brcmfmac.ko.orig"`
3. `cp /home/pi/nexmon/patches/bcm43455c0/7_45_189/nexmon/brcmfmac_4.19.y-nexmon/brcmfmac.ko "<PATH>/"`
4. `depmod -a`

ここまでできたら再起動して、上述のインターフェース追加のコマンドが動くかどうか確かめる。もしも再起動後にコマンドが通らなくなったら、「パケットキャプチャの準備」の2, 3, 4, 5を飛ばしてやり直せば戻せる。はず。

## CSIの取得
ここからの説明におけるCSIは、厳密にはCompressed Beamforming Reportと呼ばれるバイナリで、例えば[802.11n CSI Tool](https://dhalperi.github.io/linux-80211n-csitool/)で取得できるような生のCSIとは異なるものです。いわゆるrawなCSIの取得方法については調査中。Compressed Beamforming Reportについて詳しく知りたい方は、IEEE公式の[ドキュメント](https://ieeexplore.ieee.org/document/7786995)を参照してください。

基本的にはtcpdumpやwiresharkなどを使って無線をキャプチャするだけ...なのですが、キャプチャ後の処理がかなり複雑なので、その処理についてまとめておきます。ちなみに後述の理由で環境によって得られる結果が違うので、サンプルコードなどを載せるのではなくキャプチャした中身の解析手順についてのみ書くことにします。

### Compressed Beamforming Report
802.11acでは、[MIMO通信](https://ja.wikipedia.org/wiki/MIMO)におけるビーム形成のためにチャネルの情報をAPとSTAでやりとりしていて、これをBeamforming Reportと言います。MIMOのストリームごとに生の情報をやり取りするのはオーバーヘッドがかかるので、チャネルの情報を数学的処理[^5]で圧縮して、いくつかの**「角度の情報」**に変換して送信しているようです。

今回はキャプチャした中身をWiresharkを使って取り出していきます。
ちなみに下の例では、`aa:bb:cc:dd:ee:ff`というMACアドレスのデバイスからAPに向かってBeamforming Reportを送っている状況を想定します。

### pcap → json
tcpdumpでもwiresharkでも、パケット時にその内容をpcapファイルに出力することができます。このpcapファイルを、jsonファイルに書き下します。絶対jsonに直さないといけないというわけではないと思いますが、後々の作業を簡単にする意味で変換しておきます。
手順は簡単で、例えば`test.pcap`というファイルを`test.json`に変換するにはターミナル等で

`tshark -nr test.pcap -Y 'wlan.fc.type_subtype == 0x000e' -T json > test.json`

を実行するだけです。ちなみにsubtype 0x000eというのは、Compressed Beamforming ReportやSNRの情報が入っているAction No Ackというフレームのサブタイプを指しているみたいです。

### json → アンテナ数/Beamforming Report
前の段階で得たjsonを解析して、通信しているAPとデバイスのアンテナ数を取得し、角度情報のビット数を調べて、Beamforming Reportを取り出します。jsonの構造において必要な値がどのキーに含まれているかも書きますが、なんと**pcapからjsonへの変換を行った環境によって、jsonの構造が必ずしも同じじゃないことがあるみたいです(めんどくせぇ...)。**なので、以降で括弧書きしているところは自分の環境にどちらかが合うと信じて、どちらかで使ってください。

アンテナ数を確認するのは、アンテナ数の組み合わせによって、Beamforming Reportで送信される内容に変化があるからです。
<img width="650" alt="スクリーンショット 2020-03-29 19.34.22.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275417/c4df9d5f-ae85-830c-02d7-3611683d6626.jpeg">
上の表はIEEE公式の[ドキュメント](https://ieeexplore.ieee.org/document/7786995)から持ってきたものですが、$Nr$と$Nc$がデバイスのアンテナ数を表しています。表の通り、アンテナ数の組み合わせによって得られる角度情報の数量が変わってきます。
$Nc$はjsonの`_source → layers → wlan_mgt (wlan_1) → Fixed parameters → wlan.vht.mimo_control.control_tree → wlan.vht.mimo_control.ncindex`に、$Nr$は`_source → layers → wlan_mgt (wlan_1) → Fixed parameters → wlan.vht.mimo_control.control_tree → wlan.vht.mimo_control.nrindex`に値があります。

2020/03/29 : 続きを執筆中

[^1]: 公式の説明には書いてなかったけど、おそらく必須のパッケージ。
[^2]: ここで`/usr/lib/arm-linux-gnueabihf/libisl.so.10`なるファイルの存在確認するよう書いてあるが、大抵存在しないのでいきなりこの手順を踏む。
[^3]: 脚注2に同じ。`/usr/lib/arm-linux-gnueabihf/libmpfr.so.4`というファイルの存在確認をするよう指示してるけど、こいつも大抵ない。
[^4]: make系のコマンドが`Makefile:3: /firmwares/bcm43455c0/7_45_189/definitions.mk: No such file or directory`というエラーで動かない時がある。その時は、`/nexmon`まで戻ってもう一回`source setup_env.sh`すれば良い
[^5]: 特異値分解とギブンス回転