Tarkistus, jota useimmat Android-sovellukset käyttävät VPN:n havaitsemiseen, on liitännän nimi, eikä se ole koskaan ollut VPN-tarkistus. Se ilmoittaa, että jokin liitäntä on olemassa kehittäjän odottamalla merkkijonolla, mikä yhdellä laitteella tarkoittaa OpenVPN:ää ja toisella jotain täysin siihen liittymätöntä. Ping on samanlaista näyttöä: se todistaa, että yksi kohde vastasi, ei sitä, että tunneli kuljetti paketin. Se, mitä alusta itse asiassa tarjoaa, on joukko verkkoja ja transportit, jotka kukin niistä ilmoittaa, ja se on eri kysymys eri vastauksella.

Myytti: jos tun0 on olemassa, VPN on päällä

Androidin VPN-sovellukset ovat VpnService-toteutuksia, jotka antavat järjestelmälle tun-tiedostokuvauksen. Liitännän nimi tulee siltä, joka sen loi: OpenVPN tuottaa yleensä tun0:n, PPTP ja L2TP tuottavat ppp0:n, WireGuard nimeää liitännän tunnelin mukaan, eikä IPsec-tunnelilla ehkä ole lainkaan tavanomaista nimeä. Niinpä /proc/net/dev -tiedoston lukeminen tai NetworkInterface-olioiden luetteleminen etsii sopimusta, ei tosiasiaa.

Molemmat virhesuunnat ovat eläviä. Laitteessa voi olla tun0 paikalliselle tunnelille, jolla ei ole mitään tekemistä käyttäjän VPN:n kanssa, ja täysin terve WireGuard-tunneli voi olla näkymätön tarkistukselle, joka tuntee vain merkkijonon tun0. Väärät positiiviset ja väärät negatiiviset samassa funktiossa tekevät siitä käyttökelvottoman diagnostiikkana, koska tukipyyntöön, jossa sanotaan ”VPN:ää ei havaittu”, ei voi luottaa kumpaankaan suuntaan.

Pääsy on myös kaventunut. Uudemmat Android-versiot rajoittavat, mitä tavallinen sovellus voi lukea /proc:sta, joten nimeen perustuva tarkistus, joka toimi Android 8:ssa, voi palauttaa tyhjän luettelon myöhemmällä laitteella ilman mitään koodimuutosta. Kun tarkistus epäonnistuu hiljaa avoimeen suuntaan, sovellus päättelee, ettei VPN:ää ole laitteistossa, jossa tunneli on kunnossa.

Myytti: onnistunut ping tarkoittaa, että tunneli kuljettaa liikennettä

Ping sisäiseen isäntään onnistuu aina, kun reitti siihen isäntään on olemassa. Jaetulla tunnelilla isäntä voi olla tavoitettavissa Wi-Fi:n kautta, kun taas tunneli on päällä ja tekee jotain aivan muuta. Sovelluskohtaisella VPN:llä pingin tekevä sovellus voi olla suljettu tunnelin ulkopuolelle, joten ICMP-kaiku lähtee sen ulkopuolelle ja palaa, mikä todistaa päinvastaista kuin tarkistus olettaa.

ICMP ja TCP suodatetaan myös erikseen. Tunneli, joka sallii kaikupyynnöt mutta pudottaa palvelun tarvitseman TCP-portin, läpäisee ping-testin ja epäonnistuu sovelluksessa, ja se on palomuurisääntö toisella puolella eikä laitteen ominaisuus. Ainoa rehellinen tulkinta onnistuneesta pingistä on, että tämä kohde vastasi tähän protokollaan tällä hetkellä.

Mitä alusta itse asiassa ilmoittaa

API 21:stä alkaen ConnectivityManager.getAllNetworks() palauttaa jokaisen verkon, jonka järjestelmä tuntee, ja getNetworkCapabilities(network) ilmoittaa, mitä kyseinen verkko ilmoittaa. VPN-verkko kantaa TRANSPORT_VPN:n. Kaikkien verkkojen läpikäynti vastaa yhteen kysymykseen: onko tässä laitteessa juuri nyt mikään VPN päällä?

Toinen, kapeampi kysymys vaatii toisen kutsun. getActiveNetwork() palauttaa verkon, jota sovelluksen liikenne käyttäisi juuri nyt, ja saman kyvykkyyden tarkistaminen siitä vastaa: kulkisiko liikenteeni VPN:n kautta? Ne kaksi voivat olla eri mieltä, ja erimielisyys on informatiivista. VPN, joka on päällä mutta ei aktiivinen, viittaa sovelluskohtaiseen reititykseen tai jaetun tunnelin kokoonpanoon, ei puuttuvaan tunneliin.

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

Suojaa molemmat kutsut. Kyvykkyydet voivat olla null verkolle, joka katosi listauksen ja kyselyn välillä, ja aktiivinen verkko voi olla null siirron aikana. NET_CAPABILITY_NOT_VPN on hyödyllinen toiseen suuntaan: sen vahvistamiseen, että verkko ei todellakaan ole tunneli, eikä vain sen, että siltä puuttuu VPN-transportin lippu.

NetworkInfo oli sama myytti API:n kanssa

ConnectivityManager.TYPE_VPN yhdessä NetworkInfon kanssa on tämän tarkistuksen toinen sukupolvi, ja se on vanhentunut syystä. Nykyaikaisissa versioissa VPN:lle palautettu olio voi olla null, kun taas tunneli on yhdistetty, joten koodi, joka kohtelee nullia merkityksessä ”ei VPN:ää”, ilmoittaa hiljaa väärän negatiivisen niillä laitteilla, joilla vastauksella on eniten merkitystä.

Myöskään lähetys, joka yleensä seuraa sitä, ei ole parempi. CONNECTIVITY_CHANGElla on dokumentoitu historia VPN-siirtymien ohittamisesta, mukaan lukien versiot, joissa se laukesi vain VPN:n yhdistyessä tai katketessa eikä jokaisella verkkomuutoksella. Sen päälle rakennettu kuuntelija tuottaa tilamuuttujan, joka ajautuu pois alustan näkemyksestä seuraavaan täydelliseen uudelleenyhdistämiseen asti.

Reagoi takaisinkutsuihin kyselyn sijaan

Rekisteröi NetworkRequest, joka vaatii TRANSPORT_VPN:n ja poistaa NET_CAPABILITY_NOT_VPN:n, ja kutsu sitten registerNetworkCallbackia tällä pyynnöllä. Silloin onAvailable ja onLost kertovat, milloin ilmoitus muuttuu, ja se on tapahtuma, joka sinua todella kiinnostaa. Poista takaisinkutsun rekisteröinti, kun komponentti pysähtyy, muuten sovellus pitää viittausta kuolleeseen näyttöön ja takaisinkutsu laukeaa edelleen.

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

Tämä vaatii ACCESS_NETWORK_STATEn, joka on normaali käyttöoikeus: ei rootia, ei käyttäjän suostumusvalintaikkunaa, ei VPN-käyttöoikeutta. Ajastimella kysely on vaihtoehto, johon ihmiset turvautuvat, ja se kuluttaa akkua ja jättää silti väliin siirtymän, joka on lyhyempi kuin väli, ja juuri sellaisen siirtymän käyttäjä raportoi.

Mitä vastaus ei vieläkään voi kertoa sinulle

Mikään näistä API:sta ei ilmoita, onko tunneli terve, mihin palvelimeen se päättyy tai onko tietyn sovelluksen liikenne sen sisällä. VPN-verkko voi olla olemassa ilman käyttökelpoisia reittejä, ja sovelluskohtainen tunneli voi sulkea pois sovelluksen, joka kysyy.

Tallenna siis se, mitä tarkistus näki, älä vain boolean-arvoa. Kirjaa verkon tunniste, ilmoitetut transportit ja se, oliko verkko aktiivinen. Raportti, joka sanoo ”VPN:ää ei havaittu”, on käyttökelvoton, kun taas raportti, joka sanoo, että verkko ilmoitti TRANSPORT_VPN:n mutta getActiveNetwork() palautti Wi-Fi:n, osoittaa suoraan reititykseen tai poissulkuluetteloon, ja se on korjaus, jonka kanssa joku voi työskennellä.

Aiheeseen liittyvät artikkelit