So schließen Sie ein Subnetz vom Android-VPN-Tunnel aus
VpnService kann keine Route ausschließen, also fügen Sie das Komplement hinzu: acht generierte Präfixe für ein /8, plus die IPv6-, DNS- und Lookup-Prüfungen, die den Rest abfangen.
Androids VpnService kennt keine Ausschlussroute, und kein noch so gründliches Lesen der API wird eine hervorbringen. Builder.addRoute() fügt dem Tunnel immer nur Ziele hinzu, ein LAN-Segment lokal zu halten bedeutet also, dass dieses Segment nie in die Routenliste gelangt. Was Sie stattdessen hinzufügen, ist das Komplement des Segments, ausgedrückt als gewöhnliche Präfixe. Die Arbeit ist Arithmetik, und sie scheitert an einer Handvoll vorhersehbarer Stellen.
Arbeiten Sie die Prüfungen unten der Reihe nach ab. Jede setzt voraus, dass die vorige gehalten hat, also stoppen Sie beim ersten Fehlschlag, statt Symptome weiter unten zu flicken.
Beginnen Sie bei dem, was der Tunnel beansprucht
0.0.0.0/0 an addRoute() zu übergeben sagt Android, dass jedes IPv4-Ziel zum Tunnel gehört. Deshalb wirken so viele Clients unfähig zum Split-Tunneling: Sie beanspruchen den gesamten Adressraum und versuchen dann, davon abzuziehen. Nichts im Builder zieht ab. Eine spezifischere Route für das Netz, das Sie lokal halten wollten, bewirkt das Gegenteil des Erwarteten, denn das längste passende Präfix gewinnt und dieses Netz landet im Tunnel.
Die Routenliste ist die gesamte Richtlinie. Dahinter gibt es kein Veto pro Ziel, und die Builder-Referenz nennt keine Methode, die eines liefern würde.
Erzeugen Sie das Komplement aus Präfix-Arithmetik
Das Komplement eines ausgeschlossenen Präfixes ist die Menge seiner Geschwister auf dem Weg zur Wurzel des Adressbaums. Nehmen Sie für jede Länge von 1 bis zur ausgeschlossenen Länge den Block dieser Länge, der Ihr Präfix enthält, kippen Sie das Bit direkt unterhalb des Präfixes und geben Sie das Ergebnis aus. Es klingt abstrakt, bis man es laufen lässt.
Der Ausschluss von 10.0.0.0/8 ergibt acht Routen: 128.0.0.0/1, 64.0.0.0/2, 32.0.0.0/3, 16.0.0.0/4, 0.0.0.0/5, 12.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8. Keine davon enthält 10.0.0.0/8, und zusammen decken sie alles andere ab.
Acht Zeilen erzeugen diese Liste für jedes Präfix:
import ipaddress
def complement(addr, length):
base = int(ipaddress.IPv4Address(addr))
out = []
for k in range(1, length + 1):
block = base & (0xFFFFFFFF << (32 - k))
sibling = block ^ (1 << (32 - k))
out.append(f"{ipaddress.IPv4Address(sibling)}/{k}")
return out
print(complement("10.0.0.0", 8))
Die Routenzahl entspricht der Präfixlänge, und das ist die Zahl, auf die man achten muss. Ein einzelner /21-Ausschluss kostet 21 Routen und bleibt lesbar. Zehn Ausschlüsse können ein paar Hundert kosten, was meist heißt, dass das Netzdesign geändert werden muss und nicht der Generator.
Prüfen Sie die Adressfamilie und den Tunnelumfang
IPv4-Routen bewirken nichts für IPv6. Beansprucht der Tunnel eine IPv6-Adresse, braucht ein IPv6-LAN-Präfix sein eigenes Komplement, erzeugt mit derselben Arithmetik über 128 Bit. Eine häufige Ursache dafür, dass der Ausschluss auf dem Papier funktioniert, während das lokale Gerät unerreichbar bleibt, ist diese: Der Host wurde über IPv6 erreicht, und nur IPv4 war herausgeschnitten. Prüfen Sie beide Familien, bevor Sie schließen, dass die Mathematik falsch ist.
Der Umfang ist die andere Hälfte der Prüfung. Android verfolgt Routing pro Netz und pro App, daher kann dasselbe Präfix für eine App durch den Tunnel gehen und für eine andere direkt, je nachdem, wie das VPN deklariert wurde.
Setzen Sie DNS- und Pro-App-Regeln dorthin, wo sie hingehören
Routenausschlüsse ändern den Resolver nicht. Ein lokaler Hostname kann weiterhin vom DNS-Server des Tunnels beantwortet werden, während seine Adresse lokal geroutet wird, und die Diskrepanz zeigt sich als Name, der zu etwas auflöst, das das LAN nicht erreicht. Legen Sie die Resolver-Richtlinie bewusst fest, mit einer Suchdomäne oder einer Split-DNS-Regel. Nehmen Sie nicht an, die Routen hätten das für Sie entschieden.
addDisallowedApplication() hält eine App aus dem Tunnel; es hält kein Ziel für die Apps draußen, die drinnen bleiben. protect() lässt einen Socket, den Sie öffnen, den Tunnel umgehen, und so vermeidet ein Tunnel, den eigenen Transport abzufangen. Keines von beiden ersetzt das Komplement.
Prüfen Sie mit einem Lookup, nicht mit Ihrer eigenen Liste
Sobald der Tunnel steht, fragen Sie das Gerät, wohin eine Adresse tatsächlich geht, statt die von Ihnen gebaute Liste erneut zu lesen. Auf einem Build, das iproute2 mitbringt, benennt adb shell ip route get 10.1.2.3 die Schnittstelle, die der Kernel gewählt hat. Führen Sie denselben Befehl für eine öffentliche Adresse aus und bestätigen Sie, dass er den Tunnel benennt.
Zwei weitere Prüfungen schließen den Kreis. Lösen Sie zuerst einen lokalen Namen auf, führen Sie dann adb shell ip route get auf der aufgelösten Adresse aus und verlangen Sie eine Schnittstelle, die nicht der Tunnel ist, denn ein erfolgreiches adb shell ping -c 1 host.lan beweist Erreichbarkeit und nicht den Pfad. Wiederholen Sie den Adressfamilien-Test über IPv6, wenn der Tunnel eine beansprucht. Testen Sie auf der API-Ebene, die Sie ausliefern, denn wie VPN-Netze der Routing-Tabelle ausgesetzt werden, hat sich über Releases hinweg geändert. Eine Routenliste, die auf dem Papier richtig aussieht, und ein Lookup, der die falsche Schnittstelle wählt, sind verschiedene Fehler, und nur der zweite erreicht Nutzer.
Wächst die Ausschlussliste weiter, hören Sie auf, Routen zu erzeugen, und nummerieren Sie das entfernte Netz neu oder verengen Sie den Tunnel stattdessen. Der Generator ist eine Notlösung für die fehlende API, kein Designwerkzeug.