OpenVPN-Server und -Client auf einem Host: Warum Clients ausfallen
Ein Host, der OpenVPN bereitstellt und selbst nutzt, kann seinen Tunnel oben halten, während lokale Clients keine Antworten mehr bekommen. Zwei Dinge erklären das: ein gemeinsamer Adresspool und eine neue Standardroute.
Fügt man einem Rechner, auf dem bereits ein VPN-Server läuft, ein Clientprofil hinzu, ist der ursprüngliche Dienst das erste Opfer. Der neue Tunnel kommt vielleicht nie zustande, oder er kommt hoch, während die bestehenden Clients des Servers still keine Antworten mehr erhalten. Zwei Kollisionen erzeugen beides: ein Tunnel-Pool, der bereits benutzt wird, und eine Standardroute, die die neue Client-Hälfte installiert und die gesamte Tabelle abdeckt. Keine von beiden erzeugt eine OpenVPN-Fehlermeldung, deshalb wird der Ausfall als Serverproblem gelesen.
Ein Rechner, zwei Rollen und zwei unvereinbare Antworten darauf, wohin Antworten gehen sollen.
Derselbe Pool, zwei Server: Wer gewinnt
Jede Serverinstanz braucht ihr eigenes Tunnel-Subnetz. Kopiert man eine Zeile wie server 10.8.0.0 255.255.255.0 in die zweite Konfiguration, erhalten zwei Instanzen dasselbe Präfix, und das Ergebnis ist entweder eine Startverweigerung oder ein stiller Wettstreit, in dem Clients des einen Servers Adressen bekommen, die der andere ebenfalls als seine ansieht. Unterschiedliche UDP-Ports und unterschiedliche Gerätenamen helfen nicht, weil keines von beiden den Adressraum berührt. Ändert den zweiten Pool und ändert ihn überall, wo die Routen ihn nennen.
Listet auf, was aktiv ist, bevor ihr etwas ändert: ip -4 addr show listet die Tunnelgeräte auf, ss -lunp listet die Listener auf, und systemctl list-units nennt die Units. Ein Blick auf die Tunneladressen entscheidet, ob die Pools kollidieren.
Was das Clientprofil der Tabelle hinzufügt
Die Client-Hälfte schreibt Routen, sobald sie sich verbindet. redirect-gateway def1 teilt die Standardroute in 0.0.0.0/1 plus 128.0.0.0/1, beide über das neue Gerät geroutet, zusammen mit einer Host-Route zum entfernten Server, die bewusst an die physische Schnittstelle gepinnt ist. Dieses Pinning existiert, damit der Tunnel einen Pfad für seinen eigenen Transport behält. Alles andere, einschließlich der Antworten, die der lokale Server seinen eigenen Clients schuldet, folgt nun der neuen Standardroute und verlässt den Host über die entfernte Verbindung.
Die zweite Kollision ist also kein kaputter Tunnel. Es ist ein funktionierender Tunnel an der falschen Stelle in der Tabelle.
Drei Wege, die Rollen zu trennen
Die am wenigsten invasive Korrektur hält die Standardroute aus dem Clientprofil heraus: route-nopull plus explizite route-Zeilen für die entfernten Präfixe, sodass sich die lokale Tabelle nur dort ändert, wo ihr es verlangt habt. Der nächste Schritt ist eine Policy-Regel, die das lokale Tunnel-Subnetz ausnimmt, sodass Antworten an lokale Clients über die Schnittstelle hinausgehen, auf der sie ankamen, selbst wenn eine Standardroute existiert. Am gründlichsten ist ein separater Netzwerk-Namespace für die Client-Rolle, der die gemeinsame Tabelle vollständig entfernt und einen zweiten Routing-Kontext zur Pflege kostet.
Welchen Weg ihr auch wählt, die Ausnahme muss das lokale Tunnel-Subnetz benennen und nicht das LAN. Die Antwort, die der lokale Server sendet, hat eine Tunneladresse als Ziel, eine LAN-förmige Ausnahme passt also nie darauf.
Welche der beiden Instanzen ist schuld
Stoppt die Clientinstanz und testet die ursprünglichen Clients erneut. Sofortige Erholung bedeutet Routing, nicht Server. Bringt den Client zurück und verfolgt eine einzelne Antwort mit ip route get, dem sowohl eine Quell- als auch eine Zieladresse mitgegeben wird, was den Pfad zeigt, den der Kernel wählen würde, und beobachtet dann mit tcpdump -ni any port 1194 die Schnittstelle, die Pakete tatsächlich benutzen. Die Statusdatei des Servers, die alle paar Sekunden über die Direktive status geschrieben wird, enthält die Liste der verbundenen Clients und Byte-Zähler für beide Richtungen.
Lest diese Zähler gegeneinander. Verkehr, der abgeht und nie zurückkommt, ist eine Routing-Antwort und kein Handshake-Problem.