Hindi mo matatanong ang Android kung konektado ang isang VPN. VpnService ay walang ibinubunyag na getter para sa kalagayan ng koneksyon, at ang icon ng susi sa status bar ay iginuguhit ng system, hindi ng anumang nababasa ng iyong app. Ang magagawa mo ay tanungin ang connectivity stack ng mas makitid na tanong: aling network ang ginagamit ng trapiko ng app na ito, at dinadala ba ng network na iyon ang transport ng VPN. Nilalakad ng gabay na ito ang mga pagsusuri sa pagkakasunod-sunod na umiiwas sa mga huwad na positibo, mula sa isang shell command sa device hanggang sa callback na iniiwan mong tumatakbo sa app.

Suriin ang device mula sa isang shell bago hawakan ang code ng app

Kumuha muna ng baseline. adb shell dumpsys connectivity ay naglilimbag ng bawat network na alam ng system, at inililista ng bawat block ang mga transport nito, kaya lumalabas ang isang network ng VPN na may VPN sa listahang iyon. Basahin muna ang block ng default na network bago ang anupaman, pagkatapos ay hanapin ang iba pang network na nagdadala ng parehong transport.

Dalawang output ang sumasagot sa magkaibang tanong. Ang pagkakaroon ng network ng VPN ay hindi kapareho ng paggamit ng iyong app dito, at sa isang device na may work profile o pangalawang user, parehong maaaring totoo nang sabay habang direktang nakikipag-usap ang iyong proseso sa internet. Ang linya ng aktibong network ang nagpapasya kung ano ang ginagawa ng iyong mga socket.

Itanong ang network na ginagamit ng app

Sa code, ang pagsubok ng kakayahan ay apat na linya. Humiling ng ACCESS_NETWORK_STATE sa manifest, pagkatapos ay ihambing ang mga kakayahan ng aktibong network sa transport ng 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);

Ang sagot ay sadyang makitid: ang default na network ba ng app ay VPN ngayon. Wala itong sinasabi tungkol sa kung aling VPN, kung iyo ba ito, o kung naaabot ng tunnel ang kabilang dulo. Ituring ang true bilang “lumabas ang trapiko sa isang tunnel” at ihiwalay ang sarili mong pagtatala. Ang boolean na tahimik na nangangahulugan ng tatlong bagay nang sabay ang dahilan kung bakit iniuulat ng screen ng settings na patay ang VPN habang nagba-browse ang user sa pamamagitan nito.

Kapag sa kabaligtaran kailangan mong malaman kung may anumang network ng VPN, ulitin sa cm.getAllNetworks() at subukan ang bawat set ng kakayahan. Nag-iiba ang dalawang sagot sa isang device na nagpapatakbo ng VPN na espesipiko sa app na sumasaklaw sa ibang aplikasyon ngunit hindi sa iyo, at ang pagkakaibang iyon ang pinagmumulan ng karamihan ng ulat ng bug na nagsasabing hindi nakikita ng app ang VPN na malinaw na nakabukas.

Subaybayan ang mga pagbabago sa halip na mag-poll

Ang pag-poll gamit ang timer ay maling hugis para sa tanong na ito, dahil dumarating ang mga pagbabago ng kalagayan bilang mga kaganapan. Magrehistro ng kahilingang tumutugma sa transport ng VPN at hayaang sabihin sa iyo ng framework kung kailan lumitaw, mawala o magbago ang isang tugmang network:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(request, callback);

Hindi kailanman dinadala ng network ng VPN ang NET_CAPABILITY_NOT_VPN, kaya ang tahasang pag-alis ay nagdodokumento ng layunin. Kung wala iyon ay tumutugma pa rin ang kahilingan, ngunit ang susunod na taong magbabasa ng code ay kailangang mag-isip nito mismo.

Ang pagpaparehistro ay kabilang sa saklaw na nangangailangan ng obserbasyon, karaniwang isang screen o isang serbisyong nasa harapan. Mag-unregister sa onStop o onDestroy, at huwag mag-iwan ng callback na nakabitin sa isang aktibidad na wala na. Ang app na walang nakikitang bahagi ay mahinang host para sa permanenteng tagamasid, at ang mga callbacks na nakalimutan mo ang siyang bumabagsak pagkatapos ng pagbabago ng configuration.

Ihiwalay ang tatlong tanong

Karamihan ng pagkalito sa larangang ito ay nagmumula sa pagtrato sa tatlong magkaibang pagsusuri bilang isa. Gumagamit sila ng magkaibang API, at bawat isa ay maaaring totoo habang ang iba ay mali.

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

Ang ikatlong hilera ang sinusubukang sagutin ng mga tao sa client, at hindi ito masasagot doon. Bigyan ang mga kliyente ng VPN ng sarili nilang pool ng address at basahin ang source address sa server; iyon ang tanging patunay na tumatagal kapag nagpalit ng tunnel ang user sa kalagitnaan ng session.

Pagkakasunod-sunod ng beripikasyon na nag-aalis ng mga huwad na positibo

Ang lumang halimbawang code na umiikot pa rin ay tumatawag sa getNetworkInfo(ConnectivityManager.TYPE_VPN). Ang tawag na iyon ay deprecated, at sa ilang release ay nagbabalik ito ng null kahit konektado ang VPN, kaya maraming kinopyang snippet ang mukhang sira sa device na gumagana. Iniiwasan ng mga pagsusuri ng kakayahan ang bitag na iyon at pareho ang ugali mula Android 5.0 pataas. Kung nagpapanatili ka ng codebase na tumatawag pa rin dito, palitan ang tawag sa halip na balutin ito sa fallback, dahil ang fallback ang gumawa ng maling sagot sa simula.

Isang shortcut ang dapat bigyan ng babala. Ang paghahambing ng pampublikong address na iniuulat ng panlabas na serbisyo sa exit address ng VPN ay may pinapatunayang isang bagay tungkol sa landas, ngunit nagdadagdag ito ng kahilingan ng ikatlong partido sa bawat pagsusuri, nakadepende ito sa pananatiling buhay ng serbisyong iyon, at maaaring i-cache o harangin. Panatilihin ito bilang manu-manong diagnostic, hindi kailanman bilang pagsusuri ng kalagayan sa loob ng app.

Patakbuhin ang mga pagsusuri sa ganitong pagkakasunod-sunod at huminto sa unang hindi sumasang-ayon sa ulat ng user: listahan ng transport mula sa shell, mga kakayahan ng aktibong network, mga kaganapan ng callback habang binubuksan at pinapatay mo ang tunnel, pagkatapos ay ang source address sa panig ng server. Kung pantay-pantay ang apat at mali pa rin ang ugali ng app, ang bug ay nasa sarili mong state machine, hindi sa pananaw ng Android sa network.

Kaugnay na mga artikulo