VpnService protect() o esclusione dell'app: cosa sistema l'instradamento
Proteggi il socket che trasporta il tunnel o escludi l'intera app: le tre condizioni che decidono quale ambito serve alla tua VPN Android.
Hai due modi per tenere i pacchetti del tunnel fuori dal tunnel: proteggere il socket di trasporto o togliere l'intera app dalla VPN. Risolvono problemi diversi, e scegliere quella più ampia perché è più facile da cablare è esattamente il modo in cui le app perdono l'accesso alla rete locale e il DNS diviso senza accorgersene. Parti da ciò che deve restare fuori dal tunnel, poi scegli l'ambito più piccolo che lo ottiene.
Cosa rimuove protect() dal tunnel
VpnService.protect() è una chiamata per socket. Le passi il socket o il descrittore che trasporta il tunnel, e Android marca quel socket così che il suo traffico usi la rete sottostante invece dell'interfaccia VPN. Tutto il resto che l'app invia segue ancora la normale rotta VPN.
Proprio questo ambito stretto è il punto. Un socket protetto non cambia come il tuo client HTTP risolve i nomi, quale interfaccia usano le tue chiamate di analisi o cosa succede a una seconda connessione aperta più tardi.
Tre condizioni che decidono l'ambito
La prima condizione è se solo il trasporto del tunnel deve uscire. Se il resto del traffico dell'app deve continuare a passare dalla VPN, la protezione del socket è l'ambito giusto. La seconda condizione è se controlli la creazione del socket. Una libreria che apre il proprio socket in profondità non ti lascia alcun punto in cui chiamare protect(), a meno che non esponga un hook o accetti un socket già pronto. La terza condizione è se l'app deve raggiungere direttamente la rete locale o un captive portal, che è un requisito diverso dal tenere vivo il tunnel.
Escludere l'app con addDisallowedApplication() è lo strumento brutale. Toglie dalla VPN ogni connessione che l'app fa, comprese quelle che volevi ispezionare, e su alcuni dispositivi cambia anche il comportamento della scoperta locale.
Una terza opzione è legare il trasporto a una rete scelta con bindProcessToNetwork() o un handle di rete a livello di socket. È utile quando il dispositivo ha Wi-Fi e rete mobile insieme e vuoi che il tunnel passi da un uplink preciso.
Chiamala mentre il trasporto viene creato
Chiama protect() nello stesso blocco che crea il trasporto, prima di connect() o send(). Se la chiamata restituisce false, chiudi il socket e fai fallire la configurazione; continuare ti lascia un trasporto i cui pacchetti possono rientrare nella tua stessa interfaccia.
La forma è abbastanza piccola da stare in un solo punto:
DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
transport.close();
throw new IOException("transport not protected");
}
transport.connect(serverAddress);Tre dettagli mordono qui. Primo,il booleano è l'unico segnale che ricevi, quindi registra il fallimento con abbastanza contesto da distinguere un consenso revocato da una chiamata rifiutata. Secondo,protect() copre il socket, non il numero del descrittore: se chiudi il socket e più tardi riusi lo stesso intero per un altro socket, il nuovo socket non è protetto. Terzo, quando una libreria nativa apre il trasporto, proteggi il descrittore prima che il codice nativo si connetta, altrimenti la rotta potrebbe essere già scelta.Il riferimento di VpnService è il posto dove verificare quali overload il tuo livello API di destinazione offre davvero.
Prova il percorso bloccato, non solo quello felice
Verifica con il tunnel attivo, perché è l'unico stato in cui i due percorsi possono differire. Invia una richiesta attraverso il trasporto protetto e una da un percorso di codice che non ha mai chiamato protect(), poi confronta ciò che vede l'estremità remota: la connessione protetta dovrebbe arrivare con la rete sottostante come origine, mentre quella non protetta mostra l'indirizzo del tunnel stesso e muove i suoi contatori di byte. Sul dispositivo,ss o netstat mostra la stessa separazione prima che qualcosa lasci la radio.
Prova la revoca separatamente, perché un cambio di consenso abbatte l'interfaccia senza toccare il tuo codice del socket. Revoca il consenso alla VPN o ferma il servizio, ricostruisci il trasporto e conferma che protect() restituisce ancora true e che il trasporto si riconnette appena il consenso viene concesso di nuovo.
Se la richiesta protetta segue comunque il tunnel, la chiamata di protezione avviene probabilmente dopo che il socket è già stato instradato. Spostala prima e riprova.
Tieni la decisione scritta accanto alla configurazione del trasporto: quali socket sono protetti, quale traffico dell'app è escluso e cosa fa l'app quando la protezione fallisce. È quella riga che impedisce a un refactor successivo di allargare l'esclusione a tutta l'app perché un singolo test falliva. Dà anche alla persona successiva un modo per controllare l'intento senza fare reverse engineering del manifest.