Det felaktiga antagandet är att en VpnService-app vänder en systembrytare och att telefonen från det ögonblicket är skyddad. Det som faktiskt händer är snävare: appen begär samtycke, bygger ett gränssnitt, läser och skriver paket genom en filbeskrivning, och lämnar tillbaka gränssnittet när den stannar. Bara en sådan tunnel kan vara levande åt gången, och systemet kan ta den utan att fråga. Det mesta av det förvirrande beteende som folk skyller på protokollet kommer från den ägarmodellen.

Behandla varje livscykelövergång som en del av designen och inte som ett randfall.

Myt: appen slår på telefonens VPN

Flödet börjar med VpnService.prepare(), som returnerar ett Intent som användaren måste acceptera, och fortsätter med establish(), som fortfarande kan misslyckas efteråt av skäl som appen inte kontrollerar. Att upprätta en andra tunnel avaktiverar det första gränssnittet och ersätter systemaviseringen. Ingenstans i den sekvensen äger appen en enhetsomfattande brytare; den äger beskrivningen den fick och paketen som landar på den. Även den beständiga aviseringen tillhör systemet och inte appens design.

Eftersom gränssnittet ges och inte grips kan en annan klient ta platsen när som helst. Appen får veta det genom en återkallelse, inte genom ett returvärde.

Att appen tvångsstoppas syns i aviseringspanelen av samma skäl: systemet rapporterar vad det har gett, inte vad appen valde att visa.

Myt: samtycket ges en gång

När systemet överlämnar tunneln till en annan klient skickar det en återkallelsetransaktion på bindern som basklassen returnerar från onBind(). En app som ersätter den bindern med sin egen måste själv vidarebefordra återkallelsetransaktionen, annars utlöses aldrig VpnService.onRevoke() och tjänsten fortsätter skriva till en beskrivning som inte längre finns. Godkännandet kan också dras tillbaka av användaren, och systemet kan stoppa tjänsten under en omstart eller en alltid-på-återanslutning.

Att hantera det är konkret arbete och inte en logglinje: sluta läsa beskrivningen, stäng den, rensa ansluten-flaggan och berätta för användaren vilket tillstånd hen är i. Bestäm sedan om appen ska återupprätta, fråga igen eller vänta, för i en alltid-på-konfiguration kan systemet ta tillbaka tjänsten själv.

En app som behandlar återkallelse som ett undantag och inte som ett tillstånd kommer att rapportera ansluten medan varje paket den skriver kastas, och det är det sämsta möjliga felmoduset för ett integritetsverktyg.

Myt: gränssnittet filtrerar trafik åt dig

Det som kommer in är rå IP. Att fånga eller filtrera innebär att tolka IP-, TCP-, UDP- och IPv6-huvuden, följa flöden och skriva svar tillbaka till beskrivningen, vilket gör en fångstapp till en liten nätverksstack och inte till en systemfunktion. protect() finns för att appens egen transportssocket ska gå förbi sin tunnel i stället för att fånga sig själv, och den returnerar ett booleskt värde som förtjänar en kontroll på varje socket som skapas efter att tunneln är uppe.

Byggarens listor över tillåtna och förbjudna appar avgör vems trafik som kommer in i gränssnittet. De ger ingen åtkomst till nyttolast bortom de IP-paket som gränssnittet bär, och inget API exponerar en annan klients tunnel eller listar systemets VPN-profiler. Nätverksfunktioner kan säga att ett nätverk är VPN-transport, men inte vems tunnel det är.

Därför får statusfrågor om andra appar motstridiga svar: informationen är helt enkelt inte exponerad för din process.

Vad livscykeln faktiskt kräver

Följ de tillstånd du styr: förberedd, upprättad, återkallad, stoppad. Återanslutning bör vara nedrivning följt av upprättande, med en enda ägare av beskrivningen och transportssocketen, eftersom två komponenter som tävlar om att upprätta kommer att slåss om den enda lediga platsen. Bakgrundsstarter är begränsade på modern Android, så en uppstartsbroadcast är ingen garanti; använd en förgrundstjänst med dess avisering där API-nivån tillåter det.

Logga övergången och inte bara sluttillståndet, och håll tillståndsmaskinen liten nog att testa varje väg. Ett tillstånd du inte kan nå i ett test är ett tillstånd du möter i fält utan en logg.

Testa livscykeln på en enhet

Emulatorer kan fejka anslutning men inte samtyckes- och återkallelsevägen övertygande, så kör dessa på en riktig enhet med en andra VPN-klient installerad för att stjäla platsen.

adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp

Starta tunneln, bekräfta aviseringen och gränssnittet, anslut sedan den andra klienten och se om återkallelse-callbacken körs och tillståndet rensas. Tvångsstoppa appen och kontrollera om något fortfarande rapporterar en levande beskrivning.

Upprepa sekvensen efter en alltid-på-omstart, eftersom den vägen skiljer sig från en användarstartad anslutning. Det du letar efter är inte en grön ikon utan bevis för att appen slutade använda en beskrivning som den inte längre ägde.

Relaterade artiklar