snarrymoon 2021-05-28 10:26 采纳率: 66.7%
浏览 222
已采纳

MQTT数据抓包可以通过TCP吗?

MQTT是基于TCP传输,如果抓包的话可以通过TCP抓MQTT数据吗?

  • 写回答

3条回答 默认 最新

  • 胖哥王老师 单片机领域新星创作者 2021-05-28 10:34
    关注

    当然可以咯,附图我的抓包

    也可以用tcp.dstport==1883 抓取完整会话,因为mqtt基于目的端口1883的

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
  • 易书生 2021-05-28 10:28
    关注

    首先看如何产生MQTT的数据包,因为MQTT协议被几乎编程界所有的主流语言支持,为了照顾所有的读者,笔者就不选任何一门语言来做示范,而是用一款工具软件来进行MQTT数据包的发送和订阅。服务器端选择当前用户最多的服务器,Mosquitto; 客户端选择Eclipse Paho 提供的MQTT的UI客户端,这个客户端工具是一个基于Java的Eclipse桌面客户端程序,其底层的和MQTT服务器进行的交互的java类库就是Eclipse Paho java库。假设我们在本机(127.0.0.1)已经启动了一个mosquitto MQTT服务器,其端口为1883。如何使用 Eclipse Paho MQTT工具?

    下载Eclipse Paho MQTT 工具

    图1:Eclipse Paho下载地址

    解压缩后,双击paho.exe,打开后的对界面如下。

    图2:Eclipse Paho UI开始界面

    点击上图中的 十字图标,就能新建一个MQTT的客户端的连接,输入正确的MQTT服务端的连接地址,比如,本例中的连接地址是tcp://localhost:1883,然后点击“Connect”按钮,这个时候,如果MQTT服务端没有设置密码(默认情况是没有密码的)的话,这个时候,我们就能看到连接得到状态(status)是“Connected”。

    图3 Eclipse Paho UI开始界面

    这个时候我们就能订阅消息了。选择“Subscription”下方的绿色十字图标,就可以输入订阅的主题(topic)的名字,比如我们设置主题名称为“test”,并点击 “Subscribe”按钮。

    图4 Eclipse Paho UI订阅界面

    往MQTT服务发送一条消息主题为“test”,内容为“大家好,这是我一条消息。”的MQTT消息。然后点击“Publish”按钮,这个时候,我们就能看到消息已经发送成功,且在步骤(4)订阅的同一主题也收到了消息。

    图5 Eclipse Paho UI消息显示界面

    神奇吧,恭喜你,已经基本学会了如何用Eclipse Paho MQTT工具作为客户端来发送和接收消息了。

    MQTT消息的模拟,已经用一种最简单的方法来模拟了,那么应该用什么工具来抓包呢? 当然是WireShark了,在Wireshark中,有capture filter和Display Filer,我们只需要在WireShark软件中的capture filter 输入下面的过滤条件,则与MQTT服务交互的相关TCP的数据包就能顺利抓取到。如下图所示意,注意,下面的1883端口是MQTT服务器的默认推荐端口,如果读者改变这个默认端口,请把1883端口改成相应的端口。

    图6 WireShark转包配置界面

    五、如何分析MQTT协议包?
    万事具备了,环境准备好了,抓包工具也准备好了,是时候通过WireShark抓取到的TCP包来分析MQTT协议了,考虑到MQTT协议有多个版本,本文采用的MQTT的协议是MQTT 3.1.1的版本,如果不做特别的说明,服务器的地址应该是192.168.80.196,用的是Mosquitto MQTT服务器。

    另外,因为牵涉到最基础的计算机知识,二进制,16进制以及10进制的转换,笔者想只要是参加Gitchat的读者朋友都是对技术有追求的朋友,笔者在这就不在赘述,默认大家都会啊,如果有不会的同学,请单独与我联系啊。

    5.1 CONNECT 消息类型分析
    假设有这么一个场景,MQTT的客户端需要连接MQTT的服务器端,这个时候,TCP的三次握手协议完成后,MQTT的客户端就会马上发送一条MQTT CONNECT消息帧。

    咱们还是以Phao UI工具为例子,当我们点击“Connect”按钮的时候,究竟在TCP协议层发生了什么?

    其实MQTT的CONNECT消息主要用来在客户端和服务端建立一个TCP的通信连接,这个CONNECT的发起者肯定是客户端。因为要建立一个连接,所以客户端在这个消息里面需要提供Client ID, 如果服务端设置了用户名和密码认证,在这个消息里面还必须要包括用户名和密码的相关信息,且如果设置了连接断开的最后遗言(Last Will and Testament),则在这个消息里面还必须包含和最后遗言相关的信息。下面是实验的具体步骤:

    首先,在点击Eclipse Phao UI客户端工具上的“Connect”按钮之前,先在客户端把WireShark软件打开,并按照上一个章节(第4章节)的最后一部分关于WireShark的Capture Filter的设置把协议和端口设置好(TCP and port 1883)。

    然后,打开Eclipse Paho的UI客户端,在本实例中,用于连接MQTT服务器的相关配置信息如下图7所示意(为了显示方便,笔者把Eclipse Paho对MQTT 连接的配置信息的不同配置页面整合到了一个图里面了,就是图7),请大家记住下面用于建立MQTT连接的相关信息,然后点击“Connect ”按钮。

    图7 Eclipse Paho UI的连接信息配置页面

    这个时候WireShark就能抓取到类似于下面的TCP数据包。具体的包如下:

    图8 Eclipse Paho UI的WireShark抓包数据

    从上面的抓包可知,TCP的三次握手连接之后,MQTT的客户端会发送第一条MQTT的CONNECT消息格式的数据给MQTT服务器。 其具体数据,见上图左下角,10 4e 00 4d 51 54 54 04 ee 00 3c 00 1e 31 31 31 ……….

    根据MQTT v3.1.1的协议,首先咱们来看头部信息。

    5.1.1 CONNECT 固定头部

    WireShark抓包抓取的固定头部(Fix head)的16进制的数据为:10 4e, 那10 4e这两个字节代表什么意思呢?

    首先来看一下,MQTT 3.1.1的协议对CONNECT 消息固定头部的2个字节的规范和定义。

    表4 CONNECT 报文的固定头部(Fixed Header)格式

    从这张图片可以知道,MQTT的Connect消息格式中,有一个固定的头部,其是由2个二进制的8位(bit)字节来表示的。

    10—表示发送的Connect的请求(也就是二进制的0001 0000, 详细信息,请参考
    表3 MQTT的14种消息类型(Command message) )** ,前4个的二进制位是0001,我们可以知道其标识的是一个MQTT CONNECT 消息类型,后面的4个bit是保留位,默认为4个二进制位的0000),MQTT CONNECT的消息格式的第一个字节分析完成。

    4e—表示后面将跟着4*16+14=78个字节,也就这个MQTT的消息帧,后面部分还有78个字节,包括10个字节的可变头部(规定是10个字节)和 68个字节的负载(payload)。具体什么是可变头部和什么是消息负载,请继续看下面的分析。

    5.1.2 CONNECT 可变头部(10个字节)

    接下来的10个字节是:00 04 4d 51 54 54 04 ee 00 3e, 这10个字节,根据MQTT的3.1.1的规范,是10个字节的可变头部,其MQTT的CONNECT的可变头部主要由四大部分按顺序组成:

    协议名称(Protocol Name,)

    协议级别(Protocol Level)

    连接标志(Connect Flags)

    保持心跳(Keep Alive)

    下面笔者就把上面通过WireShark抓包抓到的10个字节填充到MQTT的3.1.1x协议可变头部的模板表格中,可变头部的规范马上就一目了然了。

    表5 固定头部格式重放

    下面对这4部分做进一步的详细分析,首先看协议名称。

    5.1.2.1 协议名称

    协议名称由6个字节组成,16进制数据为:00 04 4d 51 54 54,其代表的是MQTT协议。对于一些第三方的网络监测软件,可以通过分析TCP的包,从而知道这是MQTT协议。

    接下来看协议级别。

    5.1.2.2 协议级别

    协议级别只有1个字节,我们这个实验中抓取到的16进制数据为:04,其实代表的就是协议的修订级别(revision level),说白了,就是修订版本,04 代表的是MQTT 3.1.1的修订协议;而03 代表的是MQTT 3.1.0的修订协议,下面来看连接标志。

    5.1.2.3 连接标志

    对于连接标志这一部分,还是挺有意思的,其对应了一个字节。并用字节中不同位(bit)是true(二进制 1)或者false(二进制 0)来表示不同的业务意义, 其本质就是定义了7种类型的连接标志位(包括最后一位的保留标志位)。

    那为什么是7种呢?8个位应该是8种啊?原来其中一种定义的是QoS(质量服务标准)占据了2个位(bit),其有三种情况(具体参考第一章节的MQTT支持三种消息发布服务质量(QoS))

    表6 连接标志位数据重放

    上面这张表也许看的还不是特别清楚,如果把上面的表转个维度和方向查看,其对应的意义就一目了然了。

    表7 连接标志位数据格式换角度重放

    其中我们可以看到,因为在Eclipse Paho UI 工具中,我们选择了认证且输入了用户名和密码,所以User Name和Password的标志位都置成了1。

    下面来看最后的遗愿(Last Will and Testament),在Eclipse Paho的连接配置上,对应有2个设置(QoS,Retained),如下图9。

    图9 Eclipse Paho UI的连接配置设置中的最后遗愿(Will)配置

    Will Retained 因为在Eclipse Paho UI工具上勾选了,所以其连接的标准为为1。

    Will QoS的值为01,刚好和Eclipse Phao的UI工具上的设置(1-At Least Once)对应了起来。

    对于Clean Session的设置,请见上图7 Eclipse Paho UI的连接信息配置页面Clean Session的单选框被勾上了,所以Clean Session对应的标志位(bit)为1。

    最后剩下的保留位(bit),保留位(bit)默认值为0。

    5.1.2.4 保持心跳间隔

    根据MQTT协议,每隔固定的时间,如果没有业务相关的消息发送的话,MQTT的客户端必须在1.5倍的Keep Alive的时间内,向MQTT的服务器发送一个PINGREQ的消息帧,以表示我们的客户端还是在线的,还活着。否则服务器则任务网络发生了故障,将会强制断掉于客户端的连接。

    表8 保持在线配置协议格式定义

    其中:

    00 Keep alive的时间的高位(MSB):0。

    3c 代表Keep alive时间的低位(LSB): 3*16+12=60秒,刚好和图7 Eclipse Paho UI的连接信息配置页面 中Keep Alive的数字输入框对应起来。10个字节的可变消息头分析完了,下面来看消息负载(Payload),但是奇怪的是消息负载没有和可变消息头直接连起来,而是中间隔了2个字节,00 1e,这就是传说中的分隔标志字节,请看下面的分析。

    5.1.3 分隔标志字节

    实际上,在MQTT的协议里面也没有找到对其的描述,其实就是一个分割标志字节,在可变消息头和消息负载(payload)之间,用00 1e进行分割;后面还能遇到00 3c; 00 05 都是分隔标志符号,用于分隔不同的数据类型;好了,下面去看望一下消息负载吧(Payload)。

    5.1.4 消息负载(Payload)

    前面的固定消息头,可变消息头,以及分隔标志字节已经分析完了,下面正式进入消息的负载(Payload)的字节分析。

    本实验中的消息负载包括了MQTT的客户端的ID,遗愿的主题(Will Topic),遗愿的消息内容(Will Topic)以及为了通过MQTT服务器的Basic认证而要提供的用户名和密码。下面逐一进行分析。

    5.1.4.1 客户端ID

    首先看客户端的ID,16进制为: 31 31 31 31 31 31 31 31 31 31 31 31 31 31 3131 31 31 31 31 31 31 31 31 31 31 31 31 31 31,如果翻译成ASCII的表示,其就是(111111111111111111111111111111),其对应的就是,图7 中的Client ID的输入,不好意思,有点调皮了,输入了这么多个1 作为MQTT的Client ID,其实笔者就是想看看Eclipse Paho这个Client ID字段最多能让我输入多个字符,好奇害死猫啊。

    接下来,看看最后的遗愿(Last Will and Testament)在消息负载中放入什么东东。

    5.1.4.2 遗愿(Will)主题

    最后的遗愿(Last Will and Testament)的主题中放入的还是图7中输入Topic。

    Will Topic: 16进制数据为:6c 77 74, 翻译成人能懂的ASCII码就是: lwt。

    后面紧跟的00 0c,是遗愿主题和遗愿消息之间的分隔标志符,具体说明请参看5.1.3.

    5.1.4.3 遗愿(Will)消息体

    后面的16进制数: 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 代表的就是人能懂得ASCII码Hello World!。 这个就是最后的遗愿(Last Will and Testament)的消息体(Message)

    后面紧跟的00 05是遗愿(Topic,Message) 和认证的用户名密码之间的分隔标志符号。

    5.1.4.4 连接MQTT服务端的用户名和密码

    下面看用户名和密码:

    16进制: 61 64 6d 69 6e 代表的ASCII码就是用户名:admin

    16进制: 70 6173 73 77 6f 72 64 代表的ASCII码就是用户名 :password

    中间用 00 08标志分隔符分开了。

    恭喜你,整个的Connect的消息格式就分析完毕了。安奈不住激动心情的你,是不是想跃跃欲试了! 通过对抓取的包和MQTT 3.1.1协议标准对比,我们非常容易掌握了其MQTT CONNECT消息类型背后的规范和定义,这个感觉是不是非常的好啊。

    5.2 CONNACK 消息类型分析
    在上节中我们分析了MQTT CONNECT消息类型的格式,我们知道CONNECT消息是客户端发送出去的,作为对客户端的连接请求,服务器端同样会有一个消息的返回,这个消息就是MQTT CONNACK的消息类型。我们在发出去CONNECT消息后,如果WireShark抓包工具依然开启的话,将会抓到类似于下面的TCP消息,其16进制为:20 02 00 00,那么其代表什么意思呢?如下图10所示意。

    图10 服务器连接应答抓包

    5.2.1 连接应答固定消息头

    在分析之前,我们先回到MQTT3.1.1的协议中对客户端连接应答(CONNACK)消息帧的定义和描述。

    表9 连接应答的固定消息头

    其和表4的连接消息帧(CONNECT)固定消息头定义是一样的,只不过,对于第1个字节,10表示的是Connect的消息帧类型;而20表示的是连接应答(CONNACK)的消息帧类型。对于第2个字节,02 表示后面将跟着2个字节. 其意义和前一节的连接消息帧(CONNECT)定义的规范是类似的,表示后面还跟有多少个字节(剩下多少个字节,既包括消息头也包括消息的负载)。

    02 表示后面将跟着2个字节。

    5.2.2 连接应答的可变消息头

    5.2.1章节提到了,连接应答的固定消息头的第2个字节是02,表示后面还跟着2个字节,这两个字节就是连接应答的可变消息头。

    表10 连接应答的可变消息头

    面我们来分析其可变头的2个字节: 00 00

    其中第一个00,目前没有什么特别的含义,是MQTT协议的保留字段,也许在将来的协议版本中会用到。

    第二个字节的00 表示连接成功。MQTT协议对于返回的结果总共定义了6种类型(请见下面的表3): 连接接受,连接拒绝(不可接受的版本),连接拒绝(Client ID服务器不允许),连接拒绝(服务器不可达),连接拒绝(错误的用户名和密码),连接拒绝(客户端没有通过授权认证)。具体含义请见下表11。

    表11 连接应答返回值状态速查表

    这个消息格式简单吧,嘿嘿,恭喜读者在学习MQTT消息格式的旅途中又往前快速前进了一步。

    剩下的12种MQTT消息类型(请见,表3 MQTT的14种消息类型(Command message)),分析过程其实也是类似的,笔者就不在啰嗦赘述了,大家只要把抓到的协议包然后对照MQTT 3.1.1协议规范做一个对比,就能非常容易的把MQTT的底层协议分析出来。

    六、总结
    在这篇Chat中,笔者首先分享了为什么使用MQTT协议,然后列出了当前那些开源产品和框架支持MQTT协议,紧接着为了照顾一些没有接触过MQTT的读者,笔者对MQTT协议做了一个扫盲;工欲善其事必先利其器,在给大家做了理论铺垫之后,笔者又给大家分享了如何不用写代码,也能模拟MQTT消息的发送,且如何通过WireShark来抓取MQTT的TCP的协议帧;接下的章节,笔者详细的给大家分享了MQTT连接请求和连接应答包两种MQTT消息类型的格式,具体是如何定义的,也分析了每个字节对应的含义;希望能够给读者抛砖引玉石,举一反三,这样大家就会在以后的工作和学习过程中,对MQTT协议的本质有一个非常深入的理解。

    评论
  • 胖哥王老师 单片机领域新星创作者 2021-05-28 10:54
    关注

    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥20 C语言字符串不区分大小写字典排序相关问题
  • ¥15 关于#python#的问题:我希望通过逆向技术爬取1688搜索页下滑加载的数据
  • ¥15 学习C++过程中遇到的问题
  • ¥15 关于Linux的终端里,模拟实现一个带口令保护的屏保程序遇到的输入输出的问题!(语言-c语言)
  • ¥15 学习C++过程中遇到的问题
  • ¥15 请问,这个嵌入式Linux系统怎么分析,crc检验区域在哪
  • ¥15 二分类改为多分类问题
  • ¥15 Unity微信小游戏上调用ReadPixels()方法报错
  • ¥15 如何通过求后验分布求得样本中属于两种物种其中一种的概率?
  • ¥15 q从常量变成sin函数,怎么改写python代码?