Je kunt Android niet vragen of een VPN verbonden is. VpnService biedt geen getter voor de verbindingsstatus, en het sleutelpictogram in de statusbalk wordt door het systeem getekend, niet door iets wat je app kan lezen. Wat je wel kunt doen, is de connectiviteitsstack een nauwere vraag stellen: welk netwerk gebruikt het verkeer van deze app, en draagt dat netwerk het VPN-transport. Deze handleiding loopt de controles af in de volgorde die valse positieven voorkomt, van een shell-opdracht op het apparaat tot de callback die je in de app laat draaien.

Controleer het apparaat vanuit een shell voordat je appcode aanraakt

Haal eerst een basislijn op. adb shell dumpsys connectivity drukt elk netwerk af dat het systeem kent, en elk blok somt zijn transporten op, dus een VPN-netwerk verschijnt met VPN in die lijst. Lees het blok van het standaardnetwerk vóór al het andere en zoek daarna naar andere netwerken die hetzelfde transport dragen.

Twee uitvoerwaarden beantwoorden verschillende vragen. Of een VPN-netwerk bestaat is niet hetzelfde als of je app het gebruikt, en op een apparaat met een werkprofiel of een tweede gebruiker kunnen beide tegelijk waar zijn terwijl je proces rechtstreeks met internet praat. De regel van het actieve netwerk is degene die bepaalt wat je sockets doen.

Vraag naar het netwerk dat de app gebruikt

In code is de mogelijkheidstest vier regels. Vraag ACCESS_NETWORK_STATE aan in het manifest en vergelijk daarna de mogelijkheden van het actieve netwerk met het VPN-transport:

ConnectivityManager cm = (ConnectivityManager)
    context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

Het antwoord is bewust smal: is het standaardnetwerk van de app op dit moment een VPN. Het zegt niets over welke VPN, of die van jou is, of de tunnel de andere kant bereikt. Behandel een true als “verkeer verlaat het toestel via een of andere tunnel” en houd je eigen boekhouding apart. Een boolean die stilzwijgend drie dingen tegelijk betekent, is de reden dat een instellingenscherm de VPN als uit rapporteert terwijl de gebruiker erdoorheen browset.

Wanneer je juist wilt weten of er überhaupt een VPN-netwerk aanwezig is, itereer je over cm.getAllNetworks() en test je elke mogelijkhedenset. De twee antwoorden lopen uiteen op een apparaat met een app-specifieke VPN die andere apps dekt maar niet de jouwe, en dat uiteenlopen is de bron van de meeste bugmeldingen die zeggen dat de app een VPN niet ziet die duidelijk aan staat.

Volg wijzigingen in plaats van te pollen

Pollen met een timer is de verkeerde vorm voor deze vraag, omdat toestandsveranderingen als gebeurtenissen binnenkomen. Registreer een verzoek dat bij het VPN-transport past en laat het framework je vertellen wanneer een passend netwerk verschijnt, verdwijnt of verandert:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(request, callback);

Een VPN-netwerk draagt nooit NET_CAPABILITY_NOT_VPN, dus het expliciet verwijderen documenteert de bedoeling. Zonder dat komt het verzoek nog steeds overeen, maar de volgende persoon die de code leest moet dat zelf uitzoeken.

Registratie hoort bij de scope die de observatie nodig heeft, meestal een scherm of een voorgrondservice. Meld af in onStop of onDestroy, en laat geen callback hangen aan een activity die al weg is. Een app zonder zichtbare component is een slechte gastheer voor een permanente observator, en de callbacks die je vergeet zijn degene die crashen na een configuratiewijziging.

Houd drie vragen apart

De meeste verwarring op dit gebied komt van het behandelen van drie verschillende controles als één. Ze gebruiken verschillende API's, en elk ervan kan waar zijn terwijl de andere onwaar zijn.

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

De derde rij is degene die mensen op de client proberen te beantwoorden, en die kan daar niet beantwoord worden. Geef VPN-clients hun eigen adrespool en lees het bronadres op de server; dat is het enige bewijs dat een gebruiker overleeft die halverwege een sessie van tunnel wisselt.

Verificatievolgorde die valse positieven uitsluit

Oudere voorbeeldcode die nog circuleert roept getNetworkInfo(ConnectivityManager.TYPE_VPN) aan. Die aanroep is verouderd, en op verschillende releases gaf hij null terug terwijl er een VPN verbonden was, waardoor zoveel gekopieerde fragmenten kapot lijken op een apparaat dat werkt. Mogelijkheidstests vermijden die valkuil en gedragen zich hetzelfde vanaf Android 5.0. Als je een codebase onderhoudt die hem nog aanroept, vervang de aanroep dan in plaats van hem in een fallback te wikkelen, want de fallback is wat het verkeerde antwoord in de eerste plaats opleverde.

Eén snelkoppeling verdient een waarschuwing. Het openbare adres dat een externe dienst meldt vergelijken met het exitadres van de VPN bewijst iets over het pad, maar het voegt een verzoek van derden toe aan elke controle, hangt af van die dienst die blijft draaien, en kan gecachet of geblokkeerd worden. Houd het als handmatige diagnostiek, nooit als de toestandscontrole in de app.

Voer de controles in deze volgorde uit en stop bij de eerste die niet overeenkomt met de melding van de gebruiker: transportlijst uit de shell, mogelijkheden van het actieve netwerk, callback-gebeurtenissen terwijl je de tunnel aan- en uitzet, en dan het bronadres aan de serverkant. Als alle vier kloppen en de app zich nog steeds misdraagt, zit de bug in je eigen toestandsmachine en niet in Androids beeld van het netwerk.

Gerelateerde artikelen