La comprovació que la majoria d'aplicacions d'Android fan servir per detectar una VPN és un nom d'interfície, i mai no ha estat una comprovació de VPN. Informa que existeix alguna interfície amb una cadena que el desenvolupador esperava, cosa que en un dispositiu significa OpenVPN i en un altre una cosa sense cap relació. Un ping és la mateixa mena de prova: demostra que un destí va respondre, no que un túnel hagi portat el paquet. El que la plataforma exposa realment és un conjunt de xarxes i els transports que cadascuna declara, i això és una altra pregunta amb una altra resposta.

Mite: si tun0 existeix, hi ha una VPN activa

Les aplicacions de VPN per a Android són implementacions de VpnService que lliuren al sistema un descriptor de fitxer tun. El nom de la interfície prové de qui la va crear: OpenVPN sol produir tun0, PPTP i L2TP produeixen ppp0, WireGuard anomena la interfície pel túnel, i un túnel IPsec pot no presentar cap nom convencional. Per tant, llegir /proc/net/dev o enumerar objectes NetworkInterface busca una convenció, no un fet.

Totes dues direccions d'error són vives. Un dispositiu pot tenir tun0 per a un túnel local que no té res a veure amb la VPN de l'usuari, i un túnel WireGuard perfectament sa pot ser invisible per a una comprovació que només coneix la cadena tun0. Els falsos positius i els falsos negatius en la mateixa funció la fan inútil com a diagnòstic, perquè un tiquet de suport que diu «no s'ha detectat cap VPN» no es pot refiar en cap direcció.

L'accés també s'ha estret. Les versions més noves d'Android limiten què pot llegir una aplicació normal de /proc, de manera que una comprovació basada en el nom que funcionava a Android 8 pot retornar una llista buida en un dispositiu posterior sense cap canvi de codi. Quan la comprovació falla silenciosament en obert, l'aplicació conclou que no hi ha VPN en un maquinari on el túnel funciona bé.

Mite: un ping correcte vol dir que el túnel porta trànsit

Un ping a un amfitrió intern té èxit sempre que existeixi una ruta cap a aquest amfitrió. Amb túnel dividit, l'amfitrió pot ser abastable per Wi-Fi mentre el túnel està actiu i fa una cosa completament diferent. Amb una VPN per aplicació, l'aplicació que fa el ping pot quedar exclosa del túnel, de manera que l'eco ICMP en surt per fora i torna, cosa que demostra el contrari del que la comprovació suposa.

ICMP i TCP també es filtren per separat. Un túnel que permet peticions d'eco però descarta el port TCP que necessita un servei passarà la prova de ping i fallarà en l'aplicació, i això és una regla de tallafoc a l'altra banda, no una propietat del dispositiu. L'única lectura honesta d'un ping correcte és que aquest destí va respondre aquest protocol en aquest moment.

Què informa realment la plataforma

Des de l'API 21, ConnectivityManager.getAllNetworks() retorna totes les xarxes que el sistema coneix, i getNetworkCapabilities(network) informa què declara aquella xarxa. Una xarxa VPN porta TRANSPORT_VPN. Recórrer totes les xarxes respon una pregunta: hi ha cap VPN activa ara en aquest dispositiu?

Una segona pregunta, més estreta, necessita una crida diferent. getActiveNetwork() retorna la xarxa que el trànsit de l'aplicació faria servir ara, i comprovar-hi la mateixa capacitat respon: el meu trànsit passaria per una VPN? Les dues poden discrepar, i la discrepància és informativa. Una VPN activa però no actual apunta a encaminament per aplicació o a una configuració de túnel dividit, no a un túnel que falta.

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

Protegiu totes dues crides. Les capacitats poden ser null per a una xarxa que ha desaparegut entre el llistat i la consulta, i la xarxa activa pot ser null durant un traspàs. NET_CAPABILITY_NOT_VPN és útil en l'altra direcció, per confirmar que una xarxa no és de cap manera un túnel en lloc de només mancar-li l'indicador de transport VPN.

NetworkInfo era el mateix mite amb una API

ConnectivityManager.TYPE_VPN amb NetworkInfo és la segona generació d'aquesta comprovació, i està obsolet per una raó. En les versions modernes l'objecte retornat per a una VPN pot ser null mentre el túnel està connectat, de manera que el codi que tracta null com a «sense VPN» informa silenciosament un fals negatiu en els dispositius on la resposta és més important.

L'emissió que normalment l'acompanya no és millor. CONNECTIVITY_CHANGE té una història documentada de transicions de VPN perdudes, incloent-hi compilacions on només s'activava quan una VPN es connectava o desconnectava en lloc de en cada canvi de xarxa. Un receptor construït sobre això produeix una variable d'estat que es desvia de la visió de la plataforma fins a la propera reconnexió completa.

Reaccioneu a les devolucions de trucada en lloc de consultar periòdicament

Registreu una NetworkRequest que requereixi TRANSPORT_VPN i elimini NET_CAPABILITY_NOT_VPN, i després cridueu registerNetworkCallback amb aquesta petició. Aleshores onAvailable i onLost us diuen quan canvia la declaració, que és l'esdeveniment que realment us importa. Cancel·leu el registre de la devolució de trucada quan el component s'atura, o l'aplicació mantindrà una referència a una pantalla morta i la devolució de trucada continuarà disparant-se.

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

Això necessita ACCESS_NETWORK_STATE, que és un permís normal: sense root, sense diàleg de consentiment de l'usuari, sense permís de VPN. Consultar amb un temporitzador és l'alternativa a què la gent recorre, i consumeix bateria tot i que encara perd una transició més curta que l'interval, que és exactament la transició que un usuari reporta.

Què encara no us pot dir la resposta

Cap d'aquestes API informa si el túnel és sa, en quin servidor acaba o si el trànsit d'una aplicació concreta hi és a dins. Una xarxa VPN pot existir sense rutes utilitzables, i un túnel per aplicació pot excloure l'aplicació que fa la pregunta.

Per tant, registreu què ha vist la comprovació, no només el booleà. Registreu l'identificador de xarxa, els transports declarats i si aquella xarxa era activa. Un informe que diu «no s'ha detectat cap VPN» és inútil, mentre que un que diu que una xarxa va declarar TRANSPORT_VPN però getActiveNetwork() va retornar Wi-Fi apunta directament a l'encaminament o a una llista d'exclusions, i això és una correcció sobre la qual algú pot treballar.

Articles relacionats