Як праверыць у кодзе, ці падключаны VPN на Android
У Android няма гетэра стану VPN. Запытвайце магчымасці сеткі, сачыце за зменамі транспарту і трымайце асобна згоду праграмы, стан прылады і доказ сервера.
Вы не можаце спытаць у Android, ці падключаны VPN. VpnService не прадастаўляе гетэра стану, а значок ключа ў радку стану малюе сістэма, а не нешта, што можа прачытаць ваша праграма. Што вы можаце зрабіць — задаць стэку злучанасці больш вузкае пытанне: якую сетку выкарыстоўвае трафік гэтай праграмы і ці нясе гэтая сетка транспарт VPN. Гэты дапаможнік праходзіць праверкі ў парадку, які пазбягае ілжывых станоўчых вынікаў, ад каманды shell на прыладзе да callback, які вы пакідаеце працаваць у праграме.
Праверце прыладу з shell перад тым, як кранаць код праграмы
Спачатку атрымайце базавую лінію. adb shell dumpsys connectivity друкуе кожную сетку, вядомую сістэме, і кожны блок пералічвае свае транспарты, таму сетка VPN з'яўляецца з VPN у гэтым спісе. Прачытайце блок сеткі па змаўчанні перад усім астатнім, затым шукайце іншыя сеткі, якія нясуць той жа транспарт.
Два вынікі адказваюць на розныя пытанні. Існаванне сеткі VPN не тое ж самае, што яе выкарыстанне вашай праграмай, а на прыладзе з працоўным профілем або другім карыстальнікам абодва могуць быць праўдай адначасова, пакуль ваш працэс звяртаецца да інтэрнэту напрамую. Радок актыўнай сеткі — гэта тое, што вызначае паводзіны вашых сокетаў.
Запрасіце сетку, якую выкарыстоўвае праграма
У кодзе праверка магчымасцей — гэта чатыры радкі. Запрасіце ACCESS_NETWORK_STATE у маніфесце, затым параўнайце магчымасці актыўнай сеткі з транспартам VPN:
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Адказ наўмысна вузкі: ці з'яўляецца сетка па змаўчанні праграмы VPN прама зараз. Ён нічога не кажа пра тое, які гэта VPN, ці належыць ён вам і ці дасягае тунель свайго далёкага канца. Разглядайце true як «трафік выходзіць праз нейкі тунель» і трымайце свой улік асобна. Лагічнае значэнне, якое маўкліва азначае тры рэчы адначасова, — гэта тое, як экран налад у рэшце рэшт паказвае VPN як выключаны, пакуль карыстальнік праглядае праз яго.
Калі замест гэтага вам трэба ведаць, ці прысутнічае любая сетка VPN, перабірайце cm.getAllNetworks() і правярайце кожны набор магчымасцей. Два адказы разыходзяцца на прыладзе з VPN для асобных праграм, які ахоплівае іншыя праграмы, але не вашу, і гэта разыходжанне — крыніца большасці паведамленняў пра памылкі, якія кажуць, што праграма не бачыць VPN, які відавочна ўключаны.
Сачыце за зменамі замест апытання
Апытанне па таймеры — няправільная форма для гэтага пытання, бо змены стану прыходзяць як падзеі. Зарэгіструйце запыт, які адпавядае транспарту VPN, і няхай фрэймворк паведаміць вам, калі адпаведная сетка з'явіцца, знікне або зменіцца:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(request, callback);
Сетка VPN ніколі не нясе NET_CAPABILITY_NOT_VPN, таму яўнае выдаленне дакументуе намер. Без яго запыт усё яшчэ адпавядае, але наступны чалавек, які чытае код, мусіць дадумацца да гэтага сам.
Рэгістрацыя належыць абсягу, якому патрэбна назіранне, звычайна экрану або службе пярэдняга плана. Адмяніце рэгістрацыю ў onStop або onDestroy і не пакідайце callback прывязаным да актыўнасці, якой ужо няма. Праграма без бачнага кампанента — дрэнны гаспадар для пастаяннага назіральніка, а callbacks, якія вы забылі, — гэта тыя, што падаюць пасля змены канфігурацыі.
Трымайце тры пытанні асобна
Большая частка блытаніны ў гэтай галіне паходзіць ад таго, што тры розныя праверкі разглядаюць як адну. Яны выкарыстоўваюць розныя API, і кожная можа быць праўдай, пакуль іншыя хлуслівыя.
| Question | How you answer it | What a yes means |
|---|---|---|
| Did the user grant VPN consent to my app? | VpnService.prepare() returns null | Consent exists; the tunnel may still be down |
| Is the device sending default traffic through a VPN? | TRANSPORT_VPN on the active network | Something is tunneling, not necessarily your app |
| Did this request arrive through the VPN? | Source address seen by the server | Proof for that connection, nothing wider |
Трэці радок — гэта тое, што людзі спрабуюць адказаць на кліенце, і там адказаць на яго немагчыма. Дайце VPN-кліентам свой пул адрасоў і чытайце адрас крыніцы на серверы; гэта адзіны доказ, які вытрымлівае пераключэнне тунеляў карыстальніком пасярод сеансу.
Парадак праверкі, які выключае ілжывыя станоўчыя вынікі
Стары прыкладны код, які яшчэ ў абарачэнні, выклікае getNetworkInfo(ConnectivityManager.TYPE_VPN). Гэты выклік састарэлы, і ў некалькіх выпусках ён вяртаў null нават пры падключаным VPN, таму шмат скапіяваных фрагментаў выглядаюць зламанымі на прыладзе, якая працуе. Праверкі магчымасцей пазбягаюць пасткі і паводзяць сябе аднолькава пачынаючы з Android 5.0. Калі вы падтрымліваеце кодавую базу, якая ўсё яшчэ выклікае яго, заменіце выклік, а не абгортвайце яго ў запасны варыянт, бо менавіта запасны варыянт і спарадзіў няправільны адказ.
Адзін кароткі шлях варты папярэджання. Параўнанне публічнага адраса, які паведамляе знешні сэрвіс, з адрасам выхаду VPN сапраўды даказвае нешта пра шлях, але яно дадае запыт да трэцяга боку ў кожную праверку, залежыць ад таго, што гэты сэрвіс працуе, і можа быць кэшаваным або заблакаваным. Трымайце яго як ручную дыягностыку, ніколі як праверку стану ўнутры праграмы.
Выконвайце праверкі ў гэтым парадку і спыніцеся на першай, якая супярэчыць справаздачы карыстальніка: спіс транспартаў у shell, магчымасці актыўнай сеткі, падзеі callback, пакуль вы перамыкаеце тунель, затым адрас крыніцы на баку сервера. Калі ўсе чатыры супадаюць, а праграма ўсё яшчэ паводзіць сябе няправільна, памылка ў вашай уласнай машыне стану, а не ва ўяўленні Android пра сетку.