Android-VPN:et kör IPv6 men webbläsaren väljer ändå IPv4
Följ de fyra stegen där en privat IPv6-adress försvinner: resolvern, VpnService-deklarationen, webbläsarens kandidatkontroll och routedleveransen.
VPN:et är uppe, det privata webbgränssnittet svarar över IPv6 från en laptop, och Android-telefonen öppnar fortfarande samma sida över IPv4 eller vägrar ladda den alls. AAAA-posten finns. Tunneln rapporterar en IPv6-adress. Inget i klienten ser fel ut. Fyra separata beslut avgör resultatet: vilken resolver som svarade, vad VpnService deklarerade för Android, vilken adressfamilj webbläsaren var villig att prova och om ett paket klarar resan fram och tillbaka. Ta dem i den ordningen i stället för att lägga till en standardrutt och hoppas att symtomet flyttar sig.
Steg 1: bevisa vilken resolver som svarade på AAAA-frågan
Android har inte en enda DNS-väg. Ett VPN kan trycka ut en DNS-server för sitt nät, systemet kan använda Private DNS för värdnamn som det anser kvalificerade, och Chrome kan köra sin egen krypterade resolver från chrome://settings/security. På en telefon där tunneln trycker ut 10.61.0.1 men Private DNS pekar på en offentlig leverantör, slås namnet i din privata zon inte upp av maskinen som äger zonen. Webbläsaren får ett svar från en resolver utan behörighet över namnet och behandlar det som saknat, och feltexten nämner aldrig DNS.
Fråga tunnelns resolver direkt innan du ändrar något annat. De två första kommandona visar vilka DNS-servrar Android kopplade till det aktiva nätverket och om Private DNS är i automatiskt eller strikt läge; det tredje körs från vilken klient som helst inne i tunneln, inte från telefonen.
adb shell dumpsys connectivity | grep -A3 "DnsAddresses"
adb shell settings get global private_dns_mode
nslookup -type=AAAA nas.lan 10.61.0.1
Steg 2: läs vad VpnService faktiskt deklarerade
Androidsidan är explicit och inte härledd. En VpnService.Builder lägger till adresser med addAddress, rutter med addRoute, DNS-servrar med addDnsServer, och kan begränsa hela tunneln till en adressfamilj med allowFamily. En byggare som anropade allowFamily(AF_INET) och senare lade till en IPv6-rutt har sagt två motstridiga saker till systemet, och det är familjebegränsningen systemet behåller. En rutt som täcker fd00:61::/64 kombinerat med en gränssnittsadress i ett annat prefix misslyckas av en liknande anledning: adressen finns, men den är inte on-link för den rutten.
Läs deklarationen i stället för att lita på reglaget i appen. dumpsys netd skriver ut adresserna och rutterna som är kopplade till tun-gränssnittet, och LinkProperties per nätverk i dumpsys connectivity visar DNS-listan bredvid de familjer nätverket hävdar.
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"
Om tun-gränssnittet har en IPv6-adress men ingen rutt matchar tjänstens prefix är IPv6-leveransen redan trasig innan en webbläsare är inblandad. Det är en annan bugg än en webbläsare som vägrar prova, och den kräver en annan åtgärd.
Steg 3: webbläsaren avgör om den alls ska prova IPv6
Chromium använder inte varje AAAA-post det får. Dess värdresolver har IPv6-räckviddstester och kandidatfiltrering som kan släppa eller nedprioritera IPv6 när nätverket bedöms oförmöget att bära det, och resolverns källkod visar den kontrollen tillsammans med villkoren runt den. Logiken beror på version och flaggor, och det är just därför två telefoner på samma tunnel kan bete sig olika. När kandidaterna finns låter Happy Eyeballs familjerna tävla per anslutning, så en IPv6-väg som svarar långsamt kan fortfarande förlora mot IPv4 som svarar snabbt.
Observera beslutet i stället för att härleda det. En session inspelad via chrome://net-export och uppspelad i netlog-visaren visar varje uppslagning och den valda familjen; chrome://net-internals/#dns visar de cachade resultaten. En uppslagning som bara returnerar A-poster för ett namn som har AAAA hos din egen resolver pekar rakt på det här steget.
Steg 4: verifiera att ett paket klarar resan fram och tillbaka
Adressval är inte leverans. Android håller ruttabeller och policyregler per nätverk som väljer tabell efter fwmark, så en rutt som syns i huvudtabellen är inte automatiskt den rutt en socket använder. Att läsa reglerna och be kärnan slå upp en destination för en källadress skiljer de två.
adb shell ip -6 rule show
adb shell ip -6 route get fd00:61::20 from fd00:61::10
Det är utdata från route get som svarar på frågan. Om den namnger tun0 och paketen fortfarande dör, gå till tunnelns MTU och gatewayens IPv6-vidarebefordran i stället för tillbaka till telefonen.
Varför det att lägga till ::/0 oftast gör det värre
Att lägga till ::/0 ändrar tunnelns anspråk från ett prefix till varje IPv6-destination. På telefonen kan det uppfylla ett räckviddstest, eftersom en testadress utanför ditt prefix nu ser rutbar ut. Det skapar ingen IPv6-uppströmskapacitet på gatewayen, och många hemgatewayer har ingen fungerande IPv6-uppströmslänk alls. Resultatet är ett svart hål för offentlig IPv6: namn som förut föll tillbaka till IPv4 hänger nu, och den privata sidan kan se bättre ut bara för att testet mäter något annat.
Det finns också en politisk kostnad. Med ::/0 i tunneln går all IPv6-trafik ut genom VPN:et, inklusive trafik som nätägaren aldrig gått med på att bära. Behandla det som ett utrullningsbeslut med en namngiven ägare, inte som ett felsökningssteg du tar klockan 1 på natten.
Arbetsordningen som avslutar det här
Bekräfta att AAAA-svaret kommer från resolvern som äger zonen, läs VpnService- och netd-status för tun-gränssnittet, spela in ett net-export-spår för att se om Chromium släppte kandidaten, och testa sedan resan fram och tillbaka med ip -6 route get. Rätta lagret som faktiskt fallerade och lämna delningspolicyn i fred. Om privata namn behöver andra svar än offentliga är det precis vad split tunneling är till för.
Anteckna telefonens build, webbläsarens version och resolvern du frågade. De tre fakta gör "IPv6 is broken" till något nästa person kan återskapa.