mqtt Advent Calendar 2日目
はじめに
mqttのパケット構造について解説します。解説した後、実際にWiresharkでパケットの値を見て解読してみましょう。
パケット構造
mqttはTCP/TLS/WebSocket上で動作するアプリケーション層のプロトコルです。tcp/tls/websocketのペイロードに乗って、バイトストリームでデータが運ばれます。
パケットの構造は下記の構造を取ることが定義されています。
+-------------------+----------------------+--------------------+
| 固定ヘッダ | 可変ヘッダ | ペイロード |
+-------------------+----------------------+--------------------+
それぞれについて中身を説明します
固定ヘッダ
固定ヘッダはそのMQTTパケットの種類と残り長さを表します。
- 1byte目: パケットの種類(上位4ビット)と制御フラグ(下位4ビット)
- 2byte目以降:パケットの残り長さが可変長エンコード形式でエンコードされた値が入る
+------------------------------------------------------------+
| Byte 1 | Control Packet Type (4bit) | Flags (4bit) |
+------------------------------------------------------------+
| Byte 2〜 | Remaining Length(可変長エンコード) |
+------------------------------------------------------------+
可変長エンコード
MQTTにおける可変長エンコードとは、下位1ビットが後続があるかないかを伝えます。下位ビットが1の場合は後続が存在し、下位が0の場合は後続が存在しません。例えば、
-- 固定ヘッダ開始
1byte目 : (種類、パケットフラグ)
-- Remain Length開始
2bytes目 : 1100 0001 ←lengthの下位7ビットを示すMSBは後続があるかどうかのフラグ、1なので後続あり
3bytes目 : 0000 0010 ←lengthの上位7ビットを示すMSBはフラグ、0なので後続なし
-- Remain Length終了
-- 固定ヘッダ終了
となります。この例では、Remain Lengthは
(0x41) + (0x02 << 7)
で計算され、10進数でいうところの321となります。
実は「固定ヘッダ」と名乗っていながら、この可変長エンコードが可変長であるので固定ヘッダの長さは固定ではありません。
Remain Lengthは固定ヘッダ自体の長さは含みません、つまり続く可変ヘッダとペイロードの長さの和になります。
可変長エンコードを使用しているプロトコルはmqtt固有ではなく、他には、ASN.1 BER / DERやProtobuf (Varint), MIDIなどがあるようです。可変長の具体的な内容は異なります
可変ヘッダ
可変ヘッダはペイロードには入らないメタ的情報です。何が入るかはパケットの種類によって異なりますが、メタ的情報の内容はConnect Packetの例では、
- Connect Flagsで前のセッションをクリアするかどうかを指示する
- Keep aliveでPing/Pongの間隔をサーバ側に伝える
...
などがあり、Packet IdentifierやReason Codeなど、複数のパケット種類にまたがって共通の構造を取る項目もあります。実際に使ったり実装する際は、OASISの規格書に網羅されている表が掲載されています。
ペイロード
ペイロードはパケットの本体ともいえますが、パケットの種類によっては可変ヘッダで完結してペイロードは存在しない場合があります。ペイロードが必須になっているのは、
- ペイロードが必須
- CONNECT → ClientIDが必須
- SUBSCRIBE → Topic Filterが必須
- SUBACK → Reason Codeが必須
- UNSUBSCRIBE → Topic Filterが必須
- UNSUBACK → Reason Codeが必須
です。PUBLISHはオプショナル、つまりペイロード長がゼロでも可能となっていますが、ユーザーとしてはPUBLISHのペイロードに自分の送信したいデータを送る場面が圧倒的に多い思います。ペイロードゼロのPublishはRetainの削除に使用されます。
[実践]Wiresharkでパケット構造をみてみよう
環境構築
見やすくするためにmqttのプラグインを入れましょう(参考: https://qiita.com/zakkied/items/c013d740e9ae052cce72
)
そして、1日目で動作させたサーバとクライアントを動かしてConnectパケットを見てみます。下記が動作させた際のWiresharkのスクショです。

TCPのペイロードでMQTTと分類されたパケットを見ると、送受信されたMQTTデータが構造化されて見ることができます。
Connectのパケットの内容
たとえば、Connectの構造は下記のようになっています
CONNECT Packet
├─ 固定ヘッダ
├─ 可変ヘッダ
│ ├─ Protocol Name
│ ├─ Protocol Level
│ ├─ Connect Flags
│ └─ Keep Alive
└─ ペイロード
└─ Client ID
固定ヘッダ
最初の0x10は1=ConnectPacket, 0=フラグ
0x16はRemainingLengthで22、可変長ですが7バイトに収まっているので16進数と同じ見た目になっています。
可変ヘッダ
可変ヘッダーはClient ID Lengthの手前までになり、プラグインのおかげで上部に表示されている通りになります。
- Protocol Name、つまりプロトコルバージョン
- Connect Flags (QoSやCleanSessionなど略)
- Keep Alive
ペイロード
ここではClient ID LengthとClient IDになります。
文字列を表現するときは、文字列の長さを示す2byte分に続いて、長さ分だけ文字列本体が連なっています。これは、UTF-8 encoded stringsで定義されており、MQTTで文字列と聞けばこの構造を取っています。
まとめ
- MQTTのパケットは3層になっていて、中身はパケットの種類によって異なる
- Wiresharkでわかりやすく確認できる(Wiresharkとそのプラグインの作者さんありがとう!)
- この構造を理解することで規格書も難なく読み解けると思います
著作権情報
Copyright © OASIS Open 2014. All Rights Reserved.
Available at: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
Copyright © OASIS Open 2019. All Rights Reserved.
Available at: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html