Kann ein OpenVPN-Hook-Skript den gerade aufgebauten Tunnel nutzen?
OpenVPN führt Hooks innerhalb seines eigenen Starts aus, also wartet ein Skript, das den Tunnel braucht, auf einen Tunnel, der auf es wartet. Koppeln Sie die Arbeit ab und wählen Sie den Hook nach Ereignis.
Ob ein Hook-Skript die andere Seite des Tunnels erreicht, hängt von drei Bedingungen ab: an welches Ereignis es gebunden ist, ob OpenVPN beim Ausführen des Skripts noch in seiner Startsequenz steckt und ob die Arbeit von dieser Sequenz entkoppelt wurde. Die meisten Skripte scheitern an der dritten. Ein Sleep hinzuzufügen behandelt ein Lebenszyklusproblem als Timingproblem, und es funktioniert gerade oft genug, um in die Irre zu führen.
Entscheiden Sie zuerst das Ereignis, dann den Mechanismus zum Aufschieben der Arbeit, dann den Ort, an dem die aufgeschobene Arbeit läuft. Diese drei Entscheidungen sind unabhängig, und sie zu vermischen ist der Grund, warum aus einem zweizeiligen Hook ein ganzer Nachmittag wird.
Warum der Hook auf einen Tunnel wartet, der auf ihn wartet
OpenVPN ruft seine Hooks synchron auf, innerhalb desselben Prozesses, der Pakete bewegt. up wird ausgelöst, sobald das tun- oder tap-Gerät existiert, aber bevor eine Route hinzugefügt wird, und route-up nach den Routen; keiner dieser Momente verspricht, dass ein Peer antwortet. Solange ein Skript aus einem der Hooks noch läuft, leitet der Daemon nicht weiter, also wartet ein Befehl, der erwartet, dass Verkehr den Tunnel durchquert, auf einen Tunnel, der auf den Befehl wartet. Diese zirkuläre Wartezeit ist der Grund, warum ein manueller Lauf durchgeht und der Hook in ein Timeout läuft.
Der naheliegende Patch, ein paar Sekunden zu schlafen, funktioniert manchmal, weil die Gegenseite von selbst hochkam, nicht weil der Sleep etwas reparierte. Ein Helfer, der die Bereitschaft abfragt, ist eine andere Antwort: Der Hook kehrt zurück, die Weiterleitung beginnt, und der Helfer bekommt, was er braucht, sobald es tatsächlich da ist.
Wählen Sie den Hook nach Ereignis, nicht nach Bequemlichkeit
Binden Sie up an Arbeit, die nur das Gerät braucht, route-up an Arbeit, die Routen braucht, und halten Sie client-connect und client-disconnect für Aufgaben pro Client auf der Serverseite frei. Die Bereinigung beim Trennen muss es aushalten, zweimal zu laufen, da Wiederverbindungen über eine persistente Schnittstelle nicht derselben Sequenz wie ein vollständiger Neustart folgen. Auf dem Server wird client-connect ausgelöst, wenn sich ein Client authentifiziert, nicht wenn der Daemon startet, was sich zu bestätigen lohnt, bevor Logik dorthin wandert. Gehört die Arbeit zum Lebenszyklus des Dienstes statt zu einer einzelnen Verbindung, ist eine systemd-Unit ein besseres Zuhause als jeder Hook.
Hook-Befehle werden vom Daemon direkt ausgeführt, ohne Shell dazwischen, daher kommt ifconfig_remote in einer up-Zeile als wörtlicher Text an. Wickeln Sie den Befehl in eine explizite Shell oder halten Sie die Logik in einem Skript, das die Umgebung liest. ipchange ist eine Direktive, die eigene Argumente hinzufügt: Sie übergibt Adresse und Port an den Befehl, sodass ein für keine geschriebenes Skript zwei unerwartete sieht.
Drei Wege, die Arbeit abzukoppeln
Die erste Option ist, den Helfer aus dem Hook heraus in den Hintergrund zu schicken: Starten Sie ihn mit setsid oder systemd-run --scope, schließen Sie die geerbten Deskriptoren und beenden Sie sofort mit null. Die zweite ist, den Hook eine kleine Zustandsdatei schreiben zu lassen und einen systemd-Timer oder eine Path-Unit darauf reagieren zu lassen, was den Hook trivial hält und die Wiederholungsrichtlinie dorthin legt, wo es Logs gibt. Die dritte ist, den Hook ganz aufzugeben und die Arbeit in eine Unit zu verschieben, die nach dem VPN-Dienst geordnet ist; das ist die richtige Antwort, wenn die Arbeit gar nicht pro Verbindung ist. Alle drei teilen eine Regel: Der Hook muss zurückkehren, bevor er den Tunnel braucht.
Wie Sie sich auch entscheiden, machen Sie den Helfer idempotent und beobachtbar. Zwei überlappende Aufrufe dürfen dieselbe unwiderrufliche Aktion nicht zweimal ausführen, und ein Helfer, der scheitert, muss etwas hinterlassen, das ein Mensch lesen kann.
Trennen Sie Sandbox-Fehler von Lebenszyklusfehlern
Wenn ein Skript aus einer Root-Eingabeaufforderung läuft und als Dienst scheitert, ist die Ursache meist eine Sandbox und nicht das Netzwerk. Ein restriktives ProtectHome, ein schreibgeschütztes ProtectSystem oder ein Arbeitsverzeichnis, das nicht existiert können das Skript oder seine Daten unerreichbar machen, und auf einem SELinux-Host braucht die Datei zusätzlich einen Kontext, der Ausführung erlaubt. Lesen Sie die Unit mit systemctl cat openvpn-server@server und das Journal mit journalctl -u openvpn-server@server -n 80, und suchen Sie dann nach einem Berechtigungs- oder Kontextfehler statt nach einem Verbindungs-Timeout. Die Sandbox zu deaktivieren sollte der letzte Schritt sein, nicht der erste.
Ein Lebenszyklusfehler sieht anders aus: Der Hook startet, der Helfer läuft, und der Socket ist einfach noch nicht da. Schreiben Sie eine zeitgestempelte Zeile aus dem Hook selbst, trennen und verbinden Sie erneut, und lesen Sie die Ereignisse der Reihe nach. Wenn der Helfer den Tunnel wirklich braucht, lassen Sie ihn die Bereitschaft abfragen, statt für eine feste Zeitspanne zu schlafen.
Drei Felder machen den nächsten Bericht nützlich: welcher Hook lief, welches Konto er nutzte und wo er stehen blieb. Ohne sie beginnt jede spätere Untersuchung von vorn.