O bypass de cgroup do nftables também precisa de correção do endereço de origem
Marcar um cgroup o envia para outra tabela, e o socket mantém o endereço que já tinha. Classificar, encaminhar e traduzir como três etapas separadas.
Marcar o tráfego de um cgroup é a parte fácil. Três decisões separadas precisam estar alinhadas antes que uma única aplicação saia pela interface física enquanto todo o resto fica no túnel: qual socket pertence à exceção, qual consulta de rota a marca dispara e qual endereço de origem o pacote carrega quando sai. A maioria das tentativas fracassadas acerta a primeira, acerta pela metade a segunda e nunca olha para a terceira.
Três decisões, não uma
A classificação responde quem está isento. O encaminhamento responde para onde o pacote marcado vai em vez disso. A seleção de origem responde o que a outra ponta verá e para onde a resposta deve voltar.
Essas coisas falham de forma independente. Um pacote pode ser classificado, reencaminhado para a interface física e ainda carregar um endereço privado que nenhum roteador a montante consegue devolver, o que produz um timeout que parece exatamente um bloqueio de firewall. Nada na etapa de classificação avisa sobre isso. Nada na etapa de encaminhamento também.
A ordem também importa. A classificação acontece quando o pacote é gerado, o encaminhamento acontece depois, e a tradução acontece no ponto de saída.
Se você não consegue nomear qual das três etapas está quebrada, você não está depurando, está reorganizando regras e torcendo.
Combine um cgroup que ainda exista amanhã
O nftables consegue testar a associação de um socket ao cgroup v2, o que é realmente útil porque segue o processo e não o destino. A pegadinha é o identificador. Um aplicativo de desktop iniciado por um menu costuma viver em um scope transitório cujo nome muda no próximo início.
nft list ruleset
systemctl status session.slice 2>/dev/null
cat /proc/self/cgroupCopiar o nome de um scope de uma sessão e codificá-lo fixamente produz uma regra que funciona hoje e silenciosamente deixa de casar amanhã. Coloque o aplicativo em um contexto de execução estável e deliberado, como um scope ou unit dedicado que você controla, e combine com ele. A regra então sobrevive a reinicializações e atualizações de pacotes. Também se torna algo sobre o qual um colega consegue raciocinar sem o seu histórico de sessão.
Também seja preciso sobre qual mecanismo você está usando. A classificação net_cls do cgroup v1 e a combinação de sockets do cgroup v2 são coisas diferentes, e um campo de nome parecido na documentação não as torna intercambiáveis.
É a cadeia de route que dispara a segunda consulta
Para tráfego gerado localmente, o tipo de cadeia decide se algo acontece depois que a marca é definida. Uma cadeia output do tipo filter pode marcar um pacote e o kernel continua usando a rota que já escolheu. Uma cadeia do tipo route é o mecanismo que pede uma nova consulta.
table inet bypass {
chain mark {
type route hook output priority mangle; policy accept;
socket cgroupv2 level 2 "app.slice" meta mark set 0x1 comment "app bypass"
}
}
ip rule add fwmark 0x1 lookup 200
ip route add default via 203.0.113.1 dev wlan0 table 200A marca então precisa de uma regra de política que a envie para uma tabela, e essa tabela precisa estar completa: uma rota padrão, os destinos locais de que o aplicativo ainda precisa e um gateway a montante que realmente responda.
Escolha o valor da marca deliberadamente. O WireGuard usa seu próprio fwmark para o tráfego de transporte externo, e cair no mesmo número por acidente pode interferir na exceção de rota do próprio túnel de formas dolorosas de diagnosticar.
O endereço de origem não segue a rota
Um socket que já escolheu um endereço de origem para o caminho do túnel mantém esse endereço depois do reencaminhamento. Nada na mudança de rota o reescreve, e é por isso que o pacote sai pela interface física com uma origem privada que a rede a montante não consegue devolver.
O sintoma visível é um aplicativo que parece enviar dados e nunca recebe resposta, e um log de firewall que não mostra nada porque o pacote nunca voltou.
A correção estreita é uma regra de tradução de origem limitada à marca daquele aplicativo e à sua interface de saída real, integrada ao NAT que já existe em vez de substituí-lo:
table inet bypass {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
meta mark 0x1 oifname "wlan0" masquerade
}
}Traduza a exceção e nada mais. Uma regra masquerade abrangente corrige o sintoma e destrói sua capacidade de dizer qual tráfego tomou qual caminho. Combine com a marca, a interface de saída real e, se puder, o endereço do próprio aplicativo. Regras amplas são fáceis de escrever e quase impossíveis de auditar depois.
O que ainda escapa da exceção
A resolução de nomes pode vir de um processo resolver separado em outro cgroup, então classificar o socket do navegador não classifica suas consultas. Se as consultas do resolver ficam no túnel enquanto a conexão sai fora dele, respostas split-horizon podem não casar com o caminho. Essa incompatibilidade produz falhas que parecem bugs de DNS e não são.
Processos filhos herdam o cgroup na maioria das configurações, mas um auxiliar que se move para o próprio scope não. Verifique em vez de presumir, especialmente para qualquer coisa que se reexecute.
Por fim, decida o que deve acontecer quando o túnel está fora do ar. Uma exceção que silenciosamente se torna um bypass completo é uma postura de segurança diferente de uma que falha fechada, e apenas uma delas é o que você quis dizer.