App local, regra VPN: por que sua cadeia FORWARD nunca dispara
Um processo na máquina usa OUTPUT, não FORWARD. Escolha o hook no namespace correto e depois decida se basta uma marca ou uma rota simples.
Você adiciona uma regra para o tráfego VPN na cadeia FORWARD, executa a requisição a partir da mesma máquina Linux, e o contador fica em zero. A regra provavelmente está correta. O pacote nunca seguiu esse caminho, porque uma requisição gerada na própria máquina não é encaminhada por ela. Acompanhar a vida do pacote do socket até o cabo explica por quê, e onde a regra deveria ficar em vez disso.
Descubra onde o pacote nasceu
A ordem dos hooks do Netfilter separa três casos, e apenas um deles envolve FORWARD:
| Origin | Path | Hook that sees it |
|---|---|---|
| Local process | OUTPUT then POSTROUTING | OUTPUT, POSTROUTING |
| Addressed to a local service | PREROUTING then INPUT | PREROUTING, INPUT |
| Received for another host | PREROUTING, FORWARD, POSTROUTING | FORWARD |
Uma aplicação local começa no caminho OUTPUT, porque o kernel já estabeleceu que o pacote se origina aqui e nunca é candidato a encaminhamento. Criar um túnel não muda essa classificação; o túnel é um dispositivo pelo qual o pacote pode sair, não uma etapa que converte a saída local em trânsito.
Dois pacotes, uma história
Depois que o túnel sobe, o pacote interno da aplicação e o pacote externo cifrado são objetos diferentes. O pacote interno carrega o destino e a carga que a aplicação escolheu. O pacote externo carrega texto cifrado para o endereço e a porta do par, e é ele que realmente cruza a interface física.
Sua política diz respeito a um deles, e escolher o errado é uma causa comum de uma regra parecer não fazer nada. Uma regra que corresponde ao endereço externo do par vê todos os pacotes do túnel igualmente, faça o que fizer a aplicação lá dentro. Uma regra que corresponde a um destino interno vê o tráfego que lhe interessa, e só disparará se esse pacote chegar ao hook onde você instalou a regra.
Namespaces movem a fronteira
Um contêiner torna isso mais difícil sem mudar o princípio. A requisição da aplicação é saída local dentro do namespace de rede do contêiner. O host recebe esse tráfego em uma interface Ethernet virtual e pode encaminhá-lo, então uma regra FORWARD no host pode fazer sentido mesmo que o processo que gerou a requisição nunca tenha tocado a cadeia OUTPUT do host.
Antes de escolher um hook, estabeleça onde realmente vivem o conjunto de regras e o túnel. Um túnel dentro do contêiner, um túnel no host com o contêiner roteado através dele e um túnel em um namespace separado são três topologias diferentes com três posicionamentos de regras diferentes. Leia as regras dentro do namespace que possui o dispositivo, não apenas no padrão.
Pergunte se você precisa mesmo de uma marca
Se o objetivo é enviar um prefixo de destino pelo túnel, uma rota para esse prefixo pode bastar. Verifique primeiro as rotas efetivas, incluindo tudo o que o servidor empurrou: uma rota padrão redirecionada envia muito mais tráfego pela VPN do que a política estreita que você tinha em mente, e nenhuma quantidade de marcação fará o resultado corresponder à intenção.
A classificação é necessária quando a decisão depende de algo que uma rota não consegue expressar, como a origem, o usuário ou o processo. Para o tráfego recebido, a marcação em PREROUTING funciona. Para o tráfego local, a marcação precisa ocorrer em um estágio de saída que consiga disparar uma nova consulta de rota depois que a marca é definida. Com nftables, isso é uma cadeia do tipo route no hook de saída; uma cadeia de filtro de saída comum definirá o valor e deixará a rota inalterada.
Com iptables, o equivalente vive na disposição mangle OUTPUT, e o mesmo requisito se aplica: a decisão de roteamento precisa ser revista depois que a marca existe. Em seguida, uma regra de política tem de consumir a marca e apontar para uma tabela que contenha uma rota utilizável. Definir uma marca de conntrack é uma terceira operação, separada; ela guarda um valor no fluxo e não coloca por si só esse valor no pacote atual para que o roteamento o leia.
Verifique a consulta e o contador juntos
nft list ruleset
ip route get 198.51.100.25
ip route get 198.51.100.25 mark 0x1
O endereço e a marca são ilustrativos, e as duas consultas de rota respondem a perguntas diferentes. A segunda pergunta o que o kernel faria se um pacote carregasse aquela marca; ela não mostra que seu conjunto de regras atribua uma. Trate-a como um teste de hipótese, não como evidência de que a política funciona.
Depois execute uma requisição inofensiva e observe duas coisas ao mesmo tempo: o contador no hook escolhido e a saída que o pacote realmente tomou. Se o contador se move e o pacote sai por outro lugar, a marca ou a tabela estão erradas. Se nenhum dos dois se move, o pacote não chega a esse hook de jeito nenhum, e a próxima pergunta é o namespace ou o caminho.
Por fim, limite o teste ao que o mecanismo consegue expressar. Uma rota para um endereço não se torna uma política permanente para um serviço cujos endereços rodam. Decida primeiro o escopo dos pacotes e depois verifique se o desenho de roteamento e marcação consegue manter esse escopo após uma reconexão.