跳到主要内容

打开导航

返回博客

用 Wireshark 调试 MQTT:同一份抓包在 Mqttable 中如何排查与复测

客户端掉线、订阅收不到消息、QoS 1 重复接收该怎么查?跟着 Wireshark 过滤器和 Mqttable 操作步骤定位问题,再用 Replay 复测。

作者:
Mqttable Team
发布:
手绘概念图:同一份 MQTT 抓包分别进入报文查看和问题分析工作台。

MQTT 客户端显示已连接,过一会儿又掉线;订阅成功了,消息却迟迟不来;明明只发布了一次,接收端却看到了两条。遇到这些问题,抓包里该先找什么?

这篇教程从这三个场景入手。你会先用 Wireshark 找到关键报文,再把同一份抓包放进 Mqttable,沿着 Client ID、连接和问题提示继续排查。修好以后,再用 Replay 发一次消息,确认接收端恢复正常。

打开抓包,先找到 MQTT 报文

准备好 Wireshark、Mqttable 和一份 .pcap 或 .pcapng 文件。下面以 mqtt-debug-lab.pcapng 为例;分析自己的文件时,把示例里的端口、Client ID 和 Topic 换成你的值。

如果还没有抓包,打开 Wireshark,选择 MQTT 流量经过的网卡。macOS 上抓本机客户端与本机 Broker 之间的通信,可以选 lo0;连接另一台机器上的 Broker 时,选实际使用的 Wi-Fi 或有线网卡。抓包过滤器填写 tcp port 1883,或换成你的 Broker 端口。先开始抓包,再连接客户端、订阅和发布,复现问题后停止并保存文件。

打开文件后,在显示过滤器中输入:

text
mqtt

如果打开文件时协议列只显示 TCP,或者 mqtt 筛不出报文,检查端口是否需要手动解码。示例使用 38885,你可以在 Analyze → Decode As… 中把该 TCP 端口指定为 MQTT,然后重新应用过滤器。

保留 No.、Time、Source、Destination 和 Info 列。展开 MQTT 报文详情,右键 Client ID、Packet Identifier、QoS 或 DUP 字段,选择 Apply as Column,就能在列表里直接比较它们。字段名称可查 Wireshark 的 MQTT 过滤器参考。

接着在 Mqttable 打开 PCAP → Open Capture…,导入同一份文件,等状态变成 Done。Overview 会列出需要关注的问题,后面三个场景都从这里继续。

在 Mqttable 的 PCAP Overview 查看问题入口和检查状态

问题一:客户端刚连上,为什么又掉线?

先检查是否有两个客户端用了同一个 Client ID。比如两个服务副本都叫 lab-dup,后来连接的那个就可能把旧连接挤下线。

在 Wireshark 输入:

text
mqtt.msgtype == 1 && mqtt.clientid == "lab-dup"

找到 CONNECT 后,展开 TCP 详情查看 stream 编号。示例的两个 CONNECT 分别在 frame 81 和 93,对应 TCP stream(连接编号)4 和 5。用这两个编号筛出完整连接:

text
tcp.stream == 4 || tcp.stream == 5

现在能看到这两个客户端各自的完整连接。旧连接在第二个 CONNECT 到来后,收到 frame 95 的 DISCONNECT 0x8E,详情为 Session taken over。这是 MQTT 5 的会话接管提示,结合两个相同 Client ID 的 CONNECT,排查重点就落在客户端身份上了。

在 Mqttable 中找到这两个连接

打开 Issues → Overlapping client id connections。这里已经把相同 Client ID 的重叠连接放在一起,你可以直接看到 lab-dup 和两条连接的端点。

Overlapping client id connections 列出 lab-dup 的两个连接

再进入 Connections,选择 lab-dup。列表会只留下这个客户端的两条 TCP 连接;点击其中一条 Flow,可以继续看它的报文和相关问题。

选择 lab-dup 后查看该 Client ID 的两条 TCP 连接

回到 Issues,打开 Session takeover signal,选择 Open decoded MQTT packets,再点击 DISCONNECT。右侧 Message Details 显示 0x8E - Session taken over。

点击 DISCONNECT 后查看 Session taken over 原因

Wireshark 这边,你需要从 CONNECT 找到 stream,再看断开原因;Mqttable 则把 Client ID、两条连接和断开报文连在了一起。查多客户端抓包时,少了手工整理这些对应关系的步骤。

接下来检查服务副本、启动参数或 Client ID 生成逻辑,让每个需要同时在线的客户端使用不同的身份。再连接一次,看看旧客户端是否还会被踢下线。关于这个原因码,可参照 MQTT 5 规范。

问题二:订阅成功,为什么还是收不到消息?

先别急着改订阅 QoS。连接和订阅都成功后,发布仍然可能被 Broker 拒绝。

示例里,接收端订阅了 lab/#,可以收到其他 Topic 的消息,但一直等不到 lab/denied/telemetry。发布端也显示连接成功,所以这次要查的是 PUBLISH 后面的响应。

在 Wireshark 找到这个 Topic 的 PUBLISH:

text
mqtt.msgtype == 3 && mqtt.topic == "lab/denied/telemetry"

frame 123 是 QoS 1 发布,Packet Identifier(报文标识符)为 1。沿着同一条 TCP stream 看后续 PUBACK,frame 125 返回 0x87 / Not authorized。也可以直接筛出这类拒绝:

text
mqtt.msgtype == 4 && mqtt.puback.reason_code == 0x87

在 Mqttable 中查看发布为什么被拒绝

打开 Issues → QoS acknowledgement error reason,可以看到 Not authorized (135)。点击报文入口,就能继续查看对应的 PUBACK。

QoS acknowledgement error reason 显示发布被拒绝的原因

这里要检查发布账号是否有这个 Topic 的写权限,以及程序有没有发错 Topic。示例的权限规则允许写 lab/allowed/#,把发布路径改成 lab/allowed/telemetry 后,接收端就收到了消息。

0x87 是 MQTT 5 PUBACK 可以携带的原因码;使用 MQTT 3.1.1 时,PUBACK 没有这一字段,发布权限还要结合 Broker 日志检查。

复测时同时看两个地方:发布端是否收到成功 PUBACK,接收端是否真的收到那条消息。示例中的 mosquitto_pub 虽然打印了拒绝原因,退出码却是 0,只看进程退出码容易漏掉这个问题。

问题三:一条 QoS 1 消息,为什么收到两次?

这次发布端只调用了一次 publish,但接收日志里出现了两条相同的数据:

json
{ "case": "ack-loss", "seq": 1 }

先查是否发生了 MQTT 重投。在 Wireshark 中筛出 DUP 标志为 1 的 PUBLISH:

text
mqtt.msgtype == 3 && mqtt.dupflag == 1

示例的 frame 173 符合条件。它和 frame 139 的原始发布使用同一个 Packet Identifier 1、相同 Topic 和 Payload,但原始报文的 DUP 为 False,重投的 DUP 为 True。

把两条发布连接放到一起看:

text
tcp.stream == 7 || tcp.stream == 9

第一次发布后,发布端没有收到 PUBACK。客户端断线重连,恢复持久会话后重投了未确认的消息,随后收到 PUBACK。接收端第一次已经收到数据,重投又带来了一次接收。

这个场景使用 MQTT 3.1.1 持久会话。规范中的重投规则说明了重连时如何处理未确认的报文;不要仅凭“等了一段时间”判断客户端一定应该重发。

在 Mqttable 中检查确认链路

打开 Issues → QoS 1 PUBLISH without PUBACK。问题详情会定位到原始发布,并提示你检查确认链路。

从 QoS 1 PUBLISH without PUBACK 进入缺少确认的发布报文

点击 Open decoded MQTT packets,检查这条连接上的 PUBLISH 和响应,再查看重连后的连接。这里缺的是 MQTT PUBACK,TCP ACK 不能代替它;Wireshark 的 tcp.analysis.retransmission 也不能直接当成 MQTT 重投。

排查自己的抓包时,还要确认采集覆盖了收发两个方向,且没有提前停止。本例在中继里阻断了 PUBACK;如果只是抓包漏了响应,修复方向就完全不同。

查明原因后,恢复确认链路,同时让接收程序按业务 ID 识别重复。DUP 是协议层的线索,是否重复处理了业务,还要看接收程序自己的日志。

修完以后,再发一次验证

修改 Client ID、Topic 权限或网络规则后,重新连接、发布并抓一份新的文件。在 Mqttable 导入它,看看原来那几个问题是否还会出现。

重新导入修复后的抓包,查看原有问题是否还出现

有的检查会显示 Not enough evidence 或 No traffic。前者表示这份文件还不够判断,后者表示没抓到相关流量;需要确认某项行为时,可以针对它再做一次操作,而不是把这两个状态当成通过。

在 Checks 点开相关检查,还能查看规则说明和关联报文。Free 显示前 5 个诊断详情,汇总、问题列表和报文等视图仍可查看。

在 Checks 中查看相关规则及不同检查状态

用 Replay 重发刚才那条消息

如果你想继续用相同 Topic 和 Payload 复测,可以把需要重发的消息整理成 TraceGrid CSV,再导入 Replay。通过 Packets → Export Trace 导出后,保留表头和完整列,只留下那条原始出站 PUBLISH,别把重投或接收端的副本也选进去。

示例使用 replay-reviewed-single-publish.csv,只保留 lab-qos1-publisher 在 15:35:42.562255+08:00 发出的原始消息,对应 Wireshark 的 frame 139。

  1. 确认阻断 PUBACK 的规则已移除,并让接收端保持订阅。
  2. 先在 Connections 保存 Broker 并创建客户端,再到 Replay 导入 CSV,选择你要复测的 Broker。示例目标是 Wireshark MQTT Lab,地址为 mqtt://127.0.0.1:38885。
  3. 点击 Dry Run,检查目标、Topic、消息数量和客户端身份。这个示例只计划发布一次,并使用隔离的 Replay Client ID,避免与原客户端争用身份。出现凭证回退等提醒时,确认目标配置符合预期再继续。
Replay Dry Run 展示目标、单条发布计划和隔离 Client ID
  1. 点击 Run Replay,到 Run Trace 检查 PUBLISH 是否收到成功 PUBACK,再到接收端确认同一条消息是否到达。
在 Run Trace 中检查 PUBLISH 与成功 PUBACK

示例的 Run Trace 显示发布完成,接收端收到这条消息一次。Replay 重发的是 MQTT 客户端动作;客户端身份冲突、网络故障和业务处理结果,仍需要按你的测试目标分别检查。需要自动判断接收结果时,可以继续配置 Replay 的 Expected result。

什么时候用 Wireshark,什么时候用 Mqttable?

左右滚动表格,查看其余列。

你现在要做什么可以从哪里开始
看 TCP 重传、窗口、连接关闭或其他协议Wireshark,继续查看底层通信细节
查一个 Client ID 对应哪些 MQTT 连接Mqttable 的 Connections,按客户端缩小范围
从掉线、拒绝响应或缺少确认找到相关报文Mqttable 的 Issues,再进入 Packets
看某项 MQTT 检查为什么需要处理Mqttable 的 Checks,查看规则和报文
修复后用相同消息再验证一次核对 CSV 后使用 Replay,并检查接收端

Wireshark 适合深入查看原始网络流量。Mqttable 在 MQTT 排障中把客户端、连接、问题提示和报文放在一起,查完以后也有继续复测的入口。你可以用 Wireshark 确认底层细节,再用 Mqttable 跟进 MQTT 的连接和消息流程。

抓包里没有你要找的消息怎么办?

如果 mqtt 筛不出报文,先检查网卡、端口和 Decode As 设置。使用 TLS 时,报文内容是加密的,仅仅把端口指定为 MQTT 并不能解密;你需要能读取明文的抓取位置,或有权限使用的解密材料。

如果能看到 CONNECT,却筛不出后续 PUBLISH,不要一直用 mqtt.clientid 过滤。PUBLISH 通常不再携带 Client ID,可以从 CONNECT 找到 TCP stream,再沿着同一条连接检查。

分享抓包前检查密码、Topic 和 Payload 中是否有敏感信息。分析 Remote Runtime 上的文件时,上传会把文件发送到该 Runtime;界面隐藏密码并不等于原始文件已经脱敏。