Der Hook funktioniert beim ersten Mal, und nach drei erneuten Verbindungen enthält die Kette drei Kopien derselben Regel. Nichts ist laut fehlgeschlagen; das Skript tat bei jedem Lauf einfach, was ihm gesagt wurde. Ein VPN-Hook ist eine Konfigurationsoperation, die mehr als einmal laufen kann, deshalb braucht er einen Besitzer, einen Zielzustand und eine serialisierte Änderung. Geh in dieser Reihenfolge vor, und die Duplikate hören auf.

Gib den Regeln einen Besitzer, bevor du eine schreibst

Entscheide, welche Kette oder Tabelle diese Integration besitzt, bevor du eine einzige Regel schreibst. Eine eigene Kette, die der Hook anlegt und leert, ist die klarste Form von Besitz, weil das Aufräumen diese Kette dann entfernen kann, ohne das Regelwerk von jemand anderem anzutasten. Wenn du Regeln zu einer gemeinsamen Kette hinzufügen musst, versieh sie mit einem Kommentar, damit eine spätere Suche genau deine findet. Besitz macht das Aufräumen sicher, und das Aufräumen ist der Teil, der still fehlschlägt.

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

Ein Kommentar ist hier keine Dekoration. Er entscheidet, ob du deine Regel entfernst oder eine Regel, die zufällig ähnlich aussieht, und er bleibt im gespeicherten Regelsatz erhalten. Schreib den Kommentar einmal in eine Variable und verwende dieselbe Zeichenkette in jedem Befehl, der hinzufügt, prüft oder löscht.

Prüfe den Zielzustand, statt erneut hinzuzufügen

Schreib auf, was bestehen soll, solange der Tunnel aktiv ist: welche Schnittstellen, welche Quelle und welches Ziel, welches Protokoll und welcher Port. Drück dann jede Regel als Test gefolgt von einer Einfügung aus, sodass ein zweiter Lauf die Regel findet und nichts tut. Der Prüfbefehl endet mit Null, wenn die Regel existiert, und genau das ist das Signal, das das Skript braucht. Die iptables-Referenz dokumentiert die hier verwendeten Prüf-, Anhäng- und Einfügeformen.

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

Sei vorsichtig mit der Position. Eine Regel zu prüfen und am Kopf einzufügen kann eine Freigabe vor eine beabsichtigte Ablehnung schieben, die bisher zuerst kam, sodass der zweite Lauf das Verhalten ändert, obwohl er nichts hinzufügt. Häng innerhalb deiner eigenen Kette an und steuere die Reihenfolge über die Position des Sprungs in diese Kette. Wenn die Reihenfolge innerhalb der Kette wichtig ist, bau die Kette aus einer bekannten Liste neu auf, statt sie Regel für Regel zu flicken.

Hör auf, wenn die benötigte Regel sich nicht als stabiler Test ausdrücken lässt. Das bedeutet meistens, dass der Zielzustand noch unklar ist.

Serialisiere die Änderung, statt zu hoffen, dass sie hält

Zwei Aufrufe des Hooks können sich überschneiden, und beide können eine fehlende Regel sehen, bevor einer von ihnen sie hinzufügt. Die Warteoption von xtables lässt einen Befehl auf die Sperre warten, aber sie macht eine Prüfen-dann-Hinzufügen-Folge nicht über zwei Befehle hinweg atomar. Serialisiere die ganze Operation mit deiner eigenen Sperre oder verwende einen Manager, der einen deklarierten Stapel als eine Transaktion anwendet. nftables kann Letzteres, was einer der Gründe ist, es auf Hosts zu bevorzugen, wo es verfügbar ist.

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

Welchen Mechanismus du auch wählst, halt den kritischen Abschnitt kurz und mach ihn wiedereintrittsfähig: Ein Hook, der auf halbem Weg scheitert, sollte erneut ausführbar sein. Protokolliere das Ergebnis, damit ein wiederholter Lauf im Journal sichtbar wird und nicht aus einer Regelanzahl erschlossen werden muss. Wenn zwei verschiedene Supervisoren den Hook aufrufen können, behebe das zuerst; keine Sperre repariert zwei Besitzer.

Räum nur ab, was dir gehört

Das Aufräumen muss eine Ersatzverbindung von der unterscheiden, die sie abgelöst hat, und es muss ein Ereignis überstehen, das nie eine saubere Bereinigung bekommt. Lösche über dieselben Kennungen, die du zum Hinzufügen verwendet hast, und leere niemals eine gemeinsame Kette, um das Aufräumen einfach zu machen. Nach einem harten Stopp führ beim nächsten Start einen Abgleich durch, statt anzunehmen, dass der Down-Hook gelaufen ist.

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

Die Lebensdauer der Schnittstelle gehört ebenfalls ins Design. Eine erneute Verbindung kann ein Gerät mit einem anderen Namen erzeugen, sodass eine Regel, die den Namen des zweiten Tunnels fest verdrahtet, veraltet, während sie noch gültig aussieht. Bevorzuge die Identität, die die Verbindung selbst offenlegt, und lies sie bei jedem Lauf neu. Teste die drei Ereignisse getrennt: eine wiederholte Verbindung, eine Ersatzverbindung und ein Herunterfahren. Der erlaubte Verkehr sollte alle drei ohne Duplikate überstehen, und unbeteiligte Dienste sollten unberührt bleiben.

Der Durchgang endet, wenn der Hook einen Besitzer, einen Test für jede hinzugefügte Regel, einen serialisierten Pfad und ein Aufräumen hat, das nur seinen eigenen Zustand entfernt. Ein Hook, der zweimal sicher läuft, ist mehr wert als ein Hook, der einmal clever ist.

Ähnliche Artikel