Skip to content

Commit

Permalink
Add DTLS support to MQTT-SN client
Browse files Browse the repository at this point in the history
  • Loading branch information
embhorn committed Sep 1, 2023
1 parent 65a88f9 commit 4a5a446
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 86 deletions.
21 changes: 1 addition & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,26 +189,7 @@ This example enables the wolfMQTT client to connect to the IBM Watson Internet o
### MQTT-SN Example
The Sensor Network client implements the MQTT-SN protocol for low-bandwidth networks. There are several differences from MQTT, including the ability to use a two byte Topic ID instead the full topic during subscribe and publish. The SN client requires an MQTT-SN gateway. The gateway acts as an intermediary between the SN clients and the broker. This client was tested with the Eclipse Paho MQTT-SN Gateway, which connects by default to the public Eclipse broker, much like our wolfMQTT Client example. The address of the gateway must be configured as the host. The example is located in `/examples/sn-client/`.

A special feature of MQTT-SN is the ability to use QoS level -1 (negative one) to publish to a predefined topic without first connecting to the gateway. There is no feedback in the application if there was an error, so confirmation of the test would involve running the `sn-client` first and watching for the publish from the `sn-client_qos-1`. There is an example provided in `/examples/sn-client/sn-client_qos-1`. It requires some configuration changes of the gateway.

* Enable the the QoS-1 feature, predefined topics, and change the gateway name in `gateway.conf`:

```
QoS-1=YES
PredefinedTopic=YES
PredefinedTopicList=./predefinedTopic.conf
.
.
.
#GatewayName=PahoGateway-01
GatewayName=WolfGateway
```

* Comment out all entries and add a new topic in `predefinedTopic.conf`:

```
WolfGatewayQoS-1,wolfMQTT/example/testTopic, 1
```
More about MQTT-SN examples in [examples/sn-client/README.md](examples/sn-client/README.md)

### Multithread Example
This example exercises the multithreading capabilities of the client library. The client implements two tasks: one that publishes to the broker; and another that waits for messages from the broker. The publish thread is created `NUM_PUB_TASKS` times (10 by default) and sends unique messages to the broker. This feature is enabled using the `--enable-mt` configuration option. The example is located in `/examples/multithread/`.
Expand Down
1 change: 1 addition & 0 deletions examples/include.am
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,5 @@ EXTRA_DIST+= examples/mqttuart.c \
examples/aws/awsiot.vcxproj \
examples/wiot/wiot.vcxproj \
examples/sn-client/sn-client.vcxproj \
examples/sn-client/README.md \
examples/multithread/multithread.vcxproj
97 changes: 80 additions & 17 deletions examples/mqttexample.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,31 +198,26 @@ void mqtt_show_usage(MQTTCtx* mqttCtx)
PRINTF("-? Help, print this usage");
PRINTF("-h <host> Host to connect to, default: %s",
mqttCtx->host);
/* Remove TLS and SNI args for sn-client */
if(XSTRNCMP(mqttCtx->app_name, "sn-client", 10)){
#ifdef ENABLE_MQTT_TLS
PRINTF("-p <num> Port to connect on, default: Normal %d, TLS %d",
MQTT_DEFAULT_PORT, MQTT_SECURE_PORT);
PRINTF("-t Enable TLS");
PRINTF("-A <file> Load CA (validate peer)");
PRINTF("-K <key> Use private key (for TLS mutual auth)");
PRINTF("-c <cert> Use certificate (for TLS mutual auth)");
#ifdef HAVE_SNI
PRINTF("-S <str> Use Host Name Indication, blank defaults to host");
#endif
#ifdef HAVE_PQC
#ifdef HAVE_SNI
/* Remove SNI args for sn-client */
if(XSTRNCMP(mqttCtx->app_name, "sn-client", 10)){
PRINTF("-S <str> Use Host Name Indication, blank defaults to host");
}
#endif
#ifdef HAVE_PQC
PRINTF("-Q <str> Use Key Share with post-quantum algorithm");
#endif
#endif
#else
PRINTF("-p <num> Port to connect on, default: %d",
MQTT_DEFAULT_PORT);
#endif
}

else{
PRINTF("-p <num> Port to connect on, default: %d",
MQTT_DEFAULT_PORT);
}
PRINTF("-q <num> Qos Level 0-2, default: %d",
mqttCtx->qos);
PRINTF("-s Disable clean session connect flag");
Expand Down Expand Up @@ -401,10 +396,9 @@ int mqtt_parse_args(MQTTCtx* mqttCtx, int argc, char** argv)
mqtt_show_usage(mqttCtx);
return MY_EX_USAGE;
}
/* Remove TLS and SNI functionality for sn-client */

/* Remove SNI functionality for sn-client */
if(!XSTRNCMP(mqttCtx->app_name, "sn-client", 10)){
mqttCtx->use_tls = 0;
#ifdef HAVE_SNI
useSNI=0;
#endif
Expand Down Expand Up @@ -646,7 +640,8 @@ int mqtt_tls_cb(MqttClient* client)
return rc;
}
}
#elif defined(WOLFMQTT_ZEPHYR)
#else
/* Note: Zephyr example uses NO_FILESYSTEM */
#ifdef WOLFSSL_ENCRYPTED_KEYS
/* Setup password callback for pkcs8 key */
wolfSSL_CTX_set_default_passwd_cb(client->tls.ctx,
Expand Down Expand Up @@ -714,12 +709,80 @@ int mqtt_tls_cb(MqttClient* client)

return rc;
}

#ifdef WOLFMQTT_SN
int mqtt_dtls_cb(MqttClient* client) {
#ifdef WOLFSSL_DTLS
int rc = WOLFSSL_FAILURE;

client->tls.ctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method());
if (client->tls.ctx) {
wolfSSL_CTX_set_verify(client->tls.ctx, WOLFSSL_VERIFY_PEER,
mqtt_tls_verify_cb);

/* default to success */
rc = WOLFSSL_SUCCESS;

#if !defined(NO_CERT) && !defined(NO_FILESYSTEM)
if (mTlsCaFile) {
/* Load CA certificate file */
rc = wolfSSL_CTX_load_verify_locations(client->tls.ctx,
mTlsCaFile, NULL);
if (rc != WOLFSSL_SUCCESS) {
PRINTF("Error loading CA %s: %d (%s)", mTlsCaFile,
rc, wolfSSL_ERR_reason_error_string(rc));
return rc;
}
}
if (mTlsCertFile && mTlsKeyFile) {
/* Load If using a mutual authentication */
rc = wolfSSL_CTX_use_certificate_file(client->tls.ctx,
mTlsCertFile, WOLFSSL_FILETYPE_PEM);
if (rc != WOLFSSL_SUCCESS) {
PRINTF("Error loading certificate %s: %d (%s)", mTlsCertFile,
rc, wolfSSL_ERR_reason_error_string(rc));
return rc;
}

rc = wolfSSL_CTX_use_PrivateKey_file(client->tls.ctx,
mTlsKeyFile, WOLFSSL_FILETYPE_PEM);
if (rc != WOLFSSL_SUCCESS) {
PRINTF("Error loading key %s: %d (%s)", mTlsKeyFile,
rc, wolfSSL_ERR_reason_error_string(rc));
return rc;
}
}
#endif

client->tls.ssl = wolfSSL_new(client->tls.ctx);
if (client->tls.ssl == NULL) {
rc = WOLFSSL_FAILURE;
return rc;
}
}

PRINTF("MQTT DTLS Setup (%d)", rc);
#else /* WOLFSSL_DTLS */
(void)client;
int rc = 0;
PRINTF("MQTT DTLS Setup - Must enable DTLS in wolfSSL!");
#endif
return rc;
}
#endif /* WOLFMQTT_SN */
#else
int mqtt_tls_cb(MqttClient* client)
{
(void)client;
return 0;
}
#ifdef WOLFMQTT_SN
int mqtt_dtls_cb(MqttClient* client)
{
(void)client;
return 0;
}
#endif
#endif /* ENABLE_MQTT_TLS */

int mqtt_file_load(const char* filePath, byte** fileBuf, int *fileLen)
Expand Down
5 changes: 5 additions & 0 deletions examples/mqttexample.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ int mqtt_parse_args(MQTTCtx* mqttCtx, int argc, char** argv);
int err_sys(const char* msg);

int mqtt_tls_cb(MqttClient* client);

#ifdef WOLFMQTT_SN
int mqtt_dtls_cb(MqttClient* client);
#endif

word16 mqtt_get_packetid(void);

#ifdef WOLFMQTT_NONBLOCK
Expand Down
31 changes: 6 additions & 25 deletions examples/mqttnet.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,14 @@
#include <config.h>
#endif

#include "wolfmqtt/mqtt_client.h"
#include "examples/mqttnet.h"
#include "examples/mqttexample.h"
#include "examples/mqttport.h"

/* Local context for Net callbacks */
typedef enum {
SOCK_BEGIN = 0,
SOCK_CONN
} NB_Stat;

#if 0 /* TODO: add multicast support */
typedef struct MulticastCtx {

} MulticastCtx;
#endif


typedef struct _SocketContext {
SOCKET_T fd;
NB_Stat stat;
SOCK_ADDR_IN addr;
#ifdef MICROCHIP_MPLAB_HARMONY
word32 bytes;
#endif
#if defined(WOLFMQTT_MULTITHREAD) && defined(WOLFMQTT_ENABLE_STDIN_CAP)
/* "self pipe" -> signal wake sleep() */
SOCKET_T pfd[2];
#endif
MQTTCtx* mqttCtx;
} SocketContext;

/* Private functions */

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -578,7 +554,7 @@ static int SN_NetConnect(void *context, const char* host, word16 port,
struct addrinfo hints;
MQTTCtx* mqttCtx = sock->mqttCtx;

PRINTF("NetConnect: Host %s, Port %u, Timeout %d ms, Use TLS %d",
PRINTF("NetConnect: Host %s, Port %u, Timeout %d ms, Use DTLS %d",
host, port, timeout_ms, mqttCtx->use_tls);

/* Get address information for host and locate IPv4 */
Expand Down Expand Up @@ -830,6 +806,11 @@ static int NetRead_ex(void *context, byte* buf, int buf_len,
}
else {
bytes += rc; /* Data */
#ifdef ENABLE_MQTT_TLS
if (mqttCtx->client.flags & MQTT_CLIENT_FLAG_IS_TLS) {
break;
}
#endif
}
}

Expand Down
21 changes: 21 additions & 0 deletions examples/mqttnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@
#endif

#include "examples/mqttexample.h"
#include "examples/mqttport.h"

/* Local context for Net callbacks */
typedef enum {
SOCK_BEGIN = 0,
SOCK_CONN
} NB_Stat;

typedef struct _SocketContext {
SOCKET_T fd;
NB_Stat stat;
SOCK_ADDR_IN addr;
#ifdef MICROCHIP_MPLAB_HARMONY
word32 bytes;
#endif
#if defined(WOLFMQTT_MULTITHREAD) && defined(WOLFMQTT_ENABLE_STDIN_CAP)
/* "self pipe" -> signal wake sleep() */
SOCKET_T pfd[2];
#endif
MQTTCtx* mqttCtx;
} SocketContext;

/* Functions used to handle the MqttNet structure creation / destruction */
int MqttClientNet_Init(MqttNet* net, MQTTCtx* mqttCtx);
Expand Down
82 changes: 82 additions & 0 deletions examples/sn-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# MQTT-SN Example
The Sensor Network client implements the MQTT-SN protocol for low-bandwidth networks. There are several differences from MQTT, including the ability to use a two byte Topic ID instead the full topic during subscribe and publish. The SN client requires an MQTT-SN gateway. The gateway acts as an intermediary between the SN clients and the broker. This client was tested with the Eclipse Paho MQTT-SN Gateway, which connects by default to the public Eclipse broker, much like our wolfMQTT Client example. The address of the gateway must be configured as the host. The example is located in `/examples/sn-client/`.

To enable Sensor Network protocol support, configure wolfMQTT using:
`./configure --enable-sn`

## QoS-1
A special feature of MQTT-SN is the ability to use QoS level -1 (negative one) to publish to a predefined topic without first connecting to the gateway. There is no feedback in the application if there was an error, so confirmation of the test would involve running the `sn-client` first and watching for the publish from the `sn-client_qos-1`. There is an example provided in `/examples/sn-client/sn-client_qos-1`. It requires some configuration changes of the gateway.

* Enable the the QoS-1 feature, predefined topics, and change the gateway name in `gateway.conf`:

```
QoS-1=YES
PredefinedTopic=YES
PredefinedTopicList=./predefinedTopic.conf
.
.
.
#GatewayName=PahoGateway-01
GatewayName=WolfGateway
```

* Comment out all entries and add a new topic in `predefinedTopic.conf`:

```
WolfGatewayQoS-1,wolfMQTT/example/testTopic, 1
```

## MQTT-SN with DTLS
MQTT-SN can be secured using DTLS. This enables encryption of sensor data to the gateway. The Eclipse Paho MQTT-SN Gateway supports DTLS clients.

To build the Eclipse Paho MQTT-SN Gateway with DTLS:
`<gateway-folder>/MQTTSNGateway$ ./build.sh dtls`

To build wolfSSL with DTLS support:
`./configure --enable-dtls && make && sudo make install`

To run the wolfMQTT sn-client example with DTLS:
`./examples/sn-client/sn-client -t`

### Notes for Gateway configuration
* To use with local mosquitto broker, edit MQTTSNGateway/gateway.conf. Also set paths to DTLS cert / key.

```
-BrokerName=mqtt.eclipseprojects.io
+BrokerName=localhost
...
-DtlsCertsKey=/etc/ssl/certs/gateway.pem
-DtlsPrivKey=/etc/ssl/private/privkey.pem
+#DtlsCertsKey=/etc/ssl/certs/gateway.pem
+DtlsCertsKey=/<path_to_repo>/wolfssl/certs/server-cert.pem
+#DtlsPrivKey=/etc/ssl/private/privkey.pem
+DtlsPrivKey=/<path_to_repo>/wolfssl/certs/server-key.pem
```
* I had to fix a bug in the gateway (could be related to the openssl or compiler version):

```
diff --git a/MQTTSNGateway/src/linux/dtls/SensorNetwork.cpp b/MQTTSNGateway/src/linux/dtls/SensorNetwork.cpp
index 3f2dcf3..363d0ba 100644
--- a/MQTTSNGateway/src/linux/dtls/SensorNetwork.cpp
+++ b/MQTTSNGateway/src/linux/dtls/SensorNetwork.cpp
@@ -308,7 +308,7 @@ Connections::~Connections()
{
for (int i = 0; i < _numfds; i++)
{
- if (_ssls[i] > 0)
+ if (_ssls[i] > (SSL *)0)
{
SSL_shutdown(_ssls[i]);
SSL_free(_ssls[i]);
@@ -416,7 +416,7 @@ void Connections::close(int index)
}
}
- if (ssl > 0)
+ if (ssl > (SSL *)0)
{
_numfds--;
SSL_shutdown(ssl);
```
15 changes: 13 additions & 2 deletions examples/sn-client/sn-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,20 @@ int sn_test(MQTTCtx *mqttCtx)
goto exit;
}

/* Setup socket direct to gateway (UDP network, so no TLS) */
/* The client.ctx will be stored in the cert callback ctx during
MqttSocket_Connect for use by mqtt_tls_verify_cb */
mqttCtx->client.ctx = mqttCtx;

#if defined(ENABLE_MQTT_TLS) && defined(WOLFSSL_DTLS)
if (mqttCtx->use_tls) {
MqttSocket_SetDTLS(&mqttCtx->client, 1);
}
#endif

/* Setup socket direct to gateway */
rc = MqttClient_NetConnect(&mqttCtx->client, mqttCtx->host,
mqttCtx->port, DEFAULT_CON_TIMEOUT_MS, 0, NULL);
mqttCtx->port, DEFAULT_CON_TIMEOUT_MS,
mqttCtx->use_tls, NULL /*mqtt_dtls_cb*/);

PRINTF("MQTT-SN Socket Connect: %s (%d)",
MqttClient_ReturnCodeToString(rc), rc);
Expand Down
Loading

0 comments on commit 4a5a446

Please sign in to comment.