Vi ne povas demandi al Android, ĉu VPN estas konektita. VpnService ne elmetas getter por konekto, kaj la ŝlosila ikono en la statobreto estas desegnata de la sistemo, ne de io, kion via programo povas legi. Tio, kion vi povas fari, estas demandi al la konekteca stako pli mallarĝan demandon: kiun reton uzas la trafiko de ĉi tiu programo, kaj ĉu tiu reto portas la transporton VPN. Ĉi tiu gvidilo trairas la kontrolojn laŭ la ordo, kiu evitas falsajn pozitivojn, de la ŝela komando sur la aparato ĝis la callback, kiun vi lasas kuranta en la programo.

Kontrolu la aparaton el ŝelo antaŭ ol tuŝi la programan kodon

Unue akiru bazlinion. adb shell dumpsys connectivity presas ĉiun reton, kiun la sistemo konas, kaj ĉiu bloko listigas siajn transportojn, do VPN-reto aperas kun VPN en tiu listo. Legu la blokon de la implicita reto antaŭ ĉio alia, poste serĉu aliajn retojn, kiuj portas la saman transporton.

Du eliroj respondas malsamajn demandojn. Ĉu ekzistas VPN-reto ne samas kiel ĉu via programo uzas ĝin, kaj sur aparato kun labora profilo aŭ dua uzanto ambaŭ povas esti veraj samtempe, dum via procezo parolas rekte kun la interreto. La linio de la aktiva reto estas tiu, kiu decidas, kion faras viaj sokloj.

Demandu la reton, kiun uzas la programo

En kodo la kapabla testo estas kvar linioj. Petu ACCESS_NETWORK_STATE en la manifesto, poste komparu la kapablojn de la aktiva reto kun la transporto 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);

La respondo estas intence mallarĝa: ĉu la implicita reto de la programo estas VPN ĝuste nun. Ĝi diras nenion pri tio, kiu VPN ĝi estas, ĉu ĝi apartenas al vi, aŭ ĉu la tunelo atingas sian malproksiman finon. Traktu true kiel “trafiko eliras tra ia tunelo” kaj tenu vian propran kontadon aparte. Bulea valoro, kiu silente signifas tri aferojn samtempe, estas la kialo, ke agorda ekrano finas raporti la VPN-on kiel malŝaltita dum la uzanto foliumas tra ĝi.

Kiam anstataŭe vi bezonas scii, ĉu ia VPN-reto ĉeestas, ripetu super cm.getAllNetworks() kaj testu ĉiun kapablan aron. La du respondoj diverĝas sur aparato, kiu rulas VPN-on por unuopa programo, kiu kovras aliajn programojn sed ne vian, kaj tiu diverĝo estas la fonto de plej multaj cimraportoj, kiuj diras, ke la programo ne vidas VPN-on, kiu estas evidente ŝaltita.

Observu ŝanĝojn anstataŭ enketi

Enketado laŭ tempigilo estas la malĝusta formo por ĉi tiu demando, ĉar statŝanĝoj alvenas kiel eventoj. Registru peton, kiu kongruas kun la transporto VPN, kaj lasu la kadron diri al vi, kiam kongrua reto aperas, malaperas aŭ ŝanĝiĝas:

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

VPN-reto neniam portas NET_CAPABILITY_NOT_VPN, do la eksplicita forigo dokumentas la intencon. Sen ĝi la peto ankoraŭ kongruas, sed la sekva persono, kiu legas la kodon, devas mem tion ellabori.

La registrado apartenas al la amplekso, kiu bezonas la observadon, kutime ekrano aŭ fona servo. Malregistru en onStop aŭ onDestroy, kaj ne lasu callback alligitan al aktiveco, kiu jam foriris. Programo sen videbla komponanto estas malbona gastiganto por konstanta observanto, kaj la callback-oj, kiujn vi forgesas, estas tiuj, kiuj kraŝas post agorda ŝanĝo.

Tenu tri demandojn aparte

Plej multo de la konfuzo en ĉi tiu kampo venas de traktado de tri malsamaj kontroloj kiel unu. Ili uzas malsamajn API-ojn, kaj ĉiu povas esti vera dum la aliaj estas malveraj.

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

La tria vico estas tiu, kiun homoj provas respondi ĉe la kliento, kaj ĝi ne povas esti respondita tie. Donu al VPN-klientoj sian propran adresaron kaj legu la fontadreson en la servilo; tio estas la sola pruvo, kiu postvivas, kiam uzanto ŝanĝas tunelon meze de seanco.

Kontrola ordo, kiu ekskludas falsajn pozitivojn

Pli malnova ekzempla kodo ankoraŭ cirkulanta vokas getNetworkInfo(ConnectivityManager.TYPE_VPN). Tiu voko estas eksdata, kaj en pluraj eldonoj ĝi redonis null eĉ kiam VPN estis konektita, tial tiom multaj kopiitaj fragmentoj aspektas rompitaj sur aparato, kiu funkcias. Kapablaj kontroloj evitas la kaptilon kaj kondutas same ekde Android 5.0. Se vi prizorgas kodaron, kiu ankoraŭ vokas ĝin, anstataŭigu la vokon anstataŭ envolvi ĝin en rezerva vojo, ĉar la rezerva vojo estis tio, kio produktis la malĝustan respondon.

Unu fuŝsolvo meritas averton. Kompari la publikan adreson raportitan de ekstera servo kun la elira adreso de la VPN ja pruvas ion pri la vojo, sed ĝi aldonas peton al tria partio al ĉiu kontrolo, dependas de tio, ke tiu servo restu funkcianta, kaj povas esti kaŝmemorigita aŭ blokita. Tenu ĝin kiel permana diagnozo, neniam kiel la statkontrolo ene de la programo.

Rulu la kontrolojn laŭ ĉi tiu ordo kaj haltu ĉe la unua, kiu malakordas kun la raporto de la uzanto: transporta listo en ŝelo, kapabloj de la aktiva reto, callback-eventoj dum vi ŝaltas kaj malŝaltas la tunelon, poste la fontadreso ĉe la servila flanko. Se ĉiuj kvar kongruas kaj la programo ankoraŭ miskondutas, la cimo estas en via propra statmaŝino, ne en la vido de Android pri la reto.

Rilataj artikoloj