Skip to content

MS_Mosquitto

nishi_74322014 edited this page Sep 11, 2026 · 2 revisions

Mosquitto

概要

MQTT ブローカーの OSS

補足(MQTT と Mosquitto の関係): MQTT
IoT 向けの軽量なメッセージング プロトコル(OASIS 標準)、
Mosquitto はそのブローカー(サーバー)実装の代表例である
(Eclipse Foundation のプロジェクト)。

RabbitMQ と並べられているが、性格はかなり違う。

Mosquitto RabbitMQ
主プロトコル MQTT AMQP(MQTT はプラグイン)
想定 多数の非力なデバイス サーバー間の業務メッセージング
モデル Topic への Pub/Sub Exchange / Binding / Queue
永続化 軽量(既定はメモリ) キューの永続化が前提
フットプリント 極小(Raspberry Pi でも動く) 大きい(Erlang VM)

「センサーが大量にあって、細かいメッセージを絶え間なく送る」なら MQTT、
「業務処理を確実に非同期化したい」なら AMQP、という住み分けになる。

詳細

ブローカーモデル

Producer

送信する人

Consumer

受信する人

Message

送受信対象のメッセージ

Queue

キュー

Topic

  • トピック

    • / から始まる Unicode UTF-8 文字列
    • 全長 65535 バイト以下
    • ヌル文字を含まない
  • ワイルドカード

    • #:マルチレベル・ワイルドカード(その階層以下の全トピック文字列と一致)
    • +:シングルレベル・ワイルドカード(当該階層以下の全トピック文字列と一致)

補足(Topic 設計が MQTT の要): MQTT には
RabbitMQ のような Exchange の種別が無く、
すべてが Topic の階層構造で表現される
したがって、Topic の命名がそのままルーティング設計になる。

factory/line1/machine3/temperature
   │      │       │         └ 何の値か
   │      │       └ どの機器か
   │      └ どのラインか
   └ どの拠点か

factory/line1/+/temperature   → line1 の全機器の温度
factory/line1/#               → line1 配下のすべて

粗いところから細かいところへ並べるのが原則で、
逆順にするとワイルドカードで絞れなくなる。
また、後から階層を増やすのは難しいため、
最初に将来の拡張を織り込む必要がある。

なお、原文の「/ から始まる」は慣例であり、
仕様上の必須ではない(先頭の / は空の階層を作るため、
実務では付けない方が多い)。

補足(QoS): 原文のコードに出てくる QoS も MQTT の中核概念である。

QoS 保証 用途
0 At most once(届かないかも) 頻繁なセンサー値。落ちても次が来る
1 At least once(重複するかも) 一般的な選択。冪等性が必要
2 Exactly once(1 回だけ) 課金・制御など。最も遅い

IoT では大半が QoS 0 か 1 で足りる。
QoS 2 は往復が 4 回必要で、大量デバイスでは負荷になる。

インストール

http://mosquitto.org/download/

ブローカー起動

>cd C:\Program Files\Mosquitto
>mosquitto -v

送受信

  • 受信
    全トピックを受信
>cd C:\Program Files\Mosquitto
>mosquitto_sub -h 127.0.0.1 -t "#" -v
  • 送信
    test トピックに送信
>cd C:\Program Files\Mosquitto
>mosquitto_pub -h 127.0.0.1 -t test -m "hoge"

その他の環境

Linux

  • インストール
    • リポジトリを追加
$ sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa
  • サーバー&クライアント
$ sudo apt-get install mosquitto mosquitto-clients

Raspberry Pi

  • インストール

    • サーバー
$ sudo apt-get install mosquitto
  • クライアント
$ sudo apt-get install mosquitto-clients
  • ブローカー起動
    不要

  • 送受信
    Windowsと同じ。

  • 参考

Docker on Windows

ミドルウェアなので、Dockerの作法で動かせるっぽい。
(ただし、コンフィグ・ファイルの準備は必要)

https://github.com/OpenTouryoProject/DxCommon/tree/develop/Edge/Mosquitto/IaC

Docker on Raspberry Pi

...

SaaS / PaaS

  • Azure:Azure IoT Hub
  • AWS:AWS IoT Core(ブリッジ機能)
  • GCP:Cloud Pub/Sub?

移行メモ: 原文の 2 行目は「Azure:AWS IoT Core」となっていたが、
AWS の誤記と判断し修正した。

補足(マネージド サービスとの違い): Azure IoT Hub
MQTT を話せるが、汎用の MQTT ブローカーではない点に注意が要る。

Mosquitto Azure IoT Hub
Topic 自由に設計できる 固定devices/{id}/messages/...
デバイス間の Pub/Sub できる できない(必ずクラウド経由)
デバイス管理 無し(自前) デバイス ツイン、DPS、双方向通信
認証 自前で構成 デバイスごとの ID と証明書を管理

つまり IoT Hub は「MQTT 互換の入口を持つデバイス管理基盤」であり、
既存の MQTT アプリをそのまま繋ぐ用途には向かない
現場に Mosquitto を置き、
IoT Edge でクラウドへ橋渡しする構成が
実際にはよく採られる。

認証

パスワード

証明書

送受信(C#)

Paho M2Mqtt

最近、更新頻度が落ちてきている。

using System;
using System.Text;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;

namespace M2MqttSub
{
    class Program
    {
        static void Main(string[] args)
        {
            var client = new MqttClient("127.0.0.1");
            client.MqttMsgPublishReceived += (sender, eventArgs) =>
            {
                var msg = Encoding.UTF8.GetString(eventArgs.Message);
                var topic = eventArgs.Topic;
                Console.WriteLine(topic + ", " + msg);
            };
            var ret = client.Connect(Guid.NewGuid().ToString());
            Console.WriteLine("Connected with result code {0}", ret);
            client.Subscribe(new[] { "test" }, new[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
            while (client.IsConnected)
            {
            }
        }
    }
}
  • 送信
using System;
using System.Text;
using uPLibrary.Networking.M2Mqtt;

namespace M2MqttPub
{
    class Program
    {
        static void Main(string[] args)
        {
            var client = new MqttClient("127.0.0.1");
            var ret = client.Connect(Guid.NewGuid().ToString());
            Console.WriteLine("Connected with result code {0}", ret);
            while (client.IsConnected)
            {
                var msg = "Test message from Publisher " + DateTime.Now;
                client.Publish("test", Encoding.UTF8.GetBytes(msg), 0, true);
                Console.WriteLine("Message published.");
                System.Threading.Thread.Sleep(1500);
            }
        }
    }
}

補足(最新化): 原文の見立てのとおり、Paho M2Mqtt は
現在ほぼ更新されていない
(アーカイブ状態)。
新規開発では次の MQTTnet を選ぶこと。

また、受信側の while (client.IsConnected) { }
CPU を 100% 使い切るビジーループである。
サンプルとしては動くが、実装では
Console.ReadLine()ManualResetEvent で待つべき
(後述の MQTTnet の例はそうなっている)。

MQTTnet

更新が頻繁で、トータル・ダウンロードが Paho M2Mqtt の4倍以上

  • 以下は認証フル実装だが、証明書検証を無効化している。
    CertificateValidationHandler で原因を見ると、
    SslPolicyErrors.RemoteCertificateChainErrors が出ている。

    • 受信
using System;
using System.Text;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Receiving;
using System.Security.Cryptography.X509Certificates;

namespace msqt_sub
{
    class Program
    {
        private static IMqttClient mqttClient = null;

        static async Task Main(string[] args)
        {
            X509Certificate2 ca = new X509Certificate2("ca.crt");
            X509Certificate2 cli = new X509Certificate2("client.pfx", "xxxxx");

            var factory = new MqttFactory();
            Program.mqttClient = factory.CreateMqttClient();
            var options = new MqttClientOptionsBuilder()
                .WithClientId("ef3614fca42b32d3")
                .WithTcpServer("localhost", 8883)
                .WithCredentials("guest", "guest")
                .WithTls(new MqttClientOptionsBuilderTlsParameters(){
                    UseTls = true,
                    SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
                    AllowUntrustedCertificates = true,
                    //CertificateValidationCallback = (a, b, c, d) => true,
                    CertificateValidationHandler = (o) =>
                    {
                      Console.WriteLine(o.ToString());
                      return true;
                    },
                    Certificates = new List<X509Certificate>()
                    {
                        ca, cli
                    }
                })
                .Build();

            // 接続後のイベントハンドラの設定
            Program.mqttClient.ApplicationMessageReceivedHandler
                = new MqttApplicationMessageReceivedHandlerDelegate(OnAppMessage);
            Program.mqttClient.ConnectedHandler
                = new MqttClientConnectedHandlerDelegate(OnConnected);

            // 接続
            await Program.mqttClient.ConnectAsync(options);

            // 待機
            Console.ReadLine();
        }

        static private async void OnConnected(MqttClientConnectedEventArgs e) {
            await Program.mqttClient.SubscribeAsync(
                new TopicFilterBuilder()
                .WithTopic("test").Build());
        }
        static private void OnAppMessage(MqttApplicationMessageReceivedEventArgs e)
        {
            string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
            Console.WriteLine(payload);
        }
    }
}
  • 送信
using System;
using System.Text;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using System.Security.Cryptography.X509Certificates;

namespace msqt_pub
{
    class Program
    {
        private static IMqttClient mqttClient = null;

        static async Task Main(string[] args)
        {
            X509Certificate2 ca = new X509Certificate2("ca.crt");
            X509Certificate2 cli = new X509Certificate2("client.pfx", "xxxxx");

            var factory = new MqttFactory();
            Program.mqttClient = factory.CreateMqttClient();
            var options = new MqttClientOptionsBuilder()
                .WithClientId("ef3614fca42b32d3")
                .WithTcpServer("localhost", 8883)
                .WithCredentials("guest", "guest")
                .WithTls(new MqttClientOptionsBuilderTlsParameters(){
                    UseTls = true,
                    SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
                    AllowUntrustedCertificates = true,
                    //CertificateValidationCallback = (a, b, c, d) => true,
                    CertificateValidationHandler = (o) =>
                    {
                      Console.WriteLine(o.ToString());
                      return true;
                    },
                    Certificates = new List<X509Certificate>()
                    {
                        ca, cli
                    }
                })
                .Build();

            // 接続
            await Program.mqttClient.ConnectAsync(options);

            // メッセージの送信するための設定
            var message = new MqttApplicationMessageBuilder()
                .WithTopic("test")
                .WithPayload("hoge")
                .WithExactlyOnceQoS()
                .Build();

            // メッセージの送信 publish
            await Program.mqttClient.PublishAsync(message);
        }
    }
}

補足(証明書検証の無効化について): AllowUntrustedCertificates = true
CertificateValidationHandler で常に true を返す実装は、
検証を完全に無効化しているため、中間者攻撃に対して無防備になる。
原文も「無効化している」と明記しているとおり検証用の便法であり、
本番では必ず外すこと。

RemoteCertificateChainErrors が出る原因は、たいてい

  • 自己署名 CA が信頼されたルート証明機関に入っていない
  • サーバー証明書の CN / SAN がホスト名と一致しない

のどちらかである。
原文のチュートリアルでも、
CA を証明書ストアに入れる手順が繰り返し出てくる。

補足(最新化): MQTTnet は v4 以降で API が大きく変わっており、
上記のコードはそのままではビルドできない。

v3(原文) v4 以降
MqttClientOptionsBuilderTlsParameters .WithTlsOptions(o => ...)
ApplicationMessageReceivedHandler ApplicationMessageReceivedAsync イベント
TopicFilterBuilder MqttTopicFilterBuilder
MqttFactory MqttClientFactory

チュートリアル

証明書認証(サーバー認証とクライアント認証)+パスワード認証を構成し、
Paho M2MqttMQTTnet からアクセスする。

サーバー認証

コチラコチラが参考になる。

  • 認証局の証明書を作成する。
>openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt
Generating a RSA private key
................................................+++++
............+++++
writing new private key to 'ca.key'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:hogeca
Email Address []:
  • サーバー証明書

    • RSA 鍵の作成
>openssl genrsa -out server.key 2048
  • 証明書要求の作成
>openssl req -out server.csr -key server.key -new
(略)
Common Name (e.g. server FQDN or YOUR name) []:localhost
  • サーバー証明書の発行
>openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
Signature ok
subject=C = JP, ST = Some-State, O = Internet Widgits Pty Ltd, CN = localhost
Getting CA Private Key
Enter pass phrase for ca.key:
  • 証明書ファイルの配置
    証明書ファイルをインストール・ディレクトリ直下の certs ディレクトリに配置

  • サーバー証明書の設定
    mosquitto.conf の先頭に以下を追加

listener 8883
cafile   ./certs/ca.crt
certfile ./certs/server.crt
keyfile  ./certs/server.key
allow_anonymous true
  • 動作確認を行う。
    • conf を指定してブローカー起動
>cd C:\Program Files\Mosquitto
>mosquitto -v -c mosquitto.conf
  • 送受信

    • 受信
>mosquitto_sub -h localhost -t "#" -v -d -p 8883 --cafile <ca.crtのパス>
- 送信
>mosquitto_pub -h localhost -t test -m "hoge" -d -p 8883 --cafile <ca.crtのパス>
  • ハマりどころ

    • 認証局の CN とサーバーの CN が同じ場合、以下のエラーが表示される。

      • SSL routines:tls_process_server_certificate:certificate verify failed
      • SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca
    • Connection error: Connection Refused: not authorised. が出る。
      「allow_anonymous true」を設定ファイルに追加する。

補足(この「ハマりどころ」は重要): CA の CN とサーバーの CN を
同じにしてはならない
という指摘は、自己署名 CA を使う際に
極めて頻繁に踏む落とし穴である。
証明書チェーンの検証で、CA 自身とサーバー証明書が
区別できなくなるために失敗する。

原文が CA を hogeca、サーバーを localhost
意図的に分けているのはこのためである。

なお、現在の OpenSSL / クライアントでは CN ではなく
SAN(Subject Alternative Name)が必須
である。
CN だけの証明書は多くのクライアントで拒否されるため、
証明書要求時に SAN を付ける必要がある。

openssl req -out server.csr -key server.key -new \
  -addext "subjectAltName = DNS:localhost,IP:127.0.0.1"

クライアント認証

コチラコチラが参考になる。

  • クライアント認証

    • RSA 鍵の作成
>openssl genrsa -out client.key 2048
  • 証明書要求の作成
>openssl req -out client.csr -key client.key -new
(略)
Common Name (e.g. server FQDN or YOUR name) []:hogecli
  • クライアント証明書の発行
>openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
  • 証明書ファイルの配置
    証明書ファイルをインストール・ディレクトリ直下の certs ディレクトリに配置

  • クライアント証明書の設定
    mosquitto.conf に以下を追加

require_certificate true
  • 動作確認を行う。
    • conf を指定してブローカー起動
>mosquitto -v -c mosquitto.conf
--cert <client.crtのパス> --key <client.keyのパス>
  • ハマりどころ
    ...

パスワード認証

  • パスワードファイルを生成
>cd C:\Program Files\Mosquitto
>mosquitto_passwd -c pwfile guest
Password:
Reenter password:
  • パスワード認証の有効化
    mosquitto.conf の allow_anonymous 周辺に修正・追記
allow_anonymous false
password_file /etc/mosquitto/pwfile
  • 動作確認を行う。
    • conf を指定してブローカー起動
>mosquitto -v -c mosquitto.conf
-u guest -P guest
  • ハマりどころ
    パスワードは「-p」ではなく「-P」で指定する。

補足: 小文字の -pポート番号の指定である。
-p guest と書くと「ポート番号 guest」と解釈されてエラーになる。

Paho M2Mqtt からアクセス

  • クライアント証明書を、*.pfx に変換する必要がありそう。

  • pkcs12 クライアント証明書(*.pfx)を合成

>openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt
Enter Export Password:
Verifying - Enter Export Password:
  • 認証局の証明書(ca.crt)は、証明書ストア
    信頼されたルート証明機関に入れておく必要がある。

  • 証明書やパスワードの追加実装を行う。

X509Certificate2 ca = new X509Certificate2("ca.crt");
X509Certificate2 cli = new X509Certificate2("client.pfx", "xxxxx");
var client = new MqttClient("localhost", 8883, true, ca, cli, MqttSslProtocols.TLSv1_2);
client.ProtocolVersion = MqttProtocolVersion.Version_3_1_1;
var ret = client.Connect(Guid.NewGuid().ToString(), "guest", "guest");

補足(なぜ *.pfx が必要か): .NET の X509Certificate2
秘密鍵を含んだ形式を求める。
OpenSSL が出す *.crt(証明書のみ)と *.key(秘密鍵)は別ファイルなので、
PKCS#12(*.pfx / *.p12)に合成する必要がある。

client.crt(証明書) ┐
                     ├──▶ client.pfx(.NET が読める)
client.key(秘密鍵) ┘

なお .NET 5 以降では X509Certificate2.CreateFromPemFile()
PEM から直接読めるようになっており、変換は必須ではなくなった。

MQTTnet からアクセス

コチラの実装で動作すると思う。

サーバー認証

  • 基本的に、Windowsと同じ。

  • 以下のように表示される場合、sudo を付ける。

req: Can't open "ファイル名" for writing, Permission denied
  • key ファイルに権限付与
    mosquitto に読み取り権限が無いらしい。
sudo chmod 644 server.key
  • ファイル設定
    パスの指定方法が異なる。
listener 8883
cafile   /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile  /etc/mosquitto/certs/server.key
allow_anonymous true
  • 後は、以下で再起動。
$ sudo systemctl restart mosquitto
  • テストを行う。

※ ca_certificates フォルダがあったので、
  CA のファイルはそちらに配置した方が良いカモ。

  • ca.key
  • ca.crt

補足(秘密鍵の権限): chmod 644 server.key
秘密鍵を誰でも読める状態にするため、検証以外では避けるべきである。
正しくは所有者を mosquitto にして 640 にする。

sudo chown root:mosquitto /etc/mosquitto/certs/server.key
sudo chmod 640 /etc/mosquitto/certs/server.key

また、原文が「CA のファイルは ca_certificates フォルダの方が良いカモ」と
述べているとおり、CA の秘密鍵(ca.key)はサーバーに置くべきではない
(証明書の発行元が漏れると、任意の証明書を偽造されるため)。
発行作業は別の安全な場所で行い、
サーバーには ca.crt だけを配置するのが正しい。

クライアント認証

  • 基本的に、Windowsと同じ。

  • 以下のように表示される場合、sudo を付ける。

req: Can't open "ファイル名" for writing, Permission denied
  • key ファイルに権限付与
    mosquitto に読み取り権限が無いらしい。
sudo chmod 644 client.key
  • ファイル設定
    以下を追記
require_certificate true
  • 後は、以下で再起動。
$ sudo systemctl restart mosquitto
  • テストを行う。

パスワード認証

  • 基本的に、Windowsと同じ。

  • 以下のように表示される場合、sudo を付ける。

Error: Unable to open file pwfile for writing. Permission denied.
  • ファイル設定
    mosquitto.conf の allow_anonymous 周辺に修正・追記
allow_anonymous false
password_file ./pwfile
  • 後は、以下で再起動。
$ sudo systemctl restart mosquitto
  • テストを行う。

Paho M2Mqtt からアクセス

  • 基本的に、Windowsと同じ。

  • /dev/msqt_pub とか言うフォルダを作成

  • プロジェクトを作成し

$ dotnet new console
  • VS Code で msqt_pub フォルダを開く

  • 必要に応じてエクステンションをインストール

    • CSharp for Visual Studio Codeをインストール
    • NuGet Package Manager をインストール
    • NuGet で M2MqttDotnetCore をインストール
  • Program.cs にコチラの送信処理を実装

  • 認証局の証明書(ca.crt)は、証明書ストア
    信頼されたルート証明機関に入れておく必要がある。

  • 証明書やパスワードの追加実装を行う。

  • デバッグ実行で動作確認

MQTTnet からアクセス

コチラの実装で動作した。

正規の証明書

正規の CN、場合によっては、
証明書ストアへの登録が必要なのかもしれない。

参考

Qiita

.NET ライブラリ


Tags: 移行, 通信技術, .NET開発, IoT

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally