Pruebas de brokers

Depurar MQTT con Wireshark: lee cada paquete

Captura y decodifica tráfico MQTT en Wireshark: filtros para CONNECT, PUBLISH y CONNACK, descifrado de TLS en el puerto 8883 con un key log y recetas de tshark.

Actualizado · 9 min de lectura

Depurar MQTT con Wireshark: lee cada paquete

Los logs te dicen lo que un cliente cree que envió. Una captura de paquetes te dice lo que realmente pasó por el cable. Wireshark trae un disector MQTT completo: decodifica los flags de CONNECT, los nombres de topic, el QoS, los packet identifiers, las propiedades de MQTT 5 y los reason codes, así que en segundos ves si un mensaje perdido nunca se publicó, nunca se confirmó o nunca se entregó.

Esta guía cubre cómo capturar tráfico MQTT, los filtros de visualización que usarás a diario, cómo descifrar TLS en el puerto 8883 y algunas líneas de tshark para cuando estás en un servidor sin interfaz gráfica.

Captura el tráfico correcto

Arranca Wireshark en la máquina que ejecuta el cliente (o el broker, si lo controlas) y elige la interfaz por la que pasa el tráfico: tu adaptador Wi-Fi o Ethernet para brokers remotos, la interfaz loopback para un broker en localhost.

Usa un filtro de captura para que el archivo no crezca demasiado. Los filtros de captura usan sintaxis BPF y se configuran antes de pulsar start:

Filtros de captura
tcp port 1883                        # MQTT sin cifrar
tcp port 1883 or tcp port 8883       # sin cifrar y TLS
host broker.connectmq.com            # todo lo que va y viene del broker

Después genera el tráfico que quieres analizar, por ejemplo un suscriptor y un publicador en un topic único:

Generar tráfico de prueba
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/wireshark/#' -q 1 -v &
mosquitto_pub -h broker.connectmq.com -p 1883 -t 'testmqtt/wireshark/hello' -q 1 -m 'ping'

Si capturas en un servidor, tcpdump -w suele ser más cómodo y luego abres el archivo en Wireshark; consulta capturar MQTT con tcpdump.

Los filtros de visualización que necesitas

Los filtros de visualización se aplican después de la captura y usan los nombres de campo de Wireshark. Los campos principales del disector MQTT:

ObjetivoFiltro de visualización
Todos los paquetes MQTTmqtt
Solo CONNECT / CONNACKmqtt.msgtype == 1 || mqtt.msgtype == 2
Solo PUBLISHmqtt.msgtype == 3
Un topic o prefijo de topicmqtt.topic contains "testmqtt/wireshark"
El CONNECT de un clientemqtt.clientid == "sensor-01"
Conexiones rechazadasmqtt.conack.val != 0
Publicaciones retenidasmqtt.retain == 1
PUBLISH con QoS 1 y 2mqtt.msgtype == 3 && mqtt.qos > 0
Tráfico de keep-alivemqtt.msgtype == 12 || mqtt.msgtype == 13
Conexiones reseteadastcp.flags.reset == 1
Retransmisiones TCPtcp.analysis.retransmission

Los valores numéricos de mqtt.msgtype son los tipos de paquete de control de MQTT:

ValorPaqueteValorPaquete
1CONNECT8SUBSCRIBE
2CONNACK9SUBACK
3PUBLISH10UNSUBSCRIBE
4PUBACK11UNSUBACK
5PUBREC12PINGREQ
6PUBREL13PINGRESP
7PUBCOMP14DISCONNECT

El tipo 15 es AUTH (solo MQTT 5). Para la estructura a nivel de bytes detrás de estos campos, consulta estructura de los paquetes MQTT.

Leer un CONNECT y un CONNACK

Selecciona un paquete CONNECT y despliega MQ Telemetry Transport Protocol. Verás:

  • Nombre y versión del protocolo: MQTT con versión 4 (3.1.1) o 5. Si el broker no acepta la versión, responde con un error de unacceptable protocol version.
  • Connect flags: clean session / clean start, will flag, will QoS, will retain, y si hay username y password.
  • Keep alive: el intervalo en segundos que el cliente prometió.
  • Client ID y, en el puerto 1883 sin cifrar, el username y el password en texto plano.
  • Propiedades de MQTT 5, como session expiry interval, receive maximum y maximum packet size.

El CONNACK que sigue lleva el resultado. En MQTT 3.1.1 es un return code (0 significa aceptado); en MQTT 5 es un reason code como 0x86 Bad User Name or Password o 0x87 Not authorized, a veces con un reason string. Busca cualquier valor con la herramienta de reason codes. El desglose campo a campo está en el paquete CONNECT de MQTT.

Si no hay CONNACK y el broker envía un RST o FIN de TCP, el broker rechazó la conexión antes de llegar a MQTT: a menudo un listener TLS que recibe MQTT sin cifrar, un paquete mal formado o un límite de conexiones.

Seguir un mensaje de extremo a extremo

Para demostrar dónde se perdió un mensaje, síguelo a través de la captura:

  1. Filtra mqtt.topic contains "testmqtt/wireshark" y encuentra el PUBLISH del publicador. Anota su Message Identifier si es QoS 1 o 2.
  2. Filtra mqtt.msgid == <id> para ver el PUBACK (QoS 1) o la secuencia PUBREC / PUBREL / PUBCOMP (QoS 2). Sin PUBACK, el broker nunca lo aceptó: revisa las ACL y el tamaño del payload.
  3. Si también capturaste del lado del suscriptor, busca el PUBLISH que entregó el broker. El packet identifier es distinto, porque cada salto tiene el suyo.

Statistics → Flow Graph con "Displayed packets" dibuja el intercambio como un diagrama de secuencia, la forma más rápida de detectar un acuse de recibo que falta.

Descifrar MQTT sobre TLS (puerto 8883)

En el puerto 8883 solo verás registros TLS hasta que Wireshark tenga las claves de sesión. La forma estándar de obtenerlas es un archivo de key log, que muchas librerías TLS pueden escribir.

Python (paho-mqtt 2.x): define keylog_filename en el contexto SSL:

tls_keylog.py
import ssl
import paho.mqtt.client as mqtt

ctx = ssl.create_default_context()
ctx.keylog_filename = "/tmp/mqtt-tls-keys.log"   # Python 3.8+

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.tls_set_context(ctx)
client.connect("broker.connectmq.com", 8883)
client.loop_start()
client.publish("testmqtt/wireshark/tls", "hello over TLS", qos=1).wait_for_publish()
client.loop_stop()
client.disconnect()

Node.js (MQTT.js): usa el flag integrado, sin cambiar el código:

Key log en Node.js
node --tls-keylog=/tmp/mqtt-tls-keys.log publish.js

Los navegadores y curl respetan la variable de entorno SSLKEYLOGFILE. Luego, en Wireshark, abre Edit → Preferences → Protocols → TLS y pon en (Pre)-Master-Secret log filename la ruta del key log. Los registros TLS del 8883 se convierten en paquetes MQTT decodificados, tanto con TLS 1.2 como con 1.3.

Si lo que falla es el propio handshake TLS, no necesitas descifrar nada: el registro de alerta (por ejemplo Unknown CA o Handshake Failure) está a la vista. Consulta TLS handshake failed.

Puertos no estándar y WebSockets

Wireshark solo asume MQTT en sus puertos registrados. Si tu broker escucha, por ejemplo, en el 11883, haz clic derecho en un paquete, elige Decode As… y asigna ese puerto TCP a MQTT.

Para MQTT sobre WebSockets (8083 / 8084), Wireshark muestra la petición HTTP de upgrade y los frames WebSocket; los paquetes MQTT van dentro de frames binarios. Para apps de navegador, suele ser más rápido leer esos frames en el propio navegador: consulta depurar MQTT en Chrome DevTools.

Recetas de tshark

tshark es el gemelo de línea de comandos de Wireshark y usa los mismos filtros:

Recetas de tshark
# En vivo: imprime tiempo, tipo de paquete, topic y payload
tshark -i any -f "tcp port 1883" -Y mqtt \
  -T fields -e frame.time_relative -e mqtt.msgtype -e mqtt.topic -e mqtt.msg

# Desde un archivo: cuenta los PUBLISH por topic
tshark -r mqtt.pcap -Y "mqtt.msgtype == 3" -T fields -e mqtt.topic | sort | uniq -c | sort -rn

# Intervalos entre pings de keep-alive (busca valores cercanos al límite de keep-alive)
tshark -r mqtt.pcap -Y "mqtt.msgtype == 12" -T fields -e frame.time_delta_displayed

La última es útil para los timeouts de keep-alive: si los intervalos superan 1,5 veces el valor de keep-alive, el broker tiene derecho a desconectar al cliente.

Qué buscar según el síntoma

SíntomaQué muestra la captura
El cliente "no conecta"CONNECT seguido de un CONNACK con código distinto de cero, o un RST sin CONNACK
Mensajes perdidos con QoS 1PUBLISH sin PUBACK y, tras reconectar, un duplicado (flag DUP activo)
Desconexiones aleatoriasHuecos en los PINGREQ, retransmisiones TCP y luego FIN o RST de un lado
Los mensajes grandes desaparecenUn PUBLISH grande seguido del cierre de la conexión por el broker: compáralo con el maximum packet size y revisa tamaños con la calculadora de tamaño de paquete
El suscriptor no recibe nadaSUBACK con código de fallo 0x80 o un topic filter que no coincide

Para reproducir un caso rápidamente, ejecuta la captura mientras publicas desde el cliente MQTT online y mira cómo llegan los paquetes en tiempo real.

Wireshark sustituye las suposiciones por evidencias. Captura de forma acotada, filtra por tipo de paquete y topic, descifra TLS solo cuando lo necesites y guarda una captura de una sesión que funciona: comparar una captura rota con una buena suele ser el diagnóstico más rápido de todos.

Preguntas frecuentes

¿Cuál es el filtro de visualización de Wireshark para MQTT?

Usa 'mqtt' para mostrar todos los paquetes MQTT decodificados. Acótalo con campos como mqtt.msgtype == 3 para PUBLISH, mqtt.topic contains "sensors" para un topic, o mqtt.clientid == "sensor-01" para el CONNECT de un cliente.

¿Por qué Wireshark muestra TLS en lugar de MQTT en el puerto 8883?

Los paquetes MQTT están cifrados. Wireshark solo puede decodificarlos si le das las claves de sesión TLS: haz que el cliente escriba un archivo de key log (SSLKEYLOGFILE, keylog_filename en Python, --tls-keylog en Node) y apunta las preferencias TLS de Wireshark a ese archivo.

¿Por qué mi tráfico MQTT aparece como datos TCP sin decodificar?

Wireshark decodifica MQTT automáticamente solo en el puerto estándar 1883 (y en el 8883 tras descifrar). Para cualquier otro puerto, haz clic derecho en un paquete, elige Decode As y asigna el puerto TCP a MQTT.