记一次坎坷的调试|Mosquitto通过TLS连接EMQ时阻塞的问题

最近两天在调试一个关于嵌入式Linux系统环境时,在系统开机之后,Mosquitto通过tls连接MQTT服务器(EMQ)时,创建MQTT连接总是阻塞的问题,现记录一下调试过程及解决问题的步骤。

先说下开发调试环境:

  • 硬件平台:EXP imx.6ull
  • 内核版本:4.1.15
  • rootfs:基于buildroot创建
  • mosquitto:2.0.11
  • openssl:1.1.1
  • MQTT服务器:支持TLS服务的EMQ

问题表象

linux系统开机之后,出现shell登录提示符之后,调用mosquitto_connect和EMQ建立基于TLS的连接,mosquitto_connect调用之后阻塞,大约90秒,该函数调用才会返回,并且报错。之后,mosquitto会触发重连机制,再次连接EMQ服务器,连接成功。这时,如果重新发起向EMQ的连接请求,mosquitto_connect不会阻塞。

起初怀疑的几点原因:

  1. 系统启动之后,网络还未初始化完成,就调用了mosquitto_connect,连接EMQ可能失败。尝试测试方法:完善网络初始化流程,优化mosquitto_connect调用时序,经测试,无效。
  2. 怀疑EMQ服务器问题,尝试测试方法:使用其他设备测试对同一EMQ发起TLS连接,没有问题。
  3. 开机时,系统时间未同步(1970-01-01 08:00:00),联网之后,ntpd服务才会同步网络时间到本地,所以中间会导致mqtt连接阻塞,经过思考,如果是系统时间的问题,mosquitto_connect应该返回TLS校验证书失效的错误提示,而不是阻塞。
  4. 怀疑应用程序其他部分干扰mosquitto_connect的调用流程,使用mosquitto_pub直接向EMQ发布消息,发现现象一样。

上述几轮怀疑和测试之后,调试陷入了僵局,我有些郁闷,经过思考之后,使出了杀手锏:就是最笨的,也是最有效的方法:向mosquitto_connect执行流程插入调试日志。其实,有些时候,看似最笨的方法,却是最为有效的调试方法,只要方向对,花些时间和精力也是值得的。

调试步骤

按照mosquitto_connect的调用流程,增加日志,确定阻塞位置,这里有一个调试的小技巧,就是先主干,再细节,即,先在需要调试的主路径上的关键位置添加日志,然后,编译调试,从而可以确定问题的大体位置,然后,在子流程上再次添加日志,再编译,在调试,这样一步一步递归下去,就会较为快速的定位问题位置。应用该方法,我最终确定了mosquitto_connect阻塞问题位置是:

SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method());

简单介绍一下SSL_CTX_new,其主要是openssl创建用于TLS通信的控制块,为了确认问题,我编写了测试程序,再次确认是不是该函数导致的问题。代码如下:

#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/ui.h>
#include <openssl/ssl.h>
 
void ssl_test(void)
{
    
    
    printf("ssl_test:TP0.\n");
    SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method());
 
    (void)ssl_ctx;
    printf("ssl_test:TP1.\n");
}

程序很简单,就是为了确认罪魁祸首是不是SSL_CTX_new。Linux开机后,立即执行改测试函数,首先打印"ssl_test:TP0",大约90秒之后,打印"ssl_test:TP1.”,问题位置确认。

发现问题

如果你是一个SSL/TLS机制小白的话,或者说,只了解SSL/TLS交互流程的话,分析SSL_CTX_new的实现原理有点强人所难,我属于这类人,所以,调试再次陷入困境。

就在一筹莫展之际,我发现了一个有趣的现象:发现终端只要打印一行:

random: nonblocking pool is initialized

SSL_CTX_new就会返回,多次测试之后,确认这两者的先后关系。可见,random的初始化和SSL_CTX_new的阻塞存在关系。之后的调试就比较顺利了,实在是山重水复疑无路,柳暗花明又一村

通过查阅资料,发现关于random: nonblocking pool is initialized有以下几种主流的解决思路:

1. Linux随机数nonblocking pool快速初始化

2. random: nonblocking pool is initialized,只是开机不打印该提示,没有实际解决问题

3. Linux随机数nonblocking pool快速初始化

经过分析,1、3和该问题的现象基本一致:可以确定,4.1.15版本的内核random驱动存在问题,导致系统启动时,random初始较慢(90s),而SSL_CTX_new在初始化的时候,用到了random,其需要等待random初始化完成,才能继续执行,从而导致SSL_CTX_new阻塞,最终导致mosquitto_connect阻塞。

解决问题

修改random内核驱动代码:drivers/char/random.c,找到函数add_interrupt_randomness,修改如下的代码:

原始代码:

 if ((fast_pool->count < 64) &&
            !time_after(now, fast_pool->last + HZ))
        return;

修改后的代码:

 if ((fast_pool->count < 64) &&
         !time_after(now, fast_pool->last + HZ) &&
         nonblocking_pool.initialized)
     return;

注意:本文内核版本是4.1.15,经过查阅内核代码,发现在较新的内核版本,该bug已解决!

编译,替换内核,经测试问题解决!

后记

反观这个问题的解决过程,不知道你发下了没有,一开始,问题的现象和引起问题的根源,可以用俗语“八竿子打不着”来形容,每次感觉问题快要解决了,最后,还是竹篮打水一场空,那种失落感、焦虑感会越来越浓。其实,事后分析解决问题的过程,不难得出解决问题的关键首先,保持冷静,确定方向,然后,使用科学的方法,坚持不懈的向前推进,相信自己,最后,问题最终会被解决,只是时间的问题

猜你喜欢

转载自blog.csdn.net/linux_embedded/article/details/126100266
今日推荐