Zaznavanje VPN na Androidu: zakaj tun0 in ping zavajata
Ime vmesnika je dogovor; ping dokazuje, da je bil en gostitelj dosegljiv. Vprašajte NetworkCapabilities, kaj omrežje razglasi, in se nato odzovite na povratne klice.
Preverjanje, ki ga večina aplikacij za Android uporablja za zaznavanje VPN, je ime vmesnika in nikoli ni bilo preverjanje VPN. Poroča, da obstaja nek vmesnik z nizom, ki ga je razvijalec pričakoval, kar na eni napravi pomeni OpenVPN, na drugi pa nekaj povsem nepovezanega. Ping je enak dokaz: dokazuje, da je en cilj odgovoril, ne da je paket prenesel tunel. Kar platforma v resnici izpostavi, je množica omrežij in transporti, ki jih vsako razglasi, in to je drugo vprašanje z drugim odgovorom.
Mit: če obstaja tun0, je VPN aktiven
Aplikacije VPN za Android so implementacije VpnService, ki sistemu predajo deskriptor datoteke tun. Ime vmesnika izvira od tistega, ki ga je ustvaril: OpenVPN običajno ustvari tun0, PPTP in L2TP ustvarita ppp0, WireGuard poimenuje vmesnik po tunelu, tunel IPsec pa morda sploh nima običajnega imena. Branje /proc/net/dev ali naštevanje objektov NetworkInterface torej išče dogovor, ne dejstva.
Obe smeri napake sta živi. Naprava ima lahko tun0 za lokalni tunel, ki nima nič skupnega z uporabnikovim VPN, popolnoma zdrav tunel WireGuard pa je lahko neviden za preverjanje, ki pozna le niz tun0. Lažno pozitivni in lažno negativni rezultati v isti funkciji jo naredijo neuporabno kot diagnostiko, ker zahtevku za podporo, ki pravi «VPN ni zaznan», ni mogoče zaupati v nobeno smer.
Dostop se je tudi zožil. Novejše različice Androida omejujejo, kaj lahko običajna aplikacija prebere iz /proc, zato lahko preverjanje na podlagi imena, ki je delovalo na Androidu 8, na poznejši napravi vrne prazen seznam brez kakršne koli spremembe kode. Ko preverjanje tiho odpove v odprto smer, aplikacija sklepa, da na strojni opremi, kjer je tunel v redu, ni VPN.
Mit: uspešen ping pomeni, da tunel prenaša promet
Ping na notranji gostitelj uspe vedno, ko obstaja pot do tega gostitelja. Pri razdeljenem tunelu je gostitelj lahko dosegljiv prek Wi-Fi, medtem ko je tunel aktiven in počne nekaj povsem drugega. Pri VPN za posamezne aplikacije je lahko aplikacija, ki izvaja ping, izključena iz tunela, zato ICMP odmev odide zunaj njega in se vrne, kar dokazuje nasprotno od tega, kar preverjanje predpostavlja.
ICMP in TCP se filtrirata tudi ločeno. Tunel, ki dovoljuje zahteve za odmev, a zavrže vrata TCP, ki jih storitev potrebuje, bo prestal test ping in spodletel v aplikaciji, in to je pravilo požarnega zidu na drugi strani, ne lastnost naprave. Edino pošteno branje uspešnega pinga je, da je ta cilj v tem trenutku odgovoril na ta protokol.
Kaj platforma v resnici poroča
Od API 21 ConnectivityManager.getAllNetworks() vrne vsako omrežje, ki ga sistem pozna, getNetworkCapabilities(network) pa poroča, kaj to omrežje razglasi. Omrežje VPN nosi TRANSPORT_VPN. Prehod čez vsa omrežja odgovori na eno vprašanje: ali je na tej napravi zdaj aktiven kak VPN?
Drugo, ožje vprašanje zahteva drug klic. getActiveNetwork() vrne omrežje, ki bi ga promet aplikacije uporabil zdaj, preverjanje iste zmožnosti na njem pa odgovori: ali bi moj promet šel skozi VPN? Oba se lahko razhajata in to razhajanje je informativno. VPN, ki je aktiven, a ni trenutni, kaže na usmerjanje za posamezne aplikacije ali na konfiguracijo razdeljenega tunela, ne na manjkajoči tunel.
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Zaščitite oba klica. Zmožnosti so lahko null za omrežje, ki je izginilo med naštevanjem in poizvedbo, aktivno omrežje pa je lahko null med predajo. NET_CAPABILITY_NOT_VPN je uporaben v nasprotno smer, da potrdite, da omrežje zagotovo ni tunel, namesto da mu le manjka zastavica transporta VPN.
NetworkInfo je bil isti mit z API
ConnectivityManager.TYPE_VPN z NetworkInfo je druga generacija tega preverjanja in je zastarelo z razlogom. V sodobnih različicah je lahko objekt, vrnjen za VPN, null, medtem ko je tunel povezan, zato koda, ki null obravnava kot «ni VPN», tiho poroča lažno negativen rezultat na napravah, kjer je odgovor najpomembnejši.
Tudi oddajanje, ki ga običajno spremlja, ni boljše. CONNECTIVITY_CHANGE ima dokumentirano zgodovino izpuščanja prehodov VPN, vključno z različicami, kjer se je sprožilo le ob povezavi ali prekinitvi VPN, ne pa ob vsaki spremembi omrežja. Poslušalec, zgrajen na njem, ustvari spremenljivko stanja, ki se odmika od videnja platforme do naslednje popolne ponovne povezave.
Odzovite se na povratne klice namesto na poizvedovanje
Registrirajte NetworkRequest, ki zahteva TRANSPORT_VPN in odstrani NET_CAPABILITY_NOT_VPN, nato pokličite registerNetworkCallback s to zahtevo. Takrat vam onAvailable in onLost povesta, kdaj se izjava spremeni, in to je dogodek, ki vas res zanima. Odjavite povratni klic, ko se komponenta ustavi, sicer aplikacija drži sklic na mrtvem zaslonu in povratni klic se še naprej sproža.
NetworkRequest req = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(req, callback);
To zahteva ACCESS_NETWORK_STATE, kar je običajno dovoljenje: brez roota, brez pogovornega okna s soglasjem uporabnika, brez dovoljenja VPN. Poizvedovanje s časovnikom je alternativa, h kateri se ljudje zatečejo, in porabi baterijo, medtem ko še vedno zgreši prehod, krajši od intervala, in to je natanko tisti prehod, ki ga uporabnik prijavi.
Česa vam odgovor še vedno ne more povedati
Nobeden od teh API ne poroča, ali je tunel zdrav, na katerem strežniku se konča in ali je promet določene aplikacije v njem. Omrežje VPN lahko obstaja brez uporabnih poti, tunel za posamezne aplikacije pa lahko izključi aplikacijo, ki postavlja vprašanje.
Zato zabeležite, kaj je preverjanje videlo, ne le logične vrednosti. Zabeležite identifikator omrežja, razglašene transporte in ali je bilo to omrežje aktivno. Poročilo, ki pravi «VPN ni zaznan», je neuporabno, medtem ko poročilo, da je omrežje razglasilo TRANSPORT_VPN, a je getActiveNetwork() vrnil Wi-Fi, kaže neposredno na usmerjanje ali na seznam izključitev, in to je popravek, na katerem lahko nekdo dela.