Por Flank Manoel da Silva Especialista Sênior Full Stack | Analista de Infraestrutura e Performance
Tem um tipo de falha que nenhum dashboard de NOC captura a tempo. O gráfico de utilização de banda mostra 40%. A CPU do equipamento de borda está tranquila. Nenhum alarme disparou. E ainda assim, novos clientes param de conseguir estabelecer conexões. Os tickets começam a chegar, sempre com a mesma descrição vaga: “internet caindo”, “aplicativo travando”, “chamada VoIP cortando”. O engenheiro de plantão olha para o tráfego e não vê nada errado. Porque o errado não está no tráfego, está na tabela de estado.
Este é o colapso silencioso. E ele acontece com uma frequência muito maior do que a comunidade técnica brasileira está disposta a admitir, especialmente em ambientes de CGNAT que cresceram organicamente, sem dimensionamento adequado da infraestrutura de conexão tracking, e que hoje servem assinantes cujos dispositivos e aplicações fazem centenas, às vezes milhares, de conexões TCP de curtíssima duração por minuto.
O que a RFC 6888 diz que os engenheiros muitas vezes ignoram
A RFC 6888, o documento normativo que define os requisitos para CGNs (Carrier-Grade NATs) é clara em um ponto que raramente recebe a atenção devida: o limite real de um equipamento CGNAT não é medido em bits por segundo, mas em sessões simultâneas e em entradas de tabela de estado.
A seção REQ-4 do documento exige que o CGN suporte limitação configurável do número de portas externas por assinante. A seção REQ-5 vai além e exige que o equipamento suporte limitação de memória de estado por mapeamento e por assinante. Essas duas diretivas existem porque os autores da RFC sabiam, em 2013, que o recurso escasso em ambiente de CGN não seria endereço IP, seria estado.
O que acontece na prática no Brasil: ISPs de pequeno e médio porte configuram seus equipamentos de CGNAT em modo dinâmico, com blocos de portas alocados sob demanda (Port Block Allocation, ou PBA), e dimensionam o número de entradas de tabela de estado com base em projeções de tráfego de 2018. A base de assinantes triplicou. O perfil de uso mudou radicalmente. A tabela continua com o mesmo tamanho.
A matemática que ninguém fez no seu provedor
Aqui está o cálculo que precisa ser feito e que raramente é:
No Linux Netfilter (o subsistema de NAT mais comum em deployments baseados em software, como pfSense, VyOS ou instâncias MikroTik com conntrack ativo), a tabela de conntrack mantém entradas por um tempo que depende do estado da conexão. Conexões TCP estabelecidas ficam na tabela por até 432.000 segundos (5 dias). Conexões em estado TIME_WAIT ficam por padrão 120 segundos. Conexões no estado CLOSE_WAIT e FIN_WAIT ficam entre 60 e 120 segundos.
O ponto crítico está aqui: mesmo uma conexão de vida curtíssima, digamos, uma chamada HTTP que dura 200ms, ocupa uma entrada na tabela de conntrack por pelo menos 120 segundos após o encerramento.
Capacidade efetiva da tabela = tamanho_máximo_conntrack ÷ tempo_mínimo_de_retenção
Exemplo com tabela de 128.000 entradas e timeout de 120s:
128.000 ÷ 120 = 1.066 novas conexões por segundo é o limite real de throughput
Um único assinante com um aplicativo de microserviços mal configurado pode consumir 300 a 500 entradas de conntrack por segundo. Em um CGNAT servindo 400 assinantes, o colapso é apenas uma questão de tempo e de qual usuário vai primeiro para o topo do ranking de conexões por segundo.
O agressor que ninguém identifica: HTTP Keep-Alive mal configurado
Existe uma ironia pesada nessa história. O HTTP Keep-Alive foi inventado exatamente para reduzir a criação de novas conexões TCP, reutilizando a mesma conexão para múltiplas requisições HTTP/1.1. É, em tese, uma tecnologia poupadora de recursos. Mas em ambientes de microserviços com configuração descuidada, ele se transforma no principal gerador de entropia de estado NAT.
O problema ocorre quando o Keep-Alive está configurado no servidor, mas não no cliente, ou vice-versa. Ou quando o timeout do Keep-Alive no servidor é inferior ao intervalo de heartbeat configurado no cliente. O resultado é uma cascata de conexões que são abertas, mantidas por alguns segundos em estado half-open, e então encerradas unilateralmente por um dos lados, sem o handshake de encerramento adequado. Essas conexões não perecem limpas, elas caem em estados como FIN_WAIT_1, FIN_WAIT_2 e CLOSE_WAIT, que o conntrack mantém na tabela por 60 a 120 segundos cada.
Cenário A: microserviço com pool de conexões correto
Uma aplicação Node.js que usa http.globalAgent com keepAlive: true e maxSockets: 10 para chamadas a um serviço interno. O pool mantém 10 conexões persistentes. Cada requisição reutiliza uma dessas conexões. O conntrack mantém 10 entradas ativas. A pressão sobre o CGNAT é mínima e constante.
Cenário B: microserviço com conexão nova a cada requisição
A mesma aplicação Node.js, mas sem configuração explícita do agente HTTP, usando o comportamento padrão que, em versões anteriores ao Node 19, cria uma nova conexão para cada requisição. Se o serviço faz 50 requisições por segundo para dependências internas, são 50 novas entradas de conntrack por segundo sendo criadas. Com timeout de 120s em TIME_WAIT, isso significa até 6.000 entradas de conntrack mantidas ao mesmo tempo, para um único processo de um único assinante.
A tabela abaixo resume o impacto comparativo:
| Parâmetro | Cenário A (Keep-Alive correto) | Cenário B (sem reuso) |
|---|---|---|
| Conexões/segundo geradas | ~0,1 | 50 |
| Entradas conntrack simultâneas | 10 | ~6.000 |
| Blocos de porta consumidos (PBA) | 1 | 3–12 |
| Risco de exaustão de tabela | Negligenciável | Alto |
| Impacto sobre outros assinantes | Nenhum | Significativo |
Como o CGNAT aloca portas e por que a estratégia importa
Existem duas estratégias principais de alocação de portas em CGNAT, e a escolha entre elas define completamente o comportamento do sistema sob carga de microserviços:
Alocação Dinâmica (on-demand): O equipamento atribui portas individualmente conforme necessário. Máxima eficiência de uso do espaço de portas, mas gera uma entrada de log por cada par de tradução criado, o que em ambientes com milhares de conexões curtas por segundo representa um volume absurdo de logging e processamento.
Port Block Allocation (PBA): O equipamento atribui blocos fixos de portas (tipicamente 256, 512, 1024 ou 2048 portas por bloco) a cada assinante. Reduz drasticamente o volume de logging, mas introduz um desperdício estrutural: assinantes de baixo consumo retêm blocos que não usam, enquanto assinantes intensivos podem esgotar seus blocos e precisar de alocação de blocos adicionais.
A RFC 6888 é neutra quanto à estratégia, mas a prática do mercado mostra convergência para PBA justamente pela viabilidade operacional do logging. O problema é que, em ISPs brasileiros com relação de 32 assinantes por IP público, uma proporção comum em redes de pequeno porte, o espaço disponível de portas efêmeras (1024 a 65535, totalizando 64.512 portas) dividido por 32 resulta em aproximadamente 2.016 portas por assinante. Um único processo de microserviço em Cenário B pode esgotar esse bloco inteiro em 40 segundos.
Leia também: Anycast não é alta disponibilidade: a falácia da proximidade em cenários de degradação parcial
A timeline de um colapso real
A progressão de um evento de exaustão de tabela de estado em CGNAT não é abrupta. Ela segue uma curva que, olhando retroativamente nos logs, sempre estava sinalizada, mas os sinais não estavam nos lugares onde a equipe de NOC estava olhando.
T=00:00 — Baseline normal. Tabela de conntrack em 45% da capacidade máxima.
Conexões por segundo: ~800. Nenhum alarme.
T=00:15 — Deploy de nova versão de microserviço em cliente empresarial.
A versão nova não herda o pool de conexões corretamente.
Começa a abrir nova conexão para cada chamada de API interna.
T=00:22 — Tabela de conntrack atinge 70% da capacidade.
Primeiros pacotes descartados para assinantes adjacentes.
Mensagem no kernel: nf_conntrack: table full, dropping packet
T=00:28 — Tabela em 89%. Conexões de assinantes residenciais começam a falhar.
O ISP recebe os primeiros tickets. A equipe de NOC verifica utilização
de banda: 38%. Nenhum alarme de hardware. Investigação inicial falha.
T=00:35 — Tabela em 98%. Colapso generalizado de novas conexões.
VoIP, streaming e aplicações críticas falham para centenas de usuários.
O cliente empresarial com microserviço mal configurado... continua funcionando,
porque suas conexões já estavam estabelecidas.
T=00:41 — Equipe identifica a causa olhando diretamente para
conntrack -L | wc -l e filtrando por IP de origem.
T=00:45 — Reinicialização do serviço no cliente. Timeout de 120s para drenagem
das entradas TIME_WAIT. Normalização completa em T=01:47.
Essa timeline, com variações, repete-se com regularidade em ambientes de ISP que começaram a hospedar clientes com cargas de microserviços sem revisar suas políticas de CGNAT.
O papel do TIME_WAIT e por que você não deve desativá-lo
Quando engenheiros descobrem que conexões em TIME_WAIT estão consumindo a tabela de conntrack, a primeira reação instintiva é ativar tcp_tw_recycle, um parâmetro do kernel Linux que foi removido na versão 4.12 justamente porque quebrava NAT. A segunda reação é ativar tcp_tw_reuse. Ambas as abordagens resolvem o sintoma de forma superficial e introduzem problemas muito mais sérios.
O TIME_WAIT existe por um motivo estrutural no protocolo TCP: garantir que pacotes atrasados da conexão anterior não sejam interpretados como parte de uma nova conexão que reutilizou o mesmo par de portas. O RFC 793 define que esse período deve ser de 2 MSL (Maximum Segment Lifetime), o que na prática resulta em 60 a 120 segundos dependendo da implementação.
Em ambiente de CGNAT, eliminar o TIME_WAIT causa um problema adicional: o RFC 6888 exige (REQ-8) que uma porta externa não seja realocada para um novo mapeamento antes de 120 segundos após sua desalocação. Se o conntrack interno reutiliza a porta antes desse prazo, e o CGNAT externo também reutiliza a mesma porta pública para um assinante diferente, o resultado é a corrupção de estado, pacotes chegando ao destino errado, conexões que falham de forma intermitente e aleatória, difíceis de reproduzir e diagnosticar.
A solução não está em contornar o TIME_WAIT. Está em não gerá-lo desnecessariamente, o que nos leva de volta ao problema raiz: aplicações que criam conexões TCP de curtíssima duração em alto volume.
Teste de estresse: o que acontece sob carga massiva de conexões curtas
O grupo de engenharia da Tigera documentou um caso que ilustra com precisão cirúrgica a dinâmica que estamos descrevendo. Um provedor de SaaS rodava servidores memcached em bare metal, processando 50.000 conexões curtas por segundo. A tabela de conntrack com 512.000 entradas e timeout padrão de 120 segundos suportava teoricamente:
512.000 ÷ 120 = 4.266 conexões/segundo de throughput sustentável
O ambiente estava operando em 11,7 vezes esse limite. O resultado não foi uma falha gradual, foi um colapso binário. Acima do threshold, cada nova tentativa de conexão era descartada pelo kernel com nf_conntrack: table full, dropping packet. Abaixo do threshold, tudo funcionava perfeitamente.
Esse comportamento de limiar, normal até determinado ponto, depois colapso total, é particularmente traiçoeiro em CGNAT porque o equipamento de borda não tem visibilidade do estado interno de conntrack dos clientes. O ISP enxerga o tráfego agregado, não a distribuição de conexões por segundo por assinante.
Para um teste de estresse controlado em ambiente de laboratório, o procedimento padrão é:
- Configurar uma instância de CGNAT com tabela de conntrack limitada (ex: 65.536 entradas)
- Usar ferramenta como
wrk,abouheypara gerar carga de conexões curtas contra um endpoint externo - Monitorar em tempo real:
watch -n 1 'cat /proc/sys/net/netfilter/nf_conntrack_count' - Observar o momento exato em que
nf_conntrack_count=nf_conntrack_max - Verificar os logs do kernel:
dmesg | grep conntrack
O colapso ocorre com precisão matemática no ponto previsto pela fórmula. O que surpreende quem faz esse teste pela primeira vez é a velocidade, em cargas de conexão típicas de microserviços modernos, a tabela pode ser saturada em minutos, não horas.
Diagnóstico em produção: o que olhar e onde
Quando um evento de exaustão de estado está em curso ou foi recentemente resolvido, existem cinco fontes de evidência que devem ser inspecionadas em ordem:
1. Contagem atual de entradas de conntrack:
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
2. Distribuição de estados TCP na tabela:
conntrack -L | awk '{print $4}' | sort | uniq -c | sort -rn
A presença de dezenas de milhares de entradas em TIME_WAIT é sintomática de conexões curtas em volume.
3. Identificação dos assinantes por volume de entradas:
conntrack -L | grep -oP 'src=\K[^ ]+' | sort | uniq -c | sort -rn | head -20
4. Verificação de descarte de pacotes:
dmesg | grep -i conntrack
# Ou em sistemas com journald:
journalctl -k | grep "table full"
5. Histórico de alocação de blocos de porta por assinante (específico para plataformas com logging de CGNAT ativo, como F5 BIG-IP ou Juniper MX).
Mitigações reais, ordenadas por impacto e custo
A tabela abaixo apresenta as principais estratégias de mitigação, com avaliação honesta de eficácia, custo operacional e efeitos colaterais:
| Estratégia | Impacto | Custo | Efeitos Colaterais |
|---|---|---|---|
| Aumentar tamanho da tabela conntrack | Alto imediato | Baixo (RAM) | Aumento de consumo de memória (~360 bytes/entrada) |
| Reduzir timeouts TIME_WAIT/FIN_WAIT | Médio | Baixo | Risco de conflito de portas se mal calibrado |
| Limitar entradas por assinante no CGNAT | Alto estrutural | Médio (config) | Pode impactar assinantes legítimos de alto volume |
| Corrigir configuração de Keep-Alive na aplicação | Alto definitivo | Médio (dev) | Nenhum — é a solução correta |
| Implementar conntrack bypass (Calico/eBPF) | Muito alto | Alto | Perda de firewall stateful; exige redesenho de política |
| Migrar para IPv6 nativo | Eliminação do problema | Muito alto | Longo prazo; dependente de CPE e aplicação |
| Adotar QUIC/HTTP/3 nos endpoints | Alto para novas apps | Médio | Elimina overhead TCP; NAT ainda presente para UDP |
A primeira ação que qualquer NOC deve tomar em um evento ativo é aumentar a dimensão da tabela, é rápido e imediato. Mas a solução definitiva está sempre na camada de aplicação: eliminar a geração desnecessária de conexões curtas.
O caso específico do Kubernetes atrás de CGNAT
Existe um cenário particularmente problemático que merece atenção separada: clusters Kubernetes cujos nodes de worker estão atrás de um CGNAT, situação que ocorre em ambientes de edge computing, provedores regionais que hospedam workloads de clientes, ou empresas que rodam Kubernetes em infraestrutura de ISP com endereçamento RFC 1918.
O kube-proxy, em seu modo padrão (iptables), usa NAT extensivamente para implementar o mecanismo de Service. Cada acesso a um Kubernetes Service gera uma entrada de conntrack para a tradução do ClusterIP para o PodIP do backend. Em clusters com alta taxa de criação e destruição de pods, cenário típico de autoscaling horizontal, a rotatividade de mapeamentos de conntrack é enorme.
Mark Betz documentou um incidente em que um cluster Kubernetes teve conntrack saturado não por volume de tráfego externo, mas por tráfego interno entre pods: o Kubernetes DNS (CoreDNS) estava gerando milhares de queries UDP por segundo, e cada query UDP cria uma entrada de conntrack com timeout padrão de 30 segundos. Com 30.000 queries DNS por segundo e timeout de 30s, a tabela precisaria suportar 900.000 entradas simultâneas, muito além do padrão de 65.536 ou mesmo 131.072 de muitas configurações.
Quando esse cluster está atrás de um CGNAT, o problema é duplo: a tabela interna do cluster satura, e a tabela do CGNAT satura com as conexões que saem para endpoints externos.
GEO e contexto regulatório brasileiro
No Brasil, a Anatel exige que os ISPs mantenham logs de NAT por no mínimo 12 meses para atendimento a requisições judiciais. Em ambientes CGNAT com alocação dinâmica de portas, isso significa registrar cada evento de criação e destruição de mapeamento e, em um ambiente com 1.000 conexões por segundo, são 86,4 milhões de entradas de log por dia, por equipamento.
Essa realidade regulatória empurra os ISPs brasileiros para o modelo de Port Block Allocation (PBA), que reduz o volume de logs para um registro por bloco alocado (e não por conexão). O efeito colateral é que blocos maiores significam menos granularidade na alocação, menos flexibilidade por assinante e maior probabilidade de exaustão precoce em assinantes de alto consumo, exatamente o perfil de quem roda microserviços.
Existe aqui uma tensão regulatória-técnica irresolvida: quanto maior for o bloco de portas por assinante (para suportar cargas de microserviços), menor o número de assinantes por IP público (por limitação matemática), maior o consumo de endereços IPv4 escassos, e maior o custo operacional do ISP. A resposta correta, e que a regulação deveria incentivar mais agressivamente, é a adoção de IPv6 nativo, que elimina o problema estruturalmente.
O que muda com HTTP/2, HTTP/3 e QUIC
Um aspecto que raramente é discutido em análises de exaustão de portas em CGNAT é o impacto da evolução dos protocolos de aplicação.
O HTTP/2 resolve parcialmente o problema de conexões curtas ao usar multiplexação de streams sobre uma única conexão TCP persistente. Em vez de abrir uma nova conexão TCP para cada requisição, como no HTTP/1.1 sem Keep-Alive correto, o HTTP/2 mantém uma única conexão e multiplexa múltiplas requisições sobre ela. Para o conntrack, isso é uma única entrada, independente de quantas requisições transitam por ela.
O HTTP/3 vai além ao migrar para UDP com QUIC, tendo implicações interessantes para CGNAT: conexões UDP têm timeout de conntrack muito menor (30 segundos padrão no Linux, contra 120 para TCP em TIME_WAIT), mas a natureza connectionless do UDP significa que o CGNAT precisa de mecanismos diferentes para manter o estado de sessão QUIC. O RFC 9000 (QUIC) define um mecanismo de Connection ID exatamente para permitir a sobrevivência de conexões mesmo quando o IP ou porta muda, o que entra em tensão direta com o modelo de mapeamento estático de portas do CGNAT.
Em ambientes onde o CGNAT tem timeout UDP configurado abaixo de 30 segundos, configuração defensiva adotada por alguns operadores para liberar tabela de estado mais rapidamente, conexões QUIC inativas por mais tempo do que o timeout do CGNAT são silenciosamente descartadas, sem que a aplicação seja notificada. O QUIC tentará reconectar usando seu mecanismo de Connection Migration, mas o novo mapeamento NAT pode conflitar com o estado antigo ainda presente na tabela do cliente remoto.
Por que o gargalo sempre será a tabela, não a banda
Voltando ao ponto de partida: a intuição engenhosa de que um CGNAT “fica sem banda” antes de “ficar sem portas” é estruturalmente equivocada. A banda é elástica, você adiciona capacidade de link. A tabela de estado tem limitação de memória física, de largura de barramento de memória, e de latência de busca para correspondência de fluxo.
Nos equipamentos de hardware dedicado (Juniper MX, Cisco ASR, Nokia 7750), a tabela de sessões NAT é implementada em TCAM, Ternary Content-Addressable Memory, que permite busca em tempo constante independente do número de entradas, mas que tem capacidade fisicamente limitada e custo exponencial por unidade de capacidade. Um upgrade de tabela de TCAM muitas vezes não é possível via software, exige substituição de linha card.
Nos deployments baseados em software (Linux com netfilter, VyOS, pfSense), a tabela de conntrack é implementada em hash table na RAM do sistema. Aumentar nf_conntrack_max é possível, mas cada entrada consome aproximadamente 360 bytes de RAM. Uma tabela de 1 milhão de entradas consome ~360MB de RAM, que precisa ser memória de baixa latência para suportar as operações de busca e inserção em linha de pacotes. Em servidores com dezenas de gigabytes de RAM, esse é um custo administrável, mas precisa ser planejado, não descoberto em produção.
A mensagem que fica é simples e direta: quando um ambiente de CGNAT começa a apresentar falhas intermitentes de conexão sem correlação com utilização de banda, a primeira hipótese de investigação deve ser a tabela de estado, não o link. O comando cat /proc/sys/net/netfilter/nf_conntrack_count deveria estar em todo dashboard de NOC de ISP que opera CGNAT — e surpreendentemente, raramente está.





