No, un’app VpnService non può accendere la VPN del telefono
L’app possiede un tunnel e il suo ciclo di vita, non l’interruttore VPN del telefono. Il consenso può essere revocato, gira una sola VPN alla volta, e i pacchetti spetta a voi analizzarli.
L’ipotesi sbagliata è che un’app VpnService azioni un interruttore di sistema e che da quel momento il telefono sia protetto. Ciò che accade davvero è più stretto: l’app chiede il consenso, costruisce un’interfaccia, legge e scrive pacchetti tramite un descrittore di file, e restituisce l’interfaccia quando si ferma. Può essere vivo un solo tunnel di questo tipo alla volta, e il sistema può portarlo via senza chiedere. La maggior parte del comportamento confuso che le persone attribuiscono al protocollo deriva da quel modello di proprietà.
Trattate ogni transizione del ciclo di vita come parte del progetto e non come un caso limite.
Mito: l’app accende la VPN del telefono
Il flusso inizia con VpnService.prepare(), che restituisce un Intent che l’utente deve accettare, e prosegue con establish(), che può comunque fallire dopo per motivi che l’app non controlla. Stabilire un secondo tunnel disattiva la prima interfaccia e sostituisce la notifica di sistema. In nessun punto di quella sequenza l’app possiede un interruttore per tutto il dispositivo; possiede il descrittore che le è stato concesso e i pacchetti che vi arrivano. Persino la notifica persistente appartiene al sistema e non al progetto dell’app.
Poiché l’interfaccia viene concessa e non presa, un altro client può occupare il posto in qualsiasi momento. L’app lo scopre tramite una callback, non tramite un valore di ritorno.
L’arresto forzato dell’app è visibile nel pannello delle notifiche per lo stesso motivo: il sistema segnala ciò che ha concesso, non ciò che l’app ha scelto di mostrare.
Mito: il consenso viene dato una volta
Quando il sistema consegna il tunnel a un altro client, invia una transazione di revoca sul binder che la classe base restituisce da onBind(). Un’app che sostituisce quel binder con il proprio deve inoltrare essa stessa la transazione di revoca, altrimenti VpnService.onRevoke() non si attiva mai e il servizio continua a scrivere in un descrittore che non esiste più. L’approvazione può essere ritirata anche dall’utente, e il sistema può fermare il servizio durante un riavvio o una riconnessione sempre attiva.
Gestirlo è lavoro concreto e non una riga di log: smettete di leggere il descrittore, chiudetelo, cancellate il flag di connessione e dite all’utente in quale stato si trova. Poi decidete se l’app deve ristabilire, chiedere di nuovo o attendere, perché in una configurazione sempre attiva il sistema può riportare il servizio da solo.
Un’app che tratta la revoca come un’eccezione e non come uno stato segnalerà di essere connessa mentre ogni pacchetto che scrive viene scartato, ed è il peggior modo di guasto possibile per uno strumento di privacy.
Mito: l’interfaccia filtra il traffico per voi
Ciò che arriva è IP grezzo. Catturare o filtrare significa analizzare le intestazioni IP, TCP, UDP e IPv6, seguire i flussi e riscrivere le risposte nel descrittore, il che rende un’app di cattura un piccolo stack di rete e non una funzione di sistema. protect() esiste perché il socket di trasporto dell’app aggiri il proprio tunnel invece di catturare se stessa, e restituisce un booleano che merita un controllo su ogni socket creata dopo che il tunnel è attivo.
Gli elenchi di applicazioni consentite e vietate del builder decidono di chi entra nell’interfaccia. Non danno accesso al carico utile oltre i pacchetti IP che l’interfaccia trasporta, e nessuna API espone il tunnel di un altro client né elenca i profili VPN di sistema. Le capacità di rete possono dire che una rete è trasporto VPN, ma non di chi è il tunnel.
Ecco perché le domande sullo stato di altre app ricevono risposte contraddittorie: l’informazione semplicemente non è esposta al vostro processo.
Che cosa richiede davvero il ciclo di vita
Seguite gli stati che controllate: preparato, stabilito, revocato, fermato. La riconnessione dovrebbe essere smontaggio seguito da stabilimento, con un solo proprietario del descrittore e del socket di trasporto, perché due componenti che gareggiano per stabilire si contenderanno l’unico posto disponibile. Gli avvii in background sono limitati su Android moderno, quindi una trasmissione di avvio non è una garanzia; usate un servizio in primo piano con la sua notifica dove il livello API lo consente.
Registrate la transizione e non solo lo stato finale, e mantenete la macchina a stati abbastanza piccola da provare ogni percorso. Uno stato che non riuscite a raggiungere in un test è uno stato che incontrerete sul campo senza log.
Provate il ciclo di vita su un dispositivo
Gli emulatori possono simulare la connettività ma non il percorso di consenso e revoca in modo convincente, quindi eseguite questi test su un dispositivo reale con un secondo client VPN installato che rubi il posto.
adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp
Avviate il tunnel, confermate la notifica e l’interfaccia, poi collegate l’altro client e osservate se la callback di revoca viene eseguita e se lo stato si pulisce. Arrestate l’app con la forza e verificate se qualcosa segnala ancora un descrittore vivo.
Ripetete la sequenza dopo un riavvio sempre attivo, perché quel percorso è diverso da una connessione avviata dall’utente. Non cercate un’icona verde ma la prova che l’app ha smesso di usare un descrittore che non possedeva più.