VpnService protect() o exclusión de la app: qué arregla el enrutamiento
Proteja el socket que transporta el túnel o excluya toda la aplicación: las tres condiciones que deciden qué alcance necesita su VPN de Android.
Tiene dos formas de mantener los paquetes del propio túnel fuera del túnel: proteger el socket de transporte o sacar toda la aplicación de la VPN. Resuelven problemas distintos, y elegir la más amplia porque es más fácil de cablear es justo cómo las aplicaciones pierden el acceso a la red local y el DNS dividido sin darse cuenta. Empiece por lo que debe quedarse fuera del túnel y luego elija el alcance más pequeño que lo consiga.
Qué quita protect() del túnel
VpnService.protect() es una llamada por socket. Le entrega el socket o el descriptor que transporta el túnel, y Android marca ese socket para que su tráfico use la red subyacente en lugar de la interfaz de la VPN. Todo lo demás que envía la aplicación sigue la ruta normal de la VPN.
Ese alcance estrecho es la clave. Un socket protegido no cambia cómo resuelve nombres su cliente HTTP, qué interfaz usan sus llamadas de analítica ni qué ocurre con una segunda conexión abierta más tarde.
Tres condiciones que deciden el alcance
La primera condición es si solo debe escapar el transporte del túnel. Si el resto del tráfico de la aplicación debe seguir pasando por la VPN, la protección del socket es el alcance correcto. La segunda condición es si usted controla la creación del socket. Una biblioteca que abre su propio socket muy adentro no le deja dónde llamar a protect() salvo que exponga un hook o acepte un socket ya construido. La tercera condición es si la aplicación debe llegar directamente a la red local o a un captive portal, lo que es un requisito distinto de mantener vivo el túnel.
Excluir la aplicación con addDisallowedApplication() es la herramienta tosca. Saca de la VPN cada conexión que hace la aplicación, incluidas las que quería inspeccionar, y en algunos dispositivos también cambia cómo se comporta el descubrimiento local.
Una tercera opción es atar el transporte a una red elegida con bindProcessToNetwork() o con un identificador de red a nivel de socket. Eso es útil cuando el dispositivo tiene Wi-Fi y móvil a la vez y quiere que el túnel vaya por un enlace de subida concreto.
Llámelo mientras se crea el transporte
Llame a protect() en el mismo bloque que crea el transporte, antes de connect() o send(). Si la llamada devuelve false, cierre el socket y haga fallar la configuración; continuar le deja un transporte cuyos paquetes pueden volver a entrar en su propia interfaz.
La forma es lo bastante pequeña para tenerla en un solo sitio:
DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
transport.close();
throw new IOException("transport not protected");
}
transport.connect(serverAddress);Tres detalles muerden aquí. Primero,el booleano es la única señal que recibe, así que registre el fallo con contexto suficiente para distinguir un consentimiento revocado de una llamada rechazada. Segundo,protect() cubre el socket, no el número de descriptor: si cierra el socket y más tarde reutiliza el mismo entero para otro socket, el nuevo socket no queda protegido. Tercero, cuando una biblioteca nativa abre el transporte, proteja el descriptor antes de que el código nativo se conecte, o la ruta puede estar ya elegida.La referencia de VpnService es el lugar donde confirmar qué sobrecargas ofrece realmente su nivel de API objetivo.
Pruebe la ruta bloqueada, no solo la ruta feliz
Verifique con el túnel levantado, porque es el único estado en el que los dos caminos pueden diferir. Envíe una petición por el transporte protegido y otra desde una ruta de código que nunca llamó a protect(), y luego compare lo que ve el extremo remoto: la conexión protegida debería llegar con la red subyacente como origen, mientras que la no protegida muestra la dirección propia del túnel y mueve sus contadores de bytes. En el dispositivo,ss o netstat muestra la misma división antes de que nada salga de la radio.
Pruebe la revocación por separado, porque un cambio de consentimiento derriba la interfaz sin tocar su código de socket. Revoque el consentimiento de la VPN o detenga el servicio, reconstruya el transporte y confirme que protect() sigue devolviendo true y que el transporte se reconecta en cuanto se vuelve a conceder el consentimiento.
Si la petición protegida sigue el túnel de todos modos, la llamada de protección probablemente ocurre después de que el socket ya se haya enrutado. Muévala antes y vuelva a probar.
Mantenga la decisión escrita junto a la configuración del transporte: qué sockets están protegidos, qué tráfico de la aplicación está excluido y qué hace la aplicación cuando falla la protección. Esa línea es lo que impide que una refactorización posterior amplíe la exclusión a toda la aplicación porque una sola prueba fallaba. También le da a la siguiente persona una forma de comprobar la intención sin hacer ingeniería inversa del manifiesto.