VPN

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 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. Android 4.0 (nivel de API 14) introdujo las APIs para que los desarrolladores de apps pudieran proporcionar sus propias soluciones de VPN. Empaquetas tu solución de VPN en una app que las personas instalan en el dispositivo. Los desarrolladores suelen compilar 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 activas y por app) y no se abarca 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 VPN activa. Android muestra los siguientes componentes de IU para conexiones VPN:

  • Antes de que una app de VPN pueda activarse por primera vez, el sistema muestra un diálogo de solicitud de conexión. El diálogo solicita a la persona que usa el dispositivo que confirme que confía en la VPN y acepta la solicitud.
  • La pantalla de configuración de VPN (Configuración > Internet y redes > VPN) muestra las apps de VPN en las que una persona aceptó las solicitudes de conexión. Hay un botón para configurar opciones del sistema u olvidar la VPN.
  • La bandeja de Configuración rápida muestra un panel de información cuando una conexión está activa. Cuando se presiona la etiqueta, se muestra un diálogo con más información y un vínculo a la 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, tu solución podría necesitar 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 activada 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 las estadísticas de la red. Si presionas la notificación, tu app se abre en primer plano. Quita la notificación después de que el servicio quede inactivo.

Servicio de VPN

Tu app conecta las redes del sistema para 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 realizas un seguimiento del estado de la conexión. Tu servicio VPN hereda de VpnService.

El servicio también actúa como tu contenedor para las conexiones de puerta de enlace de VPN y sus interfaces de dispositivos locales. Tu instancia de servicio llama a los métodos VpnService.Builder para establecer una nueva interfaz local.

Figura 1: Cómo conecta VpnService las redes de Android a la puerta de enlace de VPN
Diagrama de arquitectura de bloques que muestra cómo VpnService crea una interfaz TUN local en las redes del sistema.

Tu 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 los 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á de forma automática un servicio existente.

Agregar un servicio

Para agregar un servicio VPN a tu app, crea un servicio de Android que herede de VpnService. Declara el servicio VPN en el archivo de manifiesto de tu app con lo siguiente:

  • Protege el servicio con el permiso BIND_VPN_SERVICE para que solo el sistema pueda vincularse a él.
  • Publicita 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 ejecuta la VPN siempre activada.

Prepara un servicio

Llama a VpnService.prepare() para preparar la app para que se convierta en el servicio de VPN actual del usuario. Si la persona que usa el dispositivo aún no ha otorgado permiso para tu app, el método muestra 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 acceso a la cámara o a los contactos. Si la app ya está preparada, el método muestra 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 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 del servicio.

Conecta un servicio

Una vez que se ejecuta el servicio, puedes establecer una interfaz local nueva que esté conectada a una puerta de enlace de VPN. Para solicitar permiso y conectarte con tu servicio a la puerta de enlace de VPN, debes completar los pasos en el siguiente orden:

  1. Llama a VpnService.prepare() para solicitar permiso (cuando sea necesario).
  2. Llama a VpnService.protect() para mantener el socket del túnel de la app fuera de la VPN del sistema y evitar una conexión circular.
  3. Llama a DatagramSocket.connect() para conectar el socket del túnel de la app a la puerta de enlace de VPN.
  4. Llama a los métodos VpnService.Builder a fin de configurar una nueva interfaz TUN local en el dispositivo para el tráfico VPN.
  5. 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 la interfaz.

Por lo general, una puerta de enlace de VPN sugiere configuraciones 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:

Kotlin

// 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()

Java

// Configure a new interface from our VpnService instance. This must be done
// from inside a VpnService.
VpnService.Builder builder = new VpnService.Builder();

// Create a local TUN interface using predetermined addresses. In your app,
// you typically use values returned from the VPN gateway during handshaking.
ParcelFileDescriptor 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 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, la 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 deseas que el sistema envíe tráfico a través de la interfaz de VPN. Las rutas filtran por direcciones de destino. Para aceptar todo el tráfico, configura una ruta abierta como 0.0.0.0/0 o ::/0.

El método establish() muestra una instancia de ParcelFileDescriptor que tu app usa para leer y escribir paquetes hacia y desde el búfer de la interfaz. El método establish() muestra null si tu app no está preparada o si alguien revoca el permiso.

Ciclo de vida del servicio

Tu app debe realizar un seguimiento del estado de la VPN seleccionada del sistema y las conexiones activas. Actualiza la interfaz de usuario (IU) de tu app para mantener a la persona que usa el dispositivo al tanto de cualquier cambio.

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 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 versiones posteriores. Si admites estos niveles de API, debes realizar la transición de tu servicio al primer plano llamando a Service.startForeground(). Si deseas 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 ocurra en el subproceso principal. Cuando el sistema llama a este método, una interfaz de red alternativa ya está enrutando el tráfico. Puedes deshacerte de forma segura de los siguientes recursos:

VPN siempre activada

Android puede iniciar un servicio de VPN cuando se inicia el dispositivo y mantenerlo en ejecución mientras está encendido. Esta función se denomina 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, tu servicio de VPN es responsable de la conexión entre la VPN y la puerta de enlace. 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 activas se desconectan o no se pueden conectar, las personas ven una notificación que no se puede descartar. Cuando se presiona la notificación, se muestra un diálogo con más detalles. La notificación desaparece cuando la VPN se vuelve a conectar o cuando alguien desactiva la opción de VPN siempre activada.
  • La VPN siempre activada permite que la persona que usa un dispositivo bloquee las conexiones de red que no usan la VPN. Cuando activas 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 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 la app:

  1. Inhabilita cualquier IU que desconecte la conexión porque el sistema y la app de Configuración controlan la conexión.
  2. Guarda cualquier configuración entre cada inicio de la app y configura una conexión con la configuración más reciente. Como el sistema inicia tu app a pedido, es posible que la persona que usa el dispositivo no siempre desee configurar una conexión.

También puedes usar configuraciones administradas 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 una instancia de servicio que inicia, puedes suponer que el sistema inició servicios sin marcar para una VPN siempre activa. Veamos un ejemplo:

  1. Crea una instancia Intent para iniciar el servicio VPN.
  2. Marca el servicio VPN poniendo un servicio adicional en el intent.
  3. En el método onStartCommand() del servicio, busca la marca en los servicios adicionales del argumento intent.

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 admite actualmente 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 del 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 VPN. Puedes crear una lista permitida o una lista no permitida, pero no ambas. Si no creas listas permitidas o no permitidas, el sistema envía 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. Debes instalar una app en el dispositivo cuando la agregues a una lista.

Kotlin

// 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()

Java

// The apps that will have access to the VPN.
String[] appPackages = {
    "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.
VpnService.Builder builder = new VpnService.Builder();
PackageManager packageManager = getPackageManager();
for (String appPackage: appPackages) {
  try {
    packageManager.getPackageInfo(appPackage, 0);
    builder.addAllowedApplication(appPackage);
  } catch (PackageManager.NameNotFoundException e) {
    // The app isn't installed.
  }
}

// Complete the VPN interface config.
ParcelFileDescriptor localTunnel = builder
    .addAddress("2001:db8::1", 64)
    .addRoute("::", 0)
    .establish();

Apps permitidas

Para agregar una app a la lista permitida, 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 estuviera en ejecución. Cuando la lista permitida está vacía, todas las aplicaciones usan la VPN.

Apps no permitidas

Para agregar una app a la lista no permitida, llama a VpnService.Builder.addDisallowedApplication(). Las apps no permitidas usan las redes del sistema como si la VPN no estuviera en ejecución; 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, su tráfico de red 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.