As regras das portas 80 e 443 não levam o navegador por uma VPN
Uma correspondência de porta seleciona fluxos TCP, não um navegador. O HTTP/3 roda sobre UDP, o DNS pode tomar outro caminho e um atualizador casa com a mesma regra que você escreveu.
Alguém pede que o navegador use o túnel, e a resposta volta como uma regra de firewall que casa com as portas de destino 80 e 443. Parece precisa e não é. Uma correspondência de porta seleciona pacotes pelo cabeçalho de transporte, não por qual programa abriu o socket, e a navegação moderna não se limita a essas duas portas no TCP. A regra vai perder parte do tráfego que você queria incluir e capturar tráfego que você nunca pretendeu tocar. Antes de escrevê-la, decida qual de três fronteiras diferentes você realmente quer.
A regra descreve uma classe de transporte, não uma aplicação
No Linux, uma correspondência de porta de destino inspeciona o cabeçalho TCP e nada acima dele. Marcar esses pacotes e encaminhá-los com uma rota de política funciona exatamente como documentado, e nenhuma dessas máquinas sabe o que a carga útil significa nem qual processo a enviou. A marca é um número preso a um pacote, não uma identidade presa a uma aplicação.
nft add rule inet mangle output tcp dport { 80, 443 } meta mark set 0x1
ip rule add fwmark 0x1 table 100
ip route add default dev wg0 table 100
Duas consequências seguem imediatamente. Um atualizador de software que contata TCP 443 recebe o mesmo tratamento do navegador, o que pode estar bom ou pode ser uma interrupção esperando o próximo ciclo de atualização. E um serviço web publicado de propósito em outra porta, digamos 8443, fica completamente fora da regra, embora ainda seja o tráfego web com que o usuário se importava.
Os números são seletores legítimos quando o requisito realmente trata dessas portas. Viram ficção quando o ticket diz «navegador» e um intervalo de portas é implementado no conjunto de regras. Essa lacuna não é um detalhe de documentação. É a diferença entre uma regra que você consegue defender numa revisão e uma que ninguém consegue testar.
O HTTP/3 abandona em silêncio a sua regra de TCP
O HTTP/3 roda sobre QUIC, e o QUIC é transportado em UDP. Uma regra que casa apenas com TCP não consegue vê-lo, então o mesmo navegador que passa na sua página de teste pode enviar uma solicitação posterior por UDP e contornar o túnel sem que ninguém mude uma configuração. RFC 9114 descreve o HTTP/3 como um protocolo baseado em QUIC, e o QUIC usa UDP com porta padrão 443 na implantação comum.
Se determinado navegador tenta HTTP/3 depende da configuração dele, do site e de se uma tentativa anterior deu certo. Isso o torna uma coisa ruim para raciocinar de memória. Pergunte à ferramenta:
curl --http3 -sS -o /dev/null -w '%{http_version} %{time_total}\n' https://example.com
ss -tunp | grep -E ':443\b'
A linha do curl imprime a versão negociada, então um 3 confirma que o caminho UDP foi usado. A listagem de sockets mostra então se essa conexão está presa à interface do túnel ou à física. Se o HTTP/3 não estiver disponível na sua compilação do curl, a opção falha ruidosamente em vez de recuar em silêncio, o que é a sua própria pequena misericórdia.
O DNS é uma decisão separada que você precisa tomar
Uma regra de porta para tráfego web não diz nada sobre resolução de nomes. O navegador pode usar o resolvedor do sistema, um resolvedor configurado dentro do navegador ou um que a última conexão empurrou. Cada um deles pode enviar consultas por um caminho diferente do que a sua regra seleciona, o que produz a cisão clássica: a página carrega, o nome é resolvido num lugar que você não esperava e o site interno resolve para um endereço público ou não resolve de jeito nenhum.
Decida isso explicitamente. Se os nomes internos precisam resolver pelo túnel, o resolvedor tem de estar dentro dele, e o conjunto de regras precisa permitir DNS para esse resolvedor mantendo as outras consultas fora. Se o navegador usa DNS over HTTPS para um provedor público, a consulta contorna o seu resolvedor por inteiro e a regra de porta não pode ajudar. Escreva a escolha no mesmo registro de mudança que a regra de roteamento, porque quem depurar a próxima falha lê as duas juntas.
Depois verifique com o resolvedor, não com a página:
resolvectl query intranet.example.internal
resolvectl status | grep -A3 'Current DNS Server'
Uma resposta ausente é um problema de resolução mesmo quando a mensagem de erro do navegador culpa a rede. Essas duas falhas parecem idênticas para o usuário e são tickets completamente diferentes.
Três fronteiras, três mecanismos diferentes
A maior parte da confusão desaparece assim que você nomeia a fronteira que quer. Elas não são intercambiáveis, e cada uma tem um modo de falha diferente durante uma queda do túnel.
| What you actually want | Mechanism that fits | What it does not cover |
|---|---|---|
| These transport ports use this exit | Firewall mark plus policy route | Applications, HTTP/3 over UDP, DNS |
| This browsing session stays separate | Application-aware VPN, or a separate network namespace plus a proxy | Other applications in the same session |
| Only these private sites are reachable | Destination-based routes with private DNS | Public sites on the same address family |
Um perfil de navegador separado é a armadilha da linha do meio. Ele separa cookies e histórico, e não muda nada na tabela de roteamento do host, então os dois perfis tomam o mesmo caminho. O isolamento exige algo que o kernel imponha, isto é, um namespace, um contêiner ou uma aplicação que tenha a sua própria interface de túnel.
Qualquer que seja a linha aplicável, escreva o que deve acontecer quando o túnel cair. Uma rota baseada em marca que desaparece com a interface costuma voltar à rota padrão física, o que é comportamento fail-open. Isso pode ser aceitável para navegar e inaceitável para todo o resto, e é uma decisão, não um detalhe de implementação.
Teste os fluxos que você afirma cobrir
O teste de aceitação precisa seguir a fronteira que você escreveu, não os números de porta que eram convenientes. Escolha um fluxo que deve passar pelo túnel, um que não deve e um que usa um transporte em que você não pensou:
ip route get 203.0.113.40
curl -sS -o /dev/null -w 'v4 %{http_version} %{remote_ip}\n' https://example.com
nft list ruleset | grep -A2 'dport'
A consulta de rota diz qual interface um destino realmente usa, e esse é o fato que a maioria dos relatórios erra. A linha do curl confirma a versão negociada e a família de endereços. O dump do conjunto de regras pega a regra que você adicionou durante os testes e esqueceu de delimitar, e é assim que uma marca temporária vira roteamento fail-open permanente.
Registre as três saídas com um carimbo de tempo e guarde-as com a mudança. Quando mais tarde alguém relatar que «a VPN quebrou a intranet», a diferença entre a fronteira pretendida e a implementada estará visível nesse registro, e a correção será uma mudança de regra e não uma discussão.