O hook funciona na primeira vez, e após três reconexões a cadeia contém três cópias da mesma regra. Nada falhou ruidosamente; o script simplesmente fez o que lhe foi dito a cada execução. Um hook de VPN é uma operação de configuração que pode rodar mais de uma vez, então precisa de um proprietário, um estado desejado e uma alteração serializada. Siga essa ordem e os duplicados param.

Dê um proprietário às regras antes de escrever qualquer uma

Decida qual cadeia ou tabela pertence a esta integração antes de escrever uma única regra. Uma cadeia dedicada que o hook cria e esvazia é a forma mais clara de propriedade, porque a limpeza pode então remover essa cadeia sem tocar na política de ninguém mais. Se precisar adicionar regras a uma cadeia compartilhada, marque-as com um comentário para que uma busca posterior encontre exatamente as suas. A propriedade é o que torna a desmontagem segura, e a desmontagem é a parte que falha em silêncio.

iptables -N VPN_HOOK 2>/dev/null || true
iptables -C INPUT -j VPN_HOOK 2>/dev/null || iptables -I INPUT -j VPN_HOOK
iptables -S VPN_HOOK

Um comentário aqui não é decoração. É a diferença entre remover a sua regra e remover uma regra que por acaso parece semelhante, e ele sobrevive no conjunto de regras salvo. Escreva o comentário uma vez numa variável e use a mesma string em cada comando que adiciona, testa ou exclui.

Teste o estado desejado em vez de adicionar de novo

Escreva o que deve existir enquanto o túnel está ativo: quais interfaces, qual origem e destino, qual protocolo e qual porta. Depois expresse cada regra como um teste seguido de uma inserção, para que uma segunda execução encontre a regra e não faça nada. O comando de verificação termina com zero quando a regra existe, e esse é exatamente o sinal de que o script precisa. A referência do iptables documenta as formas de verificação, adição e inserção usadas aqui.

iptables -C VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT ||
  iptables -A VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT

Cuidado com a posição. Verificar uma regra e inseri-la no topo pode mover uma permissão para antes de uma negação deliberada que antes vinha primeiro, então a segunda execução muda o comportamento mesmo sem adicionar nada. Adicione no fim dentro da sua própria cadeia e controle a ordem pela posição do salto para essa cadeia. Se a ordem dentro da cadeia importa, reconstrua a cadeia a partir de uma lista conhecida em vez de remendá-la regra por regra.

Pare se a regra de que você precisa não puder ser expressa como um teste estável. Isso normalmente significa que o estado desejado ainda está vago.

Serialize a alteração em vez de esperar que ela se mantenha

Duas invocações do hook podem se sobrepor, e ambas podem ver uma regra ausente antes de qualquer uma adicioná-la. A opção de espera do xtables faz um comando esperar o bloqueio, mas não torna atômica uma sequência de verificar-e-depois-adicionar entre dois comandos. Serialize toda a operação com seu próprio bloqueio, ou use um gerenciador que aplique um lote declarado como uma única transação. O nftables consegue o último, e essa é uma razão para preferi-lo em hosts onde está disponível.

flock /run/vpn-hook.lock /usr/local/sbin/vpn-firewall-apply
nft -f /etc/nftables/vpn-hook.nft

Qualquer que seja o mecanismo escolhido, mantenha a seção crítica curta e torne-a reentrante: um hook que falha no meio do caminho deve ser seguro de executar de novo. Registre o resultado para que uma execução repetida seja visível no journal em vez de deduzida da contagem de regras. Se dois supervisores diferentes podem invocar o hook, corrija isso primeiro; nenhum bloqueio conserta dois proprietários.

Desmonte apenas o que é seu

A desmontagem tem de distinguir uma conexão de substituição daquela que aposentou, e tem de sobreviver a um evento que nunca recebe uma limpeza tranquila. Exclua pelos mesmos identificadores que você usou para adicionar, e nunca esvazie uma cadeia compartilhada para simplificar a limpeza. Após uma parada dura, execute uma passagem de reconciliação no início seguinte em vez de supor que o hook de descida rodou.

iptables -D VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT
iptables -F VPN_HOOK
ip -brief link show type tun

A vida útil da interface também faz parte do projeto. Uma reconexão pode criar um dispositivo com outro nome, então uma regra que fixa o nome do segundo túnel fica obsoleta enquanto ainda parece válida. Prefira a identidade que a própria conexão expõe, e releia-a a cada execução. Teste os três eventos separadamente: uma conexão repetida, uma conexão de substituição e um desligamento. O tráfego permitido deve sobreviver aos três sem duplicados, e os serviços não relacionados devem ficar intactos.

O percurso termina quando o hook tem um proprietário, um teste para cada regra que adiciona, um caminho serializado e uma desmontagem que remove apenas o seu próprio estado. Um hook que é seguro executar duas vezes vale mais do que um hook que é esperto uma vez.

Artigos relacionados