Fire antakelser om Android VpnService kostet meg en gang mesteparten av en uke, og ingen av dem handlet om nettverksprotokollen. Appen sa tilkoblet, nøkkelikonet satt i statuslinjen, og trafikken gikk fortsatt ut over den vanlige ruten. Jeg fortsatte å anta at tunnelen var min til jeg stoppet den, at rutene jeg la til var rutene jeg fikk, og at et kall til stopSelf ville ta grensesnittet ned med seg. Tjenesten oppførte seg nøyaktig som dokumentert. Min mentale modell var den ødelagte delen.

Myte 1: tunnelen blir oppe til du stopper den

Android holder ett VPN-grensesnitt om gangen. Sett opp et nummer to, og plattformen setter det ikke i kø: den deaktiverer ditt og kaller onRevoke() på tjenesten din. Hvis koden din bare river ned ved et knappetrykk, etterlater en overtakelse en deskriptor og en lesetråd, og neste oppstart arver begge.

VpnService-referansen sier uttrykkelig at prepare() returnerer null når appen allerede har samtykke, og en Intent når brukeren fortsatt må godkjenne. Det samtykket kommer bare frem hvis du leverer intensjonen gjennom startActivityForResult(). Hopp over overleveringen, og establish() returnerer null, som leses som en stille feil snarere enn et tillatelsesproblem.

Always-on VPN legger til en eier du ikke kontrollerer. Systemet kan hente tunnelen tilbake etter oppstart eller etter en krasj, og det fortrenger en VPN-app brukeren startet for hånd. Bygg onRevoke() slik at den lukker de samme ressursene som din vanlige nedstenging, og behandle den som en primær stoppvei i stedet for et hjørnetilfelle. Brukere bytter VPN-app; koden din må tåle det.

Myte 2: addRoute fanger nøyaktig det du listet opp

Rutene avgjør hvilke pakker som når deskriptoren din, og en feil i omfanget gir det vanvittige tilfellet der noe av trafikken går gjennom tunnelen og noe ikke. addRoute("0.0.0.0", 0) tar hele IPv4, noe som betyr at hver pakke nå ankommer hos deg og må videresendes eller droppes. En /32 for én enkelt vert tar nesten ingenting, og alt annet oppfører seg som om VPN-en aldri ble installert.

Familiene telles hver for seg. En rute kun for IPv4 lar IPv6 bli på det underliggende nettverket, så en dobbeltstakket vert kan fortsatt slå opp og koble til utenfor tunnelen mens den ser perfekt tilkoblet ut inni.

DNS følger samme logikk. Servere lagt til med addDnsServer() går til apper som bruker tunnelens resolver, mens en app med eget kryptert DNS-endepunkt eller Private DNS satt til et vertsnavn ignorerer dem helt. Når navn feiler for én app og virker for en annen, sjekk resolverstien før du rører en eneste rute.

Myte 3: regler per app er en bryter du kan vippe begge veier

addDisallowedApplication() og addAllowedApplication() er to moduser, ikke to filtre. Byggeren godtar én modell per tunnel, så en app som vil ha alt unntatt tre pakker bruker den forbudte listen og lar den tillatte listen være tom. De to er gjensidig utelukkende, og folk finner det ut ved kjøring etter at innstillingsskjermen allerede har tilbudt begge bryterne.

Pakkesynlighet biter også. På Android 11 og nyere kan du bare navngi pakker du kan se, så en utelukkelsesliste bygget på en spørring appen din ikke har lov til å kjøre, vil stille miste oppføringer. Sammenlign listen med det pakkebehandleren faktisk returnerer før du stoler på den.

Arbeidsprofiler deler publikum igjen. En tunnel startet inne i profilen dekker den profilen og appene dens, mens den personlige siden beholder sin egen tilkobling. Det er en omfangsbeslutning som tas én gang, og den hører i grensesnittet, ikke i en støttebillett.

Myte 4: stopSelf avslutter tunnelen

Det gjør den ikke. Grensesnittet lever i deskriptoren som establish() returnerer, og tjenesteobjektet kan dø mens den deskriptoren forblir åpen. Lukk ParcelFileDescriptor først, slipp referansen, join eller avbryt lese- og skrivetrådene, og kall deretter stopForeground(true) og stopSelf() i den rekkefølgen.

En glemt deskriptor er den vanlige grunnen til at en tjeneste ser ut til å overleve sin egen stoppforespørsel, og den forklarer en utdatert rute som lever lenger enn appen som la den til.

Den motsatte feilen er like vanlig: å lukke deskriptoren mens en lesetråd er midt i en lesing, og deretter la den tråden håndtere et unntak ingen har skrevet håndtering for. Hold nedstengingssekvensen på ett sted, og la både brukerhandlingen og onRevoke() kalle inn i den.

Hva du bør sjekke når tunnelen ser død ut

Beskytte langlivede sockets med protect() før tunnelen kommer opp. Uten det blir tunnelens egen transport rutet tilbake i tunnelen, og forbindelsen stopper på en måte som ser nøyaktig ut som en serverfeil. Test mot en adresse som bare finnes bak det eksterne nettverket, ikke et offentlig nettsted som svarer fra hurtigbufferen eller fra den underliggende veien.

Logg resultatet av establish(), rutene, de utelukkede pakkene og årsaken til nedstengingen. De fire linjene skiller et samtykkeproblem fra et omfangsproblem og fra et livssyklusproblem uten pakkefangst.

Relaterte artikler