Come verificare nel codice se una VPN è connessa su Android
Android non ha un getter per lo stato connesso della VPN. Interroga le capacità di rete, osserva i cambiamenti di trasporto e tieni separati il consenso dell'app, lo stato del dispositivo e la prova lato server.
Non puoi chiedere ad Android se una VPN è connessa. VpnService non espone alcun getter per lo stato connesso, e l'icona della chiave nella barra di stato è disegnata dal sistema, non da qualcosa che la tua app possa leggere. Quello che puoi fare è porre allo stack di connettività una domanda più ristretta: quale rete sta usando il traffico di questa app, e quella rete porta il trasporto VPN. Questo runbook illustra i controlli nell'ordine che evita i falsi positivi, da un comando shell sul dispositivo fino alla callback che lasci in esecuzione nell'app.
Controlla il dispositivo da una shell prima di toccare il codice dell'app
Prima ottieni una base di riferimento. adb shell dumpsys connectivity stampa ogni rete che il sistema conosce, e ogni blocco elenca i suoi trasporti, quindi una rete VPN compare con VPN in quell'elenco. Leggi il blocco della rete predefinita prima di ogni altra cosa, poi cerca altre reti che portano lo stesso trasporto.
Due output rispondono a domande diverse. Che esista una rete VPN non è lo stesso che la tua app la stia usando, e su un dispositivo con un profilo di lavoro o un secondo utente entrambe le cose possono essere vere contemporaneamente mentre il tuo processo parla direttamente con Internet. La riga della rete attiva è quella che decide cosa fanno i tuoi socket.
Chiedi la rete che l'app sta usando
Nel codice il test delle capacità è di quattro righe. Richiedi ACCESS_NETWORK_STATE nel manifest, poi confronta le capacità della rete attiva con il trasporto VPN:
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);
La risposta è volutamente ristretta: la rete predefinita dell'app è una VPN in questo momento. Non dice nulla su quale VPN, se appartiene a te, o se il tunnel raggiunge l'altra estremità. Tratta un true come “il traffico esce attraverso un tunnel” e tieni separata la tua contabilità. Un booleano che significa silenziosamente tre cose insieme è il modo in cui una schermata di impostazioni finisce per segnalare la VPN come disattivata mentre l'utente sta navigando attraverso di essa.
Quando invece devi sapere se è presente una qualche rete VPN, itera cm.getAllNetworks() e verifica ogni insieme di capacità. Le due risposte divergono su un dispositivo che esegue una VPN per app che copre altre applicazioni ma non la tua, e quella divergenza è l'origine della maggior parte delle segnalazioni di bug che dicono che l'app non vede una VPN chiaramente attiva.
Osserva i cambiamenti invece di fare polling
Il polling a timer è la forma sbagliata per questa domanda, perché i cambiamenti di stato arrivano come eventi. Registra una richiesta che corrisponde al trasporto VPN e lascia che il framework ti dica quando una rete corrispondente appare, scompare o cambia:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(request, callback);
Una rete VPN non porta mai NET_CAPABILITY_NOT_VPN, quindi la rimozione esplicita documenta l'intento. Senza di essa la richiesta corrisponde comunque, ma la prossima persona che legge il codice deve arrivarci da sola.
La registrazione appartiene all'ambito che ha bisogno dell'osservazione, di solito una schermata o un servizio in primo piano. Annulla la registrazione in onStop o onDestroy, e non lasciare una callback attaccata a un'activity già scomparsa. Un'app senza componenti visibili è un cattivo ospite per un osservatore permanente, e le callback che dimentichi sono quelle che vanno in crash dopo un cambio di configurazione.
Tieni separati tre interrogativi
La maggior parte della confusione in questo ambito nasce dal trattare tre controlli diversi come uno solo. Usano API diverse, e ciascuno può essere vero mentre gli altri sono falsi.
| 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 |
La terza riga è quella che le persone cercano di rispondere sul client, e non può essere risolta lì. Assegna ai client VPN un proprio pool di indirizzi e leggi l'indirizzo di origine sul server; è l'unica prova che sopravvive a un utente che cambia tunnel a metà sessione.
Ordine di verifica che esclude i falsi positivi
Codice di esempio più vecchio ancora in circolazione chiama getNetworkInfo(ConnectivityManager.TYPE_VPN). Quella chiamata è deprecata, e su diverse versioni restituiva null anche mentre una VPN era connessa, ed è per questo che tanti snippet copiati sembrano rotti su un dispositivo che funziona. I controlli delle capacità evitano la trappola e si comportano allo stesso modo da Android 5.0 in poi. Se gestisci una base di codice che la chiama ancora, sostituisci la chiamata invece di avvolgerla in un fallback, perché il fallback è ciò che ha prodotto la risposta sbagliata in primo luogo.
Una scorciatoia merita un avvertimento. Confrontare l'indirizzo pubblico riportato da un servizio esterno con l'indirizzo di uscita della VPN dimostra qualcosa sul percorso, ma aggiunge una richiesta di terze parti a ogni controllo, dipende dal fatto che quel servizio resti attivo e può essere memorizzata in cache o bloccata. Tienila come diagnostica manuale, mai come controllo di stato dentro l'app.
Esegui i controlli in questo ordine e fermati al primo che non concorda con la segnalazione dell'utente: elenco dei trasporti da shell, capacità della rete attiva, eventi di callback mentre attivi e disattivi il tunnel, poi indirizzo di origine lato server. Se tutti e quattro coincidono e l'app si comporta ancora male, il bug è nella tua macchina a stati, non nella visione della rete di Android.