Ein einzelnes Programm unter Linux an ein VPN zu binden ist vor allem ein Routingproblem, und die Firewall-Regel entscheidet nur, was nach der Wahl der Route geschieht. Wenn Sie die UID des Programms in OUTPUT abgleichen, bevor der Tunnel die Standardroute besitzt, verwirft die Regel genau den Verkehr, den Sie schützen wollten. Dieselbe Regel sieht auch nicht den Resolver-Prozess, der die DNS-Abfragen des Programms beantwortet. Bringen Sie die Reihenfolge in Ordnung, und ein einzelnes Konto ohne Privilegien wird zu einem verlässlichen Auswähler für den geteilten Tunnel.

Geben Sie dem Konto eine Route, bevor Sie urteilen

Der Owner-Abgleich beantwortet eine Frage: Hat ein lokaler Socket, der zu dieser UID gehört, das Paket gebaut? Er wählt keine Schnittstelle und kann Verkehr nicht verschieben, den die Haupttabelle bereits über die physische NIC sendet. Deshalb blockiert eine UID-Regel ohne passende Route das Programm, statt es umzuleiten.

Schicken Sie das Konto zuerst durch seine eigene Tabelle, und lassen Sie dann die Firewall das Ergebnis bestätigen. Das funktioniert auf einem Host, dessen iproute2 uidrange versteht, was ip rule help in einer Zeile meldet. Tabelle 100 enthält eine Standardroute durch den Tunnel, und die Regel schickt nur UID 1000 in diese Tabelle. Alles andere bleibt in der Haupttabelle und nutzt weiter die physische Verbindung.

ip rule add uidrange 1000-1000 table 100
ip route add default dev tun0 table 100

Ein zweites Konto, das einen anderen Tunnel braucht, bekommt Tabelle 101 und seine eigene Regel, weil zwei Profile nicht beide die Standardroute in derselben Tabelle besitzen können. NetworkManager-Profile gelten systemweit, daher bleibt dieser Teil manuell, und die Regeln verschwinden beim Neustart, wenn nichts sie wiederherstellt. Schreiben Sie die Tabellen in ein Startskript neben die Tunnel-Definition, damit ein Wiederverbinden kein Konto auf dem falschen Weg zurücklässt.

Warum die Loopback-Ausnahme den Tunnel tötet

Die meisten kopierten Fassungen der Regel verwerfen jedes Paket, das nicht über Loopback hinausgeht. Der Tunnel ist kein Loopback, also verwirft diese Form auch den Tunnelverkehr, und das Ergebnis sieht wie ein totes VPN aus, nicht wie ein Firewall-Fehler.

Wenn OUTPUT läuft, hat der Kernel bereits eine Ausgangsschnittstelle gewählt, also -o wird gegen die Antwort der Route verglichen, nicht gegen den Paketkopf. Der Owner-Abgleich wird nur in OUTPUT und POSTROUTING herangezogen, und weitergeleitete Pakete tragen keinen Socket, den er lesen könnte. Diese Kombination bedeutet, dass die Ausnahme die Tunnel-Schnittstelle nennen und diesen Abgleich negieren muss, statt Loopback zu negieren.

Loopback verdient weiterhin sein eigenes ACCEPT über dem DROP, weil das erste DNS-Paket des Programms über lo zum lokalen Stub geht. Stellen Sie die beiden Regeln in diese Reihenfolge, und das Konto erreicht den Stub, dann den Tunnel und nichts sonst.

iptables -I OUTPUT 1 -o lo -m owner --uid-owner vpnapp -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner vpnapp ! -o tun0 -j DROP

IPv6 ist ein getrennter Regelsatz, und ein Paar nur für v4 lässt ip6tables unberührt. Ein Programm, das AAAA-Einträge bevorzugt, würde dann über die physische Verbindung hinausgehen, ohne je den DROP zu erreichen. Spiegeln Sie beide Regeln in ip6tables, oder deaktivieren Sie IPv6 auf dem Host, wenn der Tunnel keine v6-Adresse trägt. Von Hand hinzugefügte Regeln verschwinden ebenfalls beim Neustart, also sichern Sie sie mit netfilter-persistent save oder bauen Sie dieselbe Logik als nftables-Tabelle neu, die von der Starteinheit geladen wird.

DNS-Antworten kommen aus einem anderen Prozess

Die Abfrage des Programms an 127.0.0.53 passiert das Loopback-ACCEPT, und dort endet der UID-Abgleich. Der Stub-Resolver leitet aus seinem eigenen Dienstkonto nach oben weiter, daher sieht eine Regel, die auf vpnapp zeigt, nie die zweite Abfrage. Wählt der Resolver einen LAN-Server für die Verbindung, kommen die Antworten über die physische NIC zurück, während die eigenen Pakete des Programms im Tunnel bleiben. Das sichtbare Ergebnis ist ein funktionierender Tunnel mit kaputten Namen oder Namen, die ein Server beantwortet, den Sie nicht gewählt haben.

Beheben Sie die Namensauflösung auf der Tunnel-Verbindung, nicht in der Anwendung. Geben Sie dem Tunnelprofil einen DNS-Server und eine Routing-Domäne, und bestätigen Sie dann mit resolvectl query und resolvectl status tun0 dass der Resolver ihn für die Namen nutzt, die zählen. Innerhalb eines Netzwerk-Namespace verschwindet das Problem, weil der Namespace-Resolver nur den Tunnel kennt.

Prüfen Sie die Regel vom Konto aus

Fragen Sie den Kernel, welche Route eine UID bekommt, stellen Sie dann eine Anfrage als dieses Konto und vergleichen Sie die Ausgangsadresse mit der Ihres eigenen Kontos. Wenn beide dieselbe Adresse melden, gleicht die Regel etwas anderes ab als den getesteten Verkehr. Der erste Befehl unten zeigt die Routensuche ohne gesendetes Paket, was einen Routingfehler von einem Firewall-Fehler trennt.

ip route get 1.1.1.1 uid 1000
sudo -u vpnapp curl -s https://ifconfig.co

Zähler zeigen, ob eine Regel je ausgelöst hat. Führen Sie iptables -vnL OUTPUT aus, lesen Sie den Paketzähler neben jeder Regel und werten Sie eine Null neben dem DROP nach echtem Verkehr als Abgleichfehler.

Weitergeleiteter Verkehr liegt außerhalb dieses Entwurfs. Ein Container oder eine VM hinter einer Bridge hat keinen lokalen Socket, der vpnapp gehört, daher löst der Owner-Abgleich nie aus und diese Pakete folgen stattdessen der FORWARD-Policy. Muss diese Arbeitslast abgedeckt werden, testen Sie sie separat und schreiben Sie eine Policy, die an die Bridge oder die Container-Adresse gebunden ist. Der Entwurf hält, wenn Tabelle, Schnittstellen-Ausnahme, Resolver und v6-Regeln alle in dieselbe Richtung zeigen; lassen Sie eines davon weg, und das Konto leckt oder verliert die Verbindung.

Ähnliche Artikel