Split tunneling i Android VpnService: hva du faktisk kan rute
VpnService sender bare pakkene builderen legger i descriptor-en sin. Sjekk prefikser, proxygodkjenning, DNS og app-lister før du gir split tunneling skylden.
Split tunneling i en VpnService-app bryter sammen på noen få konkrete steder, og reparasjonen er forskjellig for hvert av dem. Klienten viser bare én destinasjon som feiler, så den første oppgaven er å finne ut hvilken grense du treffer: rutene builderen fikk, proxyen du ikke kan godkjenne, resolveren du bare annonserte, eller app-listen plattformen bruker per bruker. Nettopp derfor holder jeg en kontroll i fire trinn. Den går i rekkefølgen nedenfor og stopper ved første feil.
Bekreft hva builderen ga plattformen
VpnService.Builder er en deklarasjonsliste. establish() returnerer en ParcelFileDescriptor for et virtuelt grensesnitt, og plattformen sender bare trafikk som samsvarer med prefiksene fra addAddress og addRoute, inn i den descriptor-en; addDnsServer forteller klientene hvilken resolver de skal spørre. En destinasjon utenfor alle prefiksene du la til, når aldri appen din, uansett hva grensesnittet lover. Denne ene kjensgjerningen forklarer de fleste rapportene om brutt split tunneling før noen kode endres.
Logg adresse-, rute- og DNS-listene builderen din satte sammen, og sammenlign dem med destinasjonene som feiler. Et manglende /32 for en administrasjonsvert er langt vanligere enn en brutt tunnel. Er listen riktig, gå videre; er den feil, er resten av sjekklisten bortkastet arbeid.
excludeRoute finnes først fra API 33
Destinasjonsbasert utelukkelse er forespørselen som oftest blir en støttetråd. builder.excludeRoute(IpPrefix) kom i API 33; før det er den eneste støttede tilnærmingen å legge til ruter for alt unntatt det utelukkede prefikset, som betyr at du må beregne de komplementære prefiksene selv. De to kallene er ikke speilbilder: det ene trekker fra rutessettet til grensesnittet, det andre bygger settet fra bunnen, og en eldre enhet vil kaste eller stille ignorere et kall den ikke kjenner. Forgren på Build.VERSION.SDK_INT, og test begge veier på ekte maskinvare.
Når du beregner komplementer, følg med på prefiksjusteringen. En /24-utelukkelse inne i en /16-rute trenger de dekkende prefiksene som faktisk beskriver resten, ikke en enkelt feilformet /8. Logg den endelige listen, og bekreft at den utelukkede verten fortsatt løses opp via en vei utenfor tunnelen.
Proxy-legitimasjon kan ikke uttrykkes
setHttpProxy(ProxyInfo) peker grensesnittet mot en HTTP- eller SOCKS-proxy, og en PAC-URL kan velge mellom dem per destinasjon. Verken ProxyInfo eller PAC-formatet bærer brukernavn og passord, så en proxy som krever godkjenning, ligger utenfor plattformens API. Apper som trenger det, bygger vanligvis inn en userspace-stack, tun2socks eller en lignende motor, og svarer selv på 407-utfordringen.
Det valget har konsekvenser. En userspace-stack ser bare trafikk du ruter til den, så den må leve innenfor samme descriptorgrense, og den arver samme protect()-krav for sin egen uplink-socket. Hopper du over protect(), fanger tunnelen sin egen transport, noe som vises som et håndtrykk som stopper etter den første pakken. Test mot en proxy som krever legitimasjon, for en åpen proxy passerer på plattformens vei og beviser ingenting.
App-lister og DNS er også deklarasjoner
Builder har både addAllowedApplication og addDisallowedApplication, og de utelukker hverandre: å kalle begge kaster en feil. Uansett brukes listen per bruker ved oppsett av grensesnittet, så en app i arbeidsprofilen styres av arbeidsprofilens tunnel og ikke din. Det ingen API gir deg, er kontroll over en annen apps beslutning: du kan ikke presse en pakke inn i tunnelen din utenfra, og du kan ikke lukke eller omkonfigurere en VPN som allerede er aktiv.
DNS følger samme mønster. addDnsServer annonserer en resolveradresse; det lar ikke appen din svare på spørringer. Vil du løse opp navn selv, kjør en resolver og rut adressen dens inn i tunnelen. Apper som bruker DoT eller DoH mot et fast vertsnavn, kan omgå adressen du annonserte, så behandl navneoppslag som beste innsats med mindre du også fanger opp spørringsveien.
Manifest, always-on og firetrinnstesten
Kvalifisering deklareres i manifestet: BIND_VPN_SERVICE, intent-filteret android.net.VpnService og en forgrunnstjeneste hvis tunnelen må overleve i bakgrunnen. Always-on VPN krever i tillegg en ren onRevoke, fordi systemet kaller den når brukeren endrer innstillinger eller en annen app tar over kontrollen.
Kjør kontrollene i rekkefølge, og stopp ved første feil. Først: dump adresse-, rute- og DNS-listene som ble gitt til builderen. Deretter: bekreft at establish() returnerte en descriptor, og at transportsocketen er beskyttet. Tredje: be om én destinasjon inne i et inkludert prefiks og én inne i et utelukket prefiks. Fjerde: sjekk hvilken resolver som faktisk svarte, ved å spørre på et vertsnavn bare din resolver kan løse.
Et fullført håndtrykk beviser ingen av de senere trinnene. Hver kontroll isolerer ett lag, så en passering ved trinn to sier ingenting om trinn fire.
VpnService.Builder-referansen lister hele builder-flaten. Alt du trenger utover det, er en annen API, en annen app eller din egen userspace-stack, og det er en designbeslutning snarere enn en split-tunnel-feil.