Sådan tjekker du i kode, om en VPN er forbundet på Android
Android har ingen getter for VPN-forbindelse. Spørg til netværksevner, overvåg transportændringer, og hold app-samtykke, enhedstilstand og serverbevis adskilt.
Du kan ikke spørge Android, om en VPN er forbundet. VpnService udstiller ingen getter for forbindelse, og nøgleikonet i statuslinjen tegnes af systemet, ikke af noget din app kan læse. Det, du kan gøre, er at stille forbindelsesstakken et snævrere spørgsmål: hvilket netværk bruger denne apps trafik, og bærer det netværk VPN-transporten. Denne vejledning gennemgår tjekkene i den rækkefølge, der undgår falske positiver, fra en shell-kommando på enheden til det callback, du lader køre i appen.
Tjek enheden fra en shell, før du rører app-koden
Få først en basislinje. adb shell dumpsys connectivity udskriver hvert netværk, systemet kender, og hver blok lister sine transporter, så et VPN-netværk vises med VPN på den liste. Læs blokken for standardnetværket før alt andet, og se derefter efter andre netværk, der bærer samme transport.
To output besvarer forskellige spørgsmål. Om et VPN-netværk findes, er ikke det samme som, om din app bruger det, og på en enhed med arbejdsprofil eller en anden bruger kan begge dele være sande på én gang, mens din proces taler direkte med internettet. Linjen for det aktive netværk er den, der afgør, hvad dine sockets gør.
Spørg efter det netværk, appen bruger
I kode er evnetesten fire linjer. Anmod om ACCESS_NETWORK_STATE i manifestet, og sammenlign derefter det aktive netværks evner med VPN-transporten:
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);
Svaret er med vilje snævert: er appens standardnetværk en VPN lige nu. Det siger intet om hvilken VPN, om den er din, eller om tunnelen når sin fjernende. Behandl en true som “trafik forlader gennem en eller anden tunnel”, og hold dit eget regnskab adskilt. En boolean, der tavst betyder tre ting på én gang, er grunden til, at en indstillingsskærm ender med at rapportere VPN som slukket, mens brugeren browser gennem den.
Når du i stedet skal vide, om der overhovedet findes et VPN-netværk, skal du iterere cm.getAllNetworks() og teste hvert evnesæt. De to svar divergerer på en enhed, der kører en per-app VPN, som dækker andre applikationer men ikke din, og den divergens er kilden til de fleste fejlrapporter, der siger, at appen ikke kan se en VPN, som tydeligt er tændt.
Overvåg ændringer i stedet for at polle
Polling på en timer er den forkerte form til dette spørgsmål, fordi tilstandsændringer ankommer som hændelser. Registrer en anmodning, der matcher VPN-transporten, og lad frameworket fortælle dig, når et matchende netværk dukker op, forsvinder eller ændrer sig:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(request, callback);
Et VPN-netværk bærer aldrig NET_CAPABILITY_NOT_VPN, så den udtrykkelige fjernelse dokumenterer hensigten. Uden den matcher anmodningen stadig, men den næste person, der læser koden, skal selv regne det ud.
Registrering hører til det omfang, der har brug for observationen, normalt en skærm eller en forgrundstjeneste. Afregistrer i onStop eller onDestroy, og lad ikke et callback hænge på en aktivitet, der allerede er væk. En app uden synlig komponent er en dårlig vært for en permanent observatør, og de callbacks, du glemmer, er dem, der crasher efter en konfigurationsændring.
Hold tre spørgsmål adskilt
Det meste af forvirringen på dette område kommer af at behandle tre forskellige tjek som ét. De bruger forskellige API'er, og hvert af dem kan være sandt, mens de andre er falske.
| Question | How you answer it | What a yes means |
|---|---|---|
| Did the user grant VPN consent to my app? | VpnService.prepare() returns null | Consent exists; the tunnel may still be down |
| Is the device sending default traffic through a VPN? | TRANSPORT_VPN on the active network | Something is tunneling, not necessarily your app |
| Did this request arrive through the VPN? | Source address seen by the server | Proof for that connection, nothing wider |
Den tredje række er den, folk prøver at besvare på klienten, og det kan ikke besvares der. Giv VPN-klienter deres egen adressepulje, og læs kildeadressen på serveren; det er det eneste bevis, der overlever, at en bruger skifter tunnel midt i en session.
Verifikationsrækkefølge, der udelukker falske positiver
Ældre eksempelkode, der stadig er i omløb, kalder getNetworkInfo(ConnectivityManager.TYPE_VPN). Det kald er forældet, og i flere udgivelser returnerede det null, selv når en VPN var forbundet, og derfor ser så mange kopierede uddrag brudte ud på en enhed, der virker. Evnetjek undgår fælden og opfører sig ens fra Android 5.0 og frem. Hvis du vedligeholder en kodebase, der stadig kalder det, skal du erstatte kaldet i stedet for at pakke det ind i en fallback, for fallbacken var det, der oprindeligt gav det forkerte svar.
Én genvej fortjener en advarsel. At sammenligne den offentlige adresse, en ekstern tjeneste rapporterer, med VPN-udgangsadressen beviser noget om stien, men det tilføjer en tredjepartsanmodning til hvert tjek, afhænger af, at tjenesten forbliver oppe, og kan blive cachelagret eller blokeret. Hold det som en manuel diagnose, aldrig som tilstandstjekket inde i appen.
Kør tjekkene i denne rækkefølge, og stop ved det første, der ikke stemmer med brugerens rapport: transportliste i shell, det aktive netværks evner, callback-hændelser mens du slår tunnelen til og fra, og derefter kildeadressen på serversiden. Hvis alle fire stemmer, og appen stadig opfører sig forkert, er fejlen i din egen tilstandsmaskine og ikke i Androids syn på netværket.