MQTT & Websocket

No Comments

前一篇文章距今,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 發布/訂閱連接到雲端:

  1. 純 MQTT 協定: 如果您的雲端支援 MQTT 協議,您可以直接使用純 MQTT 來進行通訊。 確保您的雲端MQTT伺服器的位址和連接埠可被本地端訪問,並使用對應的 MQTT 用戶端程式庫來進行發布(mqtt_pub)和訂閱(mqtt_sub)操作。
  2. 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 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:控制輸出

Categories: 未分類

PHP Code Snippets Powered By : XYZScripts.com