Не, приложение VpnService не може да включи VPN на телефона
Приложението притежава един тунел и неговия жизнен цикъл, а не превключвателя за VPN на телефона. Съгласието може да бъде оттеглено, само един VPN работи в даден момент, а пакетите са за разбор от вас.
Погрешното допускане е, че приложение VpnService щраква системен превключвател и оттам нататък телефонът е защитен. Всъщност случващото се е по-тясно: приложението иска съгласие, изгражда интерфейс, чете и пише пакети през файлов дескриптор и връща интерфейса, когато спре. Само един такъв тунел може да е жив в даден момент и системата може да го отнеме, без да пита. Повечето объркващо поведение, което хората приписват на протокола, идва точно от този модел на собственост.
Третирайте всеки преход на жизнения цикъл като част от дизайна, а не като краен случай.
Мит: приложението включва VPN на телефона
Потокът започва с VpnService.prepare(), който връща Intent, който потребителят трябва да приеме, и продължава с establish(), който след това все още може да се провали по причини, които приложението не контролира. Установяването на втори тунел деактивира първия интерфейс и заменя системното известие. Никъде в тази последователност приложението не притежава превключвател за цялото устройство; то притежава дескриптора, който му е даден, и пакетите, които попадат в него. Дори постоянното известие принадлежи на системата, а не на дизайна на приложението.
Тъй като интерфейсът се дава, а не се заграбва, друг клиент може да заеме мястото по всяко време. Приложението научава това чрез обратно извикване, а не чрез върната стойност.
Принудителното спиране на приложението се вижда в панела с известия по същата причина: системата съобщава какво е дала, а не какво е избрало да покаже приложението.
Мит: съгласието се дава веднъж
Когато системата предаде тунела на друг клиент, тя изпраща транзакция за отнемане по binder, който базовият клас връща от onBind(). Приложение, което замени този binder със свой, трябва само да препрати транзакцията за отнемане, иначе VpnService.onRevoke() никога не се задейства и услугата продължава да пише в дескриптор, който вече не съществува. Одобрението може да бъде оттеглено и от потребителя, а системата може да спре услугата при рестарт или при повторно свързване в режим винаги включен.
Справянето с това е конкретна работа, а не ред в дневника: спрете четенето на дескриптора, затворете го, изчистете флага за свързаност и кажете на потребителя в какво състояние се намира. След това решете дали приложението да установи отново, да попита пак или да изчака, защото при конфигурация винаги включен системата може сама да върне услугата.
Приложение, което третира отнемането като изключение, а не като състояние, ще съобщава за свързаност, докато всеки записан от него пакет се изхвърля, което е най-лошият възможен режим на отказ за инструмент за поверителност.
Мит: интерфейсът филтрира трафика вместо вас
Това, което пристига, е суров IP. Прихващането или филтрирането означава разбор на заглавките на IP, TCP, UDP и IPv6, проследяване на потоци и запис на отговори обратно в дескриптора, което прави приложението за прихващане малък мрежов стек, а не системно средство. protect() съществува, за да може собственият транспортен сокет на приложението да заобикаля своя тунел, вместо да прихваща себе си, и връща булева стойност, която заслужава проверка при всеки сокет, създаден след вдигането на тунела.
Списъците с разрешени и забранени приложения в конструктора определят чий трафик влиза в интерфейса. Те не дават достъп до полезния товар отвъд IP пакетите, които интерфейсът носи, и нито един API не разкрива тунела на друг клиент или изброява VPN профилите на системата. Мрежовите възможности могат да кажат, че дадена мрежа е VPN транспорт, но не и чий е тунелът.
Затова въпросите за състоянието на други приложения получават противоречиви отговори: информацията просто не се показва на вашия процес.
Какво всъщност изисква жизненият цикъл
Проследявайте състоянията, които контролирате: подготвено, установено, отнето, спряно. Повторното свързване трябва да е разглобяване, последвано от установяване, с един собственик на дескриптора и транспортния сокет, защото два компонента, които се състезават да установят, ще се бият за единственото свободно място. Фоновите стартирания са ограничени в съвременния Android, така че broadcast при зареждане не е гаранция; използвайте foreground услуга с нейното известие, където нивото на API го позволява.
Записвайте прехода, а не само крайното състояние, и дръжте машината на състоянията достатъчно малка, за да тествате всеки път. Състояние, до което не можете да стигнете в тест, е състояние, което ще срещнете на терен без лог.
Тествайте жизнения цикъл на устройство
Емулаторите могат да имитират свързаност, но не и пътя на съгласието и отнемането убедително, затова ги пускайте на реално устройство с инсталиран втори VPN клиент, който да заеме мястото.
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
Стартирайте тунела, потвърдете известието и интерфейса, след което свържете другия клиент и наблюдавайте дали обратното извикване за отнемане се изпълнява и дали състоянието се изчиства. Спрете приложението принудително и проверете дали нещо все още съобщава за жив дескриптор.
Повторете последователността след рестарт в режим винаги включен, защото този път се различава от свързване, започнато от потребителя. Това, което търсите, не е зелена икона, а доказателство, че приложението е спряло да използва дескриптор, който вече не притежава.