La VPN Android funziona in IPv6, ma il browser sceglie ancora IPv4
Seguite le quattro fasi in cui un indirizzo IPv6 privato scompare: il resolver, la dichiarazione VpnService, il controllo dei candidati del browser e la consegna della rotta.
La VPN è attiva, l'interfaccia web privata risponde in IPv6 da un portatile, e il telefono Android apre ancora la stessa pagina in IPv4 o si rifiuta di caricarla del tutto. Il record AAAA esiste. Il tunnel riporta un indirizzo IPv6. Nulla nel client sembra sbagliato. Quattro decisioni separate determinano l'esito: quale resolver ha risposto, che cosa ha dichiarato il VpnService ad Android, quale famiglia di indirizzi il browser era disposto a provare e se un pacchetto può fare il viaggio di andata e ritorno. Affrontatele in questo ordine invece di aggiungere una rotta predefinita e sperare che il sintomo si sposti.
Passo 1: dimostrate quale resolver ha risposto alla query AAAA
Android non ha un unico percorso DNS. Una VPN può spingere un server DNS per la sua rete, il sistema può usare Private DNS per i nomi host che ritiene idonei, e Chrome può eseguire il proprio resolver cifrato da chrome://settings/security. Su un telefono in cui il tunnel spinge 10.61.0.1 ma Private DNS punta a un provider pubblico, il nome dentro la vostra zona privata non viene risolto dalla macchina che possiede la zona. Il browser ottiene una risposta da un resolver senza autorità su quel nome e la tratta come assente, e il testo dell'errore non menziona mai DNS.
Interrogate direttamente il resolver del tunnel prima di cambiare qualsiasi altra cosa. I primi due comandi mostrano quali server DNS Android ha collegato alla rete attiva e se Private DNS è in modalità automatica o rigorosa; il terzo viene eseguito da qualsiasi client dentro il tunnel, non dal telefono.
adb shell dumpsys connectivity | grep -A3 "DnsAddresses"
adb shell settings get global private_dns_mode
nslookup -type=AAAA nas.lan 10.61.0.1
Passo 2: leggete che cosa ha dichiarato davvero il VpnService
Il lato Android è esplicito, non dedotto. Un VpnService.Builder aggiunge indirizzi con addAddress, rotte con addRoute, server DNS con addDnsServer, e può limitare l'intero tunnel a una sola famiglia di indirizzi con allowFamily. Un builder che ha chiamato allowFamily(AF_INET) e poi ha aggiunto una rotta IPv6 ha detto al sistema due cose contraddittorie, e la restrizione di famiglia è ciò che il sistema conserva. Una rotta che copre fd00:61::/64 combinata con un indirizzo di interfaccia in un altro prefisso fallisce per una ragione simile: l'indirizzo esiste, ma non è on-link per quella rotta.
Leggete la dichiarazione invece di fidarvi dell'interruttore nell'app. dumpsys netd stampa gli indirizzi e le rotte associate all'interfaccia tun, e il LinkProperties per rete dentro dumpsys connectivity mostra l'elenco DNS accanto alle famiglie che la rete dichiara.
adb shell ip -6 addr show tun0
adb shell ip -6 route show table all | grep tun0
adb shell dumpsys connectivity | grep -E "LinkAddresses|Routes"
Se l'interfaccia tun ha un indirizzo IPv6 ma nessuna rotta corrisponde al prefisso del servizio, la consegna IPv6 è già rotta prima che entri in gioco un browser. È un bug diverso da quello di un browser che si rifiuta di provare, e richiede una correzione diversa.
Passo 3: il browser decide se tentare IPv6
Chromium non usa ogni record AAAA che riceve. Il suo resolver degli host porta controlli di raggiungibilità IPv6 e filtri dei candidati che possono scartare o abbassare la priorità di IPv6 quando la rete è giudicata incapace di trasportarlo, e il codice sorgente del resolver mostra quella sonda insieme alle condizioni che la circondano. La logica dipende dalla versione e dai flag, ed è esattamente per questo che due telefoni sullo stesso tunnel possono comportarsi diversamente. Una volta che esistono candidati, Happy Eyeballs fa gareggiare le famiglie per ogni connessione, quindi un percorso IPv6 che risponde lentamente può ancora perdere contro IPv4 che risponde in fretta.
Osservate la decisione invece di dedurla. Una sessione registrata tramite chrome://net-export e riprodotta nel visualizzatore netlog mostra ogni risoluzione e la famiglia scelta; chrome://net-internals/#dns mostra i risultati in cache. Una risoluzione che restituisce solo record A per un nome che ha AAAA sul vostro resolver indica direttamente questa fase.
Passo 4: verificate che un pacchetto possa fare il viaggio di andata e ritorno
La selezione dell'indirizzo non è consegna. Android mantiene tabelle di routing e regole di policy per rete che scelgono una tabella per fwmark, quindi una rotta visibile nella tabella principale non è automaticamente quella usata da un socket. Leggere le regole e chiedere al kernel di risolvere una destinazione per un indirizzo sorgente separa le due cose.
adb shell ip -6 rule show
adb shell ip -6 route get fd00:61::20 from fd00:61::10
È l'output di route get che risponde alla domanda. Se nomina tun0 e i pacchetti muoiono ancora, passate all'MTU del tunnel e all'inoltro IPv6 del gateway invece di tornare al telefono.
Perché aggiungere ::/0 di solito peggiora le cose
Aggiungere ::/0 cambia la pretesa del tunnel da un prefisso a ogni destinazione IPv6. Sul telefono questo può soddisfare una sonda di raggiungibilità, perché un indirizzo di test fuori dal vostro prefisso ora sembra instradabile. Non crea capacità IPv6 a monte sul gateway, e molti gateway domestici non hanno alcun uplink IPv6 funzionante. Il risultato è un buco nero per l'IPv6 pubblico: nomi che prima ricadevano su IPv4 ora restano appesi, e la pagina privata può sembrare migliore solo perché la sonda misura qualcos'altro.
C'è anche un costo di policy. Con ::/0 nel tunnel, tutto il traffico IPv6 esce dalla VPN, compreso il traffico che il proprietario della rete non ha mai accettato di trasportare. Trattatelo come una decisione di distribuzione con un responsabile nominato, non come un passo di debug che si fa all'1 di notte.
L'ordine di lavoro che chiude la questione
Confermate che la risposta AAAA arriva dal resolver che detiene la zona, leggete lo stato di VpnService e netd per l'interfaccia tun, catturate una traccia net-export per vedere se Chromium ha scartato il candidato, poi provate il viaggio di andata e ritorno con ip -6 route get. Correggete lo strato che ha davvero fallito e lasciate in pace la policy di separazione. Se i nomi privati hanno bisogno di risposte diverse da quelli pubblici, è esattamente a questo che serve lo split tunneling.
Annotate la build del telefono, la versione del browser e il resolver interrogato. Questi tre fatti trasformano «IPv6 is broken» in qualcosa che la persona successiva può riprodurre.