A VPN do Android funciona em IPv6, mas o navegador continua escolhendo IPv4
Percorra as quatro etapas em que um endereço IPv6 privado desaparece: o resolvedor, a declaração do VpnService, a verificação de candidatos do navegador e a entrega da rota.
A VPN está ativa, a interface web privada responde por IPv6 a partir de um portátil, e o telemóvel Android continua a abrir a mesma página por IPv4 ou recusa carregá-la de todo. O registo AAAA existe. O túnel comunica um endereço IPv6. Nada no cliente parece errado. Quatro decisões separadas determinam o resultado: que resolvedor respondeu, o que o VpnService declarou ao Android, que família de endereços o navegador estava disposto a experimentar e se um pacote consegue fazer a viagem de ida e volta. Trate-as nessa ordem em vez de acrescentar uma rota predefinida e esperar que o sintoma se desloque.
Passo 1: prove que resolvedor respondeu à consulta AAAA
O Android não tem um único caminho DNS. Uma VPN pode empurrar um servidor DNS para a sua rede, o sistema pode usar Private DNS para os nomes de anfitrião que considerar elegíveis, e o Chrome pode executar o seu próprio resolvedor cifrado a partir de chrome://settings/security. Num telemóvel em que o túnel empurra 10.61.0.1 mas o Private DNS aponta para um fornecedor público, o nome dentro da sua zona privada não é resolvido pela máquina que detém a zona. O navegador recebe uma resposta de um resolvedor sem autoridade sobre esse nome e trata-a como ausente, e o texto do erro nunca menciona DNS.
Pergunte ao resolvedor do túnel diretamente antes de mudar mais alguma coisa. Os dois primeiros comandos mostram que servidores DNS o Android associou à rede ativa e se o Private DNS está em modo automático ou estrito; o terceiro é executado a partir de qualquer cliente dentro do túnel, não do telemóvel.
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: leia o que o VpnService declarou de facto
O lado do Android é explícito e não inferido. Um VpnService.Builder acrescenta endereços com addAddress, rotas com addRoute, servidores DNS com addDnsServer, e pode restringir todo o túnel a uma única família de endereços com allowFamily. Um construtor que chamou allowFamily(AF_INET) e depois acrescentou uma rota IPv6 disse ao sistema duas coisas contraditórias, e a restrição de família é o que o sistema mantém. Uma rota que cobre fd00:61::/64 combinada com um endereço de interface noutro prefixo falha por uma razão semelhante: o endereço existe, mas não está on-link para essa rota.
Leia a declaração em vez de confiar no interruptor da aplicação. dumpsys netd imprime os endereços e rotas associados à interface tun, e o LinkProperties por rede dentro de dumpsys connectivity mostra a lista DNS ao lado das famílias que a rede reivindica.
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 a interface tun tiver um endereço IPv6 mas nenhuma rota corresponder ao prefixo do serviço, a entrega de IPv6 já está quebrada antes de qualquer navegador entrar em cena. É um erro diferente do de um navegador que se recusa a tentar, e exige uma correção diferente.
Passo 3: o navegador decide se tenta IPv6
O Chromium não usa todos os registos AAAA que recebe. O seu resolvedor de anfitriões transporta verificações de alcançabilidade IPv6 e filtragem de candidatos que podem descartar ou baixar a prioridade do IPv6 quando a rede é considerada incapaz de o transportar, e o código-fonte do resolvedor mostra essa sonda juntamente com as condições em redor. A lógica depende da versão e das flags, e é exatamente por isso que dois telemóveis no mesmo túnel se podem comportar de forma diferente. Assim que existem candidatos, o Happy Eyeballs faz competir as famílias por cada ligação, pelo que um caminho IPv6 que responde devagar ainda pode perder para IPv4 que responde depressa.
Observe a decisão em vez de a inferir. Uma sessão gravada através de chrome://net-export e reproduzida no visualizador netlog mostra cada resolução e a família escolhida; chrome://net-internals/#dns mostra os resultados em cache. Uma resolução que devolve apenas registos A para um nome que tem AAAA no seu próprio resolvedor aponta diretamente para esta etapa.
Passo 4: verifique que um pacote consegue fazer a viagem de ida e volta
A seleção de endereço não é entrega. O Android mantém tabelas de encaminhamento e regras de política por rede que escolhem uma tabela por fwmark, pelo que uma rota visível na tabela principal não é automaticamente a rota usada por um socket. Ler as regras e pedir ao kernel que resolva um destino para um endereço de origem separa as duas coisas.
adb shell ip -6 rule show
adb shell ip -6 route get fd00:61::20 from fd00:61::10
É a saída de route get que responde à pergunta. Se indicar tun0 e os pacotes continuarem a morrer, passe para a MTU do túnel e para o encaminhamento IPv6 da gateway em vez de voltar ao telemóvel.
Porque acrescentar ::/0 costuma piorar as coisas
Acrescentar ::/0 altera a reivindicação do túnel de um prefixo para todos os destinos IPv6. No telemóvel isso pode satisfazer uma sonda de alcançabilidade, porque um endereço de teste fora do seu prefixo passa a parecer encaminhável. Não cria capacidade IPv6 ascendente na gateway, e muitas gateways domésticas não têm qualquer ligação ascendente IPv6 a funcionar. O resultado é um buraco negro para o IPv6 público: nomes que antes recuavam para IPv4 ficam agora pendurados, e a página privada pode parecer melhor só porque a sonda mede outra coisa.
Também há um custo de política. Com ::/0 no túnel, todo o tráfego IPv6 sai pela VPN, incluindo tráfego que o proprietário da rede nunca aceitou transportar. Trate isso como uma decisão de implementação com um responsável nomeado, não como um passo de depuração tomado à 1 da manhã.
A ordem de trabalho que termina com isto
Confirme que a resposta AAAA vem do resolvedor que detém a zona, leia o estado do VpnService e do netd para a interface tun, capture um rasto net-export para ver se o Chromium descartou o candidato, e depois teste a viagem de ida e volta com ip -6 route get. Corrija a camada que realmente falhou e deixe a política de separação em paz. Se os nomes privados precisam de respostas diferentes dos públicos, é para isso que existe o split tunneling.
Anote a compilação do telemóvel, a versão do navegador e o resolvedor que consultou. Esses três factos transformam "IPv6 is broken" em algo que a próxima pessoa consegue reproduzir.