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:
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 brokerDespués genera el tráfico que quieres analizar, por ejemplo un suscriptor y un publicador en un topic único:
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:
| Objetivo | Filtro de visualización |
|---|---|
| Todos los paquetes MQTT | mqtt |
| Solo CONNECT / CONNACK | mqtt.msgtype == 1 || mqtt.msgtype == 2 |
| Solo PUBLISH | mqtt.msgtype == 3 |
| Un topic o prefijo de topic | mqtt.topic contains "testmqtt/wireshark" |
| El CONNECT de un cliente | mqtt.clientid == "sensor-01" |
| Conexiones rechazadas | mqtt.conack.val != 0 |
| Publicaciones retenidas | mqtt.retain == 1 |
| PUBLISH con QoS 1 y 2 | mqtt.msgtype == 3 && mqtt.qos > 0 |
| Tráfico de keep-alive | mqtt.msgtype == 12 || mqtt.msgtype == 13 |
| Conexiones reseteadas | tcp.flags.reset == 1 |
| Retransmisiones TCP | tcp.analysis.retransmission |
Los valores numéricos de mqtt.msgtype son los tipos de paquete de control de MQTT:
| Valor | Paquete | Valor | Paquete |
|---|---|---|---|
| 1 | CONNECT | 8 | SUBSCRIBE |
| 2 | CONNACK | 9 | SUBACK |
| 3 | PUBLISH | 10 | UNSUBSCRIBE |
| 4 | PUBACK | 11 | UNSUBACK |
| 5 | PUBREC | 12 | PINGREQ |
| 6 | PUBREL | 13 | PINGRESP |
| 7 | PUBCOMP | 14 | DISCONNECT |
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:
MQTTcon 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:
- Filtra
mqtt.topic contains "testmqtt/wireshark"y encuentra el PUBLISH del publicador. Anota su Message Identifier si es QoS 1 o 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. - 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:
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:
node --tls-keylog=/tmp/mqtt-tls-keys.log publish.jsLos 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:
# 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_displayedLa ú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íntoma | Qué 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 1 | PUBLISH sin PUBACK y, tras reconectar, un duplicado (flag DUP activo) |
| Desconexiones aleatorias | Huecos en los PINGREQ, retransmisiones TCP y luego FIN o RST de un lado |
| Los mensajes grandes desaparecen | Un 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 nada | SUBACK 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.

