Tillatelseslisten i Android VpnService: derfor henger fortsatt tillatte sider
Tillatelseslisten din er antakelig i orden. Sider henger på grunn av DNS-svaret, rutetabellen, resolver-socketen eller MTU-en. Dette er rekkefølgen du bør sjekke i.
Du la et domene inn i tillatelseslisten, spørringen når appen din, matcheren svarer tillatt, og siden lastes likevel aldri. Der begynner folk flest å skrive om matcheren og legge inn jokertegnsammenligninger. Etter min erfaring er matcheren som oftest riktig, og feilen sitter ett nivå lenger ned: i DNS-svaret du bygde selv, i ruten du la inn, eller i socketen du brukte til å sende spørringen videre. Selve listen er den lette delen; det er pakkeveien som knekker tillatelseslister.
Matcheren er sjelden synderen
Det første du må skille er blokkert fra i stykker. Et blokkert navn skal feile raskt og synlig, fordi nettleseren får beskjed om at navnet ikke finnes og gir seg. Et navn som er i stykker, henger, prøver igjen over IPv6, faller tilbake til en annen transport og lar deg sitte med en snurrende indikator. Ser du en snurrende indikator, svarer tillatelseslisten din sannsynligvis noe klienten ikke får gjort noe med.
En tom A-post er den klassiske tabben. Å svare NOERROR med null svar ser ut som et gyldig svar, så stub-resolveren godtar det, nettleseren prøver igjen, prøver AAAA, prøver en annen resolver og gir opp lenge etterpå. Svarkode 3, altså NXDOMAIN, med spørsmålsdelen gjentatt og transaksjons-ID-en kopiert, avslutter samtalen umiddelbart. Bruk den på hvert navn du bevisst avviser.
IPv6 fortjener samme avgjørelse og ikke en standardverdi. Svarer du NXDOMAIN på A og slipper AAAA gjennom, kan klienten nå det blokkerte navnet over IPv6, og filteret lekker. Enkelte implementasjoner svarer blokkerte AAAA-spørringer med en loopback-adresse, slik at tilkoblingen feiler med en gang i stedet for å falle tilbake. Velg én policy, bruk den på begge posttypene, og logg begge.
En rute som fanger alt, eier alt
Et opplegg med bare DNS legger aldri til addRoute("0.0.0.0", 0), så bare spørringer kommer inn i TUN-grensesnittet, og alt annet fortsetter å bruke nettet under. Det er den trygge formen, og det forklarer også hvorfor enkelte tillatelseslister ser ut til å virke på Wi-Fi og feile på mobilnett.
I det øyeblikket en standardrute finnes, er hver strøm din. En TCP-pakke som kommer til grensesnittet og verken videresendes eller besvares, blir liggende i køen til klienten får tidsavbrudd, og det finnes ingen feil å logge fordi ingenting feilet. Jeg har sett noen bruke en hel ettermiddag på en ødelagt tillatelsesliste som egentlig var en manglende videresender for port 443. Logg protokoll og mål for hver pakke du velger å forkaste.
Resolver-socketen må ut av tunnelen
Videresending er der den neste fellen ligger. Resolvere hos operatøren bor ofte på en privat adresse som tunnelen ikke når, så spørringen du sender videre, går ingen steder, og appen rapporterer lesetidsavbrudd. Send godkjente spørringer til en resolver du vet er tilgjengelig, og gi den socketen til VpnService.protect() først. En ubeskyttet socket sender spørringen tilbake i din egen tunnel, der den når appen igjen og går i ring til tidsavbruddet slår til.
Feilen ser helt lik ut som et nettbrudd, og derfor overlever den så lenge i feilrapporter. Hold tellere for sendte spørringer og mottatte svar: hvis det første tallet stiger og det andre blir stående på null, ser du på socketen og ikke på listen.
Størrelse og kontrollsummer feiler uten melding
Stille dropp kommer også av størrelse. Et DNS-svar med et stort svarssett, eller en TCP-håndtrykk med rause valg, kan overskride en sti-MTU du aldri satte eksplisitt; fragmentet forsvinner oppover og klienten prøver enten på nytt med mindre eller slutter å prøve. Å sette grensesnittets MTU til 1280 er et forsiktig valg som overlever de fleste mobilnett, og å være eksplisitt er bedre enn å arve en standard ingen har målt.
Bygger du pakkene selv, er kontrollsummen den andre stille drapsmannen. En UDP-kontrollsum regnet ut over en pseudo-header som utelater UDP-lengden eller protokollfeltet, gir en pakke som ser riktig ut på din side og blir forkastet av mottakeren uten spor. Sammenlign pseudo-headeren din med spesifikasjonen felt for felt, og skriv ut den beregnede kontrollsummen for én kjent strøm mens du feilsøker.
Hva VpnService ikke gjør for deg
To grenser er verdt å kjenne før du designer rundt dem. Ingen vanlig app kan installere en systemvid proxy eller en PAC-fil utenfor Wi-Fi, og den delen har ikke endret seg. Veien per app har endret seg: før API 29 kunne VpnService.Builder ikke bære proxy i det hele tatt, og fra API 29 gir setHttpProxy(ProxyInfo.buildPacProxy(uri)) plattformen et PAC-skript å evaluere for VPN-nettet. Vurder regler i din egen kode når du retter deg mot eldre API-er eller når PAC-modellen ikke kan uttrykke regelen. UDP er heller ikke gratis: nettlesere sender bare TCP via en SOCKS-proxy, og de fleste SOCKS5-klienter implementerer aldri UDP ASSOCIATE, så et proxybasert design slipper eller lekker UDP mens TCP ser friskt ut.
Den andre grensen er grunnen til at en UDP-basert protokoll kan virke på et vanlig nett og feile i samme øyeblikk som VPN-en din kobler til. Skal tunnelen bære UDP, må videresenderen implementere videresendingsveien for det, ikke bare TCP-veien med en annen sockettype.
Sjekk i denne rekkefølgen
Gå gjennom listen i rekkefølge og stopp ved det første trinnet som feiler, fordi hver senere sjekk bare er støy til den forrige holder.
- List opp de installerte rutene og bekreft om pakker som ikke er DNS i det hele tatt kommer inn i TUN-grensesnittet.
- Logg spørringsnavn, beslutning og svarkode for ett blokkert og ett godkjent navn.
- Bekreft at resolver-socketen er beskyttet og at den valgte resolveren svarer fra innsiden av tunnelen.
- Bekreft grensesnittets MTU og kontrollsummen til ett svar du har bygd for hånd.
- Gjenta den samme siden på Wi-Fi og mobilnett, siden operatørresolvere og sti-MTU er forskjellige.
Holder alle fem og henger siden fortsatt, er den neste mistenkte videresenderen selv: sammenlign en fanget forespørsel med et fanget svar og se etter et manglende byttall eller en halvlukket socket. VpnService.Builder-referansen dekker kontraktene for rute, DNS og MTU, og protect() er et eget VpnService-kall og ikke et builder-valg; et tilkoblet grensesnitt betyr bare at den første kontrakten er oppfylt.