美文网首页路由联盟
5-Openwrt MQTT client使用

5-Openwrt MQTT client使用

作者: Creator_Ly | 来源:发表于2020-07-07 13:45 被阅读0次

    mosquitto已经集成了命令行mosquitto_sub和mosquitto_pub,这个一般就是调试的时候使用,后面还是要使用mosquitto提供的库函数实现C语言代码层的客户端。

    1. 添加client

    在mosquitto里面有个client目录,里面就是使用libmosquitto实现的客户端程序,封装成mosquitto_sub和mosquitto_pub命令行。

    所以新建一个跟client同一级,自己的client,添加对应的文件

    tree myclient/
    myclient/
    ├── main.c
    ├── Makefile
    └── myclient.h
    
    0 directories, 3 files
    

    Makefile的内容

    include ../config.mk
    
    .PHONY: all install uninstall reallyclean clean
    
    all : myclient
    
    myclient : main.o
        ${CROSS_COMPILE}${CC} $^ -o $@ ${CLIENT_LDFLAGS} -lpthread
    
    main.o : main.c ../lib/libmosquitto.so.${SOVERSION}
        ${CROSS_COMPILE}${CC} -c $< -o $@ ${CLIENT_CFLAGS}
    
    ../lib/libmosquitto.so.${SOVERSION} :
        $(MAKE) -C ../lib
    
    ../lib/libmosquitto.a :
        $(MAKE) -C ../lib libmosquitto.a
    
    install : all
        $(INSTALL) -d "${DESTDIR}$(prefix)/bin"
        $(INSTALL) ${STRIP_OPTS} myclient "${DESTDIR}${prefix}/bin/myclient"
    
    uninstall :
        -rm -f "${DESTDIR}${prefix}/bin/myclient"
    
    reallyclean : clean
    
    clean : 
        -rm -f *.o myclient
    

    main.c的内容

    #include <stdio.h>
    #include <signal.h>
    #include <pthread.h>
    #include <mosquitto.h>
    #include "myclient.h"
    
    int debugLevel = MSG_INFO;
    
    int main (int argc, char **argv)
    {
        dbg_printf(MSG_DEBUG, "start...\r\n");
        return 0;
    }
    

    myclient.h的内容

    #ifndef MYCLIENT_H
    #define MYCLIENT_H
    
    #include <assert.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <syslog.h>
    
    extern int debugLevel;
    
    enum{
        MSG_ERROR,
        MSG_WARNING,
        MSG_INFO,
        MSG_DEBUG,
        MSG_MSGDUMP,
        MSG_EXCESSIVE,
    };
    
    #define dbg_printf(level, ...)                      \
    do                                                  \
    {                                                   \
        if (debugLevel >= level)                         \
        {                                               \
            syslog(LOG_WARNING, __VA_ARGS__);           \
        }                                               \
    }                                                   \
    while (0)
    //printf(__VA_ARGS__);
    
    #endif
    

    外层的mosquitto/src/Makefile里面添加myclient文件的编译

    DIRS=lib client src myclient
    

    编译测试一切正常,接下去添加mqtt的内容

    2. 添加mqtt订阅test1,发布test2

    mqtt client里面最主要的就是几个回调函数,先调用lib_init,正常后,就这只各个callback,然后在callback里面做逻辑。

    int main (int argc, char **argv)
    {
        int rc;
    
        mosquitto_lib_init();  //初始化库
    
        mosq = mosquitto_new("client_name", CLEAN_SESSION, NULL);  //创建一个客户端实例
        if(!mosq){
            mosquitto_lib_cleanup();
            return 1;
        }
        mosquitto_log_callback_set(mosq, myclient_log_callback);  //打印与broker的交互log
        mosquitto_connect_callback_set(mosq, myclient_connect_callback);  //连接成功的回调函数
        mosquitto_disconnect_callback_set(mosq, myclient_disconnect_callback);  //离线的回调函数
        mosquitto_message_callback_set(mosq, myclient_message_callback);  //收到消息的回调函数
        mosquitto_subscribe_callback_set(mosq, myclient_subscribe_callback);  //订阅成功的回调函数
        mosquitto_reconnect_delay_set(mosq, RECONNECT_DELAY, RECONNECT_DELAY_MAX, RECONNECT_EXPONENTIAL_BACKOFF);  //设置离线后重连时间
    
        dbg_printf(MSG_INFO, "start connect\n");
        while(mosquitto_connect(mosq, BROKER_NAME, BROKER_PORT, BROKER_TIMEOUT) != MOSQ_ERR_SUCCESS) {
            dbg_printf(MSG_ERROR, "AC Client connect faild\r\n");
            sleep(5);
        }  //开始连接
    
        rc = mosquitto_loop_forever(mosq, -1, 1);  //循环,事件触发到各自的回调函数里面
        
    out:
        dbg_printf(MSG_ERROR, "loop error %d\r\n", rc);
    
        mosquitto_destroy(mosq);
        mosquitto_lib_cleanup();
    
        mosq = NULL;
        
        if(rc){
            fprintf(stderr, "Subscribe Error: %s\n", mosquitto_strerror(rc));
        }
    
        return 0;
    }
    

    各回调函数的内容

    static void publish_test2_msg(void)
    {
        int mid = 0;
        char payload[32] = "\"test2\":\"1111\"";
    
        mosquitto_publish(mosq, &mid, TOPIC_TEST2, strlen(payload), payload, TOPIC_QOS, false);
    
    }
    
    static void myclient_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message)
    {
        if (message->payloadlen) {
    
            dbg_printf(MSG_DEBUG, "收到主题:%s\r\n", message->topic);
            dbg_printf(MSG_DEBUG, "收到消息:%s, 消息长度:%d\r\n", message->payload, message->payloadlen);
        }
        
        return;
    }
    
    static void myclient_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos)
    {
        dbg_printf(MSG_DEBUG, "Subscribed (mid: %d): %d\r\n", mid, granted_qos[0]);
    }
    
    static void myclient_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str)
    {
        dbg_printf(MSG_DEBUG, "%s\r\n", str);
    }
    
    static void myclient_connect_callback(struct mosquitto *mosq, void *obj, int result)
    {
        if(!result) {
            dbg_printf(MSG_INFO, "connect ok\n");
            mosquitto_subscribe(mosq, NULL, TOPIC_TEST1, TOPIC_QOS);
            publish_test2_msg();
        }
    }
    
    static void myclient_disconnect_callback(struct mosquitto *mosq, void *obj, int result)
    {
        dbg_printf(MSG_WARNING, "connect lost\r\n");
    }
    

    逻辑应该也比较直观,当connect成功后,在回调函数里面订阅test1主题的内容,然后发布test2主题的内容。

    收到内容就在myclient_message_callback回调里面打印处理。

    3. 账号密码登录

    正常情况我们都会让客户端的连接做一些账号密码的设置,避免别人攻击。

    3.1 默认方式添加账号密码

    将allow_anonymous改成不允许匿名登陆,并制定pwfile。

    vim /etc/mosquittoConf/mosquitto.conf

    allow_anonymous false
    password_file /etc/mosquittoConf/pwfile
    

    在ubuntu上面使用mosquitto_passwd生成密码

    mosquitto_passwd -c /home/linye/mqtt/pwfile root
    Password:
    Reenter password:
    

    就会在pwfile文件下生成账号和加密的密码root/admin

    cat ~/mqtt/pwfile
    root:$6$Mf+7EtctNlbTlwvV$IYYMsLiGos6OLwxCzYRKOOEilm5haU/K88ChsYDsru/oj2S8dn9XL4B4XX/CkdY9TIp5GcU5g7WziNd5lxVD/w==
    

    这是后登陆的时候就需要-u root -P admin进行登陆

    osquitto_sub  -c -h 127.0.0.1 -p 1883 -k 30 -t /local/test1
    cacert.pem -u root -P admin
    start connect
    connect ok
    
    3.2 自定义方式添加账号密码

    mosquitto提供了mosquitto_passwd命令来生成账号密码等,不过这个方式不喜欢,因为没办法定制化自己想要的账号密码加密方式,所以做了一些小改动。

    在myclient里面加我们想要的加密方式,然后在mosquitto broker的源码里面添加对应的解密方式即可。

    如下,账号为client_name,然后通过rsa和base64生成密码,myclient的试下调用mosquitto_username_pw_set函数。

    static void myclient_set_username_pw(struct mosquitto *mosq)
    {
        unsigned char *username_rsa = NULL;
        unsigned char *username_base64 = NULL;
    
        username_rsa = rsa_encrypt(client_name);
        if(username_rsa){
            username_base64 = base64_encode(username_rsa, RSA_LENGTH);
        }
    
        if(username_base64){
            mosquitto_username_pw_set(mosq, client_name, username_base64);
        }
        
        if(username_rsa){
            free(username_rsa);
            username_rsa = NULL;
        }
    
        if(username_base64){
            free(username_base64);
            username_base64 = NULL;
        }
    }
    

    然后在mosquitto broker里面添加解密,位于mosquitto/src/security.c文件的mosquitto_unpwd_check函数里面。

    int mosquitto_myclient_auth_unpwd_check(struct mosquitto *context,const char *username, const char *password)
    {    
        if(context->address == NULL)
            return MOSQ_ERR_AUTH;
        
        int rc = MOSQ_ERR_AUTH;
        unsigned char *user_rsa =NULL;
        unsigned char *user =NULL;
    
        user_rsa = base64_decode(password);
        if(user_rsa){
            user = rsa_decrypt(user_rsa);
        }
        
        if(user){
            if(!strcmp(user, username)){
                rc = MOSQ_ERR_SUCCESS;
            }
            else{
                rc = MOSQ_ERR_AUTH;
            }
        }
        
        if(user_rsa){
            free(user_rsa);
            user_rsa = NULL;
        }
        
        if(user){
            free(user);
            user = NULL;
        }
    
        return rc;
        
    }
    
    int mosquitto_unpwd_check(struct mosquitto_db *db, struct mosquitto *context, const char *username, const char *password)
    {
        int rc;
        int i;
        struct mosquitto__security_options *opts;
    
        rc = mosquitto_unpwd_check_default(db, context, username, password);
        if(rc != MOSQ_ERR_PLUGIN_DEFER){
            return rc;
        }
        ...
    
        rc = mosquitto_myclient_auth_unpwd_check(context, username, password);
    
        ...
        return rc;
    }
    

    4. SSL认证

    另一个加密方式就是SSL认证,给客户端提供相应的证书,和配置协议(mqtt or websockets)一样,在配置文件监听的端口下面可以添加ssl的配置选项,每个port都可以单独配置ssl的证书内同容。

    如下:从端口7885连接进来的设备需要下面的证书要求

    port 7885
    
    #CA证书文件
    cafile /etc/mosquittoConf/cacert.pem
    #PEM证书文件
    certfile /etc/mosquittoConf/deviceCert.pem
    #PEM密钥文件
    keyfile /etc/mosquittoConf/deviceKey.pem
    

    设备的认证有单向认证和双向认证两种:

    • 不管单向还是双向,broker端都需要配置cafile、certfile、keyfile
    • 单向的时候,client只需要提供cafile,双向的时候client需要提供cafile、certfile、keyfile
    • 双向的时候,broker需要将require_certificateuse_identity_as_username设置成true

    单向认证,只需要提供ca证书

    mosquitto_sub -h 192.168.18.1 -p 6885 -t "local/iot/ziroom/broadcast" --cafile ~/mqtt/zgateway/cacert.pem --insecure
    
    mosquitto_sub -h 192.168.18.1 -p 1883 -t "local/iot/ziroom/broadcast"
    

    双向认证,需要ca,pem,key三个

    mosquitto_sub -h 10.30.11.47 -p 8883 -t "local/test1" --cafile ./ca/ca.crt --cert ./client/client.pem --key ./client/client.key &
    
    mosquitto_pub -h 10.30.11.47 -p 8883 -t "mqtt/server/topic" -m "hello,world!" --cafile ./ca/ca.crt --cert ./server/server.pem --key ./server/server.key
    

    按步骤一步一步执行,生成证书(里面也可以指定各参数,有效时间):

    // ======================================================
    //                  SSL 
    // ======================================================
    openssl req -new -x509 -days 36500 -extensions v3_ca -keyout ca.key -out ca.crt
    # PEM pass phrase: 123456
    # Country Name: CH
    # State Or Province Name: Shanghai
    # Locality Name: Yangpu
    # Organization Name: Fanyi
    # Organizational Unit Name: Embed
    # Common Name: 192.168.100.33
    # Email Address: 916634969@qq.com
     
    // 给mosquitto_server端
    openssl genrsa -out server.key 2048
    openssl req -out server.csr -key server.key -new
    # Country Name: CH
    # State Or Province Name: Shanghai
    # Locality Name: Yangpu
    # Organization Name: Fanyi
    # Organizational Unit Name: Embed
    # Common Name: 192.168.100.34
    # Email Address: 916634969@qq.com
    # A challenge password: 123456
    # An optional company name: Fanyi
    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 36500
    # Enter pass phrase for ca.key: 123456
     
    // 给mosquitto_client_sub端
    openssl genrsa -out client_sub.key 2048
    openssl req -out client_sub.csr -key client_sub.key -new
    # Country Name: CH
    # State Or Province Name: Shanghai
    # Locality Name: Yangpu
    # Organization Name: Fanyi
    # Organizational Unit Name: Embed
    # Common Name: 192.168.100.40
    # Email Address: 916634969@qq.com
    # A challenge password: 123456
    # An optional company name: Fanyi
    openssl x509 -req -in client_sub.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client_sub.crt -days 36500
    # Enter pass phrase for ca.key: 123456
     
    // 给mosquitto_client_pub端
    openssl genrsa -out client_pub.key 2048
    openssl req -out client_pub.csr -key client_pub.key -new
    # Country Name: CH
    # State Or Province Name: Shanghai
    # Locality Name: Yangpu
    # Organization Name: Fanyi
    # Organizational Unit Name: Embed
    # Common Name: 192.168.100.41
    # Email Address: 916634969@qq.com
    # A challenge password: 123456
    # An optional company name: Fanyi
    openssl x509 -req -in client_pub.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client_pub.crt -days 36500
    # Enter pass phrase for ca.key: 123456
     
    // 验证生成的服务端和客户端的证书是否可用
    openssl verify -CAfile /home/ares/mqtt/server_ssl/ca.crt /home/ares/mqtt/server_ssl/server.crt
    openssl verify -CAfile /home/ares/mqtt/client_sub_ssl/ca.crt /home/ares/mqtt/client_sub_ssl/client_sub.crt
    openssl verify -CAfile /home/ares/mqtt/client_pub_ssl/ca.crt /home/ares/mqtt/client_pub_ssl/client_pub.crt
    ————————————————
    原文链接:https://blog.csdn.net/Auris/article/details/92570180
    

    按上面的步骤可以生成如下文件

    ubuntu:~/mqtt/ssl$ ls
    ca.crt  client_pub.crt  client_sub.crt  server.crt
    ca.key  client_pub.csr  client_sub.csr  server.csr
    ca.srl  client_pub.key  client_sub.key  server.key
    

    在服务器端需要放三个文件

    • ca.crt即cafile
    • server.crt即certfile
    • server.key即keyfile

    如果是单向认证,客户端只需要一个文件

    • ca.crt即cafile

    如果是双向认证,客户端只需要三个文件

    • ca.crt即cafile
    • client.crt即certfile
    • client.key即keyfile

    查看证书的有效时间

    ubuntu:~$ openssl x509 -in cacert.pem -noout -dates
    notBefore=Apr 23 12:40:56 2019 GMT
    notAfter=Apr 19 12:40:56 2032 GMT
    

    相关文章

      网友评论

        本文标题:5-Openwrt MQTT client使用

        本文链接:https://www.haomeiwen.com/subject/rrpaqktx.html