Patikra, kurią dauguma Android programėlių naudoja VPN aptikti, yra sąsajos pavadinimas, ir ji niekada nebuvo VPN patikra. Ji praneša, kad egzistuoja kokia nors sąsaja su kūrėjo laukta eilute, o tai viename įrenginyje reiškia OpenVPN, kitame – visiškai nesusijusį dalyką. ping yra toks pat įrodymas: jis įrodo, kad vienas tikslas atsakė, o ne kad tunelis pernešė paketą. Tai, ką platforma iš tikrųjų atskleidžia, yra tinklų rinkinys ir transportai, kuriuos kiekvienas jų deklaruoja, ir tai yra kitas klausimas su kitu atsakymu.

Mitas: jei yra tun0, VPN įjungta

Android VPN programėlės yra VpnService įgyvendinimai, kurie sistemai perduoda tun failo deskriptorių. Sąsajos pavadinimas kyla iš to, kas ją sukūrė: OpenVPN paprastai sukuria tun0, PPTP ir L2TP sukuria ppp0, WireGuard pavadina sąsają tunelio vardu, o IPsec tunelis gali iš viso neturėti įprasto pavadinimo. Todėl /proc/net/dev skaitymas ar NetworkInterface objektų išvardijimas ieško susitarimo, o ne fakto.

Abi klaidos kryptys gyvos. Įrenginyje gali būti tun0 vietiniam tuneliui, kuris neturi nieko bendra su naudotojo VPN, o visiškai sveikas WireGuard tunelis gali būti nematomas patikrai, kuri žino tik eilutę tun0. Klaidingi teigiami ir klaidingi neigiami rezultatai toje pačioje funkcijoje daro ją netinkamą diagnostikai, nes palaikymo bilietui, kuriame sakoma „VPN nerasta“, negalima pasitikėti nė viena kryptimi.

Prieiga taip pat susiaurėjo. Naujesnės Android versijos riboja, ką įprasta programėlė gali skaityti iš /proc, todėl pavadinimu pagrįsta patikra, kuri veikė Android 8, vėlesniame įrenginyje gali grąžinti tuščią sąrašą be jokių kodo pakeitimų. Kai patikra tyliai nepavyksta atviros pusės link, programėlė daro išvadą, kad VPN nėra toje aparatinėje įrangoje, kurioje tunelis veikia tinkamai.

Mitas: sėkmingas ping reiškia, kad tunelis perduoda srautą

ping į vidinį hostą pavyksta visada, kai egzistuoja maršrutas į tą hostą. Esant skaidytam tuneliui, hostas gali būti pasiekiamas per Wi-Fi, kol tunelis įjungtas ir daro ką nors visiškai kito. Esant VPN pagal programėlę, programėlė, kuri atlieka ping, gali būti pašalinta iš tunelio, todėl ICMP atgarsis išeina už jo ribų ir grįžta, o tai įrodo priešingai, nei mano patikra.

ICMP ir TCP taip pat filtruojami atskirai. Tunelis, kuris leidžia atgarsio užklausas, bet atmeta TCP prievadą, kurio reikia paslaugai, praeis ping testą ir nepavyks programėlėje, o tai yra ugniasienės taisyklė kitoje pusėje, o ne įrenginio savybė. Vienintelis sąžiningas sėkmingo ping skaitymas yra tai, kad šis tikslas atsakė šiam protokolui šiuo momentu.

Ką platforma iš tikrųjų praneša

Nuo API 21 ConnectivityManager.getAllNetworks() grąžina kiekvieną tinklą, kurį sistema žino, o getNetworkCapabilities(network) praneša, ką tas tinklas deklaruoja. VPN tinklas turi TRANSPORT_VPN. Perėjus visus tinklus atsakoma į vieną klausimą: ar šiame įrenginyje dabar veikia koks nors VPN?

Antras, siauresnis klausimas reikalauja kito kvietimo. getActiveNetwork() grąžina tinklą, kurį programėlės srautas naudotų dabar, o tos pačios galimybės patikra jame atsako: ar mano srautas eitų per VPN? Abu gali nesutapti, ir šis nesutapimas yra informatyvus. VPN, kuri įjungta, bet nėra dabartinė, rodo į maršrutizavimą pagal programėlę arba skaidyto tunelio konfigūraciją, o ne į trūkstamą tunelį.

Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

Apsaugokite abu kvietimus. Galimybės gali būti null tinklui, kuris dingo tarp išvardijimo ir užklausos, o aktyvus tinklas gali būti null perdavimo metu. NET_CAPABILITY_NOT_VPN naudinga priešinga kryptimi – patvirtinti, kad tinklas tikrai nėra tunelis, o ne tik kad jam trūksta VPN transporto vėliavėlės.

NetworkInfo buvo tas pats mitas su API

ConnectivityManager.TYPE_VPN su NetworkInfo yra antroji šios patikros karta, ir ji pasenusi dėl priežasties. Šiuolaikinėse versijose objektas, grąžinamas VPN, gali būti null, kol tunelis prijungtas, todėl kodas, kuris null traktuoja kaip „nėra VPN“, tyliai praneša klaidingą neigiamą rezultatą įrenginiuose, kuriuose atsakymas svarbiausias.

Lydintis transliacija taip pat ne geresnė. CONNECTIVITY_CHANGE turi dokumentuotą VPN perėjimų praleidimo istoriją, įskaitant versijas, kuriose ji suveikdavo tik prisijungus ar atsijungus VPN, o ne prie kiekvieno tinklo pakeitimo. Ja pagrįstas klausytojas sukuria būsenos kintamąjį, kuris tolsta nuo platformos požiūrio iki kito visiško prisijungimo.

Reaguokite į atgalinius iškvietimus, o ne apklausinėkite

Užregistruokite NetworkRequest, kuris reikalauja TRANSPORT_VPN ir pašalina NET_CAPABILITY_NOT_VPN, tada iškvieskite registerNetworkCallback su ta užklausa. Tada onAvailable ir onLost pasakys, kada deklaracija pasikeičia, ir tai yra įvykis, kuris jums tikrai rūpi. Atšaukite atgalinio iškvietimo registraciją, kai komponentas sustoja, kitaip programėlė laiko nuorodą į negyvą ekraną ir atgalinis iškvietimas toliau suveikia.

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

Tam reikia ACCESS_NETWORK_STATE, kuri yra įprasta teisė: be root, be naudotojo sutikimo dialogo, be VPN teisės. Apklausinėjimas laikmačiu yra alternatyva, į kurią žmonės atsigręžia, ir ji eikvoja bateriją, o vis tiek praleidžia trumpesnį už intervalą perėjimą, ir būtent tą perėjimą naudotojas ir praneša.

Ko atsakymas vis dar negali pasakyti

Nė vienas iš šių API nepraneša, ar tunelis sveikas, kuriame serveryje jis baigiasi, ar tam tikros programėlės srautas yra jame. VPN tinklas gali egzistuoti be naudingų maršrutų, o tunelis pagal programėlę gali pašalinti programėlę, kuri užduoda klausimą.

Todėl užrašykite, ką patikra matė, o ne vien loginę reikšmę. Užregistruokite tinklo identifikatorių, deklaruotus transportus ir ar tas tinklas buvo aktyvus. Ataskaita, kurioje sakoma „VPN nerasta“, yra netinkama, o ataskaita, kurioje sakoma, kad tinklas deklaravo TRANSPORT_VPN, bet getActiveNetwork() grąžino Wi-Fi, tiesiogiai nurodo maršrutizavimą arba išimčių sąrašą, ir tai yra pataisa, su kuria kas nors gali dirbti.

Susiję straipsniai