标签 mqtt 下的文章

“”

mosquitto有同步和异步两种通讯方式。同步的方式是通信+等待的阻塞模式。
官方手册api地址:https://mosquitto.org/api/files/mosquitto-h.html#mosquitto_connect
有疑问的地方,可以直接看官方手册,比较好懂。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mosquitto.h"

#define HOST "localhost"
#define PORT  1883
#define KEEP_ALIVE 60
#define MSG_MAX_SIZE  512

// 定义运行标志决定是否需要结束
static int running = 1;

void my_connect_callback(struct mosquitto *mosq, void *obj, int rc)
{
        printf("Call the function: on_connect\n");

        if(rc){
                // 连接错误,退出程序
                printf("on_connect error!\n");
                exit(1);
        }else{
                // 订阅主题
                // 参数:句柄、id、订阅的主题、qos
                if(mosquitto_subscribe(mosq, NULL, "topic1", 2)){
                        printf("Set the topic error!\n");
                        exit(1);
                }
        }
}

void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc)
{
        printf("Call the function: my_disconnect_callback\n");
        running = 0;
}

void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos)
{
        printf("Call the function: on_subscribe\n");
}

void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg)
{
        printf("Call the function: on_message\n");
        printf("Recieve a message of %s : %s\n", (char *)msg->topic, (char *)msg->payload);

        if(0 == strcmp(msg->payload, "quit")){
                mosquitto_disconnect(mosq);
        }
}


int main()
{
        int ret;
        struct mosquitto *mosq;

        // 初始化mosquitto库
        ret = mosquitto_lib_init();
        if(ret){
                printf("Init lib error!\n");
                return -1;
        }

        // 创建一个订阅端实例
        // 参数:id(不需要则为NULL)、clean_start、用户数据
        mosq =  mosquitto_new("sub_test", true, NULL);
        if(mosq == NULL){
                printf("New sub_test error!\n");
                mosquitto_lib_cleanup();
                return -1;
        }

        // 设置回调函数
        // 参数:句柄、回调函数
        mosquitto_connect_callback_set(mosq, my_connect_callback);
        mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
        mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
        mosquitto_message_callback_set(mosq, my_message_callback);

        // 连接至服务器
        // 参数:句柄、ip(host)、端口、心跳
       ret = mosquitto_connect(mosq, HOST, PORT, KEEP_ALIVE);
        if(ret){
                printf("Connect server error!\n");
                mosquitto_destroy(mosq);
                mosquitto_lib_cleanup();
                return -1;
        }


         // 开始通信:循环执行、直到运行标志running被改变
        printf("Start!\n");
        while(running)
        {
                mosquitto_loop(mosq, -1, 1);
        }

        // 结束后的清理工作
        mosquitto_destroy(mosq);
        mosquitto_lib_cleanup();
        printf("End!\n");

        return 0;
}

使用mqtts的时候,连接提示错误:

mosquitto_sub -L mqtts://124.71.233.xx:8883/ -t "command///req/#" --cert cert.pem --key key.pem --cafile ../trusted-certs.pem -q 1

Unable to connect (A TLS error occurred.).

加上调试信息打印

mosquitto_sub -L mqtts://124.71.233.xx:8883/ -t "command///req/#" --cert cert.pem --key key.pem --cafile ../trusted-certs.pem -q 1 -d

Error: Unable to load client certificate "cert.pem".
OpenSSL Error[0]: error:140AB18E:SSL routines:SSL_CTX_use_certificate:ca md too weak
Unable to connect (A TLS error occurred.).

查看openssl软件版本
openssl
OpenSSL> version
OpenSSL 1.1.1f 31 Mar 2020
OpenSSL> quit

使用openssl s_client 测试

openssl s_client -connect 124.71.233.xx:8883 -cert cert.pem -key key.pem -CAfile ../trusted-certs.pem -showcerts

error setting certificate
140341384697152:error:140AB18E:SSL routines:SSL_CTX_use_certificate:ca md too weak:../ssl/ssl_rsa.c:310:
出现同样的错误。

查看证书信息

openssl x509 -text -in cert.pem

Signature Algorithm: sha1WithRSAEncryption

众所周知,sha1算法很弱且已过时。应该就是这个原因了。
解决办法:

openssl s_client -cipher @SECLEVEL=0:ALL -connect 124.71.233.xx:8883 -cert cert.pem -key key.pem -CAfile ../trusted-certs.pem -showcerts

要确认就是sha1的问题,只需要这样就可以

openssl s_client -cipher @SECLEVEL=0:SHA1 -connect 124.71.233.62:8883 -cert cert.pem -key key.pem -CAfile ../trusted-certs.pem -showcerts

mqtt在编译openssl的时候,使用DOPENSSL_TLS_SECURITY_LEVEL=0就可以了。

还有一个修改配置文件的方法,
修改/etc/ssl/openssl.cnf文件
在文件开头加上

openssl_conf = default_conf

在文件结尾加上

[ default_conf ]

ssl_conf = ssl_sect

[ssl_sect]

system_default = ssl_default_sect

[ssl_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT:@SECLEVEL=0

验证测试:

openssl s_client  -connect 124.71.233.62:8883 -cert cert.pem -key key.pem -CAfile ../trusted-certs.pem -showcerts

CONNECTED(00000003)
...
verify return:1
这个方法来源于:https://askubuntu.com/questions/1231799/certificate-error-after-upgrade-to-20-04
原文:

I found a solution, according to the accepted answer of this question:
Ubuntu 20.04 - how to set lower SSL security level?

In particular, the openSSL configuration file /etc/ssl/openssl.cnf
shall be modified in the following way.

At the beginning, add openssl_conf = default_conf

At the end, add

[ default_conf ]

ssl_conf = ssl_sect

[ssl_sect]

system_default = ssl_default_sect

[ssl_default_sect] MinProtocol = TLSv1.2 CipherString =
DEFAULT:@SECLEVEL=0 After this modification, the certificate is
recognized without security errors.

https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_security_level.html

WARNING at this time setting the security level higher than 1 for
general internet use is likely to cause considerable interoperability
issues and is not recommended. This is because the SHA1 algorithm is
very widely used in certificates and will be rejected at levels higher
than 1 because it only offers 80 bits of security.

The default security level can be configured when OpenSSL is compiled
by setting -DOPENSSL_TLS_SECURITY_LEVEL=level. If not set then 1 is
used.

https://github.com/drwetter/testssl.sh/issues/1433

OpenSSL 1.1.0 introduced the ability to specify a security level:
https://github.com/openssl/openssl/blob/master/doc/man3/SSL_CTX_set_security_level.pod.
By default the security level is set to 1 unless a compile-time option
is used to set the default a different value. The security level may
also be set by the command line, e.g.,

openssl s_client -cipher @SECLEVEL=0:ALL -connect 127.0.0.1:443 At the
moment, testssl.sh does not use the @SECLEVEL=n directive, but we may
want to look into using s_client_options() to add it in some
circumstances.

使用mosquitto订阅消息的时候,出现host name verification failed错误。

mosquitto_sub -L mqtts://124.71.233.xx:8883/ -t topic --cert cert.pem --key key.pem --cafile ../trusted-certs.pem -q 1 -d 

Client (null) sending CONNECT
Error: host name verification failed.
OpenSSL Error[0]: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
Error: A TLS error occurred.

使用openssl s_client验证tls本身没有问题,问题应该出在mqtt的处理上面。

查看mosquitto 的源码。
在mosquitto-2.0.11/lib/tls_mosq.c中,看到如下实现。

if(mosq->tls_insecure == false
#ifndef WITH_BROKER
                        && mosq->port != 0 /* no hostname checking for unix sockets */
#endif
)
...
return preverify_ok;
}

直接设置mosq->tls_insecure为true就不进行这个判断了。
修改后如下:

mosquitto_sub -L mqtts://124.71.233.xx:8883/ -t topic --cert cert.pem --key key.pem --cafile ../trusted-certs.pem -q 1 -d --insecure

MQTTv3.1.1与MQTTv5 的对比

MQTT v5 features and v 3.1.1 features comparison
在這個版本中整體使用方式,MQTT v5與v3.1.1之間功能上的的差別在於 QoS 1 以上不再重傳訊息、retained messages、persistent sessions不再支援了。

MQTT QoS v5 、v3.1.1 之間的定義是一樣的,但v5 的版本不在TCP 連線健康的情況下重傳訊息。 原先的v3.1.1 版本若在一段時間內沒收到 ack 將會在retry,這可能造成因為效能問題導致未回傳的裝置loading 更重。
retained messages: v5中Message Expiry Interval用來取代此功能,可以將其設定一個時間後併刪除。
persistent sessions: 在v3.1.1中若中途有producer將clean session設定為true時,之前所存的message將會被一起被刪除,v5 中Session Expiry Interval 進來取代此功能。
MQTT v5 User Properties

類似http header 的概念,可以在每一個訊息上加入一個property header ,consumer 端可依賴該欄位進行運用。Broker 根據consumer 所訂閱的設定進行訊息routing。

MQTT v5 Shared Subscriptions
在v5的版本中,原生支援load balance的功能,consumer 可在建立連線的時候設定Broker shared選項綁定多個consumer 成為一個群組。
Referenced from:https://ithelp.ithome.com.tw/articles/10257223

连接上mqtt服务器后,一段时间就出现下面提示:
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0
on_disconnect callback rc = 7
on_connect callback rc = 0

在网上看了一下,有说同一个clientid二个连接同时使用可以有导致这个问题,也有说调小keepalive就好了。

I guess i solve this question.

i set a small keep-alive number. And NOT happen until now.

将原来的keepalive从60调到30就好了。