Su aplicación llama a VpnService.prepare, recibe null, construye un túnel y el descriptor parece válido, pero nada llega al servidor. O prepare devuelve un Intent, el usuario lo aprueba y establish sigue fallando. Ambos resultados suelen significar lo mismo: otra aplicación ya posee la interfaz VPN de este usuario, y sus llamadas protect marcan sockets que su túnel no puede usar. Recorra las comprobaciones siguientes en orden y deténgase en la primera que falle, porque un paso posterior no puede reparar uno anterior.

Compruebe la plaza VPN antes de tocar protect

Android da a cada usuario exactamente una interfaz VPN activa. Es una regla de la plataforma, no un permiso que su aplicación pueda solicitar, así que dos clientes que quieran ser el túnel no pueden coexistir.

Una VPN siempre activa configurada para otra aplicación es el caso más difícil, porque el sistema mantiene esa aplicación conectada y rechaza una toma de control. Un filtro de tráfico como AdGuard, un cliente corporativo o una VPN de consumo pueden ocupar la plaza mientras el usuario ha olvidado que está instalado. Pida al usuario que abra Ajustes y lea la entrada que aparece en VPN; el nombre que hay allí es el propietario con el que compite. Hasta que esa entrada se desconecte o se borre el ajuste de siempre activa, su VpnService no puede convertirse en la interfaz.

Paso 1: lea qué devuelve prepare

VpnService.prepare responde a una sola pregunta: ¿puede esta aplicación convertirse en la VPN? Devuelve null cuando ya existe consentimiento, y un Intent cuando el usuario aún debe aprobarlo, que debe lanzar con startActivityForResult. Trate un retorno null como permiso para continuar, no como prueba de que establish tendrá éxito.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

Si la ruta del Intent vuelve y el usuario ha rechazado, prepare seguirá devolviendo un Intent en el siguiente intento. Registre el código de resultado en lugar de reintentar en bucle, porque un consentimiento rechazado es una decisión del usuario, no un error transitorio.

Paso 2: confirme que la interfaz pertenece a su aplicación

Tras establish, el ParcelFileDescriptor que recibe es su prueba de propiedad, y merece la pena comprobarlo antes de cualquier trabajo con sockets. Un descriptor null o ya cerrado significa que la interfaz nunca se creó, incluso si no apareció ninguna excepción. Compare el nombre de la interfaz del builder con el que el sistema informa para la VPN activa, usando dumpsys en un dispositivo de prueba. Si difieren, el túnel que construyó no es el túnel que transporta tráfico.

adb shell dumpsys connectivity

La misma discrepancia explica un icono conectado con tráfico muerto. El sistema muestra el túnel de la otra aplicación, mientras su código tiene un descriptor de una interfaz que no posee.

Paso 3: llame a protect antes de connect

Una vez existe la interfaz, los sockets que abre su aplicación se enrutan al túnel por defecto. El transporte exterior no puede seguir esa regla o el paquete encapsulado volvería a la interfaz, así que VpnService protect marca un socket para saltarse la VPN y usar la red subyacente. La llamada devuelve un booleano, e ignorarlo es como acabas con un socket que parece sano y no transporta nada.

Llame a protect antes de connect o bind, nunca después. La protección debe estar en su sitio antes de que el socket se conecte o se enlace, porque un socket conectado ya eligió su ruta y un protect posterior no la mueve de forma fiable. Si protect devuelve false, deténgase y vuelva a comprobar la propiedad: falla cuando la interfaz no pertenece a su aplicación, que es exactamente el estado que deja otra VPN. Un retorno false es una señal para volver al Paso 1, no para reintentar el mismo socket.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

Paso 4: enlace un proceso cuando un socket sea incómodo

Algunos transportes no son un solo socket: una biblioteca puede abrir varios, o puede que quiera que cada conexión de un proceso auxiliar use la misma salida. ConnectivityManager.bindProcessToNetwork enlaza el proceso a una red elegida, y el antiguo setProcessDefaultNetwork hacía lo mismo antes de quedar obsoleto. Solicite la conexión subyacente con NET_CAPABILITY_NOT_VPN para que el enlace seleccione la red bajo su túnel y no el túnel mismo.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

Enlazar un proceso no es una segunda VPN. Elige una salida para ese proceso, lo que resulta útil para su transporte e inútil para ejecutar dos túneles a la vez.

Arregle el dispositivo del usuario, no la ruta del código

No hay ninguna API que permita a dos aplicaciones compartir la plaza VPN, así que la solución está en el estado del dispositivo. Ofrezca al usuario dos caminos concretos: desconecte o borre el siempre activa de la otra aplicación para que su prepare pueda tomar el relevo, o abra los ajustes por aplicación de esa aplicación y excluya su aplicación de su túnel. La exclusión mantiene el filtro funcionando para todo lo demás y permite que su aplicación use la conexión ordinaria, que a menudo es el resultado que el usuario realmente quiere.

Escriba ambos caminos en su documentación de soporte, porque los usuarios rara vez saben qué aplicación posee la VPN. Pídales que envíen el nombre de la VPN activa de Ajustes y los resultados de prepare, establish y protect de sus registros, y en un minuto podrá decir qué paso falló. Luego verifique con tráfico real: una consulta de nombre, una conexión TCP y una petición a través del túnel, no solo un icono conectado. Un descriptor válido y un indicador verde prueban la propiedad en el mejor de los casos, nunca que el transporte haya sobrevivido a las reglas de enrutamiento.

Si las cuatro comprobaciones pasan y el tráfico sigue muriendo, la propiedad ya no es su problema. Pase al enrutamiento y a la accesibilidad del servidor, y pruebe las mismas credenciales desde un dispositivo sin ninguna aplicación VPN instalada.

Artículos relacionados