La comprobación que la mayoría de las aplicaciones de Android usan para detectar una VPN es un nombre de interfaz, y nunca fue una comprobación de VPN. Informa de que existe alguna interfaz con una cadena que el desarrollador esperaba, lo que en un dispositivo significa OpenVPN y en otro significa algo sin relación alguna. Un ping es el mismo tipo de prueba: demuestra que un destino respondió, no que un túnel transportara el paquete. Lo que la plataforma expone realmente es un conjunto de redes y los transportes que cada una declara, y esa es otra pregunta con otra respuesta.

Mito: si existe tun0, hay una VPN activa

Las aplicaciones de VPN para Android son implementaciones de VpnService que entregan al sistema un descriptor de archivo tun. El nombre de la interfaz procede de quien la creó: OpenVPN suele producir tun0, PPTP y L2TP producen ppp0, WireGuard nombra su interfaz según el túnel, y un túnel IPsec puede no presentar ningún nombre convencional. Por tanto, leer /proc/net/dev o enumerar objetos NetworkInterface busca una convención, no un hecho.

Ambas direcciones de error están vivas. Un dispositivo puede tener tun0 para un túnel local que no tiene nada que ver con la VPN del usuario, y un túnel WireGuard perfectamente sano puede ser invisible para una comprobación que solo conoce la cadena tun0. Los falsos positivos y los falsos negativos en la misma función la hacen inservible como diagnóstico, porque un ticket de soporte que dice «no se detectó ninguna VPN» no es fiable en ninguna dirección.

El acceso también se ha estrechado. Las versiones más recientes de Android restringen lo que una aplicación normal puede leer de /proc, así que una comprobación basada en el nombre que funcionaba en Android 8 puede devolver una lista vacía en un dispositivo posterior sin ningún cambio de código. Cuando la comprobación falla en silencio hacia el lado abierto, la aplicación concluye que no hay VPN en un hardware donde el túnel está bien.

Mito: un ping correcto significa que el túnel transporta tráfico

Un ping a un host interno tiene éxito siempre que exista una ruta hacia ese host. Con túnel dividido, el host puede ser accesible por Wi-Fi mientras el túnel está activo y hace algo completamente distinto. Con una VPN por aplicación, la aplicación que hace el ping puede quedar excluida del túnel, así que el eco ICMP sale fuera de él y vuelve, lo que demuestra lo contrario de lo que la comprobación supone.

ICMP y TCP también se filtran por separado. Un túnel que permite solicitudes de eco pero descarta el puerto TCP que necesita un servicio pasará la prueba de ping y fallará en la aplicación, y eso es una regla de cortafuegos en el otro extremo, no una propiedad del dispositivo. La única lectura honesta de un ping correcto es que este destino respondió a este protocolo en este momento.

Qué informa realmente la plataforma

Desde la API 21, ConnectivityManager.getAllNetworks() devuelve todas las redes que el sistema conoce, y getNetworkCapabilities(network) informa de lo que declara esa red. Una red VPN lleva TRANSPORT_VPN. Recorrer todas las redes responde a una pregunta: ¿hay alguna VPN activa ahora mismo en este dispositivo?

Una segunda pregunta, más estrecha, necesita otra llamada. getActiveNetwork() devuelve la red que el tráfico de la aplicación usaría ahora, y comprobar la misma capacidad en ella responde: ¿pasaría mi tráfico por una VPN? Las dos pueden discrepar, y la discrepancia es informativa. Una VPN activa pero no actual apunta a enrutamiento por aplicación o a una configuración de túnel dividido, no a un túnel ausente.

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

Proteja ambas llamadas. Las capacidades pueden ser null para una red que desapareció entre el listado y la consulta, y la red activa puede ser null durante un traspaso. NET_CAPABILITY_NOT_VPN es útil en la otra dirección, para confirmar que una red no es en absoluto un túnel en lugar de solo carecer del indicador de transporte VPN.

NetworkInfo era el mismo mito con una API

ConnectivityManager.TYPE_VPN con NetworkInfo es la segunda generación de esta comprobación, y está obsoleta por una razón. En las versiones modernas el objeto devuelto para una VPN puede ser null mientras el túnel está conectado, así que el código que trata null como «sin VPN» informa en silencio de un falso negativo en los dispositivos donde la respuesta más importa.

La emisión que suele acompañarla no es mejor. CONNECTIVITY_CHANGE tiene una historia documentada de transiciones de VPN perdidas, incluidas compilaciones en las que solo se disparaba cuando una VPN se conectaba o desconectaba en lugar de en cada cambio de red. Un receptor construido sobre ella produce una variable de estado que se aleja de la visión de la plataforma hasta la siguiente reconexión completa.

Reaccione a las devoluciones de llamada en lugar de sondear

Registre una NetworkRequest que exija TRANSPORT_VPN y elimine NET_CAPABILITY_NOT_VPN, y luego llame a registerNetworkCallback con esa solicitud. onAvailable y onLost le dicen entonces cuándo cambia la declaración, que es el evento que realmente le importa. Anule el registro de la devolución de llamada cuando el componente se detenga, o la aplicación mantendrá una referencia a una pantalla muerta y la devolución de llamada seguirá disparándose.

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

Esto requiere ACCESS_NETWORK_STATE, que es un permiso normal: sin root, sin diálogo de consentimiento del usuario, sin permiso de VPN. Sondear con un temporizador es la alternativa a la que la gente recurre, y consume batería mientras sigue perdiendo una transición más corta que el intervalo, que es exactamente la transición que un usuario reporta.

Qué sigue sin poder decirle la respuesta

Ninguna de estas API informa de si el túnel está sano, en qué servidor termina o si el tráfico de una aplicación concreta está dentro. Una red VPN puede existir sin rutas utilizables, y un túnel por aplicación puede excluir a la aplicación que hace la pregunta.

Así que registre lo que vio la comprobación, no solo el booleano. Registre el identificador de red, los transportes declarados y si esa red estaba activa. Un informe que dice «no se detectó ninguna VPN» es inutilizable, mientras que uno que dice que una red declaró TRANSPORT_VPN pero getActiveNetwork() devolvió Wi-Fi apunta directamente al enrutamiento o a una lista de exclusiones, y eso es una corrección sobre la que alguien puede trabajar.

Artículos relacionados