Android proporciona APIs para que los desarrolladores creen soluciones de red privada virtual (VPN). Después de leer esta guía, sabrás cómo desarrollar y probar tu propio cliente de VPN para dispositivos con Android.
Descripción general
Las VPN permiten que los dispositivos que no están físicamente en una red accedan a ella de forma segura.
Android incluye un cliente de VPN integrado (PPTP y L2TP/IPSec), que a veces se denomina VPN heredada. En Android 4.0 (nivel de API 14), se introdujeron APIs para que los desarrolladores de apps pudieran proporcionar sus propias soluciones de VPN. Empaqueta tu solución de VPN en una app que las personas instalen en el dispositivo. Por lo general, los desarrolladores crean una app de VPN por uno de los siguientes motivos:
- Ofrecer protocolos de VPN que el cliente integrado no admite
- Ayudar a las personas a conectarse a un servicio VPN sin una configuración compleja
En el resto de esta guía, se explica cómo desarrollar apps de VPN (incluidas las VPN siempre activadas y por app) y no se incluye el cliente de VPN integrado.
Experiencia del usuario
Android proporciona una interfaz de usuario (IU) para ayudar a alguien a configurar, iniciar y detener tu solución de VPN. La IU del sistema también informa a la persona que usa el dispositivo sobre una conexión de VPN activa. Android muestra los siguientes componentes de la IU para las conexiones de VPN:
- Antes de que una app de VPN pueda activarse por primera vez, el sistema muestra un diálogo de solicitud de conexión. En el diálogo, se le solicita a la persona que usa el dispositivo que confirme que confía en la VPN y que acepte la solicitud.
- En la pantalla de configuración de VPN (Configuración > Internet y redes > VPN), se muestran las apps de VPN en las que una persona aceptó solicitudes de conexión. Hay un botón para configurar las opciones del sistema o para olvidar la VPN.
- La bandeja de Configuración rápida muestra un panel de información cuando hay una conexión activa. Si presionas la etiqueta, se mostrará un diálogo con más información y un vínculo a Configuración.
- La barra de estado incluye un ícono de VPN (clave) para indicar una conexión activa.
Tu app también debe proporcionar una IU para que la persona que usa el dispositivo pueda configurar las opciones de tu servicio. Por ejemplo, es posible que tu solución deba capturar la configuración de autenticación de la cuenta. Las apps deben mostrar las siguientes IU:
- Controles para iniciar y detener manualmente una conexión. La VPN siempre activa puede conectarse cuando sea necesario, pero permite que las personas configuren la conexión la primera vez que usen tu VPN.
- Una notificación no descartable cuando el servicio está activo. La notificación puede mostrar el estado de la conexión o proporcionar más información, como estadísticas de red. Si presionas la notificación, tu app se abre en primer plano. Quita la notificación después de que el servicio se vuelva inactivo.
Servicio de VPN
Tu app conecta las herramientas de redes del sistema de un usuario (o un perfil de trabajo) a una puerta de enlace de VPN. Cada usuario (o perfil de trabajo) puede ejecutar una app de VPN diferente. Creas un servicio de VPN que el sistema usa para iniciar y detener tu VPN, y hacer un seguimiento del estado de la conexión. Tu servicio de VPN hereda de
VpnService.
El servicio también actúa como contenedor para las conexiones de la puerta de enlace de VPN y
sus interfaces de dispositivos locales. La instancia de servicio llama a los métodos VpnService.Builder para establecer una nueva interfaz local.
VpnService conecta las redes de Android
a la puerta de enlace de VPNTu app transfiere los siguientes datos para conectar el dispositivo a la puerta de enlace VPN:
- Lee los paquetes IP salientes del descriptor de archivos de la interfaz local, los encripta y los envía a la puerta de enlace de VPN.
- Escribe paquetes entrantes (recibidos y desencriptados de la puerta de enlace de VPN) en el descriptor de archivos de la interfaz local.
Solo hay un servicio activo por usuario o perfil. Si inicias un servicio nuevo, se detendrá automáticamente el existente.
Agregar un servicio
Para agregar un servicio de VPN a tu app, crea un servicio de Android que herede de
VpnService. Declara el servicio de VPN en el archivo de manifiesto de tu app con las siguientes adiciones:
- Protege el servicio con el permiso
BIND_VPN_SERVICEpara que solo el sistema pueda vincularse a tu servicio. - Anuncia el servicio con el filtro de intents
"android.net.VpnService"para que el sistema pueda encontrarlo.
En este ejemplo, se muestra cómo puedes declarar el servicio en el archivo de manifiesto de tu app:
<service android:name=".MyVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
Ahora que tu app declara el servicio, el sistema puede iniciar y detener automáticamente el servicio de VPN de tu app cuando sea necesario. Por ejemplo, el sistema controla tu servicio cuando se ejecuta la VPN siempre activa.
Prepara un servicio
Para preparar la app para que se convierta en el servicio de VPN actual del usuario, llama a VpnService.prepare(). Si la persona que usa el dispositivo aún no otorgó permiso para tu app, el método devuelve un intent de actividad.
Usa ese intent para iniciar una actividad del sistema que solicita permiso. El sistema muestra un diálogo similar a otros diálogos de permisos, como el de acceso a la cámara o a los contactos. Si tu app ya está preparada, el método devuelve
null.
Solo una app puede ser el servicio VPN preparado actual. Siempre llama a VpnService.prepare(), ya que es posible que una persona haya configurado una app diferente como el servicio de VPN desde la última vez que tu app llamó al método. Para obtener más información, consulta
la sección Ciclo de vida de los servicios.
Conecta un servicio
Una vez que el servicio esté en ejecución, podrás establecer una nueva interfaz local que esté conectada a una puerta de enlace de VPN. Para solicitar permiso y conectar tu servicio a la puerta de enlace de VPN, debes completar los pasos en el siguiente orden:
- Llama a
VpnService.prepare()para solicitar permiso (cuando sea necesario). - Llama a
VpnService.protect()para mantener el socket del túnel de tu app fuera de la VPN del sistema y evitar una conexión circular. - Llama a
DatagramSocket.connect()para conectar el socket del túnel de tu app a la puerta de enlace de VPN. - Llama a los métodos
VpnService.Builderpara configurar una nueva interfaz TUN local en el dispositivo para el tráfico de VPN. - Llama a
VpnService.Builder.establish()para que el sistema establezca la interfaz TUN local y comience a enrutar el tráfico a través de ella.
Una puerta de enlace de VPN suele sugerir parámetros de configuración para la interfaz TUN local durante
el protocolo de enlace. Tu app llama a los métodos VpnService.Builder para configurar un servicio, como se muestra en el siguiente ejemplo:
// Configure a new interface from our VpnService instance. This must be done // from inside a VpnService. val builder = Builder() // Create a local TUN interface using predetermined addresses. In your app, // you typically use values returned from the VPN gateway during handshaking. val localTunnel = builder .addAddress("192.168.2.2", 24) .addRoute("0.0.0.0", 0) .addDnsServer("192.168.1.1") .establish()
En el ejemplo de la sección VPN por app, se muestra una configuración de IPv6 que incluye
más opciones. Debes agregar los siguientes valores de VpnService.Builder
antes de poder establecer una interfaz nueva:
addAddress()- Agrega al menos una dirección IPv4 o IPv6 junto con una máscara de subred que el sistema asigne como la dirección de la interfaz TUN local. Por lo general, tu app recibe las direcciones IP y las máscaras de subred de una puerta de enlace de VPN durante el protocolo de enlace.
addRoute()- Agrega al menos una ruta si quieres que el sistema envíe tráfico a través de la interfaz
de la VPN. Las rutas filtran por direcciones de destino. Para aceptar todo el tráfico, establece una
ruta abierta, como
0.0.0.0/0o::/0.
El método establish() devuelve una instancia de ParcelFileDescriptor que tu app usa para leer y escribir paquetes desde y hacia el búfer de la interfaz. El método establish() devuelve null si tu app no está preparada o si alguien revoca el permiso.
Ciclo de vida del servicio
Tu app debe hacer un seguimiento del estado de la VPN seleccionada del sistema y de las conexiones activas. Actualiza la interfaz de usuario (IU) de tu app para que la persona que usa el dispositivo esté al tanto de los cambios.
Cómo iniciar un servicio
Tu servicio VPN se puede iniciar de las siguientes maneras:
- Tu app inicia el servicio, normalmente porque una persona presionó un botón de conexión.
- El sistema inicia el servicio porque la VPN siempre activada está encendida.
Tu app inicia el servicio de VPN pasando un intent a startService(). Para obtener más información, consulta Cómo iniciar un servicio.
El sistema inicia tu servicio en segundo plano llamando a onStartCommand(). Sin embargo, Android impone restricciones a las apps en segundo plano en la versión 8.0 (nivel de API 26) o posterior. Si admites estos niveles de API, debes llamar a Service.startForeground() para que tu servicio pase a primer plano. Para obtener más información, consulta Cómo ejecutar un servicio en primer plano.
Cómo detener un servicio
Una persona que usa el dispositivo puede detener su servicio usando la IU de tu app. Detén el servicio en lugar de solo cerrar la conexión. El sistema también detiene una conexión activa cuando la persona que usa el dispositivo hace lo siguiente en la pantalla de VPN de la app de Configuración:
- desconecta la app de VPN o se olvida de ella
- apaga la VPN siempre activada para una conexión activa
El sistema llama al método onRevoke() de tu servicio, pero es posible que esta llamada no se realice en el subproceso principal. Cuando el sistema llama a este método, una interfaz de red alternativa ya está enrutando el tráfico. Puedes desechar de forma segura
los siguientes recursos:
- Llama a
DatagramSocket.close()para cerrar el socket del túnel protegido a la puerta de enlace de VPN. - Llama a
ParcelFileDescriptor.close()para cerrar el descriptor de archivo de parcel (no es necesario que lo vacíes).
VPN siempre activada
Android puede iniciar un servicio de VPN cuando se inicia el dispositivo y mantenerlo en ejecución mientras el dispositivo está encendido. Esta función se llama VPN siempre activa y está disponible en Android 7.0 (nivel de API 24) o versiones posteriores. Si bien Android mantiene el ciclo de vida del servicio, es tu servicio de VPN el responsable de la conexión de la puerta de enlace de VPN. La función de VPN siempre activada también puede bloquear las conexiones que no usan la VPN.
Experiencia del usuario
En Android 8.0 o versiones posteriores, el sistema muestra los siguientes diálogos para que la persona que usa el dispositivo conozca la VPN siempre activa:
- Cuando las conexiones de VPN siempre activada se desconectan o no se pueden conectar, las personas ven una notificación que no se puede descartar. Si presionas la notificación, se muestra un diálogo con más información. La notificación desaparece cuando la VPN se vuelve a conectar o alguien desactiva la opción de VPN siempre activada.
- La VPN siempre activa permite que la persona que usa un dispositivo bloquee cualquier conexión de red que no use la VPN. Cuando se activa esta opción, la app de Configuración advierte a las personas que no tienen conexión a Internet antes de que se conecte la VPN. La app de Configuración le solicita a la persona que usa el dispositivo que continúe o cancele.
Debido a que el sistema (y no una persona) inicia y detiene una conexión siempre activa, debes adaptar el comportamiento y la interfaz de usuario de tu app:
- Inhabilita cualquier IU que desconecte la conexión, ya que el sistema y la app de Configuración controlan la conexión.
- Guarda cualquier configuración entre cada inicio de la app y configura una conexión con los parámetros más recientes. Como el sistema inicia tu app a pedido, es posible que la persona que usa el dispositivo no siempre quiera configurar una conexión.
También puedes usar parámetros de configuración administrados para configurar una conexión. Las configuraciones administradas ayudan a un administrador de TI a configurar tu VPN de forma remota.
Detección de la función siempre activada
Android no incluye APIs para confirmar si el sistema inició tu servicio de VPN. Sin embargo, cuando tu app marca las instancias de servicio que inicia, puedes suponer que el sistema inició servicios sin marcar para la VPN siempre activa. Veamos un ejemplo:
- Crea una instancia
Intentpara iniciar el servicio VPN. - Marca el servicio VPN poniendo un servicio adicional en el intent.
- En el método
onStartCommand()del servicio, busca la marca en los extras del argumentointent.
Conexiones bloqueadas
Una persona que usa el dispositivo (o un administrador de TI) puede forzar todo el tráfico para que use la VPN. El sistema bloquea cualquier tráfico de red que no use la VPN. Las personas que usan el dispositivo pueden encontrar el interruptor Bloquear conexiones sin VPN en el panel de opciones de VPN en Configuración.
Inhabilita la función siempre activada
Si tu app no puede admitir la VPN siempre activa, puedes inhabilitarla (en Android 8.1 o versiones posteriores) configurando los metadatos del servicio SERVICE_META_DATA_SUPPORTS_ALWAYS_ON en false. En el siguiente ejemplo de manifiesto de la app, se muestra cómo agregar el elemento de metadatos:
<service android:name=".MyVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
android:value=false/>
</service>
Cuando tu app inhabilita la VPN siempre activada, el sistema inhabilita los controles de la IU de opciones en Configuración.
VPN por app
Las apps de VPN pueden filtrar qué apps instaladas pueden enviar tráfico a través de la conexión de VPN. Puedes crear una lista de permitidos o una de no permitidos, pero no ambas. Si no creas listas de apps permitidas o no permitidas, el sistema enviará todo el tráfico de red a través de la VPN.
Tu app de VPN debe configurar las listas antes de establecer la conexión. Si necesitas cambiar las listas, establece una nueva conexión de VPN. La app debe estar instalada en el dispositivo cuando la agregues a una lista.
// The apps that will have access to the VPN. val appPackages = arrayOf( "com.android.chrome", "com.google.android.youtube", "com.example.a.missing.app" ) // Loop through the app packages in the array and confirm that the app is // installed before adding the app to the allowed list. val builder = Builder() for (appPackage in appPackages) { try { packageManager.getPackageInfo(appPackage, 0) builder.addAllowedApplication(appPackage) } catch (e: PackageManager.NameNotFoundException) { // The app isn't installed. } } // Complete the VPN interface config. val localTunnel = builder .addAddress("2001:db8::1", 64) .addRoute("::", 0) .establish()
Apps permitidas
Para agregar una app a la lista de entidades permitidas, llama a VpnService.Builder.addAllowedApplication(). Si
la lista incluye una o más apps, solo las apps de la lista usarán la VPN.
Todas las demás apps (que no están en la lista) usan las redes del sistema como si la VPN
no se estuviera ejecutando. Cuando la lista permitida está vacía, todas las aplicaciones usan la VPN.
Apps no permitidas
Para agregar una app a la lista de apps no permitidas, llama a VpnService.Builder.addDisallowedApplication().
Las apps no permitidas usan las redes del sistema como si la VPN no se estuviera ejecutando; todas las demás apps usan la VPN.
Omite la VPN
Tu VPN puede permitir que las apps la omitan y seleccionen su propia red. Para omitir la VPN, llama a VpnService.Builder.allowBypass() cuando establezcas una interfaz de VPN. No puedes cambiar este valor después de iniciar el servicio de
VPN. Si una app no vincula su proceso o un socket a una red específica, el tráfico de red de la app continúa a través de la VPN.
Las apps que se vinculan a una red específica no tienen conexión cuando alguien
bloquea el tráfico que no pasa por la VPN. Para enviar tráfico a través de una red específica, las apps llaman a métodos, como ConnectivityManager.bindProcessToNetwork() o Network.bindSocket(), antes de conectar el socket.
Código de muestra
El Proyecto de código abierto de Android incluye una app de muestra llamada ToyVPN. Esta app muestra cómo configurar y conectar un servicio VPN.