iiOTESP32 Brasil
Modo de contingência: páginas públicas e downloads seguem disponíveis; login, comunidade e dados do servidor podem ficar indisponíveis.
Voltar para catálogo de tutoriais
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
AvançadoWi-FiLeitura: 14 min

Como funciona NAT em um ESP32: Roteamento e Repetidor Wi-Fi

NAPT não funciona no Arduino Core: o lwIP dele vem sem IP_FORWARD. O sdkconfig do ESP-IDF, a propagação de DNS por DHCP e um repetidor medido rodando.

Tecnologias:#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

O que é NAT num ESP32, e o que ele não é

O NAT é o que permite que vários dispositivos numa rede privada alcancem a internet através de um único endereço. Num ESP32 a variante usada é o NAPT, que traduz endereço e porta ao mesmo tempo — é o que transforma a placa num repetidor Wi-Fi de verdade, e não apenas num ponto de acesso isolado.

A ideia é simples. O ESP32 sobe as duas interfaces ao mesmo tempo: a STA conecta no seu roteador, a AP cria uma rede própria com servidor DHCP. O motor NAPT reescreve o cabeçalho dos pacotes vindos dos clientes do AP e os retransmite pela STA, guardando numa tabela para onde devolver a resposta.

O que quase todo tutorial esquece de dizer é que isso não compila no Arduino Core. A próxima seção explica por quê, e o resto do artigo mostra o caminho que funciona.

Por que o Arduino Core não faz NAT

Você vai encontrar por aí exemplos com ip_napt_enable e ip_portmap_init incluindo lwip/napt.h direto num sketch Arduino. Esse código não funciona nas builds atuais do Arduino Core para ESP32-S3, por três motivos concretos:

A opção CONFIG_LWIP_IP_FORWARD vem desabilitada no sdkconfig pré-compilado do Arduino. Sem encaminhamento de IP, o pacote do cliente chega na placa e morre ali.

A liblwip.a distribuída não expõe o símbolo ip_napt_enable. Dependendo da versão, o link falha ou o código compila com um stub que não faz nada.

E como o sdkconfig do Arduino já vem compilado, não dá para ligar a opção depois. Ela precisa estar ativa no momento em que o lwIP é compilado.

O resultado prático é cruel: em algumas versões o sketch compila, sobe, imprime a mensagem de sucesso, e o cliente conectado no AP simplesmente não navega. Não há erro para investigar.

O caminho correto é firmware ESP-IDF com sdkconfig próprio. É o que este artigo faz.

As quatro opções de sdkconfig que habilitam NAPT

Coloque no seu arquivo 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 liga o encaminhamento entre interfaces. IPV4_NAPT liga a tradução de endereço e porta. NAPT_PORTMAP habilita mapeamento de portas, necessário se você quiser expor um serviço interno. E L2_TO_L3_COPY copia o pacote da camada 2 para a 3 antes do roteamento, sem o qual o encaminhamento entre AP e STA se comporta de forma errática.

Depois de mexer aqui, apague a pasta build e recompile do zero. Alterar sdkconfig sem limpar é fonte clássica de comportamento inexplicável.

esp_netif_napt_enable, a API que você deve usar

Esqueça o ip_napt_enable cru do lwIP. O ESP-IDF moderno expõe uma API de mais alto nível, que recebe o handle da interface e cuida dos detalhes:

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}

Dois detalhes importam. O NAPT é habilitado na interface AP, que é de onde vem o tráfego a ser traduzido, e não na STA. E vale desabilitar explicitamente na STA antes: se sobrar NAPT ativo do outro lado, o roteamento fica ambíguo.

Guarde o retorno numa variável em vez de descartar. Ele é a diferença entre "achei que ligou" e "ligou".

O DNS é a metade que ninguém conta

Aqui está o motivo real pelo qual a maioria das tentativas de repetidor ESP32 "não funciona".

Com o NAPT ativo, o cliente conecta, recebe IP do DHCP e o roteamento funciona. Mesmo assim o navegador não abre nada. A pessoa conclui que o NAT falhou — quando na verdade ninguém entregou um servidor DNS ao cliente. Sem resolver nomes, a internet parece morta ainda que os pacotes estejam sendo roteados perfeitamente.

O DHCP do AP precisa oferecer o DNS, e a opção só é aceita com o servidor parado. A sequência correta é parar, configurar, iniciar:

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, "DNS do roteador veio vazio, usando 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}

O DNS é lido da interface STA, ou seja, é o mesmo que o seu roteador entregou ao ESP32. Quando ele volta vazio — acontece com alguns provedores — o código cai para 8.8.8.8 em vez de deixar o cliente sem resolução.

As duas flags de oferta precisam ser ligadas explicitamente. ESP_NETIF_DOMAIN_NAME_SERVER faz o DHCP anunciar o DNS, e ESP_NETIF_ROUTER_SOLICITATION_ADDRESS faz ele anunciar o gateway. Faltando a segunda, o cliente recebe IP e DNS mas não sabe para onde mandar o tráfego de saída.

Chame essa função depois que a STA obtiver IP, não antes: o DNS do roteador só existe a partir daí.

Como saber se funcionou de verdade

Não confie em log de boot. Exponha o estado num endpoint HTTP e leia enquanto o repetidor está rodando. Este é o retorno real de um ESP32-S3 N16R8 servindo como repetidor, com os campos que importam:

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}

O campo nat_result em ESP_OK com nat_interface em ap confirma que o NAPT subiu no lugar certo. O dhcp_status mostra as três operações da seção anterior, e as três precisam estar em ESP_OK — se a do meio falhar, o cliente fica sem DNS. E dns precisa mostrar o endereço do seu roteador; se aparecer 8.8.8.8, o fallback entrou em ação e vale investigar por quê.

Diagnóstico: IP puro contra nome

Este é o teste que separa os dois problemas mais confundidos do mundo, e cabe em duas requisições.

Sonde um endereço IP literal e um hostname, ambos em HTTP simples, nunca HTTPS — TLS adiciona uma camada de falha que atrapalha o 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}

A leitura do resultado é direta. Se o IP puro responde e o nome falha, o roteamento está bom e o problema é resolução de nomes. Se os dois falham, o problema é roteamento ou o link de subida. Se os dois respondem, o caminho está inteiro.

Uma sutileza que custa horas: o resolver do próprio ESP32 e o DNS entregue aos clientes são caminhos diferentes. O cliente pergunta ao servidor anunciado pelo DHCP e essa consulta atravessa o NAPT como qualquer outro pacote. Já a sondagem acima usa a pilha interna da placa.

Isso não é teoria. No dispositivo medido para este artigo, a sondagem por nome falhava com ESP_ERR_HTTP_CONNECT enquanto a sondagem por IP puro respondia normalmente — e, ao mesmo tempo, o celular conectado ao AP resolvia domínios e navegava sem qualquer problema, conforme o teste de IP público descrito adiante.

Ou seja: o repetidor estava perfeitamente funcional para os clientes, e o autodiagnóstico da própria placa acusava falha de DNS. Se você cair nesse cenário, investigue o resolver interno do firmware e não o NAT. E desconfie do painel: um diagnóstico que mede só o IP puro vai exibir tudo em ordem enquanto o usuário não abre um site sequer.

Vale também rodar esse diagnóstico periodicamente, e não só quando a STA pega IP. Um valor medido no boot e exibido horas depois como se fosse atual é pior que não exibir nada.

Medições de um repetidor real

Os números abaixo são de um ESP32-S3 N16R8 rodando o firmware descrito aqui, monitorado por polling a cada dez segundos.

O NAPT subiu com nat_result em ESP_OK na interface AP, e o DHCP propagou o DNS com as três operações em ESP_OK, entregando o endereço do roteador local em vez do fallback.

Um celular Android conectou no AP e o contador ap_clients passou de 0 para 1, permanecendo estável ao longo de oito minutos de leituras contínuas, sem reassociação.

Como provar o roteamento sem depender de boa fé

Cliente associado não é cliente navegando, e abrir o portal em 192.168.4.1 também não prova nada: esse endereço é o próprio ESP32, então o tráfego não atravessa a tradução. Dizer "desliguei os dados móveis, confia" também não serve como evidência.

Existe um teste que resolve isso de forma objetiva, e ele cabe em uma única imagem: compare o endereço IP público visto pelo cliente com o do seu link.

Descubra primeiro qual é o IPv4 público da sua rede, a partir de qualquer máquina cabeada:

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

Depois, no celular conectado ao AP do ESP32, abra api.ipify.org no navegador. Se o endereço exibido for o mesmo do seu link, o tráfego saiu pela sua banda larga — ou seja, atravessou o repetidor. Se for outro, o celular escapou pela rede móvel e o teste não vale.

A elegância do método é que ele não depende de configuração declarada. O IP mostrado identifica sozinho por onde o pacote passou. E como você digita um nome de domínio em vez de um IP, o mesmo print prova três coisas de uma vez: o DNS resolveu, o TCP saiu e voltou, e a rota foi pelo seu link.

No teste deste artigo o celular exibiu o mesmo endereço IPv4 do link de casa, com o ap_clients registrando 1 em vinte leituras consecutivas cobrindo o instante da captura, sem nenhuma leitura zerada no intervalo. Repetidor validado de ponta a ponta.

Um detalhe que pode confundir: o NAPT do ESP32 é somente IPv4. Se o endereço exibido vier no formato IPv6, começando com algo como 2804, o celular saiu por IPv6 nativo e não usou o NAT. Force o teste em IPv4.

O detalhe mais interessante apareceu na memória. O menor heap livre registrado caiu de 8.590.580 para 8.527.300 bytes depois que o cliente conectou, uma diferença de cerca de 63 KB. Essa queda é a tabela NAPT e o rastreamento de conexões sendo alocados: é a assinatura de tráfego realmente atravessando a tradução, e não apenas de um cliente associado sem trafegar.

O dispositivo acumulava 3 horas e 48 minutos de operação contínua, com sinal de −39 dBm no canal 11. Vale notar que os 8,6 MB de heap livre só existem porque a PSRAM octal está ativa: o ESP32-S3 tem cerca de 320 KB de RAM interna, e sem PSRAM a tabela NAPT compete com o resto do firmware por um espaço bem mais apertado.

Tutorial: do computador zerado ao repetidor funcionando

A partir daqui é passo a passo, sem pular etapa. O ponto de partida é um Windows sem nada instalado e uma placa ESP32-S3 na caixa. O destino é um celular navegando através dela.

Reserve de 40 a 60 minutos na primeira vez. A maior parte é download: o ESP-IDF baixa o compilador Xtensa inteiro.

Passo 1: instalar o ESP-IDF

O Arduino não serve aqui, conforme a segunda seção deste artigo. Baixe o instalador oficial do ESP-IDF para Windows no site da Espressif e escolha a versão 5.x ou superior.

Durante a instalação, aceite o caminho padrão e deixe marcada a opção de criar os atalhos. O instalador baixa o Python, o Git e a toolchain de compilação — é normal demorar.

Ao terminar, procure no menu iniciar por ESP-IDF PowerShell. Esse atalho abre um terminal com todas as variáveis de ambiente já configuradas. Use sempre ele, e não o PowerShell comum: fora dele o comando idf.py não existe.

Confirme que funcionou:

code.cpp
cpp
1idf.py --version

Passo 2: criar o projeto

Ainda no ESP-IDF PowerShell, vá até a pasta onde você guarda projetos e gere o esqueleto:

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

Isso cria a estrutura mínima, com a pasta main e o CMakeLists.txt. O código do repetidor vai no arquivo main dentro dessa pasta, usando as funções mostradas nas seções anteriores.

Passo 3: escrever o sdkconfig.defaults

Este é o passo que define se o NAT vai existir ou não. Crie um arquivo chamado sdkconfig.defaults na raiz do projeto, ao lado do CMakeLists.txt, com o conteúdo abaixo. As quatro primeiras linhas são as que habilitam o NAPT, e as demais ligam a PSRAM octal e a flash de 16 MB de uma 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

Se a sua placa não tiver PSRAM, remova as quatro linhas de SPIRAM. Se a flash for de 8 MB, troque o valor de FLASHSIZE. As quatro primeiras nunca mudam.

Passo 4: escolher o alvo e compilar

Diga ao ESP-IDF qual chip você está usando. Esse comando precisa vir antes da primeira compilação, porque ele é quem monta o sdkconfig a partir do seu sdkconfig.defaults:

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

A primeira compilação é demorada, porque compila o ESP-IDF inteiro junto. As seguintes levam segundos.

Se você editar o sdkconfig.defaults depois, apague a pasta build e rode o set-target de novo. Alterar as opções sem limpar é a causa mais comum de "mudei a configuração e nada aconteceu".

Passo 5: descobrir a porta e gravar

Conecte a placa pelo cabo USB. Numa ESP32-S3 com duas portas, use a marcada UART.

Placa ESP32-S3-DevKitC-1 com as duas portas USB-C identificadas: a UART, que carrega o monitor serial nesta configuracao, e a USB nativa
Placa ESP32-S3-DevKitC-1 com as duas portas USB-C identificadas: a UART, que carrega o monitor serial nesta configuracao, e a USB nativa

Para descobrir qual porta COM apareceu:

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

Troque COM3 pelo número que apareceu no Gerenciador de Dispositivos do Windows, em Portas. Se a gravação falhar com uma mensagem de falha ao conectar, segure o botão BOOT da placa, toque o RESET, solte o BOOT e rode o comando de novo.

Passo 6: acompanhar pelo monitor serial

Ainda com a placa conectada, abra o monitor:

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

Você deve ver o log de boot e, logo depois que a placa subir, a linha do NAPT. Ela é a primeira confirmação de que deu certo:

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

Se aparecer qualquer coisa diferente de ESP_OK, volte ao passo 3: as opções de sdkconfig não entraram. Para sair do monitor, use Ctrl mais o colchete direito.

Passo 7: informar as credenciais da sua rede

Agora a placa está rodando, mas ainda não sabe a senha da sua rede. E aqui vale um aviso que economiza tempo: o portal do firmware NAT é um painel de status, não um formulário. Ele mostra o estado do repetidor e oferece apenas atualizar e reiniciar — não tem campo de SSID nem botão de varredura de redes.

Isso é decisão de projeto, não limitação. Varrer redes exige colocar o rádio em modo de varredura, o que interrompe momentaneamente as duas interfaces e desestabiliza justamente o roteamento que o repetidor precisa manter de pé. Por isso o provisionamento com busca de redes vive num firmware separado, e é assunto do tutorial do portal de configuração — o complemento natural deste: um configura, o outro roteia.

Para informar a rede a esta placa, envie as credenciais direto ao endpoint de configuração, de um computador na mesma rede ou conectado ao ponto de acesso dela:

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\\"}"

As credenciais são gravadas na NVS e sobrevivem a reinício e a nova gravação de firmware.

Um detalhe que economiza tempo: o ESP32-S3 não conecta em redes de 5 GHz. Se a sua rede aparece com o sufixo 5G, use a de 2,4 GHz.

Passo 8: validar o repetidor

Com a placa conectada na sua rede, ela ganha um endereço na rede de casa. Descubra qual no monitor serial ou na lista de dispositivos do seu roteador, e abra esse endereço no navegador do computador.

O painel mostra o estado completo, e é onde você confirma o que interessa:

Portal de status do repetidor: NAT ativo, um cliente conectado, sinal de -39 dBm e mais de quatro horas de operação contínua
Portal de status do repetidor: NAT ativo, um cliente conectado, sinal de -39 dBm e mais de quatro horas de operação contínua

Confira três campos antes de comemorar. O nat_result precisa estar em ESP_OK. O dhcp_status precisa mostrar as três operações em ESP_OK. E o dns precisa trazer o endereço do seu roteador.

O teste final é o mais simples de todos: mantenha o celular conectado na rede da placa e abra qualquer site. Se carregar, o contador de clientes vai para 1 e o repetidor está validado de ponta a ponta.

Erros comuns e como resolver

O cliente conecta mas não navega, e o NAPT está em ESP_OK. É DNS. Confira o dhcp_status: as três operações precisam estar em ESP_OK, e a flag ESP_NETIF_DOMAIN_NAME_SERVER precisa estar ligada.

O cliente recebe IP e DNS mas nada sai. Falta anunciar o gateway. Ligue ESP_NETIF_ROUTER_SOLICITATION_ADDRESS junto com a de DNS.

esp_netif_napt_enable retorna erro. As opções de sdkconfig não entraram. Confirme as quatro e recompile com a pasta build apagada.

Compilei no Arduino e não funciona. Não vai funcionar. Migre para ESP-IDF, conforme a segunda seção.

Funciona por alguns minutos e degrada. A tabela NAPT tem limite de entradas. Num chip sem PSRAM, poucos clientes com muitas conexões simultâneas esgotam o espaço rápido.

O ESP32 acusa falha de DNS mas os clientes navegam. Comportamento esperado, conforme explicado no diagnóstico: são caminhos distintos. Investigue o resolver interno, não o NAT.

Ver mais tutoriais técnicosIIoT ESP32 Brasil — Documentação Prática