Le hook fonctionne la première fois, et après trois reconnexions la chaîne contient trois copies de la même règle. Rien n’a échoué bruyamment ; le script a simplement fait ce qu’on lui disait à chaque exécution. Un hook VPN est une opération de configuration qui peut s’exécuter plus d’une fois, il a donc besoin d’un propriétaire, d’un état désiré et d’un changement sérialisé. Suivez cet ordre et les doublons s’arrêtent.

Donnez un propriétaire aux règles avant d’en écrire une

Décidez quelle chaîne ou table cette intégration possède avant d’écrire une seule règle. Une chaîne dédiée que le hook crée et vide est la forme de propriété la plus claire, car le nettoyage peut ensuite la supprimer sans toucher à la politique de quelqu’un d’autre. Si vous devez ajouter des règles à une chaîne partagée, marquez-les d’un commentaire pour qu’une recherche ultérieure trouve exactement les vôtres. La propriété rend le démontage sûr, et le démontage est la partie qui échoue en silence.

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

Un commentaire n’est pas une décoration ici. C’est la différence entre supprimer votre règle et supprimer une règle qui se trouve être similaire, et il survit dans le jeu de règles enregistré. Écrivez le commentaire une fois dans une variable et utilisez la même chaîne dans chaque commande qui ajoute, teste ou supprime.

Testez l’état désiré au lieu d’ajouter à nouveau

Écrivez ce qui doit exister pendant que le tunnel est actif : quelles interfaces, quelle source et destination, quel protocole et quel port. Exprimez ensuite chaque règle comme un test suivi d’une insertion, pour qu’une seconde exécution trouve la règle et ne fasse rien. La commande de vérification se termine par zéro lorsque la règle existe, et c’est exactement le signal dont le script a besoin. La référence iptables documente les formes de vérification, d’ajout et d’insertion utilisées ici.

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

Attention à la position. Vérifier une règle et l’insérer en tête peut déplacer une autorisation devant un refus délibéré qui venait auparavant en premier, si bien que la seconde exécution change le comportement même sans rien ajouter. Ajoutez à la fin dans votre propre chaîne et contrôlez l’ordre par l’emplacement du saut vers cette chaîne. Si l’ordre compte à l’intérieur de la chaîne, reconstruisez la chaîne à partir d’une liste connue plutôt que de la rapiécer règle par règle.

Arrêtez-vous si la règle dont vous avez besoin ne peut pas être exprimée comme un test stable. Cela signifie généralement que l’état désiré est encore vague.

Sérialisez le changement au lieu d’espérer qu’il tienne

Deux invocations du hook peuvent se chevaucher, et toutes deux peuvent voir une règle absente avant que l’une d’elles ne l’ajoute. L’option d’attente d’xtables fait attendre une commande après le verrou, mais elle ne rend pas une séquence vérifier-puis-ajouter atomique sur deux commandes. Sérialisez toute l’opération avec votre propre verrou, ou utilisez un gestionnaire qui applique un lot déclaré comme une seule transaction. nftables peut faire ce dernier, ce qui est une raison de le préférer sur les hôtes où il est disponible.

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

Quel que soit le mécanisme choisi, gardez la section critique courte et rendez-la réentrante : un hook qui échoue à mi-chemin doit pouvoir être relancé sans danger. Journalisez le résultat pour qu’une exécution répétée soit visible dans le journal au lieu d’être déduite d’un nombre de règles. Si deux superviseurs différents peuvent invoquer le hook, corrigez cela d’abord ; aucun verrou ne répare deux propriétaires.

Ne démontez que ce qui vous appartient

Le démontage doit distinguer une connexion de remplacement de celle qu’il a retirée, et il doit survivre à un événement qui n’obtient jamais de nettoyage propre. Supprimez avec les mêmes identifiants que ceux utilisés pour l’ajout, et ne videz jamais une chaîne partagée pour simplifier le nettoyage. Après un arrêt brutal, exécutez une passe de réconciliation au démarrage suivant au lieu de supposer que le hook de descente a tourné.

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

La durée de vie de l’interface fait aussi partie de la conception. Une reconnexion peut créer un périphérique portant un autre nom, si bien qu’une règle qui code en dur le nom du second tunnel devient obsolète tout en paraissant encore valide. Préférez l’identité que la connexion elle-même expose, et relisez-la à chaque exécution. Testez les trois événements séparément : une connexion répétée, une connexion de remplacement et un arrêt. Le trafic autorisé doit survivre aux trois sans doublons, et les services sans rapport doivent rester intacts.

La visite se termine lorsque le hook a un propriétaire, un test pour chaque règle qu’il ajoute, un chemin sérialisé et un démontage qui ne retire que son propre état. Un hook sûr à exécuter deux fois vaut plus qu’un hook intelligent une seule fois.

Articles similaires