Android-VPN utan root: vem äger samtycket, rutterna och blockeringen
Samtycke, TUN-gränssnittet och systemets blockeringspolicy är tre olika tillstånd. Spåra vilken komponent som äger var och ett innan du granskar en Android-VPN-app.
Root är inte det som gör att en Android-VPN-app kan föra trafik, blockera läckor eller överleva en omstart. Folk sammanfattar allt detta som „appen har VPN-tillstånd“, vilket döljer tre separata mekanismer: ett samtycke från användaren, ett paketgränssnitt som systemet upprättar och en blockeringspolicy på plattformen som inte är appens att definiera. Att granska en Android-VPN-design innebär att fråga vilken av de tre ett påstående faktiskt vilar på, eftersom var och en har olika ägare och olika felmod.
Samtycket kommer genom en systemaktivitet
VpnService.prepare() ger ingenting. Den returnerar en intent till systemets samtyckesaktivitet när samtycke saknas, och appen startar dialogen så att användaren kan godkänna tjänsten. Att starta appens egen process, lägga till ett tillstånd i manifestet eller visa en egen „aktivera VPN“-skärm skapar inte tillståndet.
Tillståndet är också avgränsat. Det hör till ett paket i en användarprofil, så den personliga kopian och arbetskopian är separata beslut, och en enhetsägare kan förbereda en hanterad profil annorlunda än en personlig. Om dialogen aldrig visas och prepare() returnerar null finns samtycket redan för det paketet i den profilen; det är inte bevis för att appen är pålitlig, bara att tillståndet finns.
Gränssnittet är en begäran som systemet uppfyller
Efter samtycket beskriver appen vad den vill ha med VpnService.Builder: adresser, rutter, DNS-servrar, MTU och, där det stöds, apparna som ska inkluderas eller uteslutas. establish() ber systemet skapa tunnelgränssnittet och leda den matchande trafiken dit. Systemet äger routingbeslutet; appen tar emot fildeskriptorn och måste sedan göra något användbart med paketen.
| What you observe | Who actually owns it |
|---|---|
| VPN consent dialog | The system, triggered by prepare() |
| Routes, DNS and app lists | The app requests them; the system applies them |
| One active VPN per profile | The platform; a new service replaces the old one |
| Always-on and block-without-VPN | Device policy enforced by the platform |
Den tabellen är arkitekturen på ett ställe. Appen deltar i pakethanteringen; den äger inte gränssnittet, routingtabellen eller antalet tjänster som får köras.
Tunnelsocketen måste undkomma sin egen tunnel
Anslutningen som bär den krypterade trafiken är själv en socket. Om den routas tillbaka in i gränssnittet som appen just skapade beror tunneln på tunneln och stannar. VpnService.protect() binder socketen till det underliggande nätet så att den kringgår infångningen, och därför skyddar en korrekt klient sin yttre socket före den första handskakningen.
adb shell dumpsys connectivity | grep -i -A5 vpn
adb shell settings get global always_on_vpn_app
adb shell settings get global always_on_vpn_lockdown
De här kommandona visar det installerade VPN-nätet, paketet för alltid på och om låsningen är satt. Det är enbart läsande observationer: om värdena inte stämmer med det designen hävdar gäller påståendet appens gränssnitt och inte systemets tillstånd.
Blockeringen kan tillhöra plattformen
Android dokumenterar en policy som blockerar anslutningar som inte använder VPN. Den tillämpningen är ett plattformskontrakt, inte en app som upprepar anslutningsförsök i en loop. Brytaren finns i enhetens VPN-inställningar som Blockera anslutningar utan VPN, och på en hanterad enhet kan ägaren slå på den för hela profilen. Skillnaden spelar roll när du bedömer ett marknadspåstående: en klient kan visa en hänglåsikon och återansluta aggressivt medan direkt trafik fortfarande är möjlig, eller så kan täckta appar förbli offline eftersom systemet tillämpar låsningen.
Hänglåsikonen ritas av appen, så den bevisar bara att appen tror att den är ansluten. Om en annan app förlorar sin nätverksåtkomst avgörs av plattformen, så den användbara kontrollen är att se den appen misslyckas medan tunneln är uppe.
En „alltid på“-etikett från appbutiken ersätter inte heller det kontraktet. Alltid på och låsning är enhetsinställningar, ofta tillämpade av en ägare, och deras beteende för uteslutna appar definieras av plattformen. Läs inställningen, inte märket.
Fyra frågor för en designgranskning
För det första, vem samtyckte: vilket paket, vilken profil, och var det användaren eller en policy för hanterade enheter? För det andra, vad begärde appen: vilka rutter, vilka adressfamiljer, vilka inkluderade eller uteslutna appar? För det tredje, vad händer när tunneln inte är tillgänglig: blockerar systemet, eller faller trafiken tillbaka till det underliggande nätet? För det fjärde, vad gör appen med paketen den tar emot?
Den fjärde frågan är den som tillståndsmodellen inte kan besvara. Samtycke och ett TUN-gränssnitt bevisar att appen kan ta emot trafik, inte att den krypterar den, autentiserar en motpart eller återhämtar sig rent. Det är implementeringsegenskaper, och det är där en ärlig granskning lägger sin tid.
En onödig root-begäran blir inte legitim av att resultatet kallas kill switch, och en samtyckedialog är inte en säkerhetsrevision. Håll de tre tillstånden åtskilda och varje påstående blir kontrollerbart.