Pārbaude, ko lielākā daļa Android lietotņu izmanto VPN noteikšanai, ir saskarnes nosaukums, un tā nekad nav bijusi VPN pārbaude. Tā ziņo, ka pastāv kāda saskarne ar izstrādātāja gaidīto virkni, kas vienā ierīcē nozīmē OpenVPN, bet citā — kaut ko pilnīgi nesaistītu. ping ir tāds pats pierādījums: tas pierāda, ka viens galamērķis atbildēja, nevis ka tunelis pārvadāja pakešu. Tas, ko platforma faktiski atklāj, ir tīklu kopa un transporti, ko katrs no tiem deklarē, un tas ir cits jautājums ar citu atbildi.

Mīts: ja ir tun0, VPN ir ieslēgts

Android VPN lietotnes ir VpnService ieviešanas, kas sistēmai nodod tun faila deskriptoru. Saskarnes nosaukums nāk no tā, kas to izveidoja: OpenVPN parasti izveido tun0, PPTP un L2TP izveido ppp0, WireGuard nosauc saskarni tuneļa vārdā, un IPsec tunelim var vispār nebūt parastā nosaukuma. Tāpēc /proc/net/dev lasīšana vai NetworkInterface objektu uzskaitīšana meklē vienošanos, nevis faktu.

Abi kļūdas virzieni ir dzīvi. Ierīcē var būt tun0 vietējam tunelim, kam nav nekā kopīga ar lietotāja VPN, un pilnīgi vesels WireGuard tunelis var būt neredzams pārbaudei, kas zina tikai virkni tun0. Viltus pozitīvie un viltus negatīvie vienā funkcijā padara to nederīgu diagnostikai, jo atbalsta biļetenam, kurā teikts «VPN nav atrasts», nevar uzticēties nevienā virzienā.

Piekļuve arī sašaurinājās. Jaunākās Android versijas ierobežo, ko parasta lietotne var lasīt no /proc, tāpēc uz nosaukumu balstīta pārbaude, kas darbojās Android 8, vēlākā ierīcē var atgriezt tukšu sarakstu bez jebkādām koda izmaiņām. Kad pārbaude klusi neizdodas atvērtajā virzienā, lietotne secina, ka VPN nav aparatūrā, kur tunelis ir kārtībā.

Mīts: veiksmīgs ping nozīmē, ka tunelis pārvadā satiksmi

ping uz iekšēju resursdatoru izdodas vienmēr, kad pastāv maršruts uz šo resursdatoru. Ar sadalītu tuneli resursdators var būt sasniedzams pa Wi-Fi, kamēr tunelis ir ieslēgts un dara ko pilnīgi citu. Ar VPN katrai lietotnei ping veicošā lietotne var būt izslēgta no tuneļa, tāpēc ICMP atbalss iziet ārpus tā un atgriežas, kas pierāda pretējo tam, ko pārbaude pieņem.

ICMP un TCP arī tiek filtrēti atsevišķi. Tunelis, kas atļauj atbalss pieprasījumus, bet izmet TCP portu, kas nepieciešams pakalpojumam, izturēs ping testu un neizdosies lietotnē, un tas ir ugunsmūra noteikums otrā pusē, nevis ierīces īpašība. Vienīgais godīgais veiksmīga ping lasījums ir tāds, ka šis galamērķis šajā brīdī atbildēja šim protokolam.

Ko platforma faktiski ziņo

No API 21 ConnectivityManager.getAllNetworks() atgriež katru tīklu, ko sistēma zina, un getNetworkCapabilities(network) ziņo, ko šis tīkls deklarē. VPN tīkls nes TRANSPORT_VPN. Pārejot pāri visiem tīkliem, tiek atbildēts uz vienu jautājumu: vai šajā ierīcē tagad darbojas kāds VPN?

Otrs, šaurāks jautājums prasa citu izsaukumu. getActiveNetwork() atgriež tīklu, ko lietotnes satiksme izmantotu tagad, un tās pašas iespējas pārbaude tajā atbild: vai mana satiksme ietu caur VPN? Abi var nesakrist, un šī nesakritība ir informatīva. VPN, kas ir ieslēgts, bet nav pašreizējais, norāda uz maršrutēšanu katrai lietotnei vai sadalīta tuneļa konfigurāciju, nevis uz trūkstošu tuneli.

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

Aizsargājiet abus izsaukumus. Iespējas var būt null tīklam, kas pazuda starp uzskaitīšanu un vaicājumu, un aktīvais tīkls var būt null pārejas laikā. NET_CAPABILITY_NOT_VPN ir noderīgs pretējā virzienā — lai apstiprinātu, ka tīkls noteikti nav tunelis, nevis tikai tam trūkst VPN transporta karodziņa.

NetworkInfo bija tas pats mīts ar API

ConnectivityManager.TYPE_VPN ar NetworkInfo ir šīs pārbaudes otrā paaudze, un tā ir novecojusi pamatota iemesla dēļ. Mūsdienu versijās objekts, kas atgriezts VPN, var būt null, kamēr tunelis ir pievienots, tāpēc kods, kas null uzskata par «nav VPN», klusi ziņo viltus negatīvu ierīcēs, kur atbilde ir vissvarīgākā.

Arī pavadošā apraide nav labāka. CONNECTIVITY_CHANGE ir dokumentēta VPN pāreju izlaišanas vēsture, tostarp versijas, kurās tā iedarbojās tikai tad, kad VPN pievienojās vai atvienojās, nevis pie katras tīkla izmaiņas. Uz tās balstīts klausītājs rada stāvokļa mainīgo, kas attālinās no platformas skatījuma līdz nākamajai pilnajai atkārtotai pievienošanai.

Reaģējiet uz atzvanēm, nevis aptaujājiet

Reģistrējiet NetworkRequest, kas pieprasa TRANSPORT_VPN un noņem NET_CAPABILITY_NOT_VPN, pēc tam izsauciet registerNetworkCallback ar šo pieprasījumu. Tad onAvailable un onLost pateiks, kad deklarācija mainās, un tas ir notikums, kas jums patiešām svarīgs. Atceliet atzvanes reģistrāciju, kad komponents apstājas, citādi lietotne tur atsauci uz mirušu ekrānu un atzvane turpina iedarboties.

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

Tam nepieciešams ACCESS_NETWORK_STATE, kas ir parasta atļauja: bez root, bez lietotāja piekrišanas dialoga, bez VPN atļaujas. Aptauja ar taimeri ir alternatīva, pie kuras cilvēki ķeras, un tā tērē akumulatoru, vienlaikus palaižot garām īsāku par intervālu pāreju, un tieši to pāreju lietotājs arī ziņo.

Ko atbilde joprojām nevar pateikt

Neviens no šiem API neziņo, vai tunelis ir vesels, kurā serverī tas beidzas, vai konkrētas lietotnes satiksme ir tajā. VPN tīkls var pastāvēt bez lietojamiem maršrutiem, un tunelis katrai lietotnei var izslēgt lietotni, kas uzdod jautājumu.

Tāpēc pierakstiet, ko pārbaude redzēja, ne tikai Būla vērtību. Pierakstiet tīkla identifikatoru, deklarētos transportus un to, vai šis tīkls bija aktīvs. Ziņojums, kurā teikts «VPN nav atrasts», ir nederīgs, savukārt ziņojums, kurā teikts, ka tīkls deklarēja TRANSPORT_VPN, bet getActiveNetwork() atgrieza Wi-Fi, tieši norāda uz maršrutēšanu vai izslēgšanas sarakstu, un tas ir labojums, ar ko kāds var strādāt.

Saistītie raksti