Fragt man eine iOS-App, ob das Telefon in einem VPN ist, lautet die ehrliche Antwort: Sie kann es nicht so wissen, wie Sie es meinen. Das SDK stellt kein Flag für «Gerät ist in einem Tunnel» bereit, und die privaten Schnittstellen, die dem nahekommen könnten, sind für veröffentlichte Apps gesperrt. Beobachten lässt sich die eigene Konfiguration der App, der daran gehängte Zustand und ein bis zwei indirekte Hinweise aus dem Netzwerk-Stack. Jede Antwort beantwortet eine andere Frage, und die meisten kaputten Prüfungen entstehen daraus, eine davon für die ganze Antwort zu halten.

Der Mythos vom einzelnen VPN-Flag

Die Annahme ist, irgendwo im SDK gebe es eine Eigenschaft, die meldet, ob das Gerät in einem Tunnel ist, so wie ein Erreichbarkeits-Flag meldet, ob ein Host antwortet. Eine solche öffentliche Eigenschaft existiert nicht. Selbst wenn es sie gäbe, sind «es existiert ein Tunnel» und «mein Verkehr nutzt ihn» verschiedene Aussagen, und für die meisten Apps zählt nur die zweite.

Genau diese Lücke ist der Grund, warum Bibliotheken, die mit einem Einzeiler-VPN-Detektor werben, meist etwas Fragiles lesen, etwa einen Schnittstellennamen oder einen Proxy-Schlüssel, und es als Urteil präsentieren.

Lade die Einstellungen, bevor du einem nil traust

Die gemeinsame Manager-Instanz existiert, sobald man sie anfordert, doch ihre Protokoll-Eigenschaft bleibt nil, bis der Lade-Callback abgeschlossen ist. Ein nil-Protokoll heißt also mindestens ebenso oft «noch nicht geladen» wie «nicht installiert». Lies innerhalb des Completion-Blocks und entscheide erst dann: Ein vorhandenes Protokollobjekt mit gesetztem Enable-Flag sieht aus wie «installiert».

Ein zweites Signal sitzt am Verbindungsobjekt: der ungültige Zustand, den das System meldet, wenn diese App keine Konfiguration installiert hat. Ungültiger Status zusammen mit einem nil-Protokoll nach abgeschlossenem Laden ist echte Abwesenheit, kein Timing-Wettlauf.

Verbindungsänderungen kommen als Mitteilungen

Handler, die an load und save übergeben werden, melden, was mit der Konfiguration geschah, nicht, was der Tunnel tut. Auf einen von ihnen beim Verbinden zu warten, ist ein Wettlauf, der nie endet. Beobachte stattdessen die Mitteilung über die Statusänderung und lies den aktuellen Wert des Verbindungsobjekts, wenn sie eintrifft.

Ist eine Konfiguration installiert, durchläuft der Verbindungszustand eine kurze Folge: getrennt, verbindend, verbunden, erneut bestätigend, trennend. «Verbunden» beschreibt den Tunnel und nicht den Server dahinter, ist also kein Beleg dafür, dass Verkehr fließt. On-Demand kann einen Tunnel auch aufbauen, während die App im Hintergrund schläft, und dann trifft die Mitteilung ein, bevor es etwas zu aktualisieren gibt.

Die eingeschränkte Proxy-Momentaufnahme ist ein Hinweis, kein Urteil

CFNetwork kann der App die aktuellen System-Proxy-Einstellungen geben, und in diesem Ergebnis steckt ein eingeschränktes Wörterbuch mit dem Schnittstellennamen als Schlüssel. Ein Name, der mit tun, utun, ppp, ipsec oder tap beginnt, ist ein Hinweis auf einen vorhandenen Tunnel. Es bleibt eine Momentaufnahme dessen, was diese App sehen kann, keine Aussage darüber, wohin Pakete reisen.

Anbieter sind sich beim Benennen uneinig, ein fehlendes Präfix beweist also nichts, und eine vorhandene Schnittstelle kann existieren, ohne Verkehr zu führen. Erreichbarkeit ist noch schwächer: Eine erfolgreiche Anfrage an eine private Adresse zeigt, dass der Weg für diese Anfrage in diesem Moment funktionierte, nicht dass ein VPN installiert ist.

Der Simulator leiht sich den Mac, andere Apps bleiben unsichtbar

Der Simulator hat keinen eigenen Netzwerk-Stack; jede Verbindung, die er aufbaut, ist die des Macs. Ein VPN auf dem Host-Mac gilt daher bereits für die simulierte App, und die eigenen Einstellungen des Simulators zeigen nichts. Kam der Tunnel erst nach dem Start des Simulators hoch, ist ein Neustart meist schneller als das Debuggen der App. Ein Host auf einem Nicht-Standard-Port braucht diesen Port in der URL oder eine lokale Weiterleitung wie socat TCP-LISTEN:12345,fork TCP:host:12345.

Öffentliche APIs zählen keine VPN-Profile auf, die anderen Apps oder Konfigurationsprofilen gehören. Für eine bekannte App ist eine URL-Schema-Prüfung über canOpenURL der portable Trick, und seit iOS 9 muss das abgefragte Schema in deiner Info.plist deklariert sein; fehlt es, gibt der Aufruf false zurück. OpenVPN Connect antwortet auf openvpn://, was es zu einem guten Beispiel und einem schlechten allgemeinen Detektor macht.

Kombiniere drei Beobachtungen und sage, was sie bedeuten

Stapel drei Ergebnisse in der Praxis: eine Konfiguration, die geladen und aktiviert ist, ein Verbindungszustand, der nicht ungültig ist, und eine Anfrage an eine Adresse, die nur durch den Tunnel erreichbar ist. Jedes für sich führt in die Irre; zusammen sagen sie, was die App wissen kann, statt was der Nutzer annimmt.

Schreib das Ergebnis als drei Zeilen in deine Diagnose, nicht als einen Boolean. Wenn ein Nutzer sagt, das VPN sei aus, während deine App widerspricht, zeigen die drei Zeilen, welche Beobachtung abwich, und das ist der Unterschied zwischen einem Support-Ticket, auf das du reagieren kannst, und einem, das mit «bei mir läuft es» endet.

Ähnliche Artikel