Espressif publicó en su blog para desarrolladores una guía para convertir un SoC de la marca con Zephyr en un sniffer de IEEE 802.15.4 y revisar en vivo, desde Wireshark, las tramas MAC de Thread, Zigbee y 802.15.4 puro. El puente entre la placa y Wireshark es un plugin extcap pequeño.
De las ondas de radio a Wireshark
Cuando una red Thread se niega a formarse o un dispositivo Zigbee nunca responde, la primera pregunta siempre es la misma. ¿Qué hay realmente en el aire? Un sniffer de paquetes la responde. Si la placa tiene un chip de Espressif con radio IEEE 802.15.4, el sniffer es lo más barato y útil que se puede construir con ella. También es la prueba más directa de un controlador de radio, porque usa solo la ruta de recepción y nada más.
El artículo muestra cómo un ejemplo de Zephyr convierte un SoC de Espressif en un sniffer 802.15.4 en modo promiscuo que alimenta Wireshark en tiempo real. La cadena completa tiene cuatro eslabones:
1. La radio 802.15.4, manejada por el controlador de Zephyr para ESP32.
2. El sniffer IEEE 802.15.4 (samples/net/ieee802154/sniffer), que recibe los paquetes en un netbuf.
3. El plugin extcap (samples/net/ieee802154/sniffer/scripts), que recibe las tramas por UART.
4. Wireshark, que recibe un flujo pcap.
El firmware corre en todo SoC de Espressif que tenga radio 802.15.4. Se compiló y probó en estas placas:
esp32h2_devkitmesp32c6_devkitc/esp32c6/hpcoreesp32c5_devkitc/esp32c5/hpcore
El ESP32 original y la serie ESP32-S no tienen radio 802.15.4, así que no sirven para esto.
Además del paso a paso, el artículo explica por qué el diseño quedó como quedó y cuenta las pocas cosas que salieron mal en el camino. La mayoría no son propias de 802.15.4 y le van a pegar a cualquier herramienta serial que corra sobre una placa de desarrollo de Espressif.
Las piezas del sistema
Son tres partes, y cada una hace lo mínimo posible:
- El firmware, en
samples/net/ieee802154/sniffer/. Pone la radio en recepción promiscua e imprime cada trama que escucha como una línea de texto. Usa solo la API genérica de radio de Zephyr (struct ieee802154_radio_api), así que no tiene nada específico de Espressif. - El UART de consola. Las líneas de captura, la shell de Zephyr y los mensajes de log comparten el único puerto serie que la placa expone por USB. No hace falta un segundo UART ni un segundo cable.
- El plugin extcap, en
samples/boards/espressif/wireshark/ieee802154_sniffer.py. Es un solo archivo Python, escrito para este ejemplo a partir de las especificaciones de extcap y de IEEE 802.15.4 TAP. Wireshark lo arranca, el plugin maneja el sniffer a través de la shell, convierte las líneas de captura en un flujo pcap y se lo devuelve a Wireshark.
El firmware vive entre los ejemplos genéricos de red porque cualquier radio 802.15.4 con controlador en Zephyr puede correrlo. El plugin está bajo samples/boards/espressif/ porque se encarga de particularidades de Espressif, como el reemplazo del campo FCS por LQI y RSSI que hace el blob del controlador.
¿Qué hace el firmware por dentro?
El ejemplo se compila con CONFIG_IEEE802154_RAW_MODE=y, que deja fuera del build la capa L2 de 802.15.4, la interfaz de red y los sockets. El controlador de radio igual entrega cada trama recibida a net_recv_data(), y como en modo crudo nada de la pila define esa función, la define la propia aplicación. Esa única función es todo el vínculo entre el controlador y el ejemplo:
/* El modo crudo (CONFIG_IEEE802154_RAW_MODE) deja la pila de red
* fuera del build, así que la ruta de recepción del controlador
* aterriza directo acá. Corre en el hilo RX del controlador.
*/
int net_recv_data(struct net_if *iface, struct net_pkt *pkt)Todo lo demás pasa directo por struct ieee802154_radio_api, con start, stop, set_channel, configure y get_capabilities. Al arrancar, el ejemplo lee las capacidades del controlador. Si este reporta IEEE802154_HW_PROMISC, activa el modo promiscuo con IEEE802154_CONFIG_PROMISCUOUS. Después sintoniza la radio en el canal de arranque (CONFIG_NET_CONFIG_IEEE802154_CHANNEL) y empieza a recibir.
En los SoC de Espressif, el modo promiscuo apaga el filtro de tramas por hardware. Se entrega toda trama con CRC válido, sin importar su PAN ID, su dirección corta ni su dirección extendida.
Captura de tramas 802.15.4 crudas
La radio de Espressif revisa la suma de verificación de la trama (FCS) por hardware y descarta las que fallan. En las que pasan, escribe el RSSI y el LQI encima de los dos bytes del FCS en el búfer de recepción antes de que el controlador las vea. O sea que, cuando una trama llega al software, sus dos últimos bytes ya no son una suma de verificación.
En modo crudo, Zephyr incluye por defecto esos dos bytes (con IEEE802154_RAW_MODE activo, CONFIG_IEEE802154_L2_PKT_INCL_FCS queda en default y). El ejemplo lo apaga en prj.conf, de modo que la línea de captura lleva la PSDU sin ellos:
# La línea de captura lleva la PSDU sin FCS y la
# herramienta del host le agrega uno recalculado. La radio
# ya descartó las tramas con checksum incorrecto, y algunas
# radios, entre ellas la del ESP32, sobrescriben los dos
# bytes del FCS con RSSI y LQI, así que lo que el
# controlador entregaría no es un checksum.
CONFIG_IEEE802154_L2_PKT_INCL_FCS=nUn efecto secundario es que este sniffer solo muestra tramas buenas. Por esta ruta no se pueden capturar tramas corruptas ni nada por debajo de la PSDU (preámbulo, SFD, temporización de símbolos). Para trabajar con protocolos sobre Thread, Zigbee o MAC 802.15.4 puro eso no cambia nada, pero no es una herramienta de depuración de RF.
La ruta de recepción tiene dos etapas, para que un UART lento nunca bloquee al controlador de radio:
net_recv_data()corre en el hilo de recepción del controlador. Copia la PSDU, el RSSI, el LQI y la marca de tiempo en un mensaje pequeño, lo mete en unak_msgqde 16 entradas y vuelve de inmediato. Si la cola está llena, la trama se cuenta como perdida en vez de esperar.- Un hilo de envío saca los mensajes de la cola y los imprime con
uart_poll_out(). Los dígitos hexadecimales salen de a un byte, sin formatearlos antes en un búfer, porque una PSDU de largo máximo ocupa por sí sola cerca de 250 caracteres en hexadecimal.
Marca de tiempo, RSSI y LQI
Cada trama lleva tres datos que entrega el controlador:
- Marca de tiempo. El controlador del ESP32 la toma con
esp_timer_get_time()cuando la radio señala el delimitador de inicio de trama, así que se mide en microsegundos desde el arranque. El ejemplo activaCONFIG_NET_PKT_TIMESTAMPpara que quede dentro delnet_pkt. La placa no tiene noción de la hora real, de modo que el plugin ancla la primera trama que recibe al reloj del host y ubica cada trama siguiente en relación con esa. - RSSI, en dBm y con signo. Cuando el controlador no tiene valor, reporta
IEEE802154_MAC_RSSI_DBM_UNDEFINED(-32768). El ejemplo imprime ese valor en vez de omitir el campo, para que todas las líneas tengan la misma forma, y el plugin deja fuera el campo RSSI en la trama que escribe para Wireshark. - LQI, el indicador de calidad del enlace, de 0 a 255.
¿Cómo salen los paquetes de la placa?
Cada trama capturada se convierte en una línea en el UART de consola:
psdu: 41881234567890abcdef power: -47 lqi: 103 time: 123456789psdues la trama en hexadecimal en minúsculas, sin separadores ni FCS.poweres el RSSI en dBm.lqiva de 0 a 255.timeson los microsegundos desde el arranque.
Se usa texto plano porque el UART alcanza a seguir el ritmo de las tramas que recibe la radio. Las cuentas son estas:
- 802.15.4 funciona a 250 kbit/s, así que una trama de 127 bytes ocupa
127 * 8 / 250000= 4,06 ms en el aire. - Su línea de captura mide unos 290 caracteres. En 8N1 son 10 bits por carácter.
- A 115.200 baudios la línea tarda
294 * 10 / 115200= 25,5 ms, seis veces el tiempo en el aire. - A 921.600 baudios tarda 3,2 ms, apenas menos que el tiempo en el aire, así que se pueden seguir tramas de largo máximo una tras otra.
Un formato binario reduciría la línea a la mitad. Con eso 115.200 baudios seguiría siendo muy lento, y a 921.600 no hace diferencia. A cambio, se perdería la propiedad que simplifica todo el diseño, que las líneas de texto pueden compartir el UART con la shell y el log. El plugin ignora toda línea que no calce con el patrón de captura, así que un mensaje de log o un prompt de la shell en medio de una captura no hacen daño. Eso también permite que el plugin mande comandos a la shell y lea tramas por el mismo puerto, que es lo que extcap necesita.
Por eso el formato sigue siendo texto y lo que sube durante la captura es la velocidad en baudios.
Extcap, la puerta de entrada a Wireshark
Extcap es la interfaz de Wireshark para programas de captura externos. Al arrancar, Wireshark le hace unas pocas preguntas a cada ejecutable que encuentra en su carpeta de extcap, y con las respuestas el programa aparece como una interfaz de captura normal. Cuando se inicia una captura, Wireshark vuelve a ejecutar el programa con la ruta a un FIFO, y el programa escribe ahí un flujo pcap hasta que lo detienen.
La única decisión de fondo es el tipo de enlace. El plugin usa DLT 283, IEEE 802.15.4 TAP. TAP antepone a cada trama una cabecera pequeña de campos tipo-largo-valor (TLV), así que el canal, el RSSI y el LQI aparecen en Wireshark como campos por paquete que se pueden filtrar y ordenar. Si se corre por separado con --metadata none, el plugin escribe en cambio tramas sin cabecera con DLT 195, IEEE 802.15.4 con FCS, útil para comprobar que las tramas mismas se decodifican bien.
El puente hacia Wireshark
La ruta de captura del plugin es corta. Lee líneas del puerto serie, compara cada una con el patrón de captura, la convierte en un registro pcap y escribe el registro en el FIFO.
Lectura de líneas. No usa Serial.readline() de pySerial. Ese método viene de io.RawIOBase, lee un byte por llamada al sistema y, cuando el tiempo de espera vence a mitad de línea, devuelve el pedazo que tiene. El resto llega después como una línea aparte y ninguna de las dos mitades calza con el patrón. El LineReader del plugin, en cambio, lee lo que esté esperando (port.read(max(1, port.in_waiting))), separa el búfer por saltos de línea y guarda la cola incompleta para la vuelta siguiente. En una prueba donde cada línea se cortaba por la mitad con una pausa de 300 ms, el lector con búfer no perdió ninguna trama y readline() las perdió todas.
La cabecera TAP. Cada registro parte con una cabecera de 4 bytes (versión 0, un byte reservado y el largo total), seguida de los TLV, cada uno rellenado a múltiplos de 4 bytes. Con los cuatro TLV presentes, la cabecera mide 36 bytes.
El FCS, regenerado. El firmware manda las tramas sin FCS y el plugin calcula uno y lo agrega al final, porque las demás herramientas que leen la captura esperan tramas completas. Es el CRC-16 estándar de 802.15.4, con polinomio reflejado 0x8408, semilla cero y agregado en little endian.
Para la PSDU 41880134127856bc9adeadbeef el resultado es 0x2abd, y Wireshark muestra FCS: 0x2abd (Correct). Hay que tener claro qué significa ese valor. La radio revisó el checksum real antes de sobrescribirlo, así que el regenerado siempre coincide con la trama, pero no es el checksum que llegó por el aire.
Los bytes agregados y el TLV del tipo de FCS van juntos. Sin el TLV, el decodificador TAP de Wireshark asume que la trama no tiene FCS y muestra los dos bytes del CRC como carga útil (Data: deadbeefbd2a en vez de deadbeef más un FCS correcto). Se agregan los dos o ninguno.
¿Cómo se instala?
Se compila y graba el firmware, y luego se instala el plugin en la carpeta personal de extcap de Wireshark. Wireshark muestra esa carpeta en Help > About Wireshark > Folders > Personal Extcap path:
mkdir -p <personal-extcap-path>
cp samples/net/ieee802154/sniffer/scripts/espressif_ieee802154_sniffer.py <personal-extcap-path>/
chmod +x <personal-extcap-path>/espressif_ieee802154_sniffer.pyLos comandos completos para compilar y grabar el firmware, y la selección de canal en tiempo de ejecución, están en la guía original de Espressif.




