3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NGINXのStreamモジュールでMQTT <=> MQTTS変換をしてAWS IoT Coreへ接続する

Last updated at Posted at 2021-10-01

IoTでよく使われるプロトコル「MQTT」ですが、クラウド側のブローカー(MQTTサーバー)がMQTTS(MQTT+TLS)を要求するケースが多くなり、MCU側への実装負担が増えています。

このエントリーでは、Proxy上でNGINXを動かし、Streamモジュールを使用して MQTT <=> MQTTS 変換の方法を解説します。
MQTTSブローカーとして、今回はAWS IoT Coreをターゲットにします。

注意

MCUとProxy間のセキュリティは別途確保してください。
例としてはIPSecWireGuardといったVPNや、3G/LTE/5G等セルラー通信による通信路レイヤーによるセキュリティ確保の方法があります。

ESP32(M5Stack Basic)上でWireGuardを使用したMQTT実装サンプル も併せてご覧ください。

確認環境

  • Proxy
    • Raspberry Pi 4 model B
      • Raspberry Pi OS (Release date: May 7th 2021)
  • MCU
    • M5Stack Basic
      • ボード定義: ESP32(1.0.6)
      • ライブラリ: M5Stack(0.3.6)

Proxyの構成 / Raspberry

事前準備

AWS IoT Coreにてクライアント証明書を発行し、 **-certificate.pem.crt**-private.pem.key ファイルを入手してから、Proxyに何らかの方法で保存してください。
また、AWS IoT Core のエンドポイント情報を入手しておいてください。

ここでは

  • /home/pi/a/xxxxxxxxxx-certificate.pem.crt
  • /home/pi/a/xxxxxxxxxx-private.pem.key
  • foobar-ats.iot.region.amazonaws.com

とした前提で、以降の設定などを解説します。

NGINXのインストールから動作確認まで

shell
sudo apt update
sudo apt install -y nginx libnginx-mod-stream
/etc/nginx/modules-available/mqtt_proxy.conf
stream {
  upstream mqtt {
    server foobar-ats.iot.region.amazonaws.com:8883;
  }
  server {
    listen 1883;
    proxy_pass mqtt;
    proxy_ssl on;
    proxy_ssl_certificate /home/pi/a/xxxxxxxxxx-certificate.pem.crt;
    proxy_ssl_certificate_key /home/pi/a/xxxxxxxxxx-private.pem.key;
  }
}
shell
(cd /etc/nginx/modules-enabled/ && sudo ln -s ../modules-available/mqtt_proxy.conf ./55-mqtt_proxy.conf)
sudo systemctl reload nginx

最終的にReloadに成功していればOKです。また、netstatで1883でLISTENしていることも確認できます。

$ sudo journalctl -u nginx -n 2
-- Logs begin at Thu 2019-02-14 19:11:59 JST, end at Sat 2021-10-02 01:40:37 JST. --
Oct 02 01:39:33 rpi3 systemd[1]: Reloading A high performance web server and a reverse proxy server.
Oct 02 01:39:33 rpi3 systemd[1]: Reloaded A high performance web server and a reverse proxy server.
$ netstat -natu | grep 1883
tcp        0      0 0.0.0.0:1883            0.0.0.0:*               LISTEN

動作確認

mosquittoを使います。
あらかじめAWS IoT CoreのMQTTテストクライアントで # (=すべてのMQTTトピック) をサブスクライブしておくと確認しやすいです。

sudo apt install mosquitto
mosquitto_pub -d -h localhost -p 1883 -t a/a -m '{"foo": "bar"}'

これでAWS IoT Coreの a/a トピックに {"foo": "bar"} が着信していることが確認できるはずです。

ちょっと解説

AWS IoT CoreのMQTTSはX.509証明書の他にServer Name Indication(SNI)拡張を要求します。
NGINXのStreamモジュール(ngx_stream_proxy_module)は、その辺いい感じにやってくれるようです。
※ちゃんと調べてなくてゴメンナサイ

MCU / M5Stack Basic側

arduino-mqttでMQTT接続します。

m5stack_mqtt_with_proxy.ino
/*
   Copyright 2021 Kohei "Max" MATSUSHITA.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
# include <M5Stack.h>

# include <WiFi.h>
char WIFI_SSID[] = "{YOUR_WIFI_SSID}";
char WIFI_PASSWORD[] = "{YOUR_WIFI_PASS}";
WiFiClient net;

# include <PubSubClient.h>
PubSubClient mqtt = PubSubClient(net);

# define THING_NAME      "M5StackBasic0"
# define ENDPOINT        "proxy-server.local"
# define PORT             (1883)
# define PUBLISH_TOPIC   "demo0/via_proxy"

void connectToNetwork() {
    Serial.print("Connecting to Wi-Fi: ");
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED){
        delay(200);
        Serial.print(".");
    }
    Serial.println("done.");
}

void connectToAWSIoTCore() {
    Serial.print("Connecting to AWS IoT Core via Proxy: ");
    mqtt.setServer(ENDPOINT, PORT);
    while (!mqtt.connect(THING_NAME)) {
        Serial.print(".");
        delay(200);
    }
    Serial.println("Connected!");
}

void setup() {
    Serial.begin(115200);
    connectToNetwork();
    connectToAWSIoTCore();
}

void publishMessage() {
    char payload[512];
    sprintf(payload, "{\"time\": %lu}", millis());
    mqtt.publish(PUBLISH_TOPIC, payload);
}

# define LOOP_INTERVAL_MS (10000)
void loop() {
    Serial.println("loop");
    publishMessage();
    unsigned long next = millis();
    while (millis() < next + LOOP_INTERVAL_MS) {
        mqtt.loop();
    }
}

メモリーの使用量

M5Stackで直接MQTTS(MQTTL+TLS)を実装した場合と比較して、メモリーの使用量はフラッシュメモリで約163KBも節約できています。これは大きいかと思います。

フラッシュメモリ RAM
MQTTのみ 748,302 39,912
MQTT+TLS 911,762 40,456
差(節約量) -163,460 -544

※単位はbyte

参考コード) MQTT+TLS 版
m5stack_mqtts.ino
/*
   Copyright 2021 Kohei "Max" MATSUSHITA.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
# include <M5Stack.h>

# include <WiFi.h>
char WIFI_SSID[] = "{YOUR_WIFI_SSID}";
char WIFI_PASSWORD[] = "{YOUR_WIFI_PASS}";
# include <WiFiClientSecure.h>
WiFiClientSecure net = WiFiClientSecure();

# include <PubSubClient.h>
PubSubClient mqtt = PubSubClient(net);

# define THING_NAME      "M5StackBasic0"
# define ENDPOINT        "foobar-ats.iot.us-west-2.amazonaws.com"
# define PORT             (8883)
# define PUBLISH_TOPIC   "demo0/directly"

static const char root_ca[] = {\
"-----BEGIN CERTIFICATE-----\n\
...
-----END CERTIFICATE-----\n"};

static const char thing_certificate[] = {\
"-----BEGIN CERTIFICATE-----\n\
...
-----END CERTIFICATE-----\n"};

const char thing_private[] = {\
"-----BEGIN RSA PRIVATE KEY-----\n\
...
-----END RSA PRIVATE KEY-----\n"};

void connectToNetwork() {
    Serial.print("Connecting to Wi-Fi: ");
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(200);
        Serial.print(".");
    }
    Serial.println("done.");
}

void connectToAWSIoTCore() {
    Serial.print("Connecting to AWS IoT Core: ");

    // MQTTS with X.509 cert.
    net.setCACert(root_ca);
    net.setCertificate(thing_certificate);
    net.setPrivateKey(thing_private);

    mqtt.setServer(ENDPOINT, PORT);
    while (!mqtt.connect(THING_NAME)) {
        Serial.print(".");
        delay(200);
    }
    Serial.println("Connected!");
}

void setup() {
    Serial.begin(115200);
    connectToNetwork();
    connectToAWSIoTCore();
}

void publishMessage() {
    char payload[512];
    sprintf(payload, "{\"time\": %lu}", millis());
    mqtt.publish(PUBLISH_TOPIC, payload);
}

# define LOOP_INTERVAL_MS (10000)
void loop() {
    Serial.println("loop");
    publishMessage();
    unsigned long next = millis();
    while (millis() < next + LOOP_INTERVAL_MS) {
        mqtt.loop();
    }
}

終わりに ~ なぜこんなことをしているのか

私の所属しているソラコムでは、IoT向けの通信をはじめとしたIoT開発で使えるプラットフォームを提供しています。提供している通信の中にLTE(閉域もあり)WireGuardベースのVPN(SORACOM Arc)があり、通信路レベルでのセキュリティ確保が容易になるため、アプリケーションに集中できるようになるわけです。

というか、今回紹介した仕組みと同じもの(中身は違うけど)をSORACOM Beam というデータ転送サービスで提供してるので、構築や運用が面倒な方はSORACOM Beamもご検討ください!

参考資料

EoT

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?