Toegestane lijst van Android VpnService: waarom toegestane pagina's toch blijven hangen
Je toegestane lijst is waarschijnlijk in orde. Pagina's blijven hangen door het DNS-antwoord, de routetabel, de resolversocket of de MTU. Dit is de volgorde om te controleren.
Je hebt een domein aan de toegestane lijst toegevoegd, de query bereikt je app, de matcher geeft toegestaan terug, en de pagina laadt nog steeds nooit. Op dat punt beginnen de meeste mensen de matcher te herschrijven en wildcard-vergelijkingen toe te voegen. In mijn ervaring is de matcher meestal correct, en zit de fout een laag dieper: in het DNS-antwoord dat je hebt gesynthetiseerd, in de route die je hebt geïnstalleerd, of in de socket die je gebruikte om de query door te sturen. De lijst is het makkelijke deel; het pakketpad is waar toegestane lijsten breken.
De matcher is zelden de schuldige
Het eerste dat je moet scheiden is geblokkeerd van kapot. Een geblokkeerde naam hoort snel en zichtbaar te mislukken, omdat de browser een antwoord krijgt dat de naam niet bestaat en stopt. Een kapotte naam blijft hangen, probeert opnieuw via IPv6, valt terug op een ander transport en laat je achter met een draaiende indicator. Zie je een draaiende indicator, dan antwoordt je toegestane lijst waarschijnlijk met iets waar de client niets mee kan.
Een leeg A-record is de klassieke fout. NOERROR teruggeven met nul antwoorden lijkt een geldig antwoord, dus de stub-resolver accepteert het, de browser probeert opnieuw, probeert AAAA, probeert een andere resolver en geeft veel later op. Een antwoordcode 3, oftewel NXDOMAIN, met de vraag-sectie herhaald en het transactie-ID gekopieerd, beëindigt het gesprek onmiddellijk. Gebruik die voor elke naam die je bewust weigert.
IPv6 verdient dezelfde beslissing in plaats van een standaardwaarde. Als je NXDOMAIN antwoordt voor A en AAAA doorlaat, kan de client de geblokkeerde naam via IPv6 bereiken en lekt het filter. Sommige implementaties beantwoorden geblokkeerde AAAA-query's met een loopback-adres zodat de verbinding meteen mislukt in plaats van terug te vallen. Kies één beleid, pas het toe op beide recordtypen en log beide.
Een route die alles vangt, bezit alles
Een ontwerp met alleen DNS voegt nooit addRoute("0.0.0.0", 0) toe, dus alleen query's komen de TUN-interface binnen en al het andere blijft het onderliggende netwerk gebruiken. Dat is de veilige vorm, en het verklaart ook waarom sommige toegestane lijsten op Wi-Fi lijken te werken en op mobiel netwerk mislukken.
Op het moment dat er een standaardroute bestaat, is elke stroom van jou. Een TCP-pakket dat bij de interface aankomt en niet wordt doorgestuurd of beantwoord, blijft in de wachtrij tot de client een time-out krijgt, en er is geen fout om te loggen omdat er niets misging. Ik zag iemand een middag besteden aan een kapotte toegestane lijst die eigenlijk een ontbrekende doorstuurder voor poort 443 was. Log het protocol en de bestemming van elk pakket dat je besluit te laten vallen.
De resolversocket moet de tunnel verlaten
Doorsturen is waar de volgende valkuil zit. Resolvers van de provider staan vaak op een privéadres dat de tunnel niet bereikt, dus de query die je doorstuurt gaat nergens heen en de app meldt een lees-time-out. Stuur goedgekeurde query's naar een resolver waarvan je weet dat die bereikbaar is, en geef die socket eerst aan VpnService.protect(). Een onbeschermde socket stuurt de query terug je eigen tunnel in, waar hij opnieuw bij de app aankomt en rondjes draait tot de time-out afgaat.
De fout ziet er identiek uit als een netwerkstoring, en daarom overleeft hij zo lang in bugrapporten. Houd tellers bij voor verzonden query's en ontvangen antwoorden: als het eerste getal stijgt en het tweede op nul blijft, kijk je naar de socket, niet naar de lijst.
Grootte en checksums falen zonder bericht
Stille drops komen ook van grootte. Een DNS-antwoord met een grote antwoordset, of een TCP-handshake met royale opties, kan een pad-MTU overschrijden die je nooit expliciet hebt ingesteld; het fragment verdwijnt stroomopwaarts en de client probeert het ofwel kleiner opnieuw of stopt met proberen. De interface-MTU op 1280 zetten is een conservatieve keuze die de meeste mobiele paden overleeft, en expliciet zijn is beter dan een standaard erven die niemand heeft gemeten.
Als je de pakketten zelf bouwt, is de checksum de andere stille moordenaar. Een UDP-checksum berekend over een pseudo-header die de UDP-lengte of het protocolveld weglaat, levert een pakket op dat aan jouw kant goed gevormd lijkt en door de ontvanger spoorloos wordt weggegooid. Vergelijk je pseudo-header veld voor veld met de specificatie, en print de berekende checksum voor één bekend stroom terwijl je debugt.
Wat een VpnService niet voor je doet
Twee grenzen zijn de moeite waard om te kennen voordat je eromheen ontwerpt. Geen gewone app kan buiten Wi-Fi een systeembrede proxy of PAC-bestand installeren, en dat deel is niet veranderd. Het pad per app wel: vóór API 29 kon VpnService.Builder helemaal geen proxy dragen, en vanaf API 29 geeft setHttpProxy(ProxyInfo.buildPacProxy(uri)) het platform een PAC-script om te evalueren voor het VPN-netwerk. Evalueer regels in je eigen code wanneer je oudere API's target of wanneer het PAC-model de regel niet kan uitdrukken. UDP is ook niet gratis: browsers sturen alleen TCP via een SOCKS-proxy, en de meeste SOCKS5-clients implementeren nooit UDP ASSOCIATE, dus een proxy-gebaseerd ontwerp laat UDP vallen of lekken terwijl TCP er gezond uitziet.
Die tweede grens is de reden dat een UDP-gebaseerd protocol op een gewoon netwerk kan werken en faalt op het moment dat je VPN verbindt. Als de tunnel UDP moet dragen, moet de doorstuurder het doorstuurpad daarvoor implementeren, niet alleen het TCP-pad met een ander sockettype.
Controleer in deze volgorde
Werk de lijst in volgorde af en stop bij de eerste stap die faalt, want elke latere controle is ruis tot de eerdere standhoudt.
- List de geïnstalleerde routes op en bevestig of niet-DNS-pakketten überhaupt de TUN-interface binnenkomen.
- Log de querynaam, de beslissing en de antwoordcode voor één geblokkeerde en één goedgekeurde naam.
- Bevestig dat de resolversocket beschermd is en dat de gekozen resolver van binnenuit de tunnel antwoordt.
- Bevestig de interface-MTU en de checksum van één met de hand gebouwd antwoord.
- Herhaal dezelfde pagina op Wi-Fi en mobiel netwerk, omdat providerresolvers en pad-MTU verschillen.
Als alle vijf standhouden en de pagina nog steeds hangt, is de volgende verdachte de doorstuurder zelf: vergelijk een opgevangen verzoek met een opgevangen antwoord en zoek naar een ontbrekend byteaantal of een half gesloten socket. De VpnService.Builder-referentie behandelt de contracten voor route, DNS en MTU, en protect() is een aparte VpnService-aanroep en geen builder-optie; een verbonden interface betekent alleen dat het eerste contract is vervuld.