さくらインターネット Advent Calendar 2020 24 日目の記事です。
当記事は @homelith さんとの合作で、さくらインターネット社内サークル「インフラ研究会」の支援の下執筆されています。
前編 (フィールドワーク編) は こちら 。
概要
-
ETC (高速道路電子料金収受システム) の通信に用いられる規格は、5.8GHz 帯の電波を用いた ARIB STD-T75 DSRC (狭域通信) システム として、一般に仕様が公開されているものです。以下ではARIB STD-T75で規定されたシステムを「DSRC」と表記します。
-
市販のソフトウェア無線機 HackRF One を設置した自動車でゲートを通過し、車載器と地上局 (本当は路肩無線装置という) との間で行われる通信を受信してみます。その際のテクニックを測定環境づくり (フィールドワーク編) とデータ解析 (デコード編) の二編に分けてご紹介します。
-
【注意事項】 当記事は公開されている無線方式や規格を学習しやすくするための解説、および測定に用いられる機材の取り扱いノウハウを記述するものであり、検証に伴って取得された通信の内容そのものは含めません。ETCシステムは通常 「ETCシステム利用規程」 に従ってETCカードの貸与を受けて利用するものであり、その通信はシステムの構成要素同士での内部的な通信と思われます。よって、その通信に含まれる情報を利用者が公開したり、その情報を窃用し何らかの便益を得ることは違法となる可能性があるためです。 (参考:Wikipedia - 傍受)
参考文献
- ARIB STD-T75 1.6版「狭域通信(DSRC)システム」
- ARIB STD-T75 Version 1.0「DEDICATED SHORT-RANGE COMMUNICATION SYSTEM」 (上記の英語版)
- NEXCO総研 「ETC車載器仕様書 平成26年7月」
後編の内容
前編のフィールドワーク編ではETCの通信が復調・デコードに適切な信号強度で記録されている I-Q信号 (Complex64 形式) を取得しました。後編となるデコード編ではこの信号に復調とデコード処理を行い、最終的にはデコードされたパケットの中身が仕様書通りであることを目視で確認できるような可視化を行います。
復調・デコード処理は GNU Radio、GNU Radio用の埋め込みPythonブロック、及びGolangで実装し、プログラム間のデータの受け渡しにはネットワークパケットの記録方式として用いられる pcap形式を使いました。可視化はWireshark用のdissector pluginによって行っています。
ETCは ASK変調、ETC 2.0 は π/4シフトQPSK変調 を利用していますが、今回はETCのみを取り扱うため、ASKモードを前提とします。また、DSRCでは一つのデータ送信単位を「チャネル」と呼びますが、以降便宜的に「パケット」を併記します。
これにあたって開発したコード・大まかな内容を以下にまとめておきます。
GNU Radio + Python Embedded Block
- 記録された I-Q信号を周波数シフトし、ベースバンド信号を取り出す
- オートゲインコントロールによりデコードに適した振幅に調整する
- I-Q信号 (Complex64) を振れ幅の実数信号 (Float32) に変換する
- プリアンブルを検出し、マンチェスタ符号をデコードすることにより、ビット列を得る
- 得られたチャネルをパケットとしてpcapファイルに保存する
MDC descrambler written in Go
- GNU Radio が出力したpcapファイルをロード
- (MDCパケットのみ) 簡易秘話スクランブルがかけられているので、デスクランブルする
- デスクランブル後の状態をpcapファイルに再保存する
Wireshark dissector plugin
- Wiresharkでpcapファイルを開いた時の可視化
通信シーケンスの解説
復調とデコードの前に、DSRCにおけるパケットとその流れを先に解説します。
DSRCでは、地上局と移動局でのパケットの一往復を「スロット」と呼びます。
スロットには一種類、ないしは二種類の「チャネル (パケット)」が含まれます。
スロットの種類
スロットには以下の4つの種類があり、それぞれ含まれる「チャネル (パケット)」は以下の通りです。スロットの長さはASK変調方式では全て 100 octet 時間であり、これは 1024kbps ASK においては 781.25マイクロ秒 となります。
- FCMS (フレームコントロールメッセージスロット)
- FCMC (フレームコントロールメッセージチャネル)
- MDS (メッセージデータスロット)
- MDC (メッセージデータチャネル)
- ACKC (アックチャネル)
- ACTS (アクチベーションスロット)
- ACTC (アクチベーションチャネル)
- WCNS (ワイヤレスコールナンバースロット)
- WCNC (ワイヤレスコールナンバーチャネル)
DSRCでは、必ず地上局から通信を開始します。必ずFCMSから始まり、それに続きMDS、ACTS、WCNSをいくつか含む通信を行います。
FCMSから始まるこの一連の通信を「フレーム」と呼びます。
仕様書の Fig. 2.4.2-1 では、2 つのフレームからなる例が示されています。
FCMS (フレームコントロールメッセージスロット)
FCMS には FCMC (フレームコントロールメッセージチャネル)を1つ含むことができ、この送信は必ず地上局から移動局に向かって行われます。FCMCにはデータ送信用のスロットについての情報を最大8個含めることができ、これら8個のスロットをどのように使うかを移動局に対し指示する目的で送信されます。
FCMCには以下の情報が含まれています。 (これが全てではありません)
- 利用する周波数 (FTI フィールド)
- 半二重か全二重か (CM フィールド)
- 割り付けスロット数 (SLN フィールド)
- 割り付けるスロットの情報が 8個 (SCI フィールド)
半二重通信の場合は、SLNで指定された数の通信スロットを8つのスロット時間に前から順番に割り当てます。
一方、全二重の場合はSLNで指定された数の通信スロットを、1 スロット時間にダウンリンク(地上局から移動局)とアップリンク(移動局から地上局)で一つづつ割り当てます。よって、SCIの 1、3、5、7 がダウンリンク、 2、4、6、8 がアップリンクの通信スロットとなります。
Fig. 4.2.4.2.1.8 に、半二重の場合と全二重の場合のスロット割り当ての例が示されています。
このFCMCを受信して解釈することにより、移動局はその後どのようなチャネル(パケット)をどのタイミングで受信したり送信したりすれば良いかがわかります。
ACTS (アクティベーションスロット)
ACTS には ACTC (アクティベーションチャネル) を6個含むことができます。ACTSはアップリンク専用 (移動局から地上局へ向けて) です。移動局はACTSの中に複数あるACTCのウインドウから一つをランダムに選び、ACTCを一つ送信します。
ACTCにはリンクアドレスフィールド (LID フィールド) が存在し、移動局は通信中は同じLIDを応答します。地上局がFCMCを継続的に送信している中で、移動局がACTCを応答することにより地上局が移動局の存在を認識、一連の通信を開始することができます。
MDS (メッセージデータスロット)
MDS は実際に通信するデータをやりとりするスロットです。
MDC (メッセージデータチャネル) と ACKC (アックチャネル) の対を含むことができます。
送信の向きはFCMCに記された SCI 情報で規定されており、データを送出する側がMDCを送信し、受信側がACKCを送信します。
MDCは71バイト固定長となっており、これよりも長いデータをやりとりする場合には複数のMDCに分割、足りない場合はパディングを行って送信します。MDCの後は 11 octet 時間を空けてACKCの送信時間となります。
WCNS (ワイヤレスコールナンバースロット)
WCNC (ワイヤレスコールナンバーチャネル) を1つだけ含むことができます。
移動局に付与された固有の識別符号を地上局に伝える時に用いられます。
フレームの流れ
地上局はFCMCを送信しつづけます。そこに移動局が参加し、ACTCを返すことで1フレーム目が成立し、以降の通信が開始されます。
その後はMDSのやりとりを開始するためのFCMCが送られます、FCMCによるスロット割り当てに沿ってMDCとACKCがやり取りされます。このようなフレームが1回ないしは複数回繰り返されることで、料金所などでのトランザクションに必要なデータのやり取りが行われます。最後に通信終了を示すMDCが地上局から送信され、移動局がそれにACKCを返すフレームにより、一連の通信が完了します。
Fig. 2.5.2 には、通信確立のためのフレームとそれに続くフレームを使ってトランザクションを行う例が示されています。
復調とデコードの詳細
基礎知識をおさらいしたところで、実際に受信した電波強度のデータからパケットを取り出す過程を見ていきましょう。最初は前編で取得した測定データを入力とした GNU Radio Companion 用のデザインで処理を行っていきます。
検波
ASK変調された信号検波は GNU Radio に内蔵されたブロックを組み合わせて実現することができます。今回の測定条件、サンプリング周波数 20MHz、-5MHz と +5MHz に変調信号の存在あり、において使用したデザインは こちら です。
詳細はお手元の GNU Radio Companion 環境で上記デザインを開いていただくこととし、上の図を用いて概略を大まかに説明します。
Signal Source と Multiply
フィールドワーク編で説明されたように、記録時にはETCで利用されている信号の中心周波数からずれた周波数 (-5MHz および +5MHz) を中心に記録されています。取得したI-Q信号に対して複素正弦波を掛け算することで周波数をシフトさせることができますので、目的の信号の中心周波数が中心になるようにそれぞれ +5MHz、-5MHz 複素正弦波を掛け合わせます。
Low Pass Filter
このままでは、+5MHz / -5MHz の2チャンネルで同時に送信が行われた場合に2つの信号が混信してしまいます。周波数シフト後の中心周波数における信号のみを取り出すため、ローパスフィルターをかけます。
Feed Forward AGC
マンチェスタ符号を強度変調で用いた時の振幅は 0 か 1 の二値を取るため、オートゲインコントロールにより 1 が 0.8 付近に来るように強度を調整します。上側に余裕を持つのは、振幅のクリッピングを防ぐためです。
Complex to Mag
最後に、複素のI-Q信号を、実数の振れ幅 (強度信号) へ変換します。
このようにしてASKで変調された信号 (振れ幅変調) を検波することができます。
パケットのデコード
上記のようにして得られた振れ幅信号はマンチェスタ符号により符号化されています。
マンチェスタ符号とは信号のシンボル中の立ち上がり/立ち下がりエッジを利用する符号化方式で、DSRCではそれぞれ「0」と「1」に割り当てられています。
DSRCのフレームは特定のビット列によるプリアンブルから必ずスタートすると規定されているため、プリアンブルを検出した後に連続するマンチェスタ符号列をビット列として解釈し得たのち、pcap に一つのパケットとして記録します。
この処理はPythonで記述し、GNU Radioのブロック「ARIB STD-T75 ASK packet extractor」として埋め込んでいます。
こちらがあるパケットの先頭を2値化してプロットし、わかりやすくするためにシンボルの境界線を引いてみた図です。
プリアンブルである「1010101010101010」の16ビットと、その後に続くUW (ユニークワード) 「00011011101010000100101100111110」が観測できることがわかると思います。
使用したpythonコードは下記grcファイル内に埋め込まれています。
https://github.com/homelith/arib-t75-dsrc-toolkit/blob/main/gnuradio/etc2pcap_20m_dual.grc
コードは内部状態として「アイドル」「プリアンブル判定中」「データデコード中」の3つの状態を持ち、信号の立ち上がり・立ち下がりエッジの到着を契機に、前のエッジが観測されてからの経過時間とエッジの方向によって状態遷移するステートマシンの構造をしています。
「アイドル」状態時はプリアンブルをあらわす最初のエッジとなる「立ち下がりエッジ」の到着を契機にカウンタ等を全てリセットして「プリアンブル判定中」状態に遷移します。
プリアンブルの間はかならず 0 と 1 の符号が繰り返されるため、その間は次のエッジは必ず前のエッジから 1 シンボル時間 (1024kbps なので 0.977 マイクロ秒) 離れた場所の近傍に発生するはずです。よって、「プリアンブル判定中」状態において、次のエッジが来た時刻が前のエッジから 1 シンボル時間分程度離れていればプリアンブル判定カウンタを増加させ、早すぎたり遅すぎたりした場合は「アイドル」状態に戻る動作をします。
「プリアンブル判定中」状態において、「アイドル」状態での最初の1回を含む16回のエッジを正常な時刻に受け取ることができたら、プリアンブルを受理した状態と考え「データデコード中」状態に遷移します。
「データデコード中」状態の間、1つ前のシンボルと異なるシンボル (シンボル値 0 -> 1 もしくは 1 -> 0) が到着するときは、「プリアンブル判定中」と同様に次のエッジは前のエッジから1シンボル時間近傍に1回のみ発生します。逆に、1つ前のシンボルと同じシンボル (シンボル値 0 -> 0 もしくは 1 -> 1) が到着するときは0.5シンボル時間経過後の近傍に1回目、1.0シンボル時間経過後の近傍に2回目のエッジが到着します。
これを利用し、エッジ到着が1回のみの時は1つ前の値と異なるビット値、エッジ到着が2回あった場合は1つ前と同じビット値、という風にビット単位でデータを取り出し、8ビット蓄積された時点で 1 octet としてpcapファイルに保存する仕組みです。
3回以上のエッジが到着 (これは雑音などで信号がかき消されている場合や、途中で信号が飛んでしまっている場合など) したり、1 シンボル時間を過ぎても1回もエッジが到着しない (パケットが終わり、無信号区間に突入した) ようであれば、データデコードを終了し「アイドル」状態に戻ります。
現在の所、このデコーダを含む処理を 2ch 同時に流す場合、最近のデスクトップPCを用いても1秒間に 500k~1M サンプル程度がせいぜいといったところです。10 ~ 20Msamples / s 程度まで高速化することで大容量のI-Q信号データの記録なしにデコードを手早く行えるため、今後の高速化を検討したいところです。
Wiresharkで見る
取り出したパケットデータはビットフィールド単位で意味を持つバイナリデータであり、これを見て意味を理解していく作業は骨が折れます。
Wiresharkではpcap形式で保存されたパケットデータを開き、そのパケットが既知の形式であれば、パケット内フィールドごとの値と意味を見やすく表示させることができます。これの対応形式はluaスクリプトで書かれたプラグイン (dissector) で拡張することができるため、本取り組みではこれを記述して使用しています。
こちらを適用した結果、ある料金所ゲート通過時の上り方向通信を下図のように可視化することができました。
なお、このdissectorは現状L2までのパケットデコード機能のみを実装しており、MDCのペイロードの中 (L7) までは解析されません。
パケットの上下マージ
一連の通信シーケンスは必ず地上局側からのFCMCで始まり、複数のチャネル (パケット) のやり取りで一つの通信 (フレーム) が完成します。
前項までで取り出したpcapデータは片方向の通信だけを取り出したものですので、フレームを解釈するためには両者を時系列順に合成 (マージ) する必要があります。
このような処理を行う場合、動画や音声記録などでは両記録に同時に記録される特別なシグナル (同期マーカー) などを用いてタイミングを合わせるテクニックなどが用いられますが、5.8GHz帯に安全に同期マーカーを埋め込む事は現状では困難そうです。
そこで両上り下り両pcapファイルの傾向を可視化してみてみます。以下に示すのは料金所ゲート通過時の上り下りのpcapの概要です。
図:上り方向 (車載器からの通信パケット) 概略
図:下り方向 (地上局からの通信パケット) 概略
青字で示しているのは、規格上「そこにあるはず」ですが、パケットサイズが異なるなどの理由で正しいパケットとは認められないものを、内部のビット列やシーケンス番号に相当するフィールドの値などから推測してサイズ修正したものです。これは受信時のエラーが由来で、最後までデータがデコードできずにパケットが終了してしまったものと思われます。
赤線で示した「上り下り同期タイミング」の前に5回の上り方向MDCパケット送信、その後に6回の下り方向MDCパケット送信が行われていることに着目しました。この傾向は色々なゲートでの観測でおおむね共通であるように思われることから、こちらを同期マーカーとして採用します。
この例では、下り方向の65番パケット (基準時刻からのオフセット 456.999 ミリ秒) のMDC送信に対して、同じスロットの終わり際に下り方向14番パケット (基準時刻からのオフセット 172.408 ミリ秒) のMDC送信が応答していることがわかります。
1つのMDC送信スロットの長さは 100 octet 固定であり、 MDCの先頭とACKCの先頭の間は 82 octet 分離れています。
よって、下り方向を基準に考えるとき、上り方向13番パケットは 82 octet 時間分 (456.999ミリ秒) 後、およそ 457.640 ミリ秒付近に到着しているはず、ということが分かります。すなわち、上り方向のpcapファイルをその差分である 285.232ミリ秒分オフセットしてからマージすることで、上下通信を時間的に同期して1つのファイルにまとめることが可能と言えます。
Wireshark用のコマンドラインツールには mergecap
と呼ばれるツールが含まれますが、タイムスタンプベースのマージは可能なもののオフセットを掛ける操作をサポートしていません。今回はpcapgoモジュールを使用しGo言語でそのツールを実装しました。
実際のコードは こちら をご覧ください。
$ go run ofsmerge.go -o {offset_in_usec} {downlink.pcap} {uplink.pcap} {output.pcap}
このツールは上記のような引数を与えて実行することで、 uplink.pcap
を offset_in_usec
分だけオフセットした状態で downlink.pcap
とマージし、結果を output.pcap
に書き出します。
秘話デスクランブラ
DSRCの仕様では、MDCの一部分に「簡易秘話スクランブル」をかけることになっています。
これを元に戻してあげないと通信の中身を見ることはできません。
Fig. 4.2.4.2.2.1-1 に示されているように、MACフィールドの次のバイトから、CRCを含む末尾までがスクランブル対象です。
秘話と言っても鍵は秘密情報ではなく、FCMSとACTSにより交換されたリンクローカルアドレスから算出される16ビットの値を鍵として使います。リンクローカルアドレスから鍵を算出する方法は「付属資料 D」に記載されていますが、ここでは省略します。
ダウンリンクにおいてMDCをブロードキャストする場合には秘話鍵として「0000 0000 0000 0000」を使います。スクランブル自体の処理はシンプルで、16段M系列のPNパターン (シフトレジスタ) が利用されています。使われている生成多項式は 1+X+X^3+X^12+X^16 です。
秘話鍵をシフトレジスタの初期値として用い、スクランブルされたビット列をシフトレジスタに順番に入れていくとデスクランブルされたビット列が出力されます。
Fig. 4.2.6.1.2 にて、丁寧に図で解説されています。
仕様書には「本標準規格では、DSRC専用に最低限レベルでのプライバシー保護/無線傍受対策として、秘話鍵配送のスクランブルによる簡易秘話方式(秘話スクランブル方式)を定める。」と記載がありますが、鍵も一緒に送っているため最低限レベルの対策とはなんなのか疑問が残ります。
秘話デスクランブラに使用した実際のコードは こちら をご覧ください。
秘話デスクランブラを適用した時のデコードログからダンプデータ本体を除去したものを以下に示します。
上り下りのpcapファイルのマージに用いた同期タイミングの前後1フレームの部分を取り出しています。
1フレーム目は上り方向MDC1つを含むフレームで、#35 / #36 / #37 パケットが該当します。
2フレーム目は下り方向 MDC 1つを含むフレームで、#38 / #39 / #40 パケットが該当します。
---- #35 packet (len=58, time=0.452530, downlink) ----
parse UW : ASK UW1 (0xd815d27c) detected.
calc CRC : calculated 0xce96 and reference 0xce96 matched.
parse IMI : standard procedure (0x00)
API #1 slot : type == 'Multi-purpose Toll Collection system (ETC)', Last Item
parse FSI : Full Duplex, 4 x 2 slot occupied
SCI #0 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #0 uplink : type == 'MDS', ask modulation, standard data channel, uplink, LID 0x5a0492b5, set 0xfba5 to MDS Key[0]
SCI #1 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #1 uplink : type == 'ACTS'
SCI #2 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #2 uplink : type == 'ACTS'
SCI #3 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #3 uplink : type == 'ACTS'
---- #36 packet (len=70, time=0.453055, uplink) ----
parse UW : ASK UW2 (0xd27c) UW detected.
descramble : packet is shorter than 71 octet, checksum disabled.
descramble : using MDS Key[0] = '0xfba5'
calc CRC : calculated 0x80b8 and auto matching disabled
---- #37 packet (len=5, time=0.453750, downlink) ----
d2 7c 80 70 74
parse UW : ASK UW2 (0xd27c) UW detected.
---- #38 packet (len=58, time=0.456430, downlink) ----
parse UW : ASK UW1 (0xd815d27c) detected.
calc CRC : calculated 0x1702 and reference 0x1702 matched.
parse IMI : standard procedure (0x00)
API #1 slot : type == 'Multi-purpose Toll Collection system (ETC)', Last Item
parse FSI : Full Duplex, 4 x 2 slot occupied
SCI #0 downlink : type == 'MDS', ask modulation, standard data channel, downlink, LID 0x5a0492b5, set 0xfba5 to MDS Key[0]
SCI #0 uplink : type == 'MDS', ask modulation, empty data channel, uplink
SCI #1 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #1 uplink : type == 'ACTS'
SCI #2 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #2 uplink : type == 'ACTS'
SCI #3 downlink : type == 'MDS', ask modulation, empty data channel, downlink
SCI #3 uplink : type == 'ACTS'
---- #39 packet (len=71, time=0.456990, downlink) ----
parse UW : ASK UW2 (0xd27c) UW detected.
descramble : using MDS Key[0] = '0xfba5'
calc CRC : calculated 0x00c1 and reference 0x00c1 matched.
---- #40 packet (len=5, time=0.457605, uplink) ----
d2 7c 80 70 74
parse UW : ASK UW2 (0xd27c) UW detected.
ログからも、FCMCに記されたMDSの割り当てがその直後のMDCの送信の向きと合致しており、そのMDSのデスクランブルがLIDを元にして算出された秘話鍵により実施され、デスクランブル後のパケットデータのCRCを取った結果がパケット内に記載されたCRCの値と合致 (≒デコードが成功) していることがわかります。
残念ですが、#36 パケットについてはデコーダの同期外れと思われるパケット長の短縮があり、最後のCRCの値が読み取れず、チェックができませんでした。
課題点
pcapファイルのマージに関する項でも触れた通り、今回の受信・デコード結果からは「パケットの正常なデータデコードが途中で停止してしまい、短いパケットが記録された」「そもそもプリアンブルを細くできておらず、あるはずのパケットが記録されていない」ケースが多数発生したため、パケット単体では正しいデコードができるものもありましたが、通信全体を時系列順に正しく読み取るには至りませんでした。
実際にデータデコードが停止したと判定された箇所の波形を手動で探ったところ、バッファオーバーフローなどが由来と思われる記録の飛びがあり、今回のデコーダ設計においてはパケット終端とみなすのに十分な波形の歪みとして表れていました。
今回記録に用いたPCは書き込み性能・演算性能とも記録するだけで精いっぱいの状況であり、時たまバッファオーバーフローの警告が出ていたことから、単純に性能不足であったということが言えそうです。
また、ダウンリンク方向で取得できたパケットキャプチャにおいて、アップリンク方向と同期している部分はほぼデータ受信開始直後の、まだ料金所からは少し距離があって受信強度が強くなく、従ってエラーに伴う短いパケットの発生が顕著なエリアに集中していました。
このことは、実際のETC車載機が測定に用いた環境よりも十分に高い受信性能を持っており、受信強度がまだ低いレーンの手前から既に通信を確立していたということを示します。実際にはこれ以上に受信環境を改善することは簡単ではありませんから、現実的には測定用アンテナをより車体の前方 (例えばボンネット付近) に設置して実際の車載機よりも先に受信感度が良好となるエリアに接近させたり、さらに指向性の高いアンテナを用いるなどの対策が必要となるでしょう。
まとめ
フィールドワーク編・デコード編を通して、通信方式に沿って規定のパケットがやり取りされている様子 (L1、L2レベルまで) を知ることができました。一方、受信したデータにはエラーが多く、L7ペイロードの中身を解釈していくためにはより適切な測定環境の確立による完全なデータキャプチャが必要なことが分かりました。
一方、その原因は測定機材となったPCの性能不足やアンテナ設置位置を改善することで対応できそうです。今後はこれらの環境改善を行い、L7ペイロードの中身を正しく解析できるように取り組みを続けたいと思います。