MQTT & Websocket
前一篇文章距今,2 個多月了,終於可以再新增一篇文章了XXD
首先來看看 ChatGPT 的回答,以讓筆者自己有所釋疑與認知,也算是此主題的開場白。
不過說真的,寫文章,真的可以交給 AI 了,通順流𣈱,切中命題且亳不離題,口條邏輯,推論演繹比普通人都還要好/至少比我好太多了XXD-> 請問 MQTT 可否說是 websocket 的 wrapper? 為何我看 mosquitto 套件包了 libwebsockets16? 二者有何關聯或從屬關係?
MQTT 和 WebSocket 是兩種不同的通訊協議,它們各自有不同的設計目的和特點。 MQTT(Message Queuing Telemetry Transport)是一種輕量級的訊息傳輸協議,專門設計用於在低頻寬、不穩定的網路環境中進行高效的通訊。 WebSocket是一種全雙工通訊協議,通常用於即時的、雙向的資料傳輸,適用於Web應用中的即時更新。在某些情況下,MQTT 可以使用 WebSocket 進行通訊。 這並不是說 MQTT 是 WebSocket 的 wrapper,而是指 MQTT 可以在 WebSocket 上運行,以便更好地適應一些網路環境和需求。
關於您提到的 mosquitto 套件包含 libwebsockets16,這可能是為了支援 MQTT over WebSocket。 libwebsockets 是一個用 C 編寫的通用的輕量級網路函式庫,它提供了對WebSocket等協定的支援。 在這種情況下,mosquitto 透過使用 libwebsockets 來實作 MQTT over WebSocket 功能,使得 MQTT 可以透過 WebSocket 進行通訊。
簡而言之,MQTT 和 WebSocket 是兩個獨立的協議,但可以結合使用,而 mosquitto 可能使用 libwebsockets 來實現 MQTT over WebSocket 的支援。
-> 若本地端要 mqtt_pub/sub (internet) 至雲端可走純 mqtt 協定或必得仰賴 websocket 之助?
您可以使用兩種方式將本地端的 MQTT 發布/訂閱連接到雲端:
- 純 MQTT 協定: 如果您的雲端支援 MQTT 協議,您可以直接使用純 MQTT 來進行通訊。 確保您的雲端MQTT伺服器的位址和連接埠可被本地端訪問,並使用對應的 MQTT 用戶端程式庫來進行發布(mqtt_pub)和訂閱(mqtt_sub)操作。
- MQTT over WebSocket: 如果您的雲端僅支援 WebSocket 或您希望透過 WebSocket 進行通信,您可以使用 MQTT over WebSocket。 在這種方式下,您的本地端可以使用支援 WebSocket 的 MQTT 用戶端程式庫,將 MQTT 通訊封裝在 WebSocket 連線上。 這樣可以更容易穿透一些網路限制,特別是在瀏覽器等環境中。
選擇哪種方式取決於您的特定需求以及雲端支援的協定。 如果雲端支援純 MQTT,您可以選擇使用該協定。 如果雲端要求或支援 WebSocket,您可以考慮使用 MQTT over WebSocket。
筆者經過幾天的爬文,
MQTT server/client 的安裝/使用算簡單,然而對於各種不同的應用的場合上,不單單只是發佈/訂閱之能做資訊的互通便已足夠,很重要地關於安全性與彈性,大數據的應付能力與資料轉化運算,雲端 broker 的架設與其更下一層的 services 等方面的課題,導致要充份運用 MQTT 來達所求, 乃至於商品化,還有一大段路得走。由網路上可搜出一大堆 MQTT 相關的主題便足以顯見其應用上之縱深。
可參考的資料網路上相當豐富,本篇筆者就不班門弄斧做 MQTT 的介紹使自形慚穢了XD,主要還是學習,熟悉此 MQTT & Websocket 兩大主題之筆記。
安裝
- 考量上,先說為何要將 mqtt & websocket 掛勾起來,
- 先說,websocket 其現存實作品很多種,由各種不同語言實現,例如 java 的 (in snap),javascript 的 (popular),laravel 的 (by php),還有官方 ppa (c inplemented) 的,及 ubuntu 內建的 (java),筆者主觀地以效率來看而偏好 ppa。也因此看回來 mqtt 之安裝便使用 ppa。
- websocket 之提出與制定是為彌補 http 之單方向發起傳輸及非長久連線之缺點而提出來的。而 mqtt 也是實現了雙向的通訊並簡化通訊邏輯流程且其同樣地為了彌補 http 的一些缺憾與更適用於 IoT & Cloud 所以實作品之功能上便被製作得相當細膩(從 mosquitto.conf 便可窺知)並具有 http 所沒有的一些優點,所以相當受歡迎與廣泛運用。
- websocket 與 mqtt 可說是對等的目的性,但有其不同的適用場合,已如前述。該如何妥善地配置 mqtt 真需有多方面與再三的考量與取決,亦已如前述。例如,該如何規劃與制定 topics 的多樹結構,10 個 devices,10k 個 devices,100 種不同 products,光此點就開始燒腦了。。。
sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt-get update
sudo apt install mosquitto mosquitto-clients
sudo 7z x /usr/share/doc/mosquitto/examples/mosquitto.conf.gz -o/etc/mosquitto/conf.d/
mosquitto server 將以 service 的形式 active 常駐。
其中將 conf example file 解開放至 ./conf.d/ 底下,我們再由該檔中自訂設定/因所有設定都在該檔中有附說明;其他說明,使用 man 5 mosquitto.conf。
安裝完成自動啟用後,server 預設就已使用/佔用 port 1883。所以記得打開防火牆的該埠若欲可外部連線;以後啟用 MQTTS 及 Websockets 也記得開埠。
mosquitto_pub 指令有點長,
初步在測試會常做手動輸入,可在 $HOME 下建立 .bash_aliases,加入以下這幾行:
alias mqttp='mosquitto_pub -d'
alias mqtts='mosquitto_sub -d'
alias mqttpw='mosquitto_passwd'
source .bash_aliases 以啟用。
########## 啟用 MQTTS。使用 Letsencrypt Certbot;其可使我們的 server 具公共可信任的地位。做法有點迂迴,不做過多說明,其用法可參考 "安裝 nginx" 一文。此例是使用 host "kenwoo.ddns.net:8883"
1. https://github.com/eclipse/mosquitto/blob/master/misc/letsencrypt/mosquitto-copy.sh
1a. 將其下載放至 /etc/mosquitto/certs/ 底下,成 cert-hook.sh.ex
1b. sudo cp cert-hook.sh.ex /etc/letsencrypt/renewal-hooks/deploy/cert-hook.sh
1c. sudo chmod ug+x /etc/letsencrypt/renewal-hooks/deploy/cert-hook.sh
1d. sudo vim /etc/letsencrypt/renewal-hooks/deploy/cert-hook.sh
請仔細檢查其內容是否適配自己的狀況。原則上,修改一行,MY_DOMAIN=example.com 成 kenwoo.ddns.net
1e. 不過,若忽略細節就會遭遇到不必要的試誤,詳情請 man 5 mosquitto.conf 中與 tls/ssl 有關的說明,關鍵字“capath directory path”。簡單講,需再修改此兩份檔案的生成名稱。因此乾脆就用底下這段直接去取代,請先複製貼上記事本上,看看沒有問題就直接取代。
# ===================>>>
# Set the directory that the certificates will be copied to.
CERTIFICATE_DIR=/etc/mosquitto/certs
for D in ${RENEWED_DOMAINS}; do
if [ "${D}" = "${MY_DOMAIN}" ]; then
# Copy new certificate to Mosquitto directory
# the CA file is here; notice about it might change and download nothing.
# https://letsencrypt.org/certs/letsencryptauthorityx3.pem.txt
wget https://letsencrypt.org/certs/letsencryptauthorityx3.pem.txt -O ${CERTIFICATE_DIR}/server_ca.pem
cp ${RENEWED_LINEAGE}/fullchain.pem ${CERTIFICATE_DIR}/server_cert.pem
cp ${RENEWED_LINEAGE}/privkey.pem ${CERTIFICATE_DIR}/server_key.pem
# Set ownership to Mosquitto
chown mosquitto: ${CERTIFICATE_DIR}/server_ca.pem ${CERTIFICATE_DIR}/server_cert.pem ${CERTIFICATE_DIR}/server_key.pem
# Ensure permissions are restrictive
chmod 0600 ${CERTIFICATE_DIR}/server_ca.pem ${CERTIFICATE_DIR}/server_cert.pem ${CERTIFICATE_DIR}/server_key.pem
# rehash
openssl rehash "${CERTIFICATE_DIR}/"
# Tell Mosquitto to reload certificates and configuration
pkill -HUP -x mosquitto
fi
done
# note the openssl rehash would warn, could ignore it.
# <<<===================
2. 修改 ./conf.d/mosquitto.conf,加入以下幾行。 *** 特別注意,此組態檔結尾可能需再加一行換行以結束。
# 啟用一般的對外通訊
listener 1883 0.0.0.0
protocol mqtt websockets
allow_anonymous true
# 啟用信任加密之對外通訊
listener 8883 0.0.0.0
protocol mqtt
cafile /etc/mosquitto/certs/server_ca.pem
certfile /etc/mosquitto/certs/server_cert.pem
keyfile /etc/mosquitto/certs/server_key.pem
#tls_version tlsv1.3
require_certificate true
use_identity_as_username true
allow_anonymous true
# 啟用 websocket 信任加密之對外通訊
listener 8884 0.0.0.0
protocol websockets
cafile /etc/mosquitto/certs/server_ca.pem
certfile /etc/mosquitto/certs/server_cert.pem
keyfile /etc/mosquitto/certs/server_key.pem
#tls_version tlsv1.3
require_certificate true
use_identity_as_username true
socket_domain ipv4
allow_anonymous true
# 註:若欲啟用對外通訊且允許 anonymous,則 allow_anonymous 之屬性將會生效,其預設為 false;故此時須顯式將其打開。即,allow_anonymous true。
3. sudo certbot certonly --standalone --preferred-challenges dns --force-renewal -d kenwoo.ddns.net
此適用於還未持有或已經持有 kenwoo.ddns.net 的憑證。注意到,使用了 dns 是因 certbot 不會有 side-effect(若用 http 則可能去修改了其它檔案)且此埠通常就已是開放的。
4. 檢查 /etc/mosquitto/certs/* 相關檔案是否生成或被更新。
5. 在 /etc/mosquitto/certs/ 目錄下下達 sudo openssl rehash。
6. 檢查 log,是否沒有 warning/error,及 sudo systemctl status mosquitto.service 是否成功啟用。
7. ***** clients 端還須簽名認證,後述 *****
##########
暫時開放測試:
# no tls/ssl, port 1883.
Subscriber:
mqtts -t hi/mqtt -h kenwoo.ddns.net -p 1883
Publisher:
mqttp -t hi/mqtt -h waterfalls.ddns.net -p 1883 -m 'test'
# tls/ssl, port 8883.
Subscriber:
mqtts -t hi/mqtt -h kenwoo.ddns.net -p 8883 --cert /some_entity/client_cert.pem --key /some_entity/client_key.pem --cafile /some_entity/client_ca.pem
Publisher:
mqttp -t hi/mqtt -h kenwoo.ddns.net -p 8883 -m 'test' --cert /some_entity/client_cert.pem --key /some_entity/client_key.pem --cafile /some_entity/client_ca.pem
MQTT TLS/SSL 信任加密通訊
- 前面使用了 Letsencrypt Certbot 以取得信任的憑證給我們伺服器(MQTTS broker)使用,信任的基礎建立在伺服器的 QDN 作為憑證的 Common Name,並 challenge 成功後(即,該 public sub/domain name 以及伺服器本身與其服務皆確實是我們所擁有)便取得核發的憑證(包含了 certificate chain, certificate, private key, public key),故,由 the Trusted Certificate Authority 所認可核發的簽名過後的憑證便稱為可信任的。相對地而言,我們也可以自己發憑證,自己簽名産生可運用的憑證,再用以對 clients 簽名與核發,則此做法皆與前述無異,均可應用於加密認證通訊,但不同的是後者就不是被公共信任的。事實上在 MQTT 的範𤴆下,有沒有公開被信任似乎不重要因為它侷限在有限特定的群體例如市區公車管控系統/憑證由中控核發。相對地例如網站,便是 global/world-wide 的,任何人都可放心訪問只要其具有信任的憑證以作用在彼此的認證加密通訊上。那麼,MQTT 之所以還是想用上 trusted CA 簽證,考量是 websockets 服務對象之一是網站網頁,於此便搭上關係了。
- 至此,筆者取得信任憑證後,就要再學習該如何簽名憑證給 clients 了,參考資料如下(撈出這麼多是因為又卡關了XD):
- clients/broker 出現錯誤,
- OpenSSL Error[0]: error:0A000086:SSL routines::certificate verify failed
- OpenSSL Error[0]: error:0A000418:SSL routines::tlsv1 alert unknown ca
- OpenSSL Error[0]: error:0A0000C7:SSL routines::peer did not return a certificate
- TLS/SSL
- https://www.hivemq.com/blog/
- Why security is paramount for IoT applications
- SSL and SSL Certificates Explained For Beginners
- Introduction to MQTT Security Mechanisms
- Mosquitto MQTT Broker SSL Configuration Using Own Certificates
- Using A Lets Encrypt Certificate on Mosquitto
- Creating and Using Client Certificates with MQTT and Mosquitto
- Easy-RSA
- How to Configure MQTT TLS and Certificate-based Authorization for Mosquitto MQTT Broker
- MQTT over SSL
- X.509 Certificate Based Authentication
- Linux Mosquitto MQTT Clients 與 公用Broker 驗証
- MQTT one-way SSL authentication
- mosquitto 开启 TLS 问题总结
- Mosquitto服务器的搭建以及SSL/TLS安全通信配置
- Others
- HiveMQ Public Broker MQTT Dashboard
- Generate a TLS client certificate for test.mosquitto.org
- Certificate Chain Composer
- MQTT Clean Sessions and QOS Examples
- Mosquitto_CA_and_Certs
卡關分析
- Letsencrypt certbot challenge success 後産生 4 支檔案,cert.pem/fullchain.pem/chain.pem/privkey.pem。哪支用作什麼目的並不是很清楚。查前面 script file 所用到的對應,及 nginx 被追加的 config,都是以 fullchain.pem 做為 certfile。換言之,cert.pem 便應當是 CA file 了吧。然而,事實不然,查看 4 支檔案內容,其實 3 支全都是一回事:
- fullchain 顧名思義是 fully-chained 的憑證,也是為何拿它做為 cert file,是最完整的。
- chain 則是中介憑證,其目的在前面連結教學介紹有提及。
- cert 則是伺服器的根憑證,即 cert+chain==fullchain。而 public key 便是嵌在此 cert.pem。比對此 3 支檔案的內容關係,雖然伺服器根憑證置於檔案的最開頭,但它卻是屬於整個憑證鏈的未端憑證,但也是伺服器的根憑證。
- 當然,這些憑證都是被 signed 過的;被具信任屬性 signed 過的,便亦具信任屬性(但若後者再自行發行憑證呢,是否也將具信任屬性?)。
- 因此,並沒有 CA file,而是必須去官網下載:
https://letsencrypt.org/zh-tw/certificates/
Letsencrypt rootCA - 其它重要的參考:
- Where can I download the trusted root CA certificates for Let’s Encrypt
- Can I create client certificates for a received LetsEncrypt certificate?
- OpenSSL Certificate Authority
- 至此,就跨關成功了。不過,mosquitto 的 log or debug 訊息並未顯示跑了 tls/ssl processing,實讓我好懷疑。。。
- 至此就不再深究,但可用 openssl 來間接看看,下,
openssl s_client -connect kenwoo.ddns.net:8883
看來似乎是沒有問題了。。。 - 總結,
- 1. 前面的 script file 也就再對應地修改妥了,當然,config file 也要對應地修改。我們另需要一支 ca file 自官網下載。
- 2. 以上的努力,可能要全推翻掉了。。。。。。請見上面藍色的說明;危言聳聽,也沒那麼糟,
- 3. 當拿被公開信任的伺服器憑證 X 去簽發新憑證 Y,Y 並非因此也被公開信任的,因為當初 CA(特指 Letsencrypt)簽發 X,是會將 X 加入公共信任列表內,這便是 X/Y 差異。當然,我們的目的並不是要讓子憑證也被公開信任,既然如此,也毌需糾結在 certbot 的憑證;或許公開信任憑證與 websocket 真有相依需求,但伺服器本身亦可不藉由公開信任憑證而自發憑證,以供 MQTT 等使用。
- 4. 故,我們就是另外再 gen 新憑證與簽發以為 MQTT 之用。前面的公開憑證獨立一個供將來(若 websockets 等)使用。
- 5. 照理講,任何第三方的 mqtt client 應是可以存取此公共可信任的 mqtt broker server,因為 clients 有既存的公共憑證存放於路徑 /etc/ssl/certs/,所以 mosquitto_ pub/sub 只要加引數 –capath /etc/ssl/certs/ (note the – -ca) 便能使用 broker。但筆者測試結果得到,
client, OpenSSL Error[0]: error:0A000086:SSL routines::certificate verify failed
server, OpenSSL Error[0]: error:0A000418:SSL routines::tlsv1 alert unknown ca
故此仍須釐清;會不會是當初 challenge 時使用引數 dns 所致。 - 6. Letsencrypt 憑證時效應是 3 個月,會再 renew;所相依者便須留意將來失效問題。同樣地自簽發也有時效問題須留意。
- 7. 最後注意幾點,
a. 當使用 tls/ssl 之 mosquitto_ sub/pub 時要指定 certficate files,當注意檔案權限問題;不然因 mosquitto 之除錯資訊太少了/令眾人垢病的地方;因之會除錯除得半死。
b. 前面提到成功通訊,但怎麼也看不到 ssl/tls 相關的提示訊息又怎知是走 ssl/tls 而非一般通道。因筆者是使用 waterfalls v.s. kenwoo 兩個可信任憑證互通的,即,並未對兩造憑證做任何加工,就能在埠 8883 上成功建立通訊,這也意謂著,其他持可信任憑證的例如其他網站,皆可使用此 tls/ssl broker。
c. 承上,換言之,使用可信任憑證,是好處也是壞處,其毌需額外的認證或簽名便可與同樣持有可信任憑證者互通,當然,這不也就是弱點了嗎。
Server/Client 自簽證
- 載至目前為止,筆者嘗試的結果仍是失敗的,也不能一直卡在這,等成功再來更新。
- 因官方所提供的 sop 會有需要輸入 password,筆者偏好不需輸入的;至於差異或每條指令的意涵目前也是一知半解。。。
# 憑證的持有者主要是以 'common name'('CN') 欄位做為識別。今設定 server 使用 'example_corp',client 使用 'device.example_corp'。
1. Client 新增一對 key pair,並産生了一支憑證求簽(CSR),其包含了 public key。而另一支 client_key.pem 就是 private key。因此若要統一僅使用一對 key-pair,則 (client_key.pem, client.csr) 就需妥善保存以供未來新的需求。
openssl req -new -newkey rsa:2048 -nodes -keyout client_key.pem -out client.csr -subj "/CN=device.example_corp"
2. Server 産生伺服器根憑證及 private key;憑證有時效為 365 天。
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout server_key.pem -out server_ca.pem -subj '/CN=example_corp'
3. Server 對 Client 的憑證求簽作簽發。輸出 client_cert.pem,即是 Server 發給 Client 的 server 自簽名憑證。
openssl x509 -req -CA server_ca.pem -CAkey server_key.pem -CAcreateserial -in client.csr -out client_cert.pem
4. Server 將 server_ca.pem & client_cert.pem 提供回給 Client。
5. 最後,所對應者,
Server:
cafile -> server_ca.pem
cert -> server_cert.pem
key -> server_key.pem
Client:
cafile -> server_ca.pem
cert -> client_cert.pem
key -> client_key.pem
ESP8266 AsyncMqttClient_Generic
- 接下來就是 ESP8266 登場了,使用它來做 publish/subscribe,
- 筆者之後會開始 focus 在 ESP32-C3 上了。而當前為求快速驗證,當然就直接拿 ESP8266 來用,因此很快就套用好了:
- 使用 mqtt client lib,AsyncMQTT_Generic
https://github.com/khoih-prog/AsyncMQTT_Generic
是 async,當然就是好東西,當前也是僅拿來做測並瞭解實現 esp8266 上的 mqtt 的難度。之後,哪一份 lib 是最 stable/bug-free/powerful 的可再來評估。 - 此函式庫相依於,ESPAsyncTCP
https://github.com/me-no-dev/ESPAsyncTCP - 以上二份函式庫可從 arduino 之函式庫管理員內下載。
- 並且,拿筆者的 esp8266 codebase 來套用,其可參考筆者之前文章 “The Most Practical Codebase for ESP8266”
- 所完成範例如下,
- broker server:broker.emqx.io:1883,當然也可以用 kenwoo.ddns.net:1883
- code 須加入 ssid/password
- topic 1:”async-mqtt/ESP8266_Pub”
上電後 30 秒後,才會啟動 mqtt client,之後,會立即 publish 三次,訊息分別為 test1, test2, test3 - 並且此時,esp8266 亦處於此 topic 1 的 subscriber,故亦可另開視窗來做發佈測試。
- 接著,20 秒後,開始 publish 另一個 topic 2:”async-mqtt/ESP8266_tic”,其於每介於 10~30 秒之隨機間,發送一次 NTP date/time。
- 至於 TLS/SSL 用法,可參考此連結,
https://github.com/khoih-prog/AsyncMQTT_Generic/blob/main/examples/ESP32/FullyFeaturedSSL_ESP32/FullyFeaturedSSL_ESP32.ino
Websocket
Websocket,目的就是對 http protocol 再加點工/包裝,使不僅相容於 http,也新增實現 http 的持續連線及毌需 clients 作 request 而即可更新已在 clients 端顯示的網頁;前者需在 transport layer/TCP() 上實現 websocket 的功能需求,而後者(實時更新)還需仰賴前端因應 websocket 規格以提供相關配套函式(或許已既有),例如 javascript。底下連結,提供相當完整而全面的一整套實作。待筆者真正要用上時再來咀嚼吸收。
至於 MQTT 與 websocket 的關聯,便是在 mosquitto 平台上,透過使用者在網頁上對資料的更新/against internal websocket objects,進而不僅該頁面會實時更新關聯到的用戶端外,也 mqtt_pub 至其他 mqtt subcribers,反向地,mqtt publishers 之發佈,使得 broker server 上之 websocket objects 更新所對應的網頁。
因此使得 MQTT 資料傳輸輕易地在網頁上顯示/存取/更新。
ESP8266 WebSocket Server:控制輸出
- 參考資料
- libwebsockets lightweight C networking library
- The Mosquitto MQTT broker gets Websockets support
- Resources for Developers by Developers
- The WebSocket API (WebSockets)
- How to Use MQTT on ESP8266 Module: Detailed Guide
20240414 更新
- 在本篇前面提及,且使用到的 kenwoo.ddns.net 的 SSL CA 憑證將在兩天後到期且筆者無能為力。
- 因為之前使用 certbot – -preferred-challenges dns 是成功取得憑證的。三個月後,也就是現在,該 plugin 似乎失效;故 renew 失敗且尋無方法;
- 其次,若轉而使用 http challenges 可能會造成困擾,原因是細數下,筆者的這顆伺服器上,一個 nginx 服務,已經啟用三個不同網域,例如假設 ken.com,woo.com,以及 ddns.net,又 ddns.net 又有(至少) waterfalls.ddns.net 以及 kenwoo.ddns.net 兩個子網域全都在這台主機上,全關聯到 nginx 的服務,且至少當前全取得了 CA 所簽發的 SSL 憑證。簡單講,當前怕牽一髮而動全身。故,
- kenwoo.ddns.net CA certed 將會放任讓它過期。MQTT::kenwoo.ddns.net::8883 將失效。不過 1883 仍開放給公眾測試使用。(這邊這麼明顯提出來,是給有緣人便利有個測試的點,切勿宣傳,若過載了,掛掉了筆者很可能也沒空可以挽回,請大俠們高抬貴手愛惜這裏,感謝)
- 目前這陣子本人工作上較忙,本網站短期(半年內)內應不會再做更新。