OpenVPN-Hook-Ereignisse: Warum Name und Zeitstempel kein Schlüssel sind
Ein Disconnect-Hook meldet die Verbindungszeit, nicht die Trennungszeit. Zusammen mit dem Benutzernamen kann er die falsche Sitzung beenden; besitzen Sie stattdessen eine Identität.
Ein Trennungsdatensatz trifft ein, nachdem eine zweite Verbindung unter demselben Namen bereits begonnen hat, und ein Handler, der die neueste Zeile für diesen Benutzer aktualisiert, schließt die falsche Sitzung. Der Fehler liegt nicht im Zeitstempelvergleich selbst. Er liegt in der Annahme, dass ein Name plus eine ganze Sekunde eine Sitzung identifiziert. Hook-Ereignisse beschreiben Verbindungen, und eine Verbindung braucht einen Schlüssel, der allein ihr gehört.
Beginnen Sie damit, was die Trennungsvariablen tatsächlich bedeuten. Erst dann ergibt die Wahl des Bezeichners Sinn.
Was der Disconnect-Hook wirklich trägt
In Community OpenVPN 2.6 definiert die Hook-Referenz time_unix als den Zeitpunkt, an dem sich der Client verbunden hat; er wird gesetzt, bevor der Connect-Hook läuft, und nicht neu definiert, wenn der Disconnect-Hook später ausgelöst wird. time_duration liefert die Länge dieser Sitzung in Sekunden, sodass das Ende ungefähr die Verbindungszeit plus die Dauer ist. Beide sind ganze Sekunden, und keines ist der Zeitpunkt, an dem Ihre Datenbank das Ereignis verarbeitet hat. Diese drei Zeiten auseinanderzuhalten ist es, was eine Korrelation tragen lässt, statt nur während des Tests richtig auszusehen.
Diese Arithmetik verdient es, aufgeschrieben zu werden, denn sie ist die einzige Endzeit, die der Hook selbst bietet. Verarbeitungsverzögerung und Uhrenunterschiede zwischen Hosts liegen weiterhin außerhalb.
Warum das naheliegende Paar nicht eindeutig ist
Den Namen mit der Verbindungszeit zu paaren verengt die Suche und identifiziert noch immer keine Sitzung. Ein Client kann sich innerhalb derselben Sekunde erneut verbinden, sodass zwei Sitzungen beide Werte teilen können. Zwei Daemon-Instanzen oder ein Serveraustausch, der dieselbe Konfiguration behält, können dieses Paar aus verschiedenen Prozessen erzeugen. Tunneladressen werden aus demselben Grund wiederverwendet, was sie zu Attributen einer Sitzung macht statt zu Bezeichnern für eine.
Der Fehlermodus ist leise. Eine verspätete Trennung der ersten Sitzung passt auf die neueste Zeile und beendet eine Zuweisung, die die zweite Sitzung noch nutzt, sodass der Benutzer einen zufälligen Zugriffsverlust erlebt, den er nicht verursacht hat. Eine Zeilenanzahl, die danach korrekt aussieht, macht das Problem schwerer zu erkennen.
Woher eine stabile Identität kommt
Die Management-Schnittstelle weist jedem Client einen Client-Instanz-Bezeichner zu, und dieser Bezeichner ist auf die Daemon-Instanz beschränkt, die ihn ausgegeben hat. Ein brauchbarer externer Schlüssel kombiniert einen stabilen Serverbezeichner, eine Epoche, die sich beim Start des Daemons ändert, und diese Client-ID. Die Epoche ist wichtig, weil Bezeichner mit dem Prozess neu starten und eine bloße Prozess-ID später von etwas Unabhängigem wiederverwendet werden kann.
Umgebungsvariablen, die in einem untergeordneten Connect-Skript gesetzt werden, wandern nicht zum späteren Disconnect-Skript des übergeordneten Daemons, weil diese Skripte als separate Prozesse mit eigenen Umgebungen laufen. Eine UUID zur Verbindungszeit zu erzeugen ist daher nur die halbe Lösung. Die Trennungsseite muss dieselbe UUID benennen können, was bedeutet, dass die Identität irgendwo leben muss, wo beide Ereignisse sie erreichen können.
Machen Sie das Beenden bedingt und idempotent
Beenden Sie nach Sitzungsinhaber, nicht nach Benutzer. Ein Handler, der jede Zuweisung löscht, die gerade zu einem Namen gehört, löscht irgendwann die falsche, und die einzige Verteidigung ist zu prüfen, dass der entfernte Datensatz der Datensatz ist, den dieses Ereignis beschreibt.
Die Management-Schnittstelle akzeptiert jeweils einen verbundenen Management-Client, also integrieren Sie sich mit dem, der sie bereits besitzt, statt einen zweiten Beobachter anzuhängen, der um denselben Socket konkurriert.
Behandeln Sie wiederholte Ereignisse, ohne das Ergebnis zweimal zu ändern, und bewahren Sie unpaarige Ereignisse für den Abgleich auf, statt sie zu verwerfen. Ein Disconnect-Callback hat dokumentierte Lebenszyklusbedingungen und kommt nach einem Absturz oder harten Stopp möglicherweise nie an, daher braucht das Design eine Möglichkeit, eine Sitzung darzustellen, deren Endereignis fehlt.
Testen Sie die unangenehmen Fälle absichtlich: zwei Wiederverbindungen innerhalb einer Sekunde, zwei Daemon-Instanzen, eine Verbindung, deren Verarbeitung fehlgeschlagen ist, und einen Daemon-Neustart mitten in einer Sitzung. Ein Schlüssel, der alle vier übersteht, ist ein Schlüssel, dem man vertrauen kann.