Der Tunnel kommt hoch, das Schlüsselsymbol erscheint, und jede Anfrage im Browser läuft weiterhin in eine Zeitüberschreitung. In einer VpnService-App bedeutet diese Kombination meist, dass die Leseschleife lebt und die Ausgangshälfte defekt ist, nicht dass der Server den Handshake abgelehnt hat. Android hat die Schnittstelle bereits angelegt, den App-Verkehr hineingeschoben und Ihrem Prozess einen Dateideskriptor übergeben. Alles nach diesem Deskriptor ist Ihr Code, und ein fehlender Aufruf genügt, um den ganzen Strom zu verschlucken.

Was das System Ihnen gibt und was nicht

establish() liefert einen ParcelFileDescriptor für eine tun-Schnittstelle. Das System schreibt ausgehende IP-Pakete hinein und erwartet, dass Ihr Prozess sie liest, entscheidet, wohin sie gehen, und sie über einen gewöhnlichen Socket sendet. Darunter gibt es keine Kernel-Weiterleitung; wenn niemand liest, werden die Pakete verworfen und die Zähler der Schnittstelle bewegen sich nie.

Zählen Sie, statt zu raten. Erhöhen Sie einen Zähler bei jedem erfolgreichen Lesevorgang und protokollieren Sie Ziel und Protokoll der ersten Pakete. Bleibt der Zähler auf null, während Verkehr erzeugt wird, sitzt der Fehler vor Ihrem Weiterleitungscode, was die Suche auf die Builder-Konfiguration und den Lesemodus eingrenzt. Bewegt sich der Zähler und der Browser scheitert weiterhin, liegt der Fehler im Sendeweg oder auf dem Rückweg.

Blockierende Lesevorgänge entscheiden, ob sich die Schleife bewegt

setBlocking(true) versetzt den Deskriptor in den blockierenden Modus und existiert seit API 21. Auf älteren Releases ist die Umgehung ein kleiner JNI-Helfer, der O_NONBLOCK per fcntl auf dem rohen Deskriptor löscht. Der Modus ist wichtig, weil er entscheidet, was ein Lesevorgang ohne anstehendes Paket tun darf.

Im nicht blockierenden Modus kehrt dieser Lesevorgang sofort mit einem negativen Wert zurück, und eine Schleife, die das Ergebnis ignoriert, dreht einen Kern, ohne Verkehr zu bewegen. Im blockierenden Modus wartet der Lesevorgang, was ein Weiterleitungsthread will, aber der Thread kann dann nicht mehr unterbrochen werden. Das Schließen des Deskriptors ist der unterstützte Weg, den Lesevorgang scheitern zu lassen, damit die Schleife beenden kann. Den Prozess zu töten oder den Thread zu unterbrechen kann stattdessen den nächsten Start mit einem Deskriptor zurücklassen, der nicht mehr zur Schnittstelle passt.

Lesen Sie ein Paket pro Aufruf in einen Puffer in Größe der MTU der Schnittstelle und behandeln Sie null oder einen negativen Rückgabewert als Fehler statt als leeres Paket.

protect() vor dem ersten Senden, nicht danach

Jeder Socket, der ein gekapseltes Paket trägt, muss zuerst geschützt werden. Ein ungeschützter Socket folgt denselben Routingregeln, die Sie gerade installiert haben, also gehen seine Pakete zurück in den Tunnel, und Ihre eigene Leseschleife nimmt sie erneut auf. Der Tunnel leitet dann seine eigene Ausgabe endlos weiter, und von außen sieht es wie eine Verbindung aus, die sich öffnet und nie antwortet.

Die Reihenfolge ist protect, dann connect oder bind, dann send. Ein protect nach connect kann die frühere Routenentscheidung bestehen lassen, sodass der Socket weiter den falschen Weg nutzt, obwohl der Aufruf true zurückgab. Prüfen Sie den zurückgegebenen Boolean und scheitern Sie laut, wenn er false ist, statt in ein Senden zu laufen, das nicht funktionieren kann.

Ein Berechtigungsfehler von sendto auf einem Socket, der protect() nie berührt hat, ist die übliche Signatur dieses Fehlers.

Das UDP-Beispiel trägt kein HTTPS

Das Weiterleitungsbeispiel, das die meisten Tutorials kopieren, behandelt UDP und sonst nichts. TCP braucht einen Handshake, Sequenznummern, Bestätigungen und erneutes Senden, und eine Schleife, die Nutzlast zwischen einem tun-Deskriptor und einem Socket kopiert, bietet nichts davon. Deshalb kommt eine UDP-DNS-Abfrage durch, während jede HTTPS-Anfrage hängt. Betreiben Sie entweder einen TCP-Stack im Userspace oder geben Sie Verbindungen an einen lokalen Proxy, der TCP auf dem Gerät beendet und den Strom weiterleitet.

Testen Sie die Trennung, bevor Sie etwas umschreiben: Senden Sie eine UDP-Abfrage und öffnen Sie eine TCP-Verbindung zum selben Host. Funktioniert der UDP-Weg und hängt der TCP-Weg beim ersten SYN, liegt das Problem im Weiterleitungscode und nicht am Server. Dieser eine Vergleich spart viel Zeit, die mit dem Lesen von Serverlogs vergeudet wird, die nichts Nützliches enthalten.

Routenliste und App-Filter begrenzen, was Sie sehen

addAddress, addRoute und addDns legen fest, was das System in den Tunnel schickt. Ein Ziel außerhalb der Routenliste erreicht Ihren Deskriptor nie, daher wird keine Menge an Weiterleitungslogik das beheben. Protokollieren Sie Ihre Builder-Aufrufe und vergleichen Sie sie mit den Adressen, die die App tatsächlich zu erreichen versucht.

Die Auswahl pro App hat ihre eigene Untergrenze: addAllowedApplication und addDisallowedApplication brauchen API 21, und auf älteren Releases fehlen die Methoden schlicht. Ein Design, das nach Paket filtert, kann dort überhaupt nicht laufen.

Neuaufbau hinterlässt veraltete Deskriptoren

Ein erneuter Aufruf von establish() liefert einen neuen Deskriptor und schließt die vorherige Schnittstelle. Der alte Deskriptor ist nun ungültig, ein dort noch blockierender Thread bekommt einen Fehler, und für den alten Pfad erstellte Sockets müssen möglicherweise ersetzt werden. onRevoke ist der Callback für einen Tunnel, den der Nutzer oder das System entzogen hat. Den Lebenszyklus bei einem Eigentümer zu halten, der den Leser vor dem erneuten Aufbau stoppt, verhindert die meisten Zustände verbunden-aber-tot.

Lesen Sie jeden Fehler nach dem Schritt, zu dem er gehört. Ein Verbindungs-Reset von recvfrom bedeutet, dass das entfernte Ende geschlossen hat, während Ihr Code noch las. Ein Fehler wegen ungültiger Adresse deutet auf Header-Parsing oder einen Offset-Bug hin, nicht auf das Netzwerk.

Reihenfolge der Arbeit: blockierender Modus an, ein Paket pro Lesevorgang, Header geparst, Socket geschützt, Nutzlast gesendet, sauberes Schließen bei der Übergabe und eine Routenliste, die das Ziel abdeckt.

Ähnliche Artikel