Fase 4: Furando NATs
2026-02-15
A maioria dos dispositivos das pessoas ficam atras de um NAT — um tradutor de enderecos de rede que permite acessar a internet mas impede conexoes de entrada. Para uma rede P2P, isso e um problema existencial: se dois nos atras de NATs nao conseguem se comunicar, a rede se fragmenta. A Fase 4 continua com uma pilha completa de travessia de NAT: descoberta via STUN, hole punching coordenado e fallback por relay.
A abordagem segue o mesmo padrao da maioria dos sistemas P2P consolidados (WebRTC, BitTorrent, IPFS): tente a opcao mais barata primeiro, escale apenas quando necessario. Conectividade direta nao custa nada. Hole punching custa alguns pacotes coordenados. Relay custa largura de banda sustentada de um terceiro. Tesseras tenta nessa ordem.
O que foi construido
Classificacao NatType (tesseras-core/src/network.rs) — Um novo enum
NatType (Public, Cone, Symmetric, Unknown) adicionado a camada de dominio
core. Esse tipo e compartilhado por toda a pilha: o cliente STUN o escreve, o
DHT o divulga em mensagens Pong, e o coordenador de punch o le para decidir se
hole punching vale a pena tentar (Cone-para-Cone funciona ~80% das vezes;
Symmetric-para-Symmetric quase nunca funciona).
Cliente STUN (tesseras-net/src/stun.rs) — Uma implementacao STUN minima
(RFC 5389 Binding Request/Response) que descobre o endereco externo de um no. O
codec codifica requisicoes de 20 bytes com um ID de transacao aleatorio e
decodifica respostas XOR-MAPPED-ADDRESS. A funcao discover_nat() consulta
multiplos servidores STUN em paralelo (Google, Cloudflare por padrao), compara
os enderecos mapeados e classifica o tipo de NAT:
- Mesmo IP e porta de todos os servidores → Public (sem NAT)
- Mesmo endereco mapeado de todos os servidores → Cone (hole punching funciona)
- Enderecos mapeados diferentes → Symmetric (hole punching nao confiavel)
- Sem respostas → Unknown
Retentativas com backoff exponencial e timeouts configuraveis. 12 testes cobrindo roundtrips de codec, todos os caminhos de classificacao e consultas async em loopback.
Coordenacao de punch assinada (tesseras-net/src/punch.rs) — Assinatura e
verificacao Ed25519 para mensagens PunchIntro, RelayRequest e
RelayMigrate. Cada introducao e assinada pelo iniciador com uma janela de
timestamp de 30 segundos, prevenindo ataques de reflexao (onde um atacante
reproduz uma introducao antiga para redirecionar trafego). O formato do payload
e target || external_addr || timestamp — alterar qualquer campo invalida a
assinatura. 6 testes unitarios mais 3 testes baseados em propriedades com
proptest (IDs de no, portas e tokens de sessao arbitrarios).
Gerenciador de sessoes de relay (tesseras-net/src/relay.rs) — Gerencia
sessoes de relay UDP transparente entre nos com NAT. Cada sessao tem um token
aleatorio de 16 bytes; os nos prefixam seus pacotes com o token, o relay remove
e encaminha. Funcionalidades:
- Encaminhamento bidirecional (A→R→B e B→R→A)
- Limite de taxa: 256 KB/s para nos reciprocos, 64 KB/s para nao reciprocos
- Duracao maxima de 10 minutos para sessoes bootstrap (nao reciprocas)
- Migracao de endereco: quando o IP de um no muda (Wi-Fi para celular), um
RelayMigrateassinado atualiza a sessao sem derruba-la - Limpeza por inatividade com timeout configuravel
- 8 testes unitarios mais 2 testes baseados em propriedades
Extensoes de mensagens DHT (tesseras-dht/src/message.rs) — Sete novas
variantes de mensagem adicionadas ao protocolo DHT:
| Mensagem | Proposito |
|---|---|
PunchIntro | "Quero conectar ao no X, aqui esta meu endereco externo assinado" |
PunchRequest | O introdutor encaminha a requisicao ao destino |
PunchReady | O destino confirma prontidao, envia seu endereco externo |
RelayRequest | "Crie uma sessao de relay para o no X" |
RelayOffer | O relay responde com seu endereco e token de sessao |
RelayClose | Encerrar uma sessao de relay |
RelayMigrate | Atualizar sessao apos mudanca de rede |
A mensagem Pong foi estendida com metadados NAT: nat_type,
relay_slots_available e relay_bandwidth_used_kbps. Todos os novos campos
usam #[serde(default)] para compatibilidade retroativa — nos antigos ignoram o
que nao reconhecem, nos novos usam defaults. 9 novos testes de roundtrip de
serializacao.
Trait NatHandler e dispatch (tesseras-dht/src/engine.rs) — Uma nova trait
async NatHandler (5 metodos) injetada no engine DHT, seguindo o mesmo padrao
de injecao de dependencia do ReplicationHandler existente. O loop de dispatch
de mensagens do engine agora roteia todas as mensagens punch/relay para o
handler. Isso mantem o engine DHT agnóstico ao protocolo enquanto permite que a
logica de travessia de NAT viva em tesseras-net.
Tipos de reconexao mobile (tesseras-embedded/src/reconnect.rs) — Uma
maquina de estados de reconexao em tres fases para dispositivos moveis:
- QuicMigration (0-2s) — tenta migracao de conexao QUIC para todos os peers ativos
- ReStun (2-5s) — redescobre endereco externo via STUN
- ReEstablish (5-10s) — reconecta peers que a migracao nao conseguiu salvar
Peers sao reconectados em ordem de prioridade: nos bootstrap primeiro, depois
nos que guardam nossos fragmentos, depois nos cujos fragmentos guardamos, depois
vizinhos DHT gerais. Uma nova variante de evento NetworkChanged foi adicionada
ao stream de eventos FFI para que o app Flutter possa mostrar progresso de
reconexao.
Configuracao NAT do daemon (tesd/src/config.rs) — Uma nova secao [nat]
na configuracao TOML com lista de servidores STUN, toggle de relay, maximo de
sessoes relay, limites de largura de banda (reciproco vs bootstrap) e timeout de
inatividade. Todos os campos tem defaults sensiveis; relay e desabilitado por
padrao.
Metricas Prometheus (tesseras-net/src/metrics.rs) — 16 metricas em quatro
subsistemas:
- STUN: requisicoes, falhas, histograma de latencia
- Punch: tentativas/sucessos/falhas (por par de tipo NAT), histograma de latencia
- Relay: sessoes ativas, sessoes totais, bytes encaminhados, timeouts por inatividade, hits de rate limit
- Reconexao: mudancas de rede, tentativas/sucessos por fase, histograma de duracao
6 testes verificando registro, incremento, cardinalidade de labels e deteccao de registro duplo.
Testes de integracao — Dois testes end-to-end usando MemTransport (rede
simulada em memoria):
punch_integration.rs— Fluxo completo de hole-punch com 3 nos: A enviaPunchIntroassinado ao introdutor I, I verifica e encaminhaPunchRequesta B, B verifica a assinatura original e enviaPunchReadyde volta, A e B trocam mensagens diretamente. Tambem testa que uma assinatura invalida e corretamente rejeitada.relay_integration.rs— Fluxo completo de relay com 3 nos: A solicita relay de R, R cria sessao e enviaRelayOffera ambos os peers, A e B trocam pacotes prefixados com token atraves de R, A migra para um novo endereco no meio da sessao, A fecha a sessao, e o teste verifica que a sessao e encerrada e encaminhamento posterior falha.
Testes de propriedade — 7 testes baseados em proptest cobrindo: roundtrips de assinatura para todos os tres tipos de mensagem assinada (IDs de no, portas e tokens arbitrarios), determinismo de classificacao NAT (mesmas entradas sempre produzem mesma saida), validade de binding request STUN, unicidade de tokens de sessao, e rejeicao de pacotes curtos pelo relay.
Alvos Justfile — just test-nat executa todos os testes de travessia NAT em
tesseras-net e tesseras-dht. just test-chaos e um placeholder para futuros
testes de caos com Docker Compose e tc netem.
Decisoes de arquitetura
- STUN ao inves de TURN: implementamos STUN (descoberta) e relay customizado ao inves de TURN completo. TURN requer alocacao autenticada e foi projetado para relay de midia; nosso relay e mais simples — encaminhamento UDP com prefixo de token e limites de taxa. Isso mantem o protocolo minimo e evita depender de servidores TURN externos.
- Assinaturas em introducoes: cada
PunchIntroe assinado pelo iniciador. Sem isso, um atacante poderia enviar introducoes forjadas para redirecionar as tentativas de hole-punch de um no para um endereco controlado pelo atacante (ataque de reflexao). A janela de timestamp de 30 segundos limita replay. - Tiers reciprocos de largura de banda: nos relay dao 4x mais largura de banda (256 vs 64 KB/s) para peers com boas pontuacoes de reciprocidade. Isso incentiva nos a armazenar fragmentos para outros — se voce contribui, recebe melhor servico de relay quando precisa.
- Extensao Pong retrocompativel: novos campos NAT em
Pongusam#[serde(default)]eOption<T>. Nos antigos que nao entendem esses campos simplesmente os pulam durante deserializacao. Nenhum bump de versao de protocolo necessario. - NatHandler como trait async: a logica de travessia NAT e injetada no
engine DHT via trait, assim como
ReplicationHandler. Isso mantem o engine DHT focado em roteamento e gerenciamento de peers, e permite que a implementacao NAT seja trocada ou desabilitada sem tocar no codigo core do DHT.
O que vem a seguir
- Fase 4 continuacao — tuning de performance (pooling de conexoes, cache de fragmentos, SQLite WAL), auditorias de seguranca, onboarding de nos institucionais, empacotamento para OS
- Fase 5: Exploracao e Cultura — navegador publico de tesseras por era/localizacao/tema/idioma, curadoria institucional, integracao genealogica, exportacao para midia fisica (M-DISC, microfilme, papel livre de acido com QR)
Com travessia de NAT, Tesseras pode conectar nos independentemente de sua topologia de rede. Nos publicos conversam diretamente. Nos com NAT Cone furam com ajuda de um introdutor. Nos com NAT Symmetric ou firewalled usam relay atraves de peers voluntarios. A rede se adapta ao mundo real, onde a maioria dos dispositivos esta atras de um NAT e as condicoes de rede mudam constantemente.