Verificarea pe care majoritatea aplicațiilor Android o folosesc pentru a detecta un VPN este un nume de interfață și nu a fost niciodată o verificare de VPN. Raportează că există o interfață cu șirul pe care dezvoltatorul îl aștepta, ceea ce pe un dispozitiv înseamnă OpenVPN, iar pe altul înseamnă ceva complet fără legătură. Un ping este același tip de dovadă: dovedește că o destinație a răspuns, nu că un tunel a transportat pachetul. Ce expune de fapt platforma este un set de rețele și transporturile pe care fiecare le declară, iar aceasta este o altă întrebare cu un alt răspuns.

Mit: dacă există tun0, un VPN este activ

Aplicațiile VPN pentru Android sunt implementări VpnService care predau sistemului un descriptor de fișier tun. Numele interfeței vine de la cel care a creat-o: OpenVPN produce de obicei tun0, PPTP și L2TP produc ppp0, WireGuard își numește interfața după tunel, iar un tunel IPsec poate să nu prezinte deloc un nume convențional. Așadar, citirea /proc/net/dev sau enumerarea obiectelor NetworkInterface caută o convenție, nu un fapt.

Ambele direcții de eroare sunt vii. Un dispozitiv poate avea tun0 pentru un tunel local care nu are nimic de-a face cu VPN-ul utilizatorului, iar un tunel WireGuard perfect sănătos poate fi invizibil pentru o verificare care cunoaște doar șirul tun0. Rezultatele fals pozitive și fals negative din aceeași funcție o fac inutilizabilă ca diagnostic, pentru că un tichet de asistență care spune «nu s-a detectat niciun VPN» nu este de încredere în nicio direcție.

Și accesul s-a restrâns. Versiunile mai noi de Android limitează ce poate citi o aplicație obișnuită din /proc, astfel încât o verificare bazată pe nume care funcționa pe Android 8 poate returna o listă goală pe un dispozitiv mai nou fără nicio modificare a codului. Când verificarea eșuează silențios în partea deschisă, aplicația concluzionează că nu există VPN pe hardware unde tunelul este în regulă.

Mit: un ping reușit înseamnă că tunelul transportă trafic

Un ping către un host intern reușește oricând există o rută către acel host. Cu tunel divizat, hostul poate fi accesibil prin Wi-Fi în timp ce tunelul este activ și face cu totul altceva. Cu un VPN per aplicație, aplicația care face pingul poate fi exclusă din tunel, așa că ecoul ICMP iese în afara lui și revine, ceea ce dovedește contrariul a ceea ce presupune verificarea.

ICMP și TCP sunt și ele filtrate separat. Un tunel care permite cereri de ecou dar aruncă portul TCP de care are nevoie un serviciu va trece testul ping și va eșua în aplicație, iar aceasta este o regulă de firewall de cealaltă parte, nu o proprietate a dispozitivului. Singura lectură onestă a unui ping reușit este că această destinație a răspuns acestui protocol în acest moment.

Ce raportează de fapt platforma

De la API 21, ConnectivityManager.getAllNetworks() returnează fiecare rețea pe care sistemul o cunoaște, iar getNetworkCapabilities(network) raportează ce declară acea rețea. O rețea VPN poartă TRANSPORT_VPN. Parcurgerea tuturor rețelelor răspunde la o întrebare: există vreun VPN activ pe acest dispozitiv acum?

O a doua întrebare, mai îngustă, necesită un alt apel. getActiveNetwork() returnează rețeaua pe care traficul aplicației ar folosi-o acum, iar verificarea aceleiași capacități pe ea răspunde: traficul meu ar trece printr-un VPN? Cele două pot să nu fie de acord, iar dezacordul este informativ. Un VPN activ dar nu curent indică rutare per aplicație sau o configurație de tunel divizat, nu un tunel lipsă.

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

Protejați ambele apeluri. Capacitățile pot fi null pentru o rețea care a dispărut între listare și interogare, iar rețeaua activă poate fi null în timpul unei predări. NET_CAPABILITY_NOT_VPN este utilă în cealaltă direcție, pentru a confirma că o rețea nu este deloc un tunel, în loc să îi lipsească doar indicatorul de transport VPN.

NetworkInfo a fost același mit cu un API

ConnectivityManager.TYPE_VPN cu NetworkInfo este a doua generație a acestei verificări și este învechit dintr-un motiv. În versiunile moderne obiectul returnat pentru un VPN poate fi null în timp ce tunelul este conectat, așa că codul care tratează null ca «fără VPN» raportează în silențiu un fals negativ pe dispozitivele unde răspunsul contează cel mai mult.

Nici difuzarea care o însoțește de obicei nu este mai bună. CONNECTIVITY_CHANGE are un istoric documentat de ratare a tranzițiilor VPN, inclusiv versiuni în care se declanșa doar când un VPN se conecta sau se deconecta, în loc de la fiecare schimbare de rețea. Un ascultător construit pe ea produce o variabilă de stare care se îndepărtează de viziunea platformei până la următoarea reconectare completă.

Reacționați la apeluri inverse în loc să interogați periodic

Înregistrați un NetworkRequest care cere TRANSPORT_VPN și elimină NET_CAPABILITY_NOT_VPN, apoi apelați registerNetworkCallback cu acea cerere. Atunci onAvailable și onLost vă spun când se schimbă declarația, iar acesta este evenimentul care vă interesează cu adevărat. Anulați înregistrarea apelului invers când componenta se oprește, altfel aplicația păstrează o referință la un ecran mort și apelul invers continuă să se declanșeze.

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

Aceasta necesită ACCESS_NETWORK_STATE, o permisiune normală: fără root, fără dialog de consimțământ al utilizatorului, fără permisiune VPN. Interogarea cu un temporizator este alternativa la care apelează oamenii, și consumă baterie în timp ce ratează în continuare o tranziție mai scurtă decât intervalul, care este exact tranziția pe care o raportează un utilizator.

Ce nu vă poate spune încă răspunsul

Niciunul dintre aceste API nu raportează dacă tunelul este sănătos, pe ce server se termină sau dacă traficul unei anumite aplicații este în interiorul lui. O rețea VPN poate exista fără rute utilizabile, iar un tunel per aplicație poate exclude aplicația care pune întrebarea.

Deci înregistrați ce a văzut verificarea, nu doar valoarea booleană. Înregistrați identificatorul rețelei, transporturile declarate și dacă acea rețea era activă. Un raport care spune «nu s-a detectat niciun VPN» este inutilizabil, în timp ce unul care spune că o rețea a declarat TRANSPORT_VPN dar getActiveNetwork() a returnat Wi-Fi indică direct rutarea sau o listă de excluderi, și aceasta este o corecție pe care cineva poate lucra.

Articole similare