Å oppdage ein VPN på Android: kvifor tun0 og ping villeder
Eit grensesnittnamn er ein konvensjon; ein ping beviser at éin vert var nåbar. Spør NetworkCapabilities kva eit nettverk erklærer, og reager deretter på callbacks.
Sjekken dei fleste Android-appar brukar for å oppdage ein VPN, er eit grensesnittnamn, og han har aldri vore ein VPN-sjekk. Han rapporterer at det finst eit eller anna grensesnitt med ein streng utviklaren venta, som på éi eining tyder OpenVPN og på ei anna tyder noko heilt urelatert. Ein ping er same slags bevis: han beviser at éin destinasjon svara, ikkje at ein tunnel bar pakken. Det plattforma faktisk eksponerer, er eit sett nettverk og transportane kvart av dei erklærer, og det er eit anna spørsmål med eit anna svar.
Myte: viss tun0 finst, er ein VPN oppe
Android VPN-appar er VpnService-implementasjonar som gir systemet ein tun-fildeskriptor. Grensesnittnamnet kjem frå den som oppretta det: OpenVPN produserer vanlegvis tun0, PPTP og L2TP produserer ppp0, WireGuard namngir grensesnittet etter tunnelen, og ein IPsec-tunnel har kanskje ikkje noko konvensjonelt namn i det heile. Å lese /proc/net/dev eller telje NetworkInterface-objekt leitar derfor etter ein konvensjon, ikkje etter eit faktum.
Begge feilretningane er levande. Ei eining kan ha tun0 for ein lokal tunnel som ikkje har noko med VPN-en til brukaren å gjere, og ein heilt sunn WireGuard-tunnel kan vere usynleg for ein sjekk som berre kjenner strengen tun0. Falske positive og falske negative i same funksjon gjer han ubrukeleg som diagnostikk, fordi ein støttebillett som seier «ingen VPN funnen» ikkje kan stolast på i noka retning.
Tilgangen er også blitt snevra inn. Nyare Android-utgåver avgrensar kva ein vanleg app kan lese frå /proc, så ein namnebasert sjekk som verka på Android 8, kan returnere ei tom liste på ei seinare eining utan noka kodeendring. Når sjekken stille feilar ope, konkluderer appen at det ikkje finst nokon VPN på maskinvare der tunnelen er i orden.
Myte: ein vellukka ping tyder at tunnelen ber trafikk
Ein ping til ein intern vert lukkast når det finst ei rute til den verten. Med delt tunnelling kan verten vere nåbar over Wi-Fi medan tunnelen er oppe og gjer noko heilt anna. Med ein per-app-VPN kan appen som pingar vere utelukka frå tunnelen, så ICMP-ekkoet går utanfor han og kjem tilbake, noko som beviser det motsette av det sjekken føreset.
ICMP og TCP blir også filtrerte separat. Ein tunnel som tillèt ekko-førespurnader men droppar TCP-porten ein teneste treng, vil bestå ping-testen og feile i applikasjonen, og det er ein brannmurregel på den andre sida, ikkje ein eigenskap ved eininga. Den einaste ærlege lesinga av ein vellukka ping er at denne destinasjonen svara denne protokollen i dette augeblinken.
Kva plattforma faktisk rapporterer
Frå API 21 og utover returnerer ConnectivityManager.getAllNetworks() kvart nettverk systemet kjenner, og getNetworkCapabilities(network) rapporterer kva det nettverket erklærer. Eit VPN-nettverk ber TRANSPORT_VPN. Å gå gjennom alle nettverk svarar på eitt spørsmål: er nokon VPN oppe på denne eininga akkurat no?
Eit andre, snevrare spørsmål treng eit anna kall. getActiveNetwork() returnerer nettverket appens trafikk ville bruke akkurat no, og å sjekke den same eigenskapen på det svarar: ville trafikken min gå gjennom ein VPN? Dei to kan vere usamde, og usemda er informativ. Ein VPN som er oppe men ikkje aktuell, peikar på per-app-ruting eller ein delt tunnel-konfigurasjon, ikkje på ein manglande tunnel.
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Vern begge kalla. Eigenskapar kan vere null for eit nettverk som forsvann mellom opplisting og spørjing, og det aktive nettverket kan vere null under ei overlevering. NET_CAPABILITY_NOT_VPN er nyttig i den andre retninga, for å stadfeste at eit nettverk definitivt ikkje er ein tunnel i staden for berre å mangle VPN-transportflagget.
NetworkInfo var den same myten med ein API
ConnectivityManager.TYPE_VPN med NetworkInfo er andre generasjon av denne sjekken, og han er utdatert av ein grunn. På moderne utgåver kan objektet som blir returnert for ein VPN vere null medan tunnelen er tilkopla, så kode som handsamar null som «ingen VPN» stilltiande rapporterer ein falsk negativ på einingane der svaret betyr mest.
Kringkastinga som vanlegvis følgjer han, er ikkje betre. CONNECTIVITY_CHANGE har ein dokumentert historie med å oversjå VPN-overgangar, inkludert bygg der han berre vart utløyst når ein VPN kopla til eller frå, i staden for ved kvar nettverksendring. Ein lyttar bygd på han produserer ein tilstandsvariabel som driv bort frå synet til plattforma til neste fulle gjenoppkopling.
Reager på callbacks i staden for å polle
Registrer ein NetworkRequest som krev TRANSPORT_VPN og fjernar NET_CAPABILITY_NOT_VPN, og kall deretter registerNetworkCallback med den førespurnaden. Då fortel onAvailable og onLost deg når erklæringa endrar seg, og det er hendinga du faktisk bryr deg om. Avregistrer callbacken når komponenten stoppar, elles held appen ein referanse til ein daud skjerm og callbacken held fram med å utløysast.
NetworkRequest req = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(req, callback);
Dette krev ACCESS_NETWORK_STATE, som er ei normal løyve: ingen root, ingen brukarsamtykkedialog, inga VPN-løyve. Polling på ein timer er alternativet folk fell tilbake på, og det brenn batteri medan det framleis overser ein overgang som er kortare enn intervallet, og det er nettopp overgangen ein brukar rapporterer.
Kva svaret framleis ikkje kan fortelje deg
Ingen av desse API-ane rapporterer om tunnelen er sunn, kva server han blir avslutta på, eller om trafikken til ein bestemt app er inne i han. Eit VPN-nettverk kan eksistere utan brukbare ruter, og ein per-app-tunnel kan utelukke appen som stiller spørsmålet.
Så registrer kva sjekken såg, ikkje berre boolean. Logg nettverksidentifikatoren, dei erklærte transportane og om det nettverket var aktivt. Ein rapport som seier «ingen VPN funnen» er ubrukeleg, medan ein som seier at eit nettverk erklærte TRANSPORT_VPN men getActiveNetwork() returnerte Wi-Fi, peikar direkte på ruting eller på ei utelukkeliste, og det er ei retting nokon kan handle på.