iiOTESP32 Brasil
Modo de contingencia: las páginas públicas y las descargas siguen disponibles; el inicio de sesión, la comunidad y los datos del servidor pueden no estar disponibles.
Volver al catálogo de tutoriales
Ilustração: um roteador Wi-Fi envia tráfego por ondas sem fio a uma placa ESP32-S3 central, que encaminha a conexão até um celular, representando o roteamento NAPT e o repetidor Wi-Fi
Imagem ilustrativa gerada por IA
AvanzadoWi-FiLectura: 14 min

Cómo funciona NAT en un ESP32: enrutamiento y repetidor Wi-Fi

NAPT no funciona en Arduino Core: su lwIP no incluye IP_FORWARD. El sdkconfig de ESP-IDF, la propagación de DNS por DHCP y un repetidor medido en funcionamiento.

Tecnologías:#ESP32-S3#ESP-IDF#lwIP#NAPT#esp_netif#DHCP

Requisitos de hardware:

  • Placa ESP32-S3 (referência: módulo N16R8)
  • Cabo USB-C com transmissão de dados
  • Windows 10 ou 11 com acesso à internet
  • Roteador Wi-Fi 2,4 GHz e um celular para testar

Qué es NAT en un ESP32 y qué no es

NAT permite que varios dispositivos de una red privada lleguen a internet usando una única dirección. En un ESP32 se utiliza la variante NAPT, que traduce la dirección y el puerto al mismo tiempo; eso es lo que convierte la placa en un repetidor Wi-Fi de verdad y no solo en un punto de acceso aislado.

La idea es sencilla. El ESP32 levanta las dos interfaces a la vez: STA se conecta al router y AP crea su propia red con un servidor DHCP. El motor NAPT reescribe las cabeceras de los paquetes que llegan desde los clientes del AP y los reenvía por STA, guardando una tabla que indica adónde debe volver cada respuesta.

Lo que casi todos los tutoriales olvidan decir es que esto no compila en Arduino Core. La siguiente sección explica por qué y el resto del artículo muestra el camino que sí funciona.

Por qué Arduino Core no hace NAT

Encontrarás ejemplos con ip_napt_enable e ip_portmap_init que incluyen lwip/napt.h directamente en un sketch Arduino. Ese código no funciona en las versiones actuales de Arduino Core para ESP32-S3 por tres motivos concretos:

La opción CONFIG_LWIP_IP_FORWARD viene desactivada en el sdkconfig precompilado de Arduino. Sin reenvío IP, el paquete del cliente llega a la placa y muere allí.

La liblwip.a distribuida no expone el símbolo ip_napt_enable. Según la versión, el enlace falla o el código compila contra un stub que no hace nada.

Y como el sdkconfig de Arduino ya está compilado, no se puede activar la opción después. Tiene que estar activa cuando se compila lwIP.

El resultado práctico es duro: en algunas versiones el sketch compila, arranca, imprime un mensaje de éxito y el cliente conectado al AP simplemente no puede navegar. No queda ningún error que investigar.

El camino correcto es un firmware ESP-IDF con su propio sdkconfig. Eso es lo que hace este artículo.

Las cuatro opciones de sdkconfig que habilitan NAPT

Colócalas en tu archivo sdkconfig.defaults antes de compilar:

code.cpp
cpp
1CONFIG_LWIP_IP_FORWARD=y
2CONFIG_LWIP_IPV4_NAPT=y
3CONFIG_LWIP_IPV4_NAPT_PORTMAP=y
4CONFIG_LWIP_L2_TO_L3_COPY=y

IP_FORWARD activa el reenvío entre interfaces. IPV4_NAPT activa la traducción de dirección y puerto. NAPT_PORTMAP habilita el mapeo de puertos, necesario si quieres exponer un servicio interno. L2_TO_L3_COPY copia el paquete de la capa 2 a la 3 antes de enrutarlo; sin eso, el reenvío entre AP y STA se comporta de forma errática.

Después de cambiar estas opciones, borra la carpeta build y compila desde cero. Cambiar sdkconfig sin limpiar es una fuente clásica de comportamientos inexplicables.

esp_netif_napt_enable, la API que debes usar

Olvida el ip_napt_enable directo de lwIP. El ESP-IDF moderno expone una API de nivel superior que recibe el handle de la interfaz y se encarga de los detalles:

code.cpp
cpp
1static void enable_nat(void)
2{
3 if (!ap_netif) {
4 nat_result = ESP_ERR_INVALID_STATE;
5 nat_enabled = false;
6 return;
7 }
8 if (sta_netif) {
9 esp_netif_napt_disable(sta_netif);
10 }
11 nat_result = esp_netif_napt_enable(ap_netif);
12 nat_enabled = nat_result == ESP_OK;
13 ESP_LOGI(TAG, "NAPT enable on AP: %s", esp_err_to_name(nat_result));
14}

Importan dos detalles. NAPT se habilita en la interfaz AP, de donde viene el tráfico que se va a traducir, y no en STA. También conviene deshabilitarlo explícitamente en STA antes: si queda NAPT activo en el otro lado, el enrutamiento se vuelve ambiguo.

Guarda el valor devuelto en lugar de descartarlo. Es la diferencia entre «creí que se activó» y «se activó».

DNS es la mitad que nadie cuenta

Aquí está el motivo real por el que la mayoría de los intentos de repetidor ESP32 «no funcionan».

Con NAPT activo, el cliente se conecta, recibe una IP por DHCP y el enrutamiento funciona. Aun así, el navegador no abre nada. La persona concluye que NAT falló, cuando en realidad nadie entregó un servidor DNS al cliente. Sin resolver nombres, internet parece muerto aunque los paquetes se estén enrutando perfectamente.

El DHCP del AP debe ofrecer DNS, y esa opción solo se acepta con el servidor detenido. La secuencia correcta es detener, configurar y arrancar:

code.cpp
cpp
1static void sync_dns_to_ap(void)
2{
3 esp_netif_dns_info_t dns = {0};
4 if (esp_netif_get_dns_info(sta_netif, ESP_NETIF_DNS_MAIN, &dns) == ESP_OK) {
5 if (dns.ip.u_addr.ip4.addr == 0) {
6 ESP_LOGW(TAG, "Router DNS was empty, using 8.8.8.8");
7 esp_netif_str_to_ip4("8.8.8.8", &dns.ip.u_addr.ip4);
8 dns.ip.type = ESP_IPADDR_TYPE_V4;
9 }
10 esp_netif_set_dns_info(ap_netif, ESP_NETIF_DNS_MAIN, &dns);
11 esp_netif_dhcps_stop(ap_netif);
12 uint8_t offer_router = 1;
13 uint8_t offer_dns = 1;
14 esp_netif_dhcps_option(ap_netif, ESP_NETIF_OP_SET,
15 ESP_NETIF_ROUTER_SOLICITATION_ADDRESS,
16 &offer_router, sizeof(offer_router));
17 esp_netif_dhcps_option(ap_netif, ESP_NETIF_OP_SET,
18 ESP_NETIF_DOMAIN_NAME_SERVER,
19 &offer_dns, sizeof(offer_dns));
20 esp_netif_dhcps_start(ap_netif);
21 }
22}

El DNS se lee de la interfaz STA; es decir, es el mismo DNS que el router entregó al ESP32. Cuando vuelve vacío, algo que ocurre con algunos proveedores, el código usa 8.8.8.8 como alternativa en vez de dejar al cliente sin resolución.

Las dos flags de oferta deben activarse explícitamente. ESP_NETIF_DOMAIN_NAME_SERVER hace que DHCP anuncie el DNS y ESP_NETIF_ROUTER_SOLICITATION_ADDRESS hace que anuncie la puerta de enlace. Si falta la segunda, el cliente recibe IP y DNS, pero no sabe adónde enviar el tráfico de salida.

Llama a esta función después de que STA obtenga una IP, no antes: el DNS del router solo existe a partir de ese momento.

Cómo saber si realmente funcionó

No confíes en el log de arranque. Expón el estado en un endpoint HTTP y léelo mientras el repetidor está funcionando. Esta es la respuesta real de un ESP32-S3 N16R8 funcionando como repetidor, con los campos importantes:

code.cpp
cpp
1{
2 "network_mode": "nat",
3 "ap_ssid": "MeuRepetidor-01",
4 "ap_ip": "192.168.4.1",
5 "ap_dhcp_start": "192.168.4.2",
6 "ap_dhcp_end": "192.168.4.20",
7 "ap_clients": 1,
8 "dns": "192.168.15.1",
9 "dhcp_status": "router:ESP_OK dns:ESP_OK start:ESP_OK",
10 "nat_enabled": true,
11 "nat_result": "ESP_OK",
12 "nat_interface": "ap",
13 "upstream_http_1_1_1_1": 301,
14 "upstream_http_neverssl": -1,
15 "rssi": -39,
16 "free_heap": 8600128,
17 "uptime_ms": 13687494
18}

El campo nat_result en ESP_OK, junto con nat_interface en ap, confirma que NAPT arrancó en el lugar correcto. dhcp_status muestra las tres operaciones de la sección anterior y las tres deben estar en ESP_OK; si falla la del medio, el cliente se queda sin DNS. dns debe mostrar la dirección de tu router; si aparece 8.8.8.8, se activó la alternativa y conviene investigar por qué.

Diagnóstico: IP pura contra nombre

Esta es la prueba que separa los dos problemas que más se confunden y cabe en dos solicitudes.

Sondea una dirección IP literal y un nombre de host, ambos por HTTP simple, nunca HTTPS; TLS añade una capa de fallos que dificulta el diagnóstico:

code.cpp
cpp
1static void run_upstream_diagnostics(void)
2{
3 upstream_http_1_1_1_1 = http_probe("http://1.1.1.1",
4 upstream_error_1_1_1_1,
5 sizeof(upstream_error_1_1_1_1));
6 upstream_http_neverssl = http_probe("http://neverssl.com",
7 upstream_error_neverssl,
8 sizeof(upstream_error_neverssl));
9}

La lectura del resultado es directa. Si responde la IP pura y falla el nombre, el enrutamiento está bien y el problema es la resolución de nombres. Si fallan los dos, el problema es el enrutamiento o el enlace de subida. Si responden los dos, todo el camino funciona.

Hay un detalle que cuesta horas: el resolver del propio ESP32 y el DNS entregado a los clientes son caminos diferentes. El cliente pregunta al servidor anunciado por DHCP y esa consulta atraviesa NAPT como cualquier otro paquete. El sondeo anterior, en cambio, usa la pila interna de la placa.

Esto no es teoría. En el dispositivo medido para este artículo, el sondeo por nombre fallaba con ESP_ERR_HTTP_CONNECT mientras el sondeo por IP pura respondía normalmente; al mismo tiempo, el teléfono conectado al AP resolvía dominios y navegaba sin problemas, como confirmó la prueba de IP pública descrita más adelante.

Es decir, el repetidor funcionaba perfectamente para los clientes, pero el autodiagnóstico de la placa informaba un fallo de DNS. Si encuentras este escenario, investiga el resolver interno del firmware y no NAT. Y desconfía del panel: un diagnóstico que mide solo la IP pura mostrará todo en orden mientras el usuario no puede abrir ningún sitio.

También conviene ejecutar este diagnóstico periódicamente y no solo cuando STA obtiene una IP. Un valor medido al arrancar y mostrado horas después como si fuera actual es peor que no mostrar nada.

Mediciones de un repetidor real

Los números siguientes corresponden a un ESP32-S3 N16R8 ejecutando el firmware descrito aquí, monitorizado mediante consultas cada diez segundos.

NAPT arrancó con nat_result en ESP_OK en la interfaz AP y DHCP propagó el DNS con las tres operaciones en ESP_OK, entregando la dirección del router local en lugar de la alternativa.

Un teléfono Android se conectó al AP y el contador ap_clients pasó de 0 a 1, manteniéndose estable durante ocho minutos de lecturas continuas, sin reasociación.

Cómo demostrar el enrutamiento sin pedir que te crean

Un cliente asociado no es necesariamente un cliente navegando, y abrir el portal en 192.168.4.1 tampoco demuestra nada: esa dirección es la del propio ESP32, así que el tráfico no atraviesa la traducción. Decir «desactivé los datos móviles, confía en mí» tampoco es evidencia.

Existe una prueba objetiva que cabe en una sola captura: compara la IP pública que ve el cliente con la de tu enlace.

Primero descubre la IPv4 pública de tu red desde cualquier máquina conectada por cable:

code.cpp
cpp
1curl -4 https://api.ipify.org

Después, en el teléfono conectado al AP del ESP32, abre api.ipify.org en el navegador. Si la dirección mostrada es la misma que la de tu enlace, el tráfico salió por tu banda ancha; atravesó el repetidor. Si es otra, el teléfono escapó por la red móvil y la prueba no sirve.

La elegancia del método es que no depende de una configuración declarada. La IP mostrada identifica por sí sola el camino que siguió el paquete. Y como escribes un nombre de dominio en vez de una IP, la misma captura demuestra tres cosas a la vez: el DNS resolvió, el TCP salió y volvió, y la ruta pasó por tu enlace.

En la prueba de este artículo, el teléfono mostró la misma dirección IPv4 del enlace de casa, con ap_clients en 1 durante veinte lecturas consecutivas que cubrían el momento de la captura, sin ninguna lectura en cero. El repetidor quedó validado de extremo a extremo.

Un detalle puede confundir: el NAPT del ESP32 es solo IPv4. Si la dirección mostrada está en formato IPv6 y comienza con algo como 2804, el teléfono salió por IPv6 nativo y no usó NAT. Fuerza la prueba por IPv4.

El detalle más interesante apareció en la memoria. El heap libre mínimo registrado cayó de 8.590.580 a 8.527.300 bytes después de que se conectara el cliente, una diferencia de aproximadamente 63 KB. Esa caída corresponde a la asignación de la tabla NAPT y del seguimiento de conexiones: es la firma de que el tráfico atraviesa realmente la traducción y no de que solo haya un cliente asociado sin transmitir.

El dispositivo acumulaba 3 horas y 48 minutos de funcionamiento continuo, con una señal de −39 dBm en el canal 11. Observa que los 8,6 MB de heap libre solo existen porque la PSRAM octal está activa: el ESP32-S3 tiene unos 320 KB de RAM interna y, sin PSRAM, la tabla NAPT compite con el resto del firmware por un espacio mucho más reducido.

Tutorial: desde un ordenador vacío hasta un repetidor funcionando

A partir de aquí tienes un paso a paso sin saltar etapas. El punto de partida es un Windows sin nada instalado y una placa ESP32-S3 en la caja. El destino es un teléfono navegando a través de ella.

Reserva entre 40 y 60 minutos la primera vez. La mayor parte es descarga: ESP-IDF baja el compilador Xtensa completo.

Paso 1: instalar ESP-IDF

Arduino no sirve aquí, como se explicó en la segunda sección. Descarga el instalador oficial de ESP-IDF para Windows desde Espressif y elige la versión 5.x o superior.

Durante la instalación, acepta la ruta predeterminada y deja activada la opción de crear accesos directos. El instalador descarga Python, Git y la toolchain de compilación; es normal que tarde.

Cuando termine, busca ESP-IDF PowerShell en el menú Inicio. Este acceso abre un terminal con todas las variables de entorno configuradas. Úsalo siempre en lugar del PowerShell normal: fuera de él no existe el comando idf.py.

Confirma que funciona:

code.cpp
cpp
1idf.py --version

Paso 2: crear el proyecto

Todavía en ESP-IDF PowerShell, ve a la carpeta donde guardas tus proyectos y genera el esqueleto:

code.cpp
cpp
1cd C:\projetos
2idf.py create-project esp32-repetidor
3cd esp32-repetidor

Esto crea la estructura mínima, con una carpeta main y CMakeLists.txt. Coloca el código del repetidor en el archivo main dentro de esa carpeta, usando las funciones mostradas en las secciones anteriores.

Paso 3: escribir sdkconfig.defaults

Este paso determina si NAT va a existir o no. Crea un archivo llamado sdkconfig.defaults en la raíz del proyecto, junto a CMakeLists.txt, con el contenido siguiente. Las cuatro primeras líneas habilitan NAPT y las demás activan la PSRAM octal y la flash de 16 MB de una placa N16R8:

code.cpp
cpp
1CONFIG_LWIP_IP_FORWARD=y
2CONFIG_LWIP_IPV4_NAPT=y
3CONFIG_LWIP_IPV4_NAPT_PORTMAP=y
4CONFIG_LWIP_L2_TO_L3_COPY=y
5CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
6CONFIG_SPIRAM=y
7CONFIG_SPIRAM_MODE_OCT=y
8CONFIG_SPIRAM_SPEED_80M=y
9CONFIG_SPIRAM_USE_MALLOC=y

Si tu placa no tiene PSRAM, elimina las cuatro líneas de SPIRAM. Si la flash es de 8 MB, cambia el valor de FLASHSIZE. Las cuatro primeras nunca cambian.

Paso 4: elegir el objetivo y compilar

Indica a ESP-IDF qué chip estás utilizando. Este comando debe ir antes de la primera compilación porque genera sdkconfig a partir de sdkconfig.defaults:

code.cpp
cpp
1idf.py set-target esp32s3
2idf.py build

La primera compilación tarda más porque también compila ESP-IDF. Las siguientes tardan segundos.

Si editas sdkconfig.defaults después, borra la carpeta build y ejecuta set-target de nuevo. Cambiar las opciones sin limpiar es la causa más común de «cambié la configuración y no pasó nada».

Paso 5: encontrar el puerto y grabar

Conecta la placa con el cable USB. En una ESP32-S3 con dos puertos, usa el marcado como UART.

Placa ESP32-S3-DevKitC-1 con sus dos puertos USB-C identificados: UART, utilizado por esta configuración del monitor serie, y USB nativo
Placa ESP32-S3-DevKitC-1 con sus dos puertos USB-C identificados: UART, utilizado por esta configuración del monitor serie, y USB nativo

Para descubrir qué puerto COM apareció:

code.cpp
cpp
1idf.py -p COM3 flash

Sustituye COM3 por el número que aparezca en el Administrador de dispositivos de Windows, en Puertos. Si la grabación falla con un error de conexión, mantén pulsado BOOT, toca RESET, suelta BOOT y ejecuta el comando otra vez.

Paso 6: observar el monitor serie

Con la placa todavía conectada, abre el monitor:

code.cpp
cpp
1idf.py -p COM3 monitor

Debes ver el log de arranque y, justo después de que la placa se inicie, la línea de NAPT. Es la primera confirmación de que funcionó:

code.cpp
cpp
1I (2841) AnalictY-NAT: NAPT enable on AP: ESP_OK

Si aparece algo distinto de ESP_OK, vuelve al paso 3: las opciones de sdkconfig no se aplicaron. Para salir del monitor, usa Ctrl más el corchete derecho.

Paso 7: informar las credenciales de tu red

La placa ya está funcionando, pero todavía no conoce la contraseña de tu red. Aquí hay una advertencia que ahorra tiempo: el portal del firmware NAT es un panel de estado, no un formulario. Muestra el estado del repetidor y ofrece actualizar y reiniciar, pero no tiene un campo SSID ni un botón para escanear redes.

Es una decisión de diseño, no una limitación. Buscar redes exige poner la radio en modo de escaneo, lo que interrumpe brevemente las dos interfaces y desestabiliza el enrutamiento que el repetidor necesita mantener. Por eso el aprovisionamiento con búsqueda de redes vive en un firmware separado, documentado en el tutorial del portal de configuración Wi-Fi, el complemento natural de este: uno configura y el otro enruta.

Para informar la red a esta placa, envía las credenciales directamente al endpoint de configuración desde un ordenador conectado a la misma red o al punto de acceso de la placa:

code.cpp
cpp
1curl -X POST http://192.168.4.1/api/config -H "Content-Type: application/json" -d "{\"wifi_ssid\":\"MinhaRede-2.4G\",\"wifi_pass\":\"minha-senha\"}"

Las credenciales se guardan en la NVS y sobreviven a un reinicio y a una nueva grabación del firmware.

Otro detalle que ahorra tiempo: el ESP32-S3 no se conecta a redes de 5 GHz. Si tu red tiene el sufijo 5G, utiliza la de 2,4 GHz.

Paso 8: validar el repetidor

Con la placa conectada a tu red, obtiene una dirección en la red de casa. Descúbrela en el monitor serie o en la lista de dispositivos del router y abre esa dirección en el navegador del ordenador.

El panel muestra el estado completo y es donde confirmas lo importante:

Portal de estado del repetidor: NAT activo, un cliente conectado, señal de −39 dBm y más de cuatro horas de funcionamiento continuo
Portal de estado del repetidor: NAT activo, un cliente conectado, señal de −39 dBm y más de cuatro horas de funcionamiento continuo

Comprueba tres campos antes de celebrar. nat_result debe estar en ESP_OK. dhcp_status debe mostrar las tres operaciones en ESP_OK. Y dns debe contener la dirección de tu router.

La prueba final es la más sencilla: mantén el teléfono conectado a la red de la placa y abre cualquier sitio web. Si carga, el contador de clientes llega a 1 y el repetidor queda validado de extremo a extremo.

Errores comunes y cómo resolverlos

El cliente se conecta pero no navega, y NAPT está en ESP_OK. Es DNS. Comprueba dhcp_status: las tres operaciones deben estar en ESP_OK y la flag ESP_NETIF_DOMAIN_NAME_SERVER debe estar activa.

El cliente recibe IP y DNS, pero no sale nada. Falta anunciar la puerta de enlace. Activa ESP_NETIF_ROUTER_SOLICITATION_ADDRESS junto con la opción de DNS.

esp_netif_napt_enable devuelve un error. Las opciones de sdkconfig no se aplicaron. Confirma las cuatro y vuelve a compilar después de borrar la carpeta build.

Compilé con Arduino y no funciona. No funcionará. Pasa a ESP-IDF, como se explicó en la segunda sección.

Funciona durante unos minutos y se degrada. La tabla NAPT tiene un número limitado de entradas. En un chip sin PSRAM, unos pocos clientes con muchas conexiones simultáneas pueden agotar rápidamente el espacio.

El ESP32 informa un fallo de DNS pero los clientes navegan. Es un comportamiento esperado, como se explicó en el diagnóstico: son caminos diferentes. Investiga el resolver interno, no NAT.

Ver más tutoriales técnicosIIoT ESP32 Brasil — Documentación práctica