Не, праграма 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, таму шырокавяшчальнае паведамленне пры загрузцы не з'яўляецца гарантыяй; выкарыстоўвайце службу пярэдняга плана з яе апавяшчэннем там, дзе ўзровень 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
Запусціце тунель, пацвердзіце апавяшчэнне і інтэрфейс, затым падключыце іншы кліент і паглядзіце, ці выконваецца зваротны выклік адклікання і ці ачышчаецца стан. Прымусова спыніце праграму і праверце, ці нешта ўсё яшчэ паведамляе пра жывы дэскрыптар.
Паўтарыце паслядоўнасць пасля перазапуску ў рэжыме пастаяннай працы, бо гэты шлях адрозніваецца ад злучэння, ініцыяванага карыстальнікам. Вы шукаеце не зялёную іконку, а доказ таго, што праграма перастала выкарыстоўваць дэскрыптар, якім больш не валодае.