ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Wireshark MQTT抓包实战:报文解析与故障排查指南

Wireshark MQTT抓包实战:报文解析与故障排查指南 做IoT设备调试的人应该都经历过这种时刻设备上报速度不对、消息偶尔丢失、或者客户端一连上就被服务端踢下线。手里没有好用的分析工具时就像在暗房里找一只黑猫。我最常用的解决办法就是把Wireshark打开把MQTT报文一封封拆开看。Wireshark是抓包工具里的老牌选手对MQTT的支持是内置的不需要装额外插件只要报文里带的端口和协议特征能被识别它就能把一条条二进制的TCP payload翻译成结构清晰的协议字段。这篇内容没有太多绕弯子的理论重点是用Wireshark把MQTT的抓包、过滤、报文解析和故障排查串起来。不管你是做嵌入式开发、上位机通信、网关接入还是只负责运维一套MQTT服务照着这套思路走基本都能快速上手。1. 为什么建议把Wireshark当成MQTT调试的第一工具1.1 一次让我印象深刻的排查我之前调试过一批温湿度采集网关现象很典型设备上电后TCP能连上但过不了几秒就自动断开后台日志只显示一句connection closed完全看不出原因。于是我在PC网卡上开了Wireshark只过滤1883端口抓到完整的CONNECT和CONNACK报文。CONNACK里的Return Code直接显示Server unavailable说明broker根本没有接受这次连接而不是网络链路问题。再回头查broker配置发现是因为设备使用了一个奇怪的clientId触发了接入校验的规则。整个过程大概十五分钟如果只看业务日志可能还在排查网线和防火墙。这个例子想说明一个道理MQTT虽然是应用层协议但大量问题都藏在协议字段里。TCP连接成功不等于MQTT连接成功而Wireshark能把那些本来需要人工对着十六进制字节猜的字段直接摊开在界面上。消息发没发出去、broker有没有回ACK、订阅时写了哪个主题全都能看见。很多时候问题不用猜包放在那里一眼就能定位。1.2 Wireshark自带的MQTT解析器到底能做什么Wireshark从很早的版本就内置了MQTT解析器支持MQTT 3.1、3.1.1以及5.0。只要版本不是太老抓包后通常会把1883端口自动识别成MQTT协议。它会把每个报文按照固定报头、可变报头、有效载荷三部分拆分并且把消息类型、DUP标志、QoS等级、Retain标志、主题名、报文标识符、返回码这些关键信息全部解析成可读字段。同时还支持mqtt显示过滤器以及通过tshark命令行做批量离线分析。为什么推荐它而不是随便一个抓包工具因为MQTT的包本身只是TCP payload里的一段二进制数据如果不做解析你看到的就只有一串十六进制。Wireshark的包详情窗口里会直接显示Message Type: PUBLISH、Topic: home/room1/temp、QoS: 1这种内容省去了查阅协议的繁琐过程。更关键的是当你同时抓MQTT、HTTP、Modbus网关数据、WebSocket流量时Wireshark可以按协议类型把不同流量区分开后续排查效率完全不一样。这也是我把它当作MQTT调试第一工具的原因。2. 搭一套随时能抓包的MQTT调试环境2.1 用Mosquitto在本地起一个Broker要反复做实验、验证各种异常行为就不要直接拿生产上的broker来抓包最好在自己电脑上跑一个本地的MQTT服务。我常用的是Eclipse Mosquitto轻量、开源、跨平台命令行一条命令就能启动。Linux下直接安装sudo apt install -y mosquitto mosquitto-clients mosquitto -vWindows下可以下载官方安装包或者用winget安装。装好之后在命令行执行mosquitto -v -p 1883就能在前台启动一个监听1883端口的本地broker。如果想要配置用户名密码可以在配置文件里指定但测试阶段我基本都是先用匿名模式避免引入额外变量。配置文件mosquitto.conf里可以这样写listener 1883 allow_anonymous true需要注意的是这里的allow_anonymous true只是为了本机测试方便生产环境千万不要这么开。本地broker跑起来之后MQTT报文会走回环接口所以后面抓包时要选择loopback网卡而不是物理网卡这个细节很多人第一次都会踩。2.2 准备最简单的发布订阅客户端Mosquitto自带两个命令行工具mosquitto_pub和mosquitto_sub它们虽然简单但足够用来制造干净的测试流量。开两个终端一个订阅mosquitto_sub -h 127.0.0.1 -p 1883 -t test/topic -v另一个发布mosquitto_pub -h 127.0.0.1 -p 1883 -t test/topic -m hello-mqtt执行完这条发布命令后Wireshark里就能看到一条完整的PUBLISH消息。如果还想验证QoS和Retain的行为可以分别加参数mosquitto_pub -q 1 -r -t test/topic -m retained-message。-q控制QoS等级-r表示保留消息。这样做的目的不是为了跑通一个demo而是让你熟悉不同参数对报文内容的影响。比如同样一条消息QoS 0和QoS 1在抓包里看到的交互过程完全不同QoS 0只有一个PUBLISHQoS 1会多一个PUBACK。纸上谈兵没有用抓一次包就全都明白了。2.3 设置Wireshark的抓包与过滤条件启动Wireshark之后第一步是选择正确的抓包接口。如果broker跑在本机客户端也连本机要选择loopback接口。Windows上安装Npcap时勾选支持loopback抓包安装后会出现一个类似“Adapter for loopback traffic capture”的接口Linux则是选择lo接口。选错接口的后果是抓半天什么都看不到。为了避免抓到大量无关流量可以在抓包选项里使用捕获过滤器比如只抓发往1883端口的包tcp port 1883如果本机同时跑了其他MQTT服务也可以写成host 127.0.0.1 and tcp port 1883。开始抓包后在显示过滤器里输入mqttWireshark就会把识别为MQTT的报文都过滤出来。如果某些报文没有被自动识别可以在报文列表里右键选择Decode As把对应TCP端口指定为MQTT。这个操作只是改变显示方式不会修改原始抓包文件可以放心尝试。3. 手把手读懂MQTT报文从连接到收发消息3.1 固定报头一个字节看出消息类型每个MQTT报文都以固定报头开头至少占两个字节。第一个字节的高四位表示消息类型数字对应关系是1是CONNECT2是CONNACK3是PUBLISH4是PUBACK5是PUBREC6是PUBREL7是PUBCOMP8是SUBSCRIBE9是SUBACK10是UNSUBSCRIBE11是UNSUBACK12是PINGREQ13是PINGRESP14是DISCONNECT。低四位是标志位其中PUBLISH会用到DUP、QoS、Retain这三个标志其他报文的标志位大多固定不需要多管。第二个字节开始是剩余长度它表示后面可变报头和有效载荷的总字节数。剩余长度不是简单的一个字节而是采用1到4字节的变长编码每个字节用7位存数据最高位如果是1就表示后面还有延续字节。例如剩余长度300编码出来就是0xAC 0x02。在Wireshark里这些都不需要人工去算但理解这个结构还是有用的因为当你用脚本解析原始payload时必须知道如何跳过剩余长度字段才能正确读后面的内容。实际在包详情窗口里Wireshark会直接显示MQTT Message Type: CONNECT下面还会展开Fixed Header相关的节点。我习惯先看一眼消息类型因为只要类型对了后续字段就八九不离十。比如调试时你预期客户端应该发PINGREQ但抓到的却是DISCONNECT那方向立刻就不一样了。3.2 CONNECT/CONNACK连接建立过程中的关键字段CONNECT是客户端连接broker时发出的第一个报文。Wireshark展开后能看到协议名“MQTT”、协议级别4或者5以及一组连接标志。连接标志位里比较关键的是Clean SessionMQTT 5.0中叫Clean Start、Will Flag、Will QoS、Will Retain、Username Flag、Password Flag。Will遗嘱字段经常被忽略但如果设备配置了遗嘱异常掉线时broker会自动发布一条遗嘱消息。在Wireshark里可以直接看到Will Topic和Will Message的内容这对排查“设备掉线后其他设备没收到通知”很有帮助。Keep Alive字段是两字节的秒数常见配置是60秒。它规定了客户端在没有业务数据时最多隔多久必须发一条PINGREQ来保活。broker如果在一个半周期内没收到任何报文就会判定客户端离线并断开连接。Wireshark会直接显示Keep Alive: 60这个字段连换算都省了。CONNACK是broker对CONNECT的回应。重点看两个地方一个是Session Present标志另一个是Return Code。Return Code为0表示连接成功非0则对应不同的拒绝原因比如4表示用户名或密码错误5表示未授权。MQTT 5.0的CONNACK里还会带Reason String和属性Wireshark同样能解析。我排查连接问题时基本上先看CONNACK因为它直接告诉你是协议版本不匹配、用户名密码错误还是服务端不可用比对着日志一行行猜要快得多。3.3 PUBLISH/SUBSCRIBE主题、载荷和QoS怎么在包里体现PUBLISH是MQTT里最核心的报文。Wireshark展开后最显眼的就是Topic字段它是一个UTF-8字符串显示成完整的主题路径。Packet Identifier只有在QoS大于0时才会出现它用来关联PUBLISH和对应的ACK。Payload就是实际业务数据Wireshark默认同时显示十六进制和ASCII。如果payload是JSON可以右键选择“Show Packet Bytes”或者直接Follow TCP Stream把完整内容复制出来看。QoS等级不同报文的交互方式也不同。QoS 0发出去就结束抓包里只有一个PUBLISH没有后续确认QoS 1会有一个PUBACKQoS 2的流程是PUBLISH、PUBREC、PUBREL、PUBCOMP四步缺任何一步都可能导致消息卡住。如果你想从抓包里判断消息可靠性问题直接看这些确认报文是否存在即可。显示过滤器可以这样写只看PUBLISH用mqtt.msgtype 3只看某QoS等级用mqtt.qos 1按主题过滤用mqtt.topic contains test。SUBSCRIBE报文里包含一个或多个Topic Filter并且每个主题后面跟着一个请求QoS值。broker收到后会回SUBACKSUBACK的返回码0、1、2表示成功且授予对应QoS0x80表示失败。如果SUBACK返回失败或者SUBSCRIBE报文根本没有到达broker那客户端自然收不到消息。在抓包里把SUBSCRIBE的Topic和PUBLISH的Topic一对比往往能发现很基础的主题不匹配问题。3.4 补充一个485设备通过MQTT上传数据的实际样本很多工业项目里485总线设备比如电表、温控器会通过一个串口网关接入MQTT。网关做的事情有两件一是把来自客户端的指令转换成Modbus RTU请求发到485总线上二是把读取到的寄存器数据打包成MQTT消息发布到指定topic。抓到的PUBLISH payload经常是这样的十六进制01 03 02 01 2C 79 84这串字节不是普通文本而是一帧完整的Modbus报文。Wireshark会原样把它显示在Payload里你需要自己对照Modbus协议手册去确认从站地址、功能码、寄存器数量、数据字节和CRC校验。这也是很多新手容易懵的地方Wireshark能解析MQTT的协议头但它不会帮你解析MQTT payload里的私有业务协议。解析到payload这层之后剩下的还是要靠你对业务报文的理解。我调试过一个电表采集项目设备上报周期正常但平台上的数据偶尔错位。抓包后发现发布报文的topic是对的可payload里的寄存器值在不同的frame里对应关系反了。这种问题看业务日志很难发现因为日志只会显示“读到数据”但用Wireshark把所有topic下的payload摊开对比很快就能看出数据被贴错了标签。再往深一层说MQTT经常只是“搬运工”真正要排查的往往是搬运工袋子里的Modbus、JSON或者自定义二进制协议。这也是为什么我建议在搞MQTT抓包的同时至少能看懂一种常用业务载荷的格式。4. 抓包实战三种最常见的MQTT故障定位4.1 设备上线又离线先查Keep Alive心跳现象很典型设备状态在平台上反复跳“在线/离线”后台日志只说连接断开了。遇到这种情况我一般先把过滤器切到只看心跳报文mqtt.msgtype 12 || mqtt.msgtype 1312是PINGREQ13是PINGRESP。如果客户端持续发PINGREQ但broker不回PINGRESP说明链路或broker侧异常。如果客户端压根没有发过PINGREQ并且长时间的抓包里也没有其他业务报文那问题就出在客户端的Keep Alive机制没有生效。broker会在1.5倍Keep Alive周期内没收到任何报文时主动把连接关掉。还有一种很隐蔽的情况CONNECT里的Keep Alive被设成了0。0在MQTT里表示不启用保活机制客户端不会主动发心跳但很多云平台又要求设备在固定周期内必须有数据。平台侧超时判定离线后设备端却完全不感知于是反复重连。这种问题光看业务日志很难猜但只要打开CONNECT报文看一眼Keep Alive字段马上就能定位是配置参数的问题还是平台策略的问题一目了然。4.2 QoS 1/2消息出现重复多半是ACK交互出了问题有时候broker会收到重复的MQTT消息不一定都是业务层面的重复上报。要区分这一点需要抓包看PUBLISH报文里的DUP标志。如果同一个Packet Identifier的PUBLISH出现了两次并且第二次的DUP等于1说明是客户端在重发。重发的常见原因是没有收到对应的PUBACK或者PUBREC。此时可以过滤确认报文mqtt.msgtype 4 || mqtt.msgtype 54是PUBACK5是PUBREC。如果Wireshark里只有PUBLISH没有后续的ACK那问题多半是broker端没有回应可能被网络丢包、线程阻塞或者防火墙拦掉了。如果broker已经回了ACK但客户端仍然重发那就需要去客户端的发送队列里找原因通常是ACK和请求的Packet Identifier对不上或者发送线程在收到ACK之前就超时了。在Wireshark里处理这种连续报文时有个很常用的小操作右键任意一条MQTT报文选择Conversation Filter - TCP就能只看当前这个TCP连接里的所有包。这样做的好处是可以把时间线压缩到一个连接里肉眼很容易看出PUBLISH和ACK的先后顺序不会因为同时抓到其他设备的流量而看花眼。4.3 订阅成功却收不到数据把Topic和Retain都查一遍这个场景我遇到太多次了客户端已经connect成功SUBACK也返回0但就是收不到任何数据。排查思路至少有两条线要同时走。第一条线是看订阅是否正确。过滤mqtt.msgtype 8展开SUBSCRIBE报文里的Topic Filter确认主题完全一致包括大小写和斜杠位置。MQTT主题是区分大小写的订阅了home/room1/temp而发布方发的是home/Room1/temp这两个根本不是一个主题收不到数据才正常。还要注意通配符的用法订阅a/#能收到a/b/c但订阅a/b收不到a/b/c。在抓包里同时展开SUBSCRIBE和PUBLISH的Topic字段一对比就能看出是否匹配。第二条线是看发布是否真的发生。过滤mqtt.msgtype 3确认目标topic下有没有消息。如果业务逻辑是“设备自己订阅后马上能收到之前的状态”那还要看PUBLISH报文里的Retain标志。RETAIN1表示broker会保留这条消息后续的新订阅者能立刻收到RETAIN0则表示消息只发给当前在线的订阅者。如果抓包里看到发布报文的RETAIN一直是0但业务预期是“新设备上线就拿到最新状态”那这就是设计问题不是通信问题。5. Wireshark解析MQTT的五个常见坑5.1 抓到的MQTT包被显示成普通TCP如果broker用的不是默认的1883端口Wireshark可能不会自动把TCP payload识别成MQTT报文列表里只会显示TCP而不是MQTT。解决办法有两种。第一种是在报文列表里右键任意一条TCP报文选择Decode As然后在当前端口上指定为MQTT。第二种是去Wireshark的偏好设置里把自定义端口加进MQTT的TCP端口列表Preferences - Protocols - MQTT - TCP ports。我自己的习惯是填1883,8883,28883这样的常用端口方便以后直接抓。这个坑不算难但很容易耽误时间。如果看到抓包里TCP payload是16进制却一直没有MQTT解析树先不要怀疑是协议问题多半就是端口没有被识别。5.2 本机Broker抓不到回环流量很多人第一次抓本地MQTT开了Wireshark发现怎么抓都是空的。原因通常是本机和本地broker之间的流量走的是loopback回环接口不走物理网卡。Windows下需要安装Npcap时勾选“Support loopback traffic”选项抓包接口里会有一个专门的回环适配器Linux下要选择lo接口。如果已经选对接口但还是看不到检查一下捕获过滤器是否写死了物理网卡的IP比如host 192.168.1.100但本地连接实际上走的是127.0.0.1那当然啥都抓不到。这个坑和MQTT本身没关系但确实是本地抓包最常见的卡点。5.3 TLS加密后一屏乱码MQTT over TLS通常用8883端口此时Wireshark看到的TCP payload是密文MQTT解析树完全是空的。想调试明文流量两个合规范的思路一是在测试环境临时关闭TLS改成1883端口抓包等业务问题复现后再恢复加密二是如果客户端是自己写的并且是自有设备、受控测试环境可以在启动客户端时设置SSLKEYLOGFILE环境变量导出TLS会话密钥然后在Wireshark的Preferences - Protocols - TLS里配置这个日志文件让Wireshark解密TLS流量并继续解析MQTT。这里必须强调一个边界解密TLS只能针对自己拥有和管理的设备、测试环境并且要遵守相关合规要求。不要试图对别人的流量做解密更不要在生产环境乱抓包。我自己的做法是能用明文复现的问题尽量在测试环境明文抓包解决实在需要分析TLS握手细节只看TLS层不做内容解密。5.4 MQTT over WebSocket无法直接过滤浏览器客户端或者一些前端设备会用WebSocket来承载MQTT典型端口是8083或8084。抓包时Wireshark可能把上层协议识别成WebSocket导致mqtt过滤器什么都过滤不出来。处理办法是找到WebSocket数据帧右键Decode As尝试把它设置为MQTT。新版Wireshark对MQTT over WebSocket的支持已经好了不少但不同版本行为有差异。实操上我一般先过滤tcp.port 8083然后看WebSocket的payload开头。MQTT CONNECT报文的固定报头第一个字节是0x10如果看到payload从10开始后面跟着“MQTT”四个可见字符那基本可以确定这就是MQTT over WebSocket。再用Decode As把它解析成MQTT后面的字段就都能正常显示了。5.5 只有TCP握手、没有MQTT报文如果显示过滤器写的是mqtt却只能看到TCP三次握手看不到任何MQTT报文通常有两种可能。第一种是服务端端口没有在监听客户端连接后直接被RST或者超时。本地可以用netstat -an | findstr 1883或者Linux的ss -lntp确认端口状态。如果端口没监听当然不会有MQTT。第二种是客户端连接到了一个中间代理或者负载均衡器这个服务并不会转发MQTT流量。此时要看Wireshark里的目标IP和端口是不是预期地址再用mosquitto_pub本地连一下broker对比一下连接行为就能定位。6. 把解析能力沉淀成离线脚本6.1 用tshark批量提取主题和载荷Wireshark的图形界面适合交互式排查但当你手里有大量pcap文件时手动一个个点会疯。这时候可以用Wireshark自带的命令行工具tshark。一条命令就能把MQTT报文里的关键字段批量导出tshark -r mqtt.pcap -Y mqtt.msgtype 3 -T fields \ -e frame.number -e mqtt.topic -e mqtt.qos -e mqtt.payload \ -E headery -E separator,这条命令会列出文件中所有PUBLISH报文的帧号、主题、QoS和payload。输出成CSV之后后续丢给Excel或者Python都很方便。如果没有某个字段导出时会是空值不影响整体。对比图形界面这种方式的优势是稳定、可重复同一份pcap可以反复跑不同的提取条件。6.2 写一套简单的MQTT消息巡检脚本如果你要处理的是周期性问题比如设备每天凌晨掉线手动盯屏幕抓包太累可以把tshark放进自动化脚本里。思路很简单定时抓一段时间的pcap然后导出MQTT报文统计。比如在线抓包10分钟tshark -i any -f tcp port 1883 -Y mqtt -T fields \ -e mqtt.msgtype -e mqtt.topic -e mqtt.clientid \ -E headery -E separator, result.csv拿到result.csv之后可以在脚本里统计有没有CONNACK返回非0、有没有异常的重复PUBLISH、有没有长时间缺少PINGREQ。我建议不要为了用脚本而用脚本日常单点问题直接开Wireshark更快。但当现象变成“每天凌晨三点设备集体掉线”这种定时问题时脚本的价值就非常明显它能帮你留下证据也能在大批量终端的场景里快速缩小范围。6.3 从MQTT协议解析延伸到Modbus/485指令调试回到经常被问到的一个问题MQTT如何给485设备发指令、读取数据。实际落地项目里MQTT报文只是货物搬运工真正的业务在payload里。用Wireshark看MQTT只能判断消息有没有从A到B但要知道指令内容对不对还得结合Modbus协议文档逐字节去拆。操作思路是抓包后过滤出指定topic的PUBLISH报文复制payload的十六进制按Modbus报文格式去拆第一个字节是从站地址第二个字节是功能码后面是寄存器地址、寄存器数量、数据长度、数据和CRC校验。拆完之后和网关上实际发出的报文做对比。这里有一个很实用的经验网关把Modbus指令打包进MQTT时一条指令对应的消息往往只有几十字节。用Wireshark的Follow TCP Stream能看到这个TCP连接里完整的报文交互记录比业务日志里截断的十六进制可靠很多。曾经有同事调485继电器控制发现指令发出去了但设备没动作。抓包后看到MQTT publish的topic正确payload看起来也正常但发给目标设备的Modbus帧CRC校验总是不对。后来比对才发现网关在组帧时把校验字节和前面的数据弄混了。没有抓包光看上层日志根本定位不了这种底层问题。7. 写在最后我自己的习惯是项目里只要涉及MQTT调试就先不急着改代码而是把抓包环境固定下来本地broker、预设显示过滤器、tshark导出模板都准备成一套固定流程。这样每次遇到问题都可以在一个小时内有一个初步结论。有一次帮同事调无人值守设备抓包后发现就是CONNECT里的Keep Alive被配置成0导致平台侧判定离线改完配置立刻正常。整个过程从抓包到定位不到十分钟比逐行翻日志高效太多。最后分享一个小技巧在Wireshark里可以把自己常用的显示过滤器保存成按钮比如下面这条(mqtt.msgtype 1 || mqtt.msgtype 2) tcp.port 1883这样只要看到连接相关的报文一键就能过滤出来。另外强烈建议你动手搭一遍本地Mosquitto加上两个MQTT客户端的测试环境亲手抓一次CONNECT、PUBLISH和SUBSCRIBE的完整报文。很多协议细节只看文档很难真正记住但亲手抓过几次包之后MQTT的交互过程就会在脑子里变得非常清晰。抓包这件事最后拼的就是对报文结构的熟悉程度和你踩过的坑够不够多。
返回列表