Android VpnService-tillatelsesliste: hvorfor tillatte sider fortsatt henger
Tillatelseslisten din er sannsynligvis i orden. Sider henger på grunn av DNS-svaret, rutetabellen, resolver-socketen eller MTU-en. Her er rekkefølgen å sjekke i.
Du la til et domene i tillatelseslisten, spørringen når appen din, matcheren returnerer tillatt, og siden lastes fortsatt aldri. På det punktet begynner de fleste å skrive om matcheren og legge til jokertegnsammenligninger. Etter min erfaring er matcheren som regel korrekt, og feilen ligger ett lag lenger ned: i DNS-svaret du syntetiserte, i ruten du installerte, eller i socketen du brukte for å videresende spørringen. Listen er den enkle delen; pakkestien er der tillatelseslister brytes.
Matcheren er sjelden feilen
Det første som må skilles, er blokkert fra ødelagt. Et blokkert navn bør feile raskt og synlig, fordi nettleseren får et svar om at navnet ikke finnes og stopper. Et ødelagt navn henger, prøver på nytt over IPv6, faller tilbake til en annen transport og lar deg sitte igjen med en snurrende indikator. Hvis du ser en snurrende indikator, svarer tillatelseslisten din sannsynligvis noe klienten ikke kan bruke.
En tom A-post er den klassiske feilen. Å returnere 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 mye senere. En svarkode 3, altså NXDOMAIN, med spørsmålsdelen gjentatt og transaksjons-ID-en kopiert, avslutter samtalen umiddelbart. Bruk den for hvert navn du bevisst avviser.
IPv6 fortjener samme beslutning i stedet for en standardverdi. Hvis du svarer NXDOMAIN på A og slipper AAAA gjennom, kan klienten nå det blokkerte navnet over IPv6, og filteret lekker. Noen implementasjoner svarer på 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 design 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 det underliggende nettet. Det er den trygge formen, og det forklarer også hvorfor noen tillatelseslister ser ut til å virke på Wi-Fi og feile på mobilnettet.
I det øyeblikket en standardrute finnes, er hver strøm din. En TCP-pakke som ankommer grensesnittet og verken videresendes eller besvares, blir liggende i køen til klienten timer ut, og det er ingen feil å logge fordi ingenting feilet. Jeg har sett noen bruke en ettermiddag på en ødelagt tillatelsesliste som i virkeligheten var en manglende videresender for port 443. Logg protokollen og målet for hver pakke du bestemmer deg for å forkaste.
Resolver-socketen må forlate tunnelen
Videresending er der den neste fellen sitter. Operatørresolvere bor ofte på en privat adresse som tunnelen ikke når, så spørringen du videresender 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 sirkulerer til tidsavbruddet utløses.
Feilen ser identisk ut med et nettverksutfall, 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, ikke listen.
Størrelse og kontrollsummer feiler uten melding
Stille dropp kommer også fra størrelse. Et DNS-svar med et stort svarssett, eller et TCP-håndtrykk med romslige valg, kan overskride en sti-MTU du aldri satte eksplisitt; fragmentet forsvinner oppstrøms, og klienten prøver enten på nytt med mindre eller slutter å prøve. Å sette grensesnittets MTU til 1280 er et konservativt valg som overlever de fleste mobilnettstier, og å være eksplisitt er bedre enn å arve en standard ingen har målt.
Hvis du bygger pakkene selv, er kontrollsummen den andre stille morderen. En UDP-kontrollsum beregnet over en pseudo-header som utelater UDP-lengden eller protokollfeltet, gir en pakke som ser velformet ut hos deg og forkastes 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 en VpnService ikke gjør for deg
To grenser er verdt å kjenne før du designer rundt dem. Ingen vanlig app kan installere en systemomfattende proxy eller PAC-fil utenfor Wi-Fi, og den delen har ikke endret seg. Stien per app har det: 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. Evaluer 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 gjennom en SOCKS-proxy, og de fleste SOCKS5-klienter implementerer aldri UDP ASSOCIATE, så et proxybasert design dropper eller lekker UDP mens TCP ser sunt ut.
Den andre grensen er grunnen til at en UDP-basert protokoll kan virke på et vanlig nett og feile i det øyeblikket VPN-en din kobler til. Hvis tunnelen forventes å bære UDP, må videresenderen implementere videresendingsstien for det, ikke bare TCP-stien med en annen sockettype.
Sjekk i denne rekkefølgen
Arbeid deg gjennom listen i rekkefølge og stopp ved det første trinnet som feiler, fordi hver senere sjekk er støy til den forrige holder.
- List opp de installerte rutene og bekreft om ikke-DNS-pakker i det hele tatt kommer inn i TUN-grensesnittet.
- Logg spørringsnavnet, beslutningen og svarkoden 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 bygde for hånd.
- Gjenta den samme siden på Wi-Fi og mobilnett, fordi operatørresolvere og sti-MTU er forskjellige.
Hvis alle fem holder og siden fortsatt henger, er den neste mistenkte videresenderen selv: sammenlign en fanget forespørsel med et fanget svar og se etter et manglende byteantall 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-alternativ; et tilkoblet grensesnitt betyr bare at den første kontrakten er oppfylt.