VpnService protect() oder App-Ausschluss: Was das Routing wirklich repariert
Den Socket schützen, der den Tunnel trägt, oder die ganze App ausschließen: die drei Bedingungen, die entscheiden, welchen Umfang Ihr Android-VPN braucht.
Es gibt zwei Wege, die eigenen Pakete des Tunnels aus dem Tunnel herauszuhalten: den Transport-Socket schützen oder die ganze App aus dem VPN nehmen. Sie lösen unterschiedliche Probleme, und den weiteren Weg zu wählen, weil er leichter zu verdrahten ist, ist genau die Art, wie Apps den Zugang zum lokalen Netz und gesplittetes DNS verlieren, ohne es zu merken. Beginnen Sie mit dem, was außerhalb des Tunnels bleiben muss, und wählen Sie dann den kleinsten Umfang, der das erreicht.
Was protect() aus dem Tunnel entfernt
VpnService.protect() ist ein Aufruf pro Socket. Sie übergeben ihm den Socket oder Deskriptor, der den Tunnel trägt, und Android markiert diesen Socket so, dass sein Verkehr das darunterliegende Netz statt der VPN-Schnittstelle nutzt. Alles andere, was die App sendet, folgt weiterhin der normalen VPN-Route.
Genau dieser enge Umfang ist der Punkt. Ein geschützter Socket ändert nicht, wie Ihr HTTP-Client Namen auflöst, welche Schnittstelle Ihre Analyseaufrufe nutzen oder was mit einer später geöffneten zweiten Verbindung passiert.
Drei Bedingungen, die den Umfang bestimmen
Die erste Bedingung ist, ob nur der Tunneltransport entkommen muss. Wenn der übrige Verkehr der App weiterhin durch das VPN gehen soll, ist der Socket-Schutz der richtige Umfang. Die zweite Bedingung ist, ob Sie die Erstellung des Sockets kontrollieren. Eine Bibliothek, die ihren eigenen Socket tief innen öffnet, lässt Ihnen keinen Ort, um protect() aufzurufen, es sei denn, sie bietet einen Hook an oder nimmt einen fertigen Socket entgegen. Die dritte Bedingung ist, ob die App das lokale Netz oder ein Captive Portal direkt erreichen muss, was eine andere Anforderung ist als den Tunnel am Leben zu halten.
Die App auszuschließen mit addDisallowedApplication() ist das grobe Werkzeug. Es entfernt jede Verbindung der App aus dem VPN, auch die, die Sie prüfen wollten, und auf manchen Geräten ändert es zudem, wie sich die lokale Erkennung verhält.
Eine dritte Möglichkeit ist, den Transport mit bindProcessToNetwork() oder einem Netzwerk-Handle auf Socket-Ebene an ein gewähltes Netz zu binden. Das ist nützlich, wenn das Gerät gleichzeitig WLAN und Mobilfunk hat und der Tunnel über einen bestimmten Uplink laufen soll.
Rufen Sie es auf, während der Transport entsteht
Rufen Sie protect() im selben Block auf, der den Transport erzeugt, vor connect() oder send(). Wenn der Aufruf false zurückgibt, schließen Sie den Socket und lassen Sie das Setup scheitern; weiterzumachen hinterlässt Ihnen einen Transport, dessen Pakete wieder in Ihre eigene Schnittstelle geraten können.
Die Form ist klein genug, um sie an einer Stelle zu halten:
DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
transport.close();
throw new IOException("transport not protected");
}
transport.connect(serverAddress);Drei Details beißen hier. Erstens,das Boolean ist das einzige Signal, das Sie bekommen, also protokollieren Sie den Fehlschlag mit genug Kontext, um eine widerrufene Zustimmung von einem abgelehnten Aufruf zu unterscheiden. Zweitens,protect() deckt den Socket ab, nicht die Deskriptor-Nummer: Wenn Sie den Socket schließen und dieselbe Ganzzahl später für einen anderen Socket wiederverwenden, ist der neue Socket nicht geschützt. Drittens: Wenn eine native Bibliothek den Transport öffnet, schützen Sie den Deskriptor, bevor der native Code sich verbindet, sonst ist die Route möglicherweise schon gewählt.Die VpnService-Referenz ist der Ort, an dem Sie bestätigen, welche Überladungen Ihre Ziel-API-Ebene tatsächlich bietet.
Testen Sie den blockierten Pfad, nicht nur den glücklichen
Prüfen Sie bei aktivem Tunnel, denn das ist der einzige Zustand, in dem sich die beiden Pfade unterscheiden können. Schicken Sie eine Anfrage über den geschützten Transport und eine aus einem Codepfad, der nie protect() aufgerufen hat, und vergleichen Sie dann, was das andere Ende sieht: Die geschützte Verbindung sollte mit dem darunterliegenden Netz als Quelle ankommen, während die ungeschützte die eigene Adresse des Tunnels zeigt und dessen Byte-Zähler bewegt. Auf dem Gerät zeigt ss oder netstat dieselbe Aufteilung, bevor irgendetwas das Funkmodul verlässt.
Testen Sie den Widerruf getrennt, denn eine Zustimmungsänderung reißt die Schnittstelle ab, ohne Ihren Socket-Code zu berühren. Widerrufen Sie die VPN-Zustimmung oder stoppen Sie den Dienst, bauen Sie den Transport neu auf und bestätigen Sie, dass protect() weiterhin true zurückgibt und der Transport sich wieder verbindet, sobald die Zustimmung erneut erteilt wird.
Wenn die geschützte Anfrage trotzdem dem Tunnel folgt, geschieht der Schutzaufruf wahrscheinlich, nachdem der Socket bereits geroutet wurde. Verschieben Sie ihn nach vorn und testen Sie erneut.
Halten Sie die Entscheidung neben dem Transport-Setup schriftlich fest: welche Sockets geschützt sind, welcher App-Verkehr ausgeschlossen ist und was die App tut, wenn der Schutz scheitert. Genau diese Zeile hält ein späteres Refactoring davon ab, den Ausschluss auf die ganze App auszuweiten, weil ein einzelner Test fehlschlug. Sie gibt der nächsten Person auch eine Möglichkeit, die Absicht zu prüfen, ohne das Manifest zurückzuentwickeln.