パケットキャプチャで電波の中身を見る ─ DMR フレームの読み方

カテゴリ: ハマりどころ / OpenCCVoice / DVSwitch タグ: DMR, DMRD, tcpdump, パケット解析, Python


前回、MD-619 のハング問題を「TGIF が先頭無音を転送していない」ことで解決しました。この決定的発見は、Pi 上で tcpdump を回して自作の解析器で分析した結果でした。今回はこの解析器 dmrd_analyze.py の作り方と使い方を紹介します。

なぜ自作したか

DMR フレームを解析するツールは既存にもあります。Wireshark にはプラグインがありますし、専用のデコーダも幾つか存在します。しかし、いずれも Windows 環境前提だったり、GUI が必要だったり、目的の情報(ヘッダの有無、埋め込みデータの内容、ストリーム比較)が取り出しにくかったりします。

Pi の Bookworm 上で外部依存なしに動く、pcap を読んで自分の解析軸で集計する Python スクリプトが欲しかった。pcap の形式は単純なので、自前パースの方が結局早いと判断しました。標準ライブラリだけで動くので、Pi にそのまま置いて使えます。

DMRD プロトコルの基本

Homebrew プロトコル(BrandMeister や TGIF が使っているもの)の DMR フレームは、UDP パケットとして送られます。ポートは 62031。1フレームは 53 バイト以上あり、こう組まれています。

"DMRD" (4B) + seq(1B) + src(3B) + dst(3B) + repeaterID(4B) + bits15(1B)
              + streamID(4B) + payload(33B) + reserved(2B)
  • src / dst: 発信元と宛先の DMR ID(TG 番号を含む)
  • bits15: フレーム種別を表すビット群。上位2bitでスロット、次の2bitでフレームタイプ、下位4bitでデータタイプ
  • payload 33B: これが実際の音声+制御データ

フレームタイプには「音声同期(A)」「音声(B/C/D/E/F)」「データ同期(VoiceLCHeader、Terminator など)」の区別があります。正常な DMR ストリームは「VoiceLCHeader → A → B → C → D → E → F → A → B → … → Terminator」の順で流れます。

中央 48bit に何が入っているか

音声フレーム(B〜F)の 264bit ペイロードのうち、中央 48bit に埋め込み情報が入っています。この48bit は「EMB(Embedded Signalling)」と「embedded LC の断片」から成り、4フレームぶんを集めて 128bit の埋め込み LC を復元します。

埋め込み LC には何が入っているか。

  • GroupLC / PrivateLC: 発信元 ID と宛先の情報(音声ヘッダが失われても同期用に使える)
  • Talker Alias Header / Block1 / Block2 / Block3: 発信者のコールサイン文字列

つまり ヘッダが TGIF に食われても、この埋め込み LC を組み立てられれば受信機は情報を復元できるはずです。しかし復号にはデインタリーブ、Hamming(16,11,4) FEC、5bit CRC の複雑な処理が必要で、これを Python で書き直しました。実装は MMDVMHost(DMR ゲートウェイのオープン実装)から移植しています。

実際の解析結果

前回の記事で紹介した通り、通常局と OCV のストリームを比較すると:

通常局 JJ2ZAR の折り返し:
  ヘッダ:3  終端:1  総フレーム:42
  並び: VVVAAAbbbcccdddeeefff...T

OpenCCVoice の折り返し:
  ヘッダ:0  終端:3  総フレーム:259  ⚠️ ヘッダ無し!
  並び: AbcdefAbcdefAbcdef...

ここまでは前回の話。今回はさらに埋め込み LC のデコード結果も見せます。

埋め込みLC/TA デコード:
    GroupLC dst=44833 src=4402396  x23
    ★ Talker Alias = 'JJ2YYK AichiPref Japan MMDV'  (format=ISO8859-1, len=27)

ちゃんと JJ2YYK として送っている。 しかも Talker Alias の中身は MMDVM_Bridge.ini の Callsign + Location + Description そのものです。つまり OCV のストリームは電波レベルでは完全に正しい。問題は「途中参加を強要される受信機がどこまで解釈できるか」なのでした。

使い方

Pi に tcpdump が入っていれば、キャプチャは1行です。

sudo tcpdump -i any -Z root -w /tmp/dmr.pcap udp port 62031
# → 気になる送信を1〜2回入れて Ctrl+C

python3 dmrd_analyze.py /tmp/dmr.pcap

これだけで、各ストリームの構造、ヘッダ有無、埋め込み LC の中身が全て見えます。

Pi-Star に tcpdump が入っていない場合は、こう入れます。

rpi-rw
sudo apt update
sudo apt install -y tcpdump

-Z root は tcpdump が起動後に権限を落とす仕様に対応するもので、これが無いと /tmp に書けないことがあります。-i any は全インタフェース対象で、Pi-Star + DVSwitch のオールインワン構成では loopback を含めた通信も拾うために必要です。

何を見るべきか

解析器の出力で真っ先に見るべきは、「送信側のフレーム数」と「配送側のフレーム数」の差です。

■ #4  [TX->net]  総フレーム:281  ヘッダ:2  終端:3   ← 送った量
■ #5  [net->RX]  総フレーム:259  ヘッダ:0  終端:3   ← 戻ってきた量

差 = 281 – 259 = 22 フレーム = 0.44 秒。これが TGIF に「食われた」時間です。V1.83 以前だと差が 51 フレーム(約3秒)でしたが、V1.89 以降の対策で数フレーム以内まで縮められました。

次に見るのは ⚠️ ヘッダ無し! の警告。これが出ている限り、その配送先の受信機は「途中参加」を強要されます。MD-619 のような一部の機種はここで固まります。

そして最後に ★ Talker Alias。ここに自局のコールサインが正しく載っていれば、送信は電波レベルで完璧です。もし別のコールサインが出るなら、Analog_Bridge のメタデータ処理が疑われます。

📝 執筆:JI2TAB(尾張旭 DMR デジピーター 管理人)
🏛 JJ2YYK あいちデジタルコミュニケーションハムクラブ

教訓: DVSwitch 系で「なんで俺の局だけ変な動きをするんだ」と思ったら、tcpdump と自作解析器の出番です。ダッシュボード表示は加工された情報なので、判断材料にできません。RF で流れているビット列そのものを見るしかありません。

dmrd_analyze.py と Talker Alias デコーダの emblc.py は、この一連のデバッグで作った副産物ですが、汎用ツールとしても使えるものになりました。同じ症状に遭遇した方はぜひ使ってみてください。

続きの話(AMBE ボコーダとの相性、Talker Alias 引きずり、並行制御の落とし穴)は次回以降で。

JJ2YYK
  • JJ2YYK

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です