Learn MQTT Basics With Mosquitto

Introduction

MQTT is evolving into the de facto protocol in the IoT world due to its topic-based pub/sub messaging pattern, which is quite different from the req/res model in the RESTful world. It requires us to think differently when it comes to designing topic-based APIs, that said, designing in a topic-centralized way.

This document is created with the intention to facilitate MQTT development in terms of message subscribing and publishing, topics design, testing, or even just for fun.

Audience

  • Developers who are developing MQTT features.
  • Those who are interested in and want to get their hands dirty with MQTT basics.

Goals

  1. Set up a local MQTT broker and clients for message subscriptions and publications.
  2. Visualize topics as well as corresponding payloads on the go.
  3. Inspire topics design or design topics in a standardized way.

Setup MQTT Broker

While there are a bunch of brokers when it comes to Choosing an MQTT broker for your IoT project, the lightweight and open-sourced Mosquitto broker is demonstrated here due to its simplicity and support for MQTT v5 and ACL in its most recent v2 release.

  1. Install mosquitto on the host machine(Mac)
    Download and install mosquitto as per the guidance from the official homepage.
brew install mosquitto
  1. Install and run Docker-based Eclipse Mosquitto Broker.
docker pull cedalo/installer:2-macos
  • Run from the docker image
docker run -it -v ~/cedalo_platform:/cedalo cedalo/installer:2-macos
  • Brings the broker up by running the start script
cd ~/cedalo_platform/
./start.sh

Once started, the following components should be working in the specific ports now

Navigate to the Management Web UI to take a look quick at what the dashboard looks like.

Screen Shot 2021-04-14 at 2.46.19 PM.png

Verify message pub & sub

  1. Makes sure MQTT broker has started
docker ps

The outputs should print the running containers that look like

CONTAINER ID   IMAGE                             COMMAND                  CREATED        STATUS        PORTS                                                                       NAMES
bf19c83f9d46   cedalo/installer:2-macos          "./docker-entrypoint…"   22 hours ago   Up 22 hours                                                                               loving_kare
e98c90d8deea   cedalo/streamsheets:2-milestone   "docker-entrypoint.s…"   43 hours ago   Up 22 hours   1883/tcp, 6379/tcp, 8080/tcp, 8083/tcp, 27017/tcp, 0.0.0.0:8081->8081/tcp   cedalo_platform_streamsheets_1
2722f7f34f1c   cedalo/management-center:2        "docker-entrypoint.s…"   43 hours ago   Up 22 hours   0.0.0.0:8088->8088/tcp                                                      cedalo_platform_management-center_1
3479c5c781c7   eclipse-mosquitto:2-openssl       "/docker-entrypoint.…"   43 hours ago   Up 22 hours   0.0.0.0:1883->1883/tcp                                                      cedalo_platform_mosquitto_1

If the containers were down, try to bring them up

docker run -it -v ~/cedalo_platform:/cedalo cedalo/installer:2-macos
  1. Creates MQTT client(s) with unique username(s) to connect to the broker.
    This is optional if anonymous authentication is allowed, which is not enabled by default as of Mosquitto v2 with the Dynamic security model enabled by default. To enable anonymous authentication, one needs to config allow_anonymousin the configuration file and restart the broker.

Enables allow_anonymous config

# ~cedalo_platform/mosquitto/config/mosquitto.conf
allow_anonymous true 

and restarts the broker to apply the new configuration (if it’s up now)

# ~cedalo_platform
./start.sh

Note: If anonymous authentication is disabled, then both publisher and subscriber should at least hold a client role, from the ACL(Access Control Lists)'s perspective, in order to connect to the broker, otherwise, subscription/publication requests will get denied and end up with an “authentication failed” error.

Here is a screenshot with two clients newly created.


Screen Shot 2021-04-14 at 5.16.47 PM.png
  1. Subscribes to a test topic
mosquitto_sub -d -k 10 -v -h localhost -u test -P 123456 -t 'test/topic'

Sample outputs:

➜ mosquitto_sub -d -k 10 -v -h localhost -u test -P 123456 -t 'test/topic'
Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending SUBSCRIBE (Mid: 1, Topic: test/topic, QoS: 0, Options: 0x00)
Client (null) received SUBACK
Subscribed (mid: 1): 0
Client (null) received PUBLISH (d0, q0, r1, m0, 'test/topic', ... (26 bytes))
test/topic {"greeting": "Cool MQTT2"}
Client (null) sending PINGREQ
Client (null) received PINGRESP
Client (null) sending PINGREQ
Client (null) received PINGRESP
...

Notes:

  • -d indicates to work in debug mode, printing verbose debug info. For example, the PINGREQ and PINGRESP pairs show an alive session.
  • -k changes the keep-alive interval value from the default 60 to the specified.
  • -v prints the topic name of the published messages.
  • -h hostname (or IP address) of the machine the broker is running on. Here, as it’s running in the container on the local machine, so localhost is used to accept pub/sub requests from terminal clients on the same machine. Other clients running on e.g. a real iPhone/Android device, or a simulator/emulator should use the IP address of the dev machine so as to be able to connect to the broker.
  • -p network port to connect to. Defaults to 1883 for plain MQTT and 8883 for MQTT over TLS.
  • -u -P user name and password of the client to authenticate with. Optional if anonymous authentication is allowed supported.
  • -t the target topic to subscribe to.

Other useful options that can be used

  • -C stops subscribing and automatically disconnects once the specified amount of messages were received.
  • -q changes the QoS level in the pub/sub requests.
  • -c -x 60 -i cid disables clean session and shares session states for the same client ID in the new connection. which means the session will never expire after the client disconnects (prior to MQTT v5) or will last at least x seconds (60 here) before it expires (for MQTT V5+), starting from the time of the most recent connection.
  1. Publishes test message to a test topic
mosquitto_pub -d -r             \
-h 192.168.7.123                \
-u test -P 123456               \
-t 'test/topic'                 \
-m "{\"greeting\": \"Cool MQTT2\"}"

Sample outputs:

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending PUBLISH (d0, q0, r1, m1, 'test/topic', ... (26 bytes))
Client (null) sending DISCONNECT

Notes:

  1. -r indicates the published message will be retained in the broker so that subsequent clients that subscribe to the same topic are able to receive the published message immediately once connected.
  2. -m specified the message payload to be published to the target topic 'test/topic'. Note that JSON-formatted payload should be escaped. Freeformatter can be used for this purpose.

Here is a screenshot that depicts the activities coming from the broker, terminal publisher, terminal subscriber, Android publisher/subscriber.


Screen Shot 2021-04-15 at 3.03.14 PM.png
Screen Shot 2021-04-15 at 3.14.13 PM.png
  1. The running broker dynamically prints the sub/sub events from clients.
  2. The Android client log dumped from
adb logcat -v -t time | grep mqttkotlinsample
  1. A terminal client subscribing to updates from the test topic.
  2. A terminal client publishing message to the test topic.

Take also a look at the dynamic topics inside the broker from the Topic Inspector


Screen Shot 2021-04-15 at 3.19.51 PM.png

MQTT Topic Naming Scheme

As we know, topics are organized in a hierarchical way, to keep the hierarchical topic tree flexible, it is important to design the topic tree very carefully and leave room for future use cases.

  1. MQTT Topic and Payload Design Notes
  • Topic-hierarchy-centralized way
    Put as much information in the topic fields as possible
  • Payload-centralized way
    Put as much information in the payload as possible
    Some principles for topics naming scheme should follow
  • Human readability
  • Traffic minimization
  • Granular access control

Some Best Practice suggested by HiveMQ team:

  • Never use a leading forward slash.
  • Never use spaces in a topic.
  • Keep the topic short and concise.
  • Use only ASCII characters, avoid non-printable characters.

Last Will And Testament Message (LWT)

LWT is used to notify subscribers (through the broker) that the publisher has lost connectivity somehow, for example, suffered from an unexpected shutdown.

  1. Starts a publisher with configured LWT message
mosquitto_pub -d -r -q 2     \
--repeat 3 --repeat-delay 30 \
-h 192.168.7.123 -p 1883     \
-u test -P 123456            \
-t 'test/topic' -m "{\"greeting\": \"Last will and testament test\"}" \
--will-topic 'test/lwt' --will-qos 2 --will-payload "publisher has lost connectivity"

--repeat 3 --repeat-delay 30 sending repeatable messages to simulate the publisher is working
--will-topic, --will-qos, --will-payload sends LWT message to the specified topic.

  1. Starts a subscriber monitoring the LWT message on the will topic test/lwt
mosquitto_sub -d -k 10 -v \
-h localhost              \
-u test -P 123456         \
-t 'test/lwt'
  1. Kills the publisher and observes if the LWT message was received.
kill -9 `ps aux | grep mosquitto_pub | awk '{print $2}'` 

Observed LWT message

...
Client (null) sending PINGREQ
Client (null) received PINGRESP
Client (null) received PUBLISH (d0, q0, r0, m0, 'test/lwt', ... (31 bytes))
test/lwt publisher has lost connectivity
Client (null) sending PINGREQ
Client (null) received PINGRESP
...

QoS Management

  • QoS 0, At most once - the message is sent only once and the client and broker take no additional steps to acknowledge delivery (fire and forget).
  • QoS 1, the message is re-tried by the sender multiple times until acknowledgment is received (acknowledged delivery).
  • QoS 2, the sender and receiver engage in a two-level handshake to ensure only one copy of the message is received (assured delivery).

The two-level and four-step handshake flow ensure the exact delivery in the case of QoS 2.


Screen Shot 2021-04-16 at 2.30.22 PM.png

Note that the QoS value can be applied to either a sub or a pub request, however, the QoS level that’s eventually used to deliver the message from the broker to the subscriber is determined by the lower of the two, for example:


Screen Shot 2021-04-16 at 6.25.06 PM.png
  1. subscribe message with qos = x to topic.
  2. publish a message with qos = y to topic.
  3. deliver the message with qos = min(x, y).

Security Concerns

Authentication

The methodology and procedure to validate that users are who they claim to be before they are allowed to log into a secure system and eventually get access to the system resources. It corresponds to the AUTH flow in the MQTT context.

Authorization

The process of giving the user permission to access a specific resource or function in a secure system, which corresponds to the ACL (Access Control Lists) rules for topics in the MQTT context.

User name and password-based authentication

Certificates (TLS) based authentication

The goal here is to use a client certificate rather than plain username password text for authentication.

Prons:

  • Encrypt and transport credentials and MQTT payload in a secure way.
  • Generate and manage certificates on a per-client basis.

Cons:

MQTT TLS Security in Mosquitto

  1. Generate CA certificate (for self-signed certificates purpose)
openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt

Note: Just in case of “Error Loading extension section v3_ca” error on MacOS, append the following config to the end of OpenSSL config file.

# sudo vi /etc/ssl/openssl.cnf
[ v3_ca ]
basicConstraints = critical, CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer:always

To inspect details about the generated cert file

openssl x509 -inform pem -in ca.crt -text
  1. Prepare server certificate (to be signed by trusted CA), which is required by the MQTT broker running in TLS mode.
openssl req -out server.csr -key server.key -new

Create self-signed server certificate

openssl x509 -req                        \
-in server.csr                           \ 
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt                          \
-days 365

Note: The CN(Common Name, eg, fully qualified host name) field provided when filling up the certificate details is very important. So be careful to provide it.

Here is an example to explore the details in the generated server.crt using openssl command

Screen Shot 2021-04-20 at 12.22.53 PM.png

  1. Prepare client certificate (to be signed by trusted CA) which is required to connect to the MQTT broker.
openssl req -out client.csr -key client.key -new

Create a self-signed client certificate

openssl x509 -req                           \
-in client.csr                              \
-CA ca.crt -CAkey ca.key -CAcreateserial    \
-out client.crt                             \
-days 365
Screen Shot 2021-04-20 at 12.33.09 PM.png

As of self-signed certificates no longer working in Android Q, the self-signed *.pem certificate should be exported to *.p12 format so as to be installed on Android 10+ devices.

openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12
  1. Config server certificates
  • Move certs to config folder so that they can be accessed thru the shared folder between the broker (running in the docker) and the host machine.
➜ pwd && ll .
/Users/nling/cedalo_platform/mosquitto/config/cert
total 72
-rw-r--r--@ 1 nling  staff   1.4K Apr 20 00:19 ca.crt
-rw-r--r--@ 1 nling  staff   1.8K Apr 20 00:19 ca.key
-rw-r--r--@ 1 nling  staff    17B Apr 20 01:13 ca.srl
-rw-r--r--@ 1 nling  staff   1.1K Apr 20 01:11 client.crt
-rw-r--r--@ 1 nling  staff   1.0K Apr 20 01:11 client.csr
-rw-r--r--@ 1 nling  staff   1.6K Apr 20 01:10 client.key
-rw-r--r--@ 1 nling  staff   1.1K Apr 20 01:13 server.crt
-rw-r--r--@ 1 nling  staff   1.0K Apr 20 01:13 server.csr
-rw-r--r--@ 1 nling  staff   1.6K Apr 20 01:12 server.key
-rw-r--r--@ 1 nling  staff   2.4K Apr 20 15:20 server.p12
  • Modify Mostiqutto config to apply the server configs.
    (➜ cert vim ../mosquitto.conf)
listener 1883

#allow_anonymous true

persistence true
persistence_location /mosquitto/data/

plugin /usr/lib/mosquitto_dynamic_security.so
plugin_opt_config_file /mosquitto/data/dynamic-security.json

listener                  8883
cafile                    mosquitto/config/cert/ca.crt
certfile                  mosquitto/config/cert/server.crt
keyfile                   mosquitto/config/cert/server.key
tls_version               tlsv1.2
require_certificate       true
use_identity_as_username  true

log_type all

Note: It’s possible to listen on both 1883 and 8883 ports.

  • Export the 8883 port in the docker config so that the broker is accessed from outside the docker, failing to do so will lead to a “Connection Refused” error.
# ➜  cert vim ../../../docker-compose.yml
services:
  mosquitto:
    image: eclipse-mosquitto:2-openssl
    ports:
      - 1883:1883
      - 8883:8883
    networks:
      - cedalo-platform
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
  • Restart the broker to bring it up again, listening on 8883 port.
  1. Initiate a test subscribe request from the terminal, using the client certificate.
 mosquitto_sub -d -k 10 -v                          \
 -i DSN000001                                       \
 -h liot.com -p 8883                                \
 --cafile ca.crt --cert client.crt --key client.key \
 -t test/topic

Congrats if you got the output look like

Client DSN000001 sending CONNECT
Client DSN000001 received CONNACK (0)
Client DSN000001 sending SUBSCRIBE (Mid: 1, Topic: test/topic, QoS: 0, Options: 0x00)
Client DSN000001 received SUBACK
Subscribed (mid: 1): 128
Client DSN000001 sending DISCONNECT
All subscription requests were denied.

Outputs from the docker console

mosquitto_1          | 1618914506: New client connected from 172.20.0.1:63288 as DSN000001 (p2, c1, k10, u'client.liot.com').
mosquitto_1          | 1618914506: Client DSN000001 disconnected.

Note 1: As the hostname of the broker is configured to be liot.com in the CN field, it’s not reachable unless a new entry is configured in the hosts file.

# ➜  cert sudo vim /private/etc/hosts
...
192.168.7.123   liot.com  # change to the IP address of the host machine
...

Note 2: Pub/Sub requests from a certificate-authenticated client will be denied due to restrictions introduced in Dynamic Security, so a fake user that stands for clients of this kind should be created and assigned client role, so that they are able to pub and sub. Note that the user name is from the CN (Common Name) field defined in the client certificate.

Screen Shot 2021-04-21 at 11.40.28 AM.png

Comparison Between MQTT and TLS Based Traffics

MQTT by default transports payload in plain text mode, whereas MQTT over TLS will encrypt and decrypt the payload in a secure way.

  1. Publish test message thru MQTT
mosquitto_pub -r -d \
-u test -P 123456 \
-h 192.168.7.123 -p 1883 \
-t 'test/topic' -m "hello there"
Screen Shot 2021-04-21 at 6.20.57 PM.png
  1. Publish test message thru MQTT over TLS
mosquitto_pub -d \
--cafile ca.crt --cert client.crt --key client.key \
-h server.liot.com -p 8883 \
-t 'test/topic' -m "hello there, this is secure message"
Screen Shot 2021-04-21 at 6.21.29 PM.png

Here are some references about MQTT over TLS

Others

How to open multiple terminals in docker?

docker exec -it <container> bash
docker ps
docker exec 3479c5c781c7 cat /mosquitto/data/dynamic-security.json

References

  1. Steve Cope. Using The Mosquitto_pub and Mosquitto_sub MQTT Client Tools- Examples. 02/21/2021, 04/16/2021.
  2. Homieiot. An MQTT Convention for IoT/M2M.
  3. OwnTracks. Topics
  4. MQTT SmartHome. Smart home automation with MQTT as the central message bus.
  5. Steve Cope. MQTT Last Will and Testament Examples. 02/19/2021, 04/16/2021.
  6. Mqtt-smarthome.
  7. MQTT Wiki Page. 04/19/2021.
  8. OASIS. MQTT Version 5.0 Spec. 03/07/2019, 04/19/2021.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容