Niente in VpnService intercetta il traffico da solo. Chiede al sistema un'interfaccia tun, consegna a quell'interfaccia un insieme di indirizzi e rotte e, da quel momento, il kernel copia ogni pacchetto che corrisponde a quelle rotte in un descrittore di file che l'applicazione può leggere. Il codice dell'applicazione non vede mai i pacchetti che le rotte non hanno selezionato, quindi l'ambito di un tunnel è una questione di tabella di routing prima di essere una questione di programmazione. Per questo la stessa implementazione del servizio può inghiottire ogni flusso su un telefono e solo una parte su un altro, e per questo aggiungere una rotta a volte ripara un tunnel che sembrava rotto. Quello che ottieni è quello che hai dichiarato, meno tutto ciò che il costruttore ha rifiutato in silenzio.

Le rotte decidono quali pacchetti arrivano

Builder.addRoute(prefix, prefixLength) installa un prefisso nella tabella di routing della rete VPN. Il sistema tratta quella rete come predefinita per le applicazioni nell'ambito, quindi le sue voci vengono consultate prima della rotta predefinita della rete fisica e un prefisso corrispondente basta a deviare un flusso. Per questo una rotta per 0.0.0.0/0 prende IPv4 per intero, inclusi la stampante, il target cast e la pagina di amministrazione del router.

I client che vogliono quella copertura senza cancellare la voce predefinita fisica usano invece la coppia 0.0.0.0/1 e 128.0.0.0/1. Due rotte più specifiche battono una rotta predefinita, insieme coprono tutto lo spazio degli indirizzi e la voce predefinita del telefono resta nella tabella per i socket ancora legati alla rete fisica.

Gli indirizzi e le voci DNS vengono dallo stesso costruttore e rispondono a un'altra domanda. addAddress dà all'interfaccia un indirizzo con una lunghezza di prefisso, e addDnsServer installa un resolver che le applicazioni nell'ambito interrogheranno attraverso l'interfaccia. Entrambe le famiglie possono stare su un'interfaccia, quindi un insieme di rotte solo IPv4 accanto a un indirizzo IPv6 è una configurazione che si legge correttamente e perde comunque. Se instradi una famiglia in cui l'interfaccia non ha indirizzo, il kernel non ha indirizzo sorgente per quei pacchetti e non compaiono mai al descrittore.

Cosa restituisce establish()

establish() restituisce un ParcelFileDescriptor, o null quando l'applicazione non è autorizzata. Null è uno stato di consenso, non un errore di rete: VpnService.prepare() deve aver già restituito null, oppure l'utente deve aver approvato la finestra di sistema prima della chiamata. Il consenso può essere revocato mentre il servizio è in esecuzione, e l'interfaccia si chiude sotto di te senza alcun callback nel tuo ciclo di lettura.

Il descrittore non è bloccante salvo diversa indicazione, quindi una lettura torna subito senza dati invece di parcheggiare il thread. Un ciclo scritto per un socket bloccante brucia un core, e un ciclo che tratta una lettura vuota come fatale strappa il tunnel in un secondo di inattività. Builder.setBlocking(true) cambia quel comportamento, e il prezzo è un thread che attende dentro la lettura.

Gli argomenti di rotta e indirizzo sono validati come valori LinkAddress, e le destinazioni multicast come 224.0.0.0/4 vengono rifiutate di netto. Un'interfaccia tun punto a punto non ha nemmeno un dominio di broadcast, quindi la scoperta multicast attraverso di essa richiede un progetto diverso invece di un'altra rotta. Il riferimento a VpnService.Builder è la lista da controllare prima di concludere che una chiamata è stata ignorata.

L'ambito per applicazione restringe l'insieme, non il tunnel

Dall'API 21 il costruttore accetta addAllowedApplication e addDisallowedApplication, e le due liste si escludono a vicenda in un solo costruttore. Una lista di consentiti fa esistere il tunnel per quei pacchetti mentre tutto il resto conserva la rete fisica; una lista di esclusi cattura tutto tranne i pacchetti nominati, che è ciò che un client offre come tunnel diviso. Prima dell'API 21 non esiste alcun meccanismo di piattaforma, e il comportamento per applicazione veniva da client di terze parti che filtravano per UID nel proprio inoltratore. La distinzione conta durante il debug, perché un tunnel per applicazione con una lista di un solo pacchetto non produce quasi traffico e questo è un comportamento corretto, non una rotta rotta.

Qualunque sia l'ambito, il socket che la tua applicazione apre verso il server VPN non deve viaggiare attraverso il tunnel che sta costruendo. VpnService.protect(socket) lega quel socket alla rete sottostante così i suoi pacchetti saltano il dispositivo tun. Salta la chiamata e la stretta di mano viene inghiottita dall'interfaccia che dovrebbe sollevarla.

VpnService.setUnderlyingNetworks chiude il cerchio dicendo al sistema quale rete fisica trasporta il tunnel. Quel valore determina come la VPN segnala la connettività alle altre applicazioni, e un valore null o obsoleto è una causa abituale di un tunnel connesso che il resto del telefono tratta come offline. Aggiornalo quando cambia la rete sottostante, non solo all'avvio.

Checksum e MTU: dove una rotta corretta fallisce comunque

Una volta che i pacchetti arrivano, due dettagli a livello di pacchetto decidono se qualcosa funziona da un capo all'altro. Il primo è l'indirizzo sorgente: con più indirizzi di interfaccia il kernel può emettere pacchetti che portano il primo, quindi un inoltratore che riscrive le intestazioni deve correggere l'indirizzo e il checksum che lo copre.

Il secondo è l'ambito del checksum. Quando l'applicazione costruisce da sé pacchetti TCP o UDP, il checksum copre una pseudo-intestazione con l'indirizzo sorgente, l'indirizzo di destinazione, il numero di protocollo e la lunghezza del trasporto, e ometterne uno solo produce pacchetti che arrivano e vengono scartati. Quel guasto sembra un timeout dopo una risposta che non arriva mai, il che rimanda la maggior parte delle persone alle rotte. setMtu fissa l'MTU dell'interfaccia, e ogni lettura restituisce esattamente un pacchetto IP completo, quindi un buffer scritto per un flusso di byte frammenterà la tua analisi, non il pacchetto.

Un passaggio di verifica che separa i livelli

Per trovare dove l'ambito ha sbagliato, prova i livelli in ordine. Chiedi al dispositivo quali rotte detiene davvero la rete VPN, poi controlla se un flusso verso un indirizzo noto compare come lettura al descrittore, poi controlla se il pacchetto che il tuo inoltratore emette è ben formato. Un problema di rotta non mostra nulla al descrittore, mentre un problema di checksum mostra un pacchetto che arriva e svanisce.

Se nulla raggiunge il descrittore, il sospetto è l'insieme delle rotte e non il lettore: conferma i prefissi, conferma che ci sia un indirizzo nella stessa famiglia e conferma che l'applicazione sia nell'ambito che hai impostato. Se i pacchetti raggiungono il descrittore ma nulla torna indietro, ispeziona protect() e il valore della rete sottostante prima di toccare di nuovo una rotta. Se il lato remoto segnala pacchetti malformati, il checksum e l'MTU sono i posti da guardare. Una sonda per livello risparmia un pomeriggio di congetture.

Articoli correlati