Om ’n VPN op Android op te spoor: waarom tun0 en pings mislei
’n Koppelvlaknaam is ’n konvensie; ’n ping bewys een gasheer was bereikbaar. Vra NetworkCapabilities wat ’n netwerk verklaar, en reageer dan op terugroepe.
Die toets wat die meeste Android-programme gebruik om ’n VPN op te spoor, is ’n koppelvlaknaam, en dit was nooit ’n VPN-toets nie. Dit rapporteer dat een of ander koppelvlak bestaan met ’n string wat die ontwikkelaar verwag het, wat op een toestel OpenVPN beteken en op ’n ander iets heeltemal anders. ’n Ping is dieselfde soort bewys: dit wys dat een bestemming geantwoord het, nie dat ’n tonnel die pakkie gedra het nie. Wat die platform werklik blootstel, is ’n stel netwerke en die transporte wat elkeen verklaar, en dit is ’n ander vraag met ’n ander antwoord.
Mite: as tun0 bestaan, is daar ’n VPN aan die gang
Android-VPN-programme is VpnService-implementasies wat ’n tun-lêerbeskrywer aan die stelsel oorhandig. Die koppelvlaknaam kom van wie dit geskep het: OpenVPN lewer gewoonlik tun0, PPTP en L2TP lewer ppp0, WireGuard noem sy koppelvlak na die tonnel, en ’n IPsec-tonnel het dikwels glad geen konvensionele naam nie. Om /proc/net/dev te lees of NetworkInterface-objekte te lys, soek dus na ’n konvensie, nie na ’n feit nie.
Albei foutrigtings is lewendig. ’n Toestel kan tun0 hê vir ’n plaaslike tonnel wat niks met die gebruiker se VPN te doen het nie, en ’n heeltemal gesonde WireGuard-tonnel kan onsigbaar wees vir ’n toets wat net die string tun0 ken. Vals positiewes en vals negatiewes in dieselfde funksie maak dit onbruikbaar as ’n diagnose, want ’n ondersteuningskaartjie wat sê ‘geen VPN opgespoor nie’ kan in geen rigting vertrou word nie.
Toegang het ook vernou. Nuwer Android-weergawes beperk wat ’n gewone program uit /proc kan lees, so ’n naamgebaseerde toets wat op Android 8 gewerk het, kan op ’n later toestel ’n leë lys teruggee sonder enige kodeverandering. Wanneer die toets stilweg oop misluk, kom die program tot die gevolgtrekking dat daar geen VPN is op hardeware waar die tonnel heeltemal reg werk nie.
Mite: ’n suksesvolle ping beteken die tonnel dra verkeer
’n Ping na ’n interne gasheer slaag wanneer daar ’n roete na daardie gasheer bestaan. Met verdeelde tonneling kan die gasheer oor Wi-Fi bereikbaar wees terwyl die tonnel op is en heeltemal iets anders doen. Met ’n per-program-VPN kan die program wat die ping doen van die tonnel uitgesluit wees, so die ICMP-eggo verlaat dit buite en kom terug, wat die teenoorgestelde bewys van wat die toets aanneem.
ICMP en TCP word ook afsonderlik gefiltreer. ’n Tonnel wat eggo-versoeke toelaat maar die TCP-poort wat ’n diens nodig het, laat val, sal die ping-toets slaag en die toepassing faal, en dit is ’n firewall-reël aan die ander kant, nie ’n eienskap van die toestel nie. Die enigste eerlike lesing van ’n suksesvolle ping is dat hierdie bestemming op hierdie oomblik hierdie protokol geantwoord het.
Wat die platform werklik rapporteer
Vanaf API 21 gee ConnectivityManager.getAllNetworks() elke netwerk wat die stelsel ken, en getNetworkCapabilities(network) rapporteer wat daardie netwerk verklaar. ’n VPN-netwerk dra TRANSPORT_VPN. Om oor alle netwerke te loop, beantwoord een vraag: is enige VPN tans op hierdie toestel aan die gang?
’n Tweede, nouer vraag vereis ’n ander oproep. getActiveNetwork() gee die netwerk wat die program se verkeer nou sou gebruik, en om dieselfde vermoë daarop te toets, beantwoord: sou my verkeer deur ’n VPN gaan? Die twee kan verskil, en die verskil is insiggewend. ’n VPN wat op is maar nie aktief nie, dui op per-program-roetering of ’n verdeelde-tonnel-konfigurasie, nie op ’n ontbrekende tonnel nie.
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Beveilig albei oproepe. Vermoëns kan null wees vir ’n netwerk wat tussen lys en navraag verdwyn het, en die aktiewe netwerk kan null wees tydens ’n oordrag. NET_CAPABILITY_NOT_VPN is nuttig in die ander rigting, om te bevestig dat ’n netwerk beslis nie ’n tonnel is nie, eerder as om net die VPN-transportvlag te kort.
NetworkInfo was dieselfde mite met ’n API
ConnectivityManager.TYPE_VPN met NetworkInfo is die tweede generasie van hierdie toets, en dit is om ’n rede verouderd. Op moderne weergawes kan die objek wat vir ’n VPN teruggegee word null wees terwyl die tonnel gekonnekteer is, so kode wat null as ‘geen VPN’ behandel, rapporteer stilweg ’n vals negatief op die toestelle waar die antwoord die meeste saak maak.
Die uitsending wat dit gewoonlik vergesel, is nie beter nie. CONNECTIVITY_CHANGE het ’n gedokumenteerde geskiedenis van gemiste VPN-oorgange, insluitend boue waar dit net gevuur het wanneer ’n VPN gekonnekteer of ontkoppel het, eerder as by elke netwerkverandering. ’n Luisteraar wat daarop gebou is, lewer ’n toestandveranderlike wat van die platform se siening wegdryf tot die volgende volle herverbinding.
Reageer op terugroepe in plaas van peiling
Registreer ’n NetworkRequest wat TRANSPORT_VPN vereis en NET_CAPABILITY_NOT_VPN verwyder, en roep dan registerNetworkCallback met daardie versoek. onAvailable en onLost vertel dan wanneer die verklaring verander, en dit is die gebeurtenis wat werklik saak maak. Deelregistreer die terugroep wanneer die komponent stop, anders hou die program ’n verwysing na ’n dooie skerm en die terugroep bly vuur.
NetworkRequest req = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(req, callback);
Dit benodig ACCESS_NETWORK_STATE, wat ’n normale toestemming is: geen root, geen gebruikerstoestemmingsdialoog, geen VPN-toestemming nie. Peiling op ’n tydhouer is die alternatief waarop mense terugval, en dit brand battery terwyl dit steeds ’n oorgang korter as die interval mis, en dit is juis die oorgang wat ’n gebruiker rapporteer.
Wat die antwoord steeds nie kan sê nie
Nie een van hierdie API’s rapporteer of die tonnel gesond is, op watter bediener dit eindig, of ’n bepaalde program se verkeer daarbinne is nie. ’n VPN-netwerk kan bestaan sonder bruikbare roetes, en ’n per-program-tonnel kan die program wat die vraag vra uitsluit.
Rekord dus wat die toets gesien het, nie net die booleaanse waarde nie. Teken die netwerkidentifiseerder, die verklaarde transporte en of daardie netwerk aktief was aan. ’n Verslag wat sê ‘geen VPN opgespoor’ is onbruikbaar, terwyl een wat sê ’n netwerk het TRANSPORT_VPN verklaar maar getActiveNetwork() het Wi-Fi teruggegee, wys direk na roetering of na ’n uitsluitingslys, en dit is ’n regstelling waaraan iemand kan werk.