Ne, aplikace VpnService nedokáže zapnout VPN telefonu
Aplikace vlastní jeden tunel a jeho životní cyklus, ne přepínač VPN telefonu. Souhlas lze odvolat, najednou běží jen jedna VPN a pakety rozebíráte vy.
Mylný předpoklad je, že aplikace VpnService přepne systémový přepínač a od té chvíle je telefon chráněný. Ve skutečnosti se děje něco užšího: aplikace požádá o souhlas, vytvoří rozhraní, čte a zapisuje pakety přes deskriptor souboru a po zastavení rozhraní vrátí. Živý může být vždy jen jeden takový tunel a systém ho může bez ptaní odebrat. Většina matoucího chování, které lidé přičítají protokolu, pochází právě z tohoto modelu vlastnictví.
Berte každý přechod životního cyklu jako součást návrhu, ne jako okrajový případ.
Mýtus: aplikace zapíná VPN telefonu
Tok začíná u VpnService.prepare(), který vrací Intent, jejž musí uživatel přijmout, a pokračuje u establish(), který poté může stále selhat z důvodů, které aplikace neovládá. Navázání druhého tunelu deaktivuje první rozhraní a nahradí systémové oznámení. Nikde v této posloupnosti aplikace nevlastní přepínač pro celé zařízení; vlastní deskriptor, který jí byl přidělen, a pakety, které na něj dopadnou. I trvalé oznámení patří systému, ne návrhu aplikace.
Protože se rozhraní přiděluje, a ne zabírá, může jiný klient místo kdykoli zaujmout. Aplikace to zjistí přes zpětné volání, ne přes návratovou hodnotu.
Vynucené zastavení aplikace je vidět v oznamovacím panelu ze stejného důvodu: systém hlásí, co přidělil, ne co se aplikace rozhodla zobrazit.
Mýtus: souhlas se dává jednou
Když systém předá tunel jinému klientovi, pošle transakci odvolání po binderu, který základní třída vrací z onBind(). Aplikace, která tento binder nahradí vlastním, musí transakci odvolání předat sama, jinak VpnService.onRevoke() nikdy nezaběhne a služba dál zapisuje do deskriptoru, který už neexistuje. Schválení může odvolat i uživatel a systém může službu zastavit při restartu nebo při opětovném připojení v režimu stále zapnuto.
Řešit to je konkrétní práce, ne řádek v logu: přestaňte číst deskriptor, zavřete ho, vymažte příznak připojení a řekněte uživateli, v jakém stavu se nachází. Pak rozhodněte, zda má aplikace znovu navázat, znovu se zeptat, nebo počkat, protože v konfiguraci stále zapnuto může systém službu vrátit sám.
Aplikace, která odvolání bere jako výjimku, a ne jako stav, bude hlásit připojení, zatímco každý paket, který zapíše, se zahodí, což je nejhorší možný režim selhání pro nástroj soukromí.
Mýtus: rozhraní za vás filtruje provoz
Přichází surové IP. Zachytávání nebo filtrování znamená parsovat hlavičky IP, TCP, UDP a IPv6, sledovat toky a zapisovat odpovědi zpět do deskriptoru, což dělá ze zachytávací aplikace malý síťový stack, ne systémovou funkci. protect() existuje proto, aby vlastní transportní socket aplikace obešel její tunel, místo aby zachytával sám sebe, a vrací booleovskou hodnotu, která si zaslouží kontrolu u každého socketu vytvořeného poté, co tunel běží.
Seznamy povolených a zakázaných aplikací v builderu určují, čí provoz do rozhraní vstoupí. Neposkytují přístup k datům za IP pakety, které rozhraní nese, a žádné API neodhaluje tunel jiného klienta ani neuvádí systémové profily VPN. Síťové možnosti mohou říci, že síť je transport VPN, ale ne čí je to tunel.
Proto otázky na stav jiných aplikací dostávají protichůdné odpovědi: informace prostě není vašemu procesu vystavena.
Co životní cyklus skutečně vyžaduje
Sledujte stavy, které ovládáte: připraveno, navázáno, odvoláno, zastaveno. Opětovné připojení má být rozebrání a pak navázání, s jediným vlastníkem deskriptoru a transportního socketu, protože dvě komponenty, které se předhánějí v navazování, se poperou o jediné volné místo. Spouštění na pozadí je v moderním Androidu omezené, takže broadcast při startu není záruka; použijte službu v popředí s jejím oznámením tam, kde to úroveň API dovolí.
Zaznamenávejte přechod, ne jen konečný stav, a držte stavový stroj dost malý, abyste otestovali každou cestu. Stav, do kterého se v testu nedostanete, je stav, se kterým se v terénu setkáte bez logu.
Otestujte životní cyklus na zařízení
Emulátory umí předstírat připojení, ale ne cestu souhlasu a odvolání přesvědčivě, proto je spouštějte na skutečném zařízení s nainstalovaným druhým VPN klientem, který místo ukradne.
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
Spusťte tunel, potvrďte oznámení a rozhraní, pak připojte druhý klient a sledujte, zda se spustí zpětné volání odvolání a zda se stav vyčistí. Vynuceně zastavte aplikaci a zkontrolujte, zda něco stále hlásí živý deskriptor.
Zopakujte posloupnost po restartu v režimu stále zapnuto, protože tato cesta se liší od připojení zahájeného uživatelem. Nehledáte zelenou ikonu, ale důkaz, že aplikace přestala používat deskriptor, který už nevlastnila.