VPN

Android ofrece API 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 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 manera segura.

Android incluye un cliente VPN integrado (PPTP y L2TP/IPSec), que a veces se denomina VPN heredada. Android 4.0 (API nivel 14) introdujo API para que los desarrolladores de apps puedan proporcionar sus propias soluciones VPN. Empaquetas tu solución VPN en una app que las personas instalan 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

A lo largo de esta guía, se explica cómo desarrollar apps de VPN (incluidas VPN siempre activadas y por app) y no se cubre el cliente 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 acerca de 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 que acepta la solicitud.
  • En la pantalla de configuración de VPN (Configuración > Internet y redes > VPN), se muestran las apps de VPN donde una persona aceptó las solicitudes de conexión. Hay un botón para configurar las opciones del sistema u olvidar la VPN.
  • La bandeja Configuración rápida muestra un panel de información cuando una conexión está activa. Si presionas la etiqueta, se muestra 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, 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 se vuelva inactivo.

Servicio VPN

Tu app conecta las redes del sistema para un usuario (o un perfil de trabajo) a una puerta de enlace VPN. Cada usuario (o perfil de trabajo) puede ejecutar una app de VPN diferente. Creas un servicio 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 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 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 VPN.
  • Escribe los paquetes entrantes (recibidos y desencriptados de la puerta de enlace VPN) en el descriptor de archivos de la interfaz local.

Solo hay un servicio activo por usuario o perfil. Cuando se inicia un nuevo servicio, se detiene automáticamente un servicio existente.

Agrega 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 las siguientes adiciones:

  • Protege tu servicio con el permiso BIND_VPN_SERVICE para que solo el sistema pueda vincularse con él.
  • Publicita tu 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 VPN de tu app cuando sea necesario. Por ejemplo, el sistema controla tu servicio cuando ejecuta la VPN siempre activada.

Prepara un servicio

Si quieres preparar la app para que se convierta en el servicio VPN actual del usuario, llama a VpnService.prepare(). 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 tu app ya está preparada, el método muestra null.

Solo una app puede ser el servicio VPN preparado actual. Siempre llama a VpnService.prepare() porque una persona podría haber configurado una app diferente como el servicio 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

Cuando el servicio está en ejecución, puedes configurar una nueva interfaz local que esté conectada a una puerta de enlace VPN. Para solicitar permiso y conectar tu servicio a la puerta de enlace 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 tu app fuera de la VPN del sistema y evitar una conexión circular.
  3. Llama a DatagramSocket.connect() para conectar el túnel de tu app a la puerta de enlace VPN.
  4. Llama a los métodos VpnService.Builder para 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 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 configurar una nueva interfaz:

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 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 VPN. Las rutas filtran por direcciones de destino. Para aceptar todo el tráfico, configura una red abierta como 0.0.0.0/0 o ::/0.

El método establish() muestra una instancia ParcelFileDescriptor que tu app usa para leer y escribir paquetes en el búfer de la interfaz y desde él. 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 de 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 el servicio en segundo plano llamando a onStartCommand(). Sin embargo, Android impone restricciones a las apps en segundo plano en la versión 8.0 (API nivel 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(). Para obtener más información, consulta Ejecución de 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 vez 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 disponer de los siguientes recursos de forma segura:

VPN siempre activada

Android puede iniciar un servicio VPN cuando el dispositivo se inicia y mantenerlo activo mientras el dispositivo está encendido. Esta función se llama VPN siempre activada y está disponible en Android 7.0 (API nivel 24) o versiones posteriores. Si bien Android mantiene el ciclo de vida del servicio, tu servicio VPN es el 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 activada:

  • Cuando las conexiones VPN siempre activadas se desconectan o no se pueden conectar, las personas ven una notificación que no se puede descartar. Si se presiona la notificación, se muestra un diálogo que explica más. La notificación desaparece cuando la VPN se vuelve a conectar o alguien apaga la opción de VPN siempre activada.
  • La función VPN siempre activada 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 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 activada, debes adaptar el comportamiento y la interfaz de usuario de tu 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 última configuración. 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 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 API para confirmar si el sistema inició tu servicio VPN. Sin embargo, cuando tu app marca cualquier instancia de servicio que inicia, puedes suponer que el sistema inició servicios sin marcar para una VPN siempre activada. A continuación, te mostramos 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 botón 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 activada, puedes inhabilitarla (en Android 8.1 o versiones posteriores) configurando los metadatos del servicio SERVICE_META_DATA_SUPPORTS_ALWAYS_ON como 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 una VPN siempre activada, el sistema inhabilita los controles de 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, configura una nueva conexión VPN. Se debe instalar una app en el dispositivo cuando la agregas 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 usan la VPN. Todas las demás aplicaciones (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 configures una interfaz VPN. No puedes cambiar este valor después de iniciar tu servicio 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.

Ejemplo de código

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.