Lista consentita di Android VpnService: perché le pagine consentite si bloccano ancora
La tua lista consentita è probabilmente a posto. Le pagine si bloccano per la risposta DNS, la tabella delle rotte, il socket del resolver o l'MTU. Ecco l'ordine di verifica.
Hai aggiunto un dominio alla lista consentita, la query raggiunge la tua app, il comparatore restituisce consentito, e la pagina continua a non caricarsi mai. A quel punto la maggior parte delle persone inizia a riscrivere il comparatore e ad aggiungere confronti con wildcard. Per la mia esperienza il comparatore di solito è corretto, e il guasto si trova un livello più in basso: nella risposta DNS che hai sintetizzato, nella rotta che hai installato o nel socket che hai usato per inoltrare la query. La lista è la parte facile; il percorso del pacchetto è dove le liste consentite si rompono.
Il comparatore è raramente il colpevole
La prima cosa da separare è bloccato da rotto. Un nome bloccato dovrebbe fallire in fretta e in modo visibile, perché il browser riceve una risposta che dice che il nome non esiste e si ferma. Un nome rotto si blocca, riprova su IPv6, passa a un altro trasporto e ti lascia con un indicatore che gira. Se vedi un indicatore che gira, la tua lista consentita probabilmente risponde qualcosa su cui il client non può agire.
Il record A vuoto è l'errore classico. Restituire NOERROR con zero risposte sembra una risposta valida, quindi il resolver stub la accetta, il browser riprova, prova AAAA, prova un altro resolver e si arrende molto più tardi. Un codice di risposta 3, NXDOMAIN, con la sezione domanda ripetuta e l'ID di transazione copiato, chiude la conversazione subito. Usalo per ogni nome che rifiuti deliberatamente.
IPv6 merita la stessa decisione e non un valore predefinito. Se rispondi NXDOMAIN per A e lasci passare AAAA, il client può raggiungere il nome bloccato su IPv6 e il filtro perde. Alcune implementazioni rispondono alle query AAAA bloccate con un indirizzo di loopback così la connessione fallisce subito invece di ripiegare. Scegli una politica, applicala a entrambi i tipi di record e registra entrambi.
Una rotta che cattura tutto possiede tutto
Un progetto solo DNS non aggiunge mai addRoute("0.0.0.0", 0), quindi solo le query entrano nell'interfaccia TUN e tutto il resto continua a usare la rete sottostante. Questa è la forma sicura, e spiega anche perché alcune liste consentite sembrano funzionare in Wi-Fi e falliscono sulla rete mobile.
Nel momento in cui esiste una rotta predefinita, ogni flusso è tuo. Un pacchetto TCP che arriva all'interfaccia e non viene né inoltrato né servito con una risposta resta in coda finché il client non va in timeout, e non c'è nessun errore da registrare perché nulla ha fallito. Ho visto qualcuno passare un pomeriggio su una lista consentita rotta che in realtà era un inoltratore mancante per la porta 443. Registra il protocollo e la destinazione di ogni pacchetto che decidi di scartare.
Il socket del resolver deve uscire dal tunnel
L'inoltro è dove si trova la trappola successiva. I resolver dell'operatore spesso vivono su un indirizzo privato che il tunnel non raggiunge, quindi la query che inoltri non va da nessuna parte e l'app segnala un timeout in lettura. Invia le query approvate a un resolver che sai essere raggiungibile, e passa prima quel socket a VpnService.protect(). Un socket non protetto rimanda la query nel tuo stesso tunnel, dove arriva di nuovo all'app e gira finché non scatta il timeout.
Il guasto sembra identico a un'interruzione di rete, ed è per questo che sopravvive così a lungo nei bug report. Tieni contatori per query inviate e risposte ricevute: se il primo numero sale e il secondo resta a zero, stai guardando il socket, non la lista.
Dimensioni e checksum falliscono senza un messaggio
Gli scarti silenziosi vengono anche dalle dimensioni. Una risposta DNS con un grande insieme di risposte, o una stretta di mano TCP con opzioni generose, può superare un MTU di percorso che non hai mai impostato esplicitamente; il frammento sparisce a monte e il client o riprova più piccolo o smette di riprovare. Impostare l'MTU dell'interfaccia a 1280 è una scelta prudente che sopravvive alla maggior parte dei percorsi mobili, ed essere espliciti è meglio che ereditare un valore predefinito che nessuno ha misurato.
Se costruisci i pacchetti da solo, il checksum è l'altro killer silenzioso. Un checksum UDP calcolato su uno pseudo-header che omette la lunghezza UDP o il campo protocollo produce un pacchetto che dalla tua parte sembra ben formato e che il destinatario scarta senza traccia. Confronta il tuo pseudo-header con la specifica campo per campo, e stampa il checksum calcolato per un flusso noto mentre esegui il debug.
Cosa un VpnService non farà per te
Vale la pena conoscere due limiti prima di progettare attorno a essi. Nessuna app ordinaria può installare un proxy o un file PAC a livello di sistema fuori dal Wi-Fi, e quella parte non è cambiata. Il percorso per app invece sì: prima dell'API 29 VpnService.Builder non poteva portare alcun proxy, e dall'API 29 setHttpProxy(ProxyInfo.buildPacProxy(uri)) consegna alla piattaforma uno script PAC da valutare per la rete VPN. Valuta le regole nel tuo codice quando punti ad API più vecchie o quando il modello PAC non riesce a esprimere la regola. Anche UDP non è gratis: i browser inviano solo TCP attraverso un proxy SOCKS, e la maggior parte dei client SOCKS5 non implementa mai UDP ASSOCIATE, quindi un progetto basato su proxy scarta o perde UDP mentre TCP sembra sano.
Questo secondo limite è il motivo per cui un protocollo basato su UDP può funzionare su una rete normale e fallire nel momento in cui la tua VPN si connette. Se si prevede che il tunnel trasporti UDP, l'inoltratore deve implementare il percorso di inoltro anche per quello, non solo il percorso TCP con un altro tipo di socket.
Verifica in questo ordine
Percorri la lista in ordine e fermati al primo stadio che fallisce, perché ogni controllo successivo è rumore finché quello precedente non regge.
- Elenca le rotte installate e conferma se i pacchetti non DNS entrano davvero nell'interfaccia TUN.
- Registra il nome della query, la decisione e il codice di risposta per un nome bloccato e uno approvato.
- Conferma che il socket del resolver sia protetto e che il resolver scelto risponda da dentro il tunnel.
- Conferma l'MTU dell'interfaccia e il checksum di una risposta costruita a mano.
- Ripeti la stessa pagina in Wi-Fi e sulla rete mobile, perché i resolver dell'operatore e l'MTU del percorso differiscono.
Se tutti e cinque reggono e la pagina si blocca ancora, il prossimo sospetto è l'inoltratore stesso: confronta una richiesta catturata con una risposta catturata e cerca un conteggio di byte mancante o un socket mezzo chiuso. Il riferimento di VpnService.Builder copre i contratti di rotta, DNS e MTU, e protect() è una chiamata VpnService separata e non un'opzione del builder; un'interfaccia connessa significa solo che il primo contratto è stato rispettato.