mqtt Advent Calendar 3日目です
はじめに
Connectパケットと動作について説明します。
Connectについて
MQTTでサーバとクライアントが通信するにはMQTTのレイヤで「未接続」から「接続済」状態へ移行する必要があります。 具体的には、クライアントからサーバに対してMQTTの下のレイヤ(tcp/tls/websocket)で接続状態になったあとに、CONNECTパケットを発行し、それに対する応答であるところのCONNACKを受け取る必要があります。
今回はmqtt version 3.1.1の前提で見ていきます。MQTT 3.1.1は5.0に比べるとシンプルです。
「どのプロトコルのバージョンか?」というのもConnect PacketのVariable Headerの"Protocol Name"と"Protocol Level"に含まれています。どんな項目があるかは規格書(3.1.2 Variable header)にも記載されています。
Variable header / Payloadに含まれるのは、大まかにわけると下記の2種類です。
- 接続に必要な情報(Username, Password, ID)
- その接続がどんな振る舞いをしてほしい種類の接続か?(QoS, Retain, Will(遺言), Clean Sessionフラグ)
です。MQTT5では変更になりますが、ここではひとまずスルーします。
では、上記1種類ずつ試してみましょう
実践
1日目のPython/Goコードを流用します。
クライアントコードの改善
まず、クライアント側を編集してConnectした際にログが出るようにしましょう。
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
print("[接続成功] Subscriber")
client.subscribe(TOPIC)
else:
print(f"[接続失敗] reason_code={reason_code}")
として
def subscriber():
sub = mqtt.Client(client_id="sub-client", callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
sub.on_message = on_message
sub.on_connect = on_connect # ここ!
sub.on_disconnect = on_disconnect
sub.connect(BROKER, 1883, 60)
sub.subscribe(TOPIC)
sub.loop_forever()
こうします。
サーバ側にパスワード認証をかける
サーバ側にパスワード認証をかけましょう。mochiの仕組みとしてauthOptionにLedger(台帳)を作ってルールを入れると適用されます。1日目のロジックは誰でも接続可能になっているところに制限をかけることになります。
authOpts := &auth.Options{
Ledger: &auth.Ledger{
Auth: auth.AuthRules{
// { Username, Password, Allow }
{Username: "user1", Password: "pass123", Allow: true},
{Username: "admin", Password: "secret", Allow: true},
},
// 必要なら ACL ルールもここに追加できます(例では空にしておく)
ACL: auth.ACLRules{},
},
}
if err := server.AddHook(new(auth.Hook), authOpts); err != nil {
log.Fatal(err)
}
この状態でサーバ側を起動した後、クライアント側を起動し、接続できないことを確認しましょう
$uv run main.py
[接続失敗] reason_code=Not authorized
[切断] reason_code=Unspecified error
[送信] Hello 0
[接続失敗] reason_code=Not authorized
[切断] reason_code=Unspecified error
[送信] Hello 1
[送信] Hello 2
[接続失敗] reason_code=Not authorized
[切断] reason_code=Unspecified error
[送信] Hello 3
[送信] Hello 4
[接続失敗] reason_code=Not authorized
[切断] reason_code=Unspecified error
パスワードが未設定ですのでちゃんとNot authorizedで切断されているようです。
クライアント側にパスワードを追加しましょう
PubとSub両方のクライアントに追加する必要があります。設定値はもちろんサーバ側に設定したものと同様
USERNAME = "user1"
PASSWORD = "pass123"
...
pub = mqtt.Client(client_id="pub-client", callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
pub.username_pw_set(USERNAME, PASSWORD) # ここ!
...
sub = mqtt.Client(client_id="sub-client", callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
sub.username_pw_set(USERNAME, PASSWORD) # ここ!
$uv run main.py
[接続成功] Subscriber
[送信] Hello 0
[受信] test/topic: Hello 0
[送信] Hello 1
[受信] test/topic: Hello 1
[送信] Hello 2
[受信] test/topic: Hello 2
[送信] Hello 3
[受信] test/topic: Hello 3
[送信] Hello 4
[受信] test/topic: Hello 4
[~/qiita/1day]$
Wiresharkでパケットを見ます
User Name, Passwordは Payloadに含まれます。
00 05は文字列の長さで75 73 65 72 31は 文字コード表とにらめっこすると、user1になります。passwordも同様にちゃんとペイロードに入っていますね。
これは実験でlocalhost環境なのでtcpで繋いでユーザー/passwordを送信していますが、丸見えなので本番環境ではtls通信を使用すべきでしょう。
まとめ
Connectパケットについて内容を見てみました。
