VPN

Android fournit des API permettant aux développeurs de créer des solutions de réseau privé virtuel (VPN). Après avoir lu ce guide, vous saurez comment développer et tester votre propre client VPN pour les appareils Android.

Présentation

Les VPN permettent aux appareils qui ne sont pas physiquement sur un réseau d'accéder au réseau de manière sécurisée.

Android inclut un client VPN intégré (PPTP et L2TP/IPSec), parfois appelé ancien VPN. Android 4.0 (niveau d'API 14) introduit des API permettant aux développeurs d'applications de fournir leurs propres solutions VPN. Vous empaquetez votre solution VPN dans une application que les utilisateurs installent sur l'appareil. Les développeurs créent généralement une application VPN pour l'une des raisons suivantes:

  • Proposer des protocoles VPN qui ne sont pas compatibles avec le client intégré
  • Aider les utilisateurs à se connecter à un service VPN sans configuration complexe

Le reste de ce guide explique comment développer des applications VPN (y compris les VPN toujours activés et par application) et ne couvre pas le client VPN intégré.

Expérience utilisateur

Android fournit une interface utilisateur (UI) pour aider les utilisateurs à configurer, démarrer et arrêter votre solution VPN. L'UI du système informe également l'utilisateur de l'appareil qu'une connexion VPN active est active. Android affiche les composants d'interface utilisateur suivants pour les connexions VPN:

  • Avant qu'une application VPN puisse devenir active pour la première fois, le système affiche une boîte de dialogue de demande de connexion. La boîte de dialogue invite l'utilisateur de l'appareil à confirmer qu'il fait confiance au VPN et à accepter la requête.
  • L'écran des paramètres VPN (Paramètres > Réseau et Internet > VPN) affiche les applications VPN pour lesquelles une personne a accepté des demandes de connexion. Un bouton permet de configurer les options du système ou d'oublier le VPN.
  • La barre de configuration rapide affiche un panneau d'informations lorsqu'une connexion est active. Si vous appuyez sur le libellé, une boîte de dialogue contenant plus d'informations et un lien vers les paramètres s'affiche.
  • La barre d'état inclut une icône VPN (clé) qui indique qu'une connexion est active.

Votre application doit également fournir une UI afin que la personne utilisant l'appareil puisse configurer les options de votre service. Par exemple, votre solution peut avoir besoin de capturer les paramètres d'authentification du compte. Les applications doivent afficher l'interface utilisateur suivante:

  • Commandes permettant de démarrer et d'arrêter manuellement une connexion. Le VPN permanent peut se connecter en cas de besoin, mais permet aux utilisateurs de configurer la connexion la première fois qu'ils utilisent votre VPN.
  • Notification impossible à ignorer lorsque le service est actif. Cette notification peut indiquer l'état de la connexion ou fournir plus d'informations, telles que des statistiques réseau. Appuyez sur la notification pour mettre votre application au premier plan. Supprimez la notification une fois le service inactif.

Service VPN

Votre application connecte le réseau du système pour un utilisateur (ou un profil professionnel) à une passerelle VPN. Chaque utilisateur (ou profil professionnel) peut exécuter une application VPN différente. Vous devez créer un service VPN permettant au système de démarrer et d'arrêter votre VPN, et de suivre l'état de la connexion. Votre service VPN hérite de VpnService.

Le service sert également de conteneur pour les connexions de la passerelle VPN et leurs interfaces d'appareil locales. Votre instance de service appelle les méthodes VpnService.Builder pour établir une nouvelle interface locale.

Figure 1 : Comment VpnService connecte le réseau Android à la passerelle VPN
Diagramme d'architecture en blocs montrant comment VpnService crée une interface TUN locale dans la mise en réseau du système

Votre application transfère les données suivantes pour connecter l'appareil à la passerelle VPN:

  • Lit les paquets IP sortants à partir du descripteur de fichier de l'interface locale, les chiffre, puis les envoie à la passerelle VPN.
  • Écrit les paquets entrants (reçus et déchiffrés depuis la passerelle VPN) dans le descripteur de fichier de l'interface locale.

Il n'y a qu'un seul service actif par utilisateur ou par profil. Le démarrage d'un nouveau service arrête automatiquement un service existant.

Ajouter un service

Pour ajouter un service VPN à votre application, créez un service Android héritant de VpnService. Déclarez le service VPN dans le fichier manifeste de votre application avec les ajouts suivants:

  • Protégez le service avec l'autorisation BIND_VPN_SERVICE afin que seul le système puisse s'associer à votre service.
  • Annoncer le service avec le filtre d'intent "android.net.VpnService" afin que le système puisse le trouver

Cet exemple montre comment déclarer le service dans le fichier manifeste de votre application:

<service android:name=".MyVpnService"
         android:permission="android.permission.BIND_VPN_SERVICE">
     <intent-filter>
         <action android:name="android.net.VpnService"/>
     </intent-filter>
</service>

Maintenant que votre application déclare le service, le système peut démarrer et arrêter automatiquement le service VPN de votre application si nécessaire. Par exemple, le système contrôle votre service lors de l'exécution d'un VPN permanent.

Préparer un service

Pour préparer l'application à devenir le service VPN actuel de l'utilisateur, appelez VpnService.prepare(). Si l'utilisateur de l'appareil n'a pas déjà donné son autorisation pour votre application, la méthode renvoie un intent d'activité. Cet intent vous permet de démarrer une activité système qui demande une autorisation. Le système affiche une boîte de dialogue semblable à d'autres boîtes de dialogue d'autorisation, telles que l'accès à l'appareil photo ou aux contacts. Si votre application est déjà préparée, la méthode renvoie null.

Le service VPN actuellement préparé ne peut avoir qu'une seule application. Appelez toujours VpnService.prepare(), car une personne peut avoir défini une autre application comme service VPN depuis le dernier appel de la méthode par votre application. Pour en savoir plus, consultez la section Cycle de vie du service.

Connecter un service

Une fois que le service est en cours d'exécution, vous pouvez établir une nouvelle interface locale connectée à une passerelle VPN. Pour demander l'autorisation et vous connecter à votre service à la passerelle VPN, procédez dans l'ordre suivant:

  1. Appelez VpnService.prepare() pour demander une autorisation (si nécessaire).
  2. Appelez VpnService.protect() pour garder le socket de tunnel de votre application en dehors du VPN système et éviter une connexion circulaire.
  3. Appelez DatagramSocket.connect() pour connecter le socket de tunnel de votre application à la passerelle VPN.
  4. Appelez les méthodes VpnService.Builder pour configurer une nouvelle interface TUN locale sur l'appareil pour le trafic VPN.
  5. Appelez VpnService.Builder.establish() pour que le système établit l'interface TUN locale et commence à acheminer le trafic via cette interface.

Une passerelle VPN suggère généralement des paramètres pour l'interface TUN locale lors du handshake. Votre application appelle les méthodes VpnService.Builder pour configurer un service, comme indiqué dans l'exemple suivant:

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

L'exemple de la section VPN par application présente une configuration IPv6 comprenant des options supplémentaires. Vous devez ajouter les valeurs VpnService.Builder suivantes avant de pouvoir établir une nouvelle interface:

addAddress()
Ajoutez au moins une adresse IPv4 ou IPv6, ainsi qu'un masque de sous-réseau que le système attribue comme adresse d'interface TUN locale. Votre application reçoit généralement les adresses IP et les masques de sous-réseau d'une passerelle VPN lors d'un handshake.
addRoute()
Ajoutez au moins une route si vous souhaitez que le système envoie du trafic via l'interface VPN. Les routes sont filtrées par adresse de destination. Pour accepter tout le trafic, définissez une route ouverte telle que 0.0.0.0/0 ou ::/0.

La méthode establish() renvoie une instance ParcelFileDescriptor que votre application utilise pour lire et écrire des paquets dans et depuis le tampon de l'interface. La méthode establish() renvoie null si votre application n'est pas préparée ou si quelqu'un révoque l'autorisation.

Cycle de vie des services

Votre application doit suivre l'état du VPN sélectionné pour le système et de toutes les connexions actives. Mettez à jour l'interface utilisateur (UI) de votre application pour tenir l'utilisateur de l'appareil informé de toute modification.

Démarrer un service

Vous pouvez démarrer votre service VPN de différentes manières:

  • Votre application lance le service, normalement parce qu'une personne a appuyé sur un bouton de connexion.
  • Le système démarre le service, car le VPN permanent est activé.

Votre application démarre le service VPN en transmettant un intent à startService(). Pour en savoir plus, consultez la section Démarrer un service.

Le système démarre votre service en arrière-plan en appelant onStartCommand(). Toutefois, Android impose des restrictions sur les applications en arrière-plan à partir de la version 8.0 (niveau d'API 26) ou ultérieure. Si ces niveaux d'API sont pris en charge, vous devez faire passer votre service au premier plan en appelant Service.startForeground(). Pour en savoir plus, consultez la section Exécuter un service au premier plan.

Arrêter un service

Une personne utilisant l'appareil peut arrêter le service via l'interface utilisateur de votre application. Arrêtez le service au lieu de simplement fermer la connexion. Le système arrête également une connexion active lorsque l'utilisateur de l'appareil effectue les opérations suivantes sur l'écran VPN de l'application Paramètres:

  • déconnecte ou oublie l'application VPN
  • désactive le VPN permanent pour une connexion active

Le système appelle la méthode onRevoke() de votre service, mais il est possible que cet appel ne se produise pas sur le thread principal. Lorsque le système appelle cette méthode, une autre interface réseau achemine déjà le trafic. Vous pouvez supprimer en toute sécurité les ressources suivantes:

VPN permanent

Android peut lancer un service VPN au démarrage de l'appareil et continuer à l'exécuter lorsqu'il est allumé. Cette fonctionnalité, appelée VPN permanent, est disponible sur Android 7.0 (niveau d'API 24) ou version ultérieure. Pendant qu'Android gère le cycle de vie du service, c'est votre service VPN qui est responsable de la connexion à la passerelle VPN. Le VPN permanent peut également bloquer les connexions qui n'utilisent pas le VPN.

Expérience utilisateur

Sous Android 8.0 ou version ultérieure, le système affiche les boîtes de dialogue suivantes pour que l'utilisateur de l'appareil soit informé du VPN permanent:

  • Lorsque les connexions VPN permanents se déconnectent ou ne peuvent pas se connecter, les utilisateurs voient une notification pouvant être ignorée. Lorsque l'utilisateur appuie sur la notification, une boîte de dialogue s'affiche pour en savoir plus. La notification disparaît lorsque le VPN se reconnecte ou lorsque quelqu'un désactive l'option "VPN permanent".
  • Le VPN permanent permet à l'utilisateur d'un appareil de bloquer toutes les connexions réseau qui n'utilisent pas le VPN. Lorsque vous activez cette option, l'application Paramètres avertit les utilisateurs qu'ils ne disposent pas d'une connexion Internet avant la connexion du VPN. L'application Paramètres invite l'utilisateur de l'appareil à continuer ou à annuler.

Étant donné que le système (et non une personne) démarre et arrête une connexion permanente, vous devez adapter le comportement et l'interface utilisateur de votre application:

  1. Désactivez toute UI qui déconnecte la connexion, car le système et l'application Paramètres contrôlent la connexion.
  2. Enregistrez une configuration entre chaque démarrage de l'application et configurez une connexion avec les derniers paramètres. Étant donné que le système démarre votre application à la demande, l'utilisateur de l'appareil ne souhaite pas toujours configurer une connexion.

Vous pouvez également utiliser des configurations gérées pour configurer une connexion. Les configurations gérées aident un administrateur informatique à configurer votre VPN à distance.

Détecter un mode Always-on

Android n'inclut pas d'API permettant de vérifier si le système a démarré votre service VPN. Toutefois, lorsque votre application signale des instances de service qu'elle démarre, vous pouvez supposer que le système a démarré des services non signalés pour le VPN permanent. Voici un exemple :

  1. Créez une instance Intent pour démarrer le service VPN.
  2. Signalez le service VPN en ajoutant un élément supplémentaire à l'intent.
  3. Dans la méthode onStartCommand() du service, recherchez l'option dans les extras de l'argument intent.

Connexions bloquées

Une personne qui utilise l'appareil (ou un administrateur informatique) peut forcer l'utilisation du VPN pour l'ensemble du trafic. Le système bloque tout trafic réseau qui n'utilise pas le VPN. Les personnes utilisant l'appareil peuvent trouver le bouton bascule Bloquer les connexions sans VPN dans le panneau d'options VPN des paramètres.

Désactiver le mode Always-on

Si votre application n'est actuellement pas compatible avec le VPN permanent, vous pouvez le désactiver (sous Android 8.1 ou version ultérieure) en définissant les métadonnées du service SERVICE_META_DATA_SUPPORTS_ALWAYS_ON sur false. L'exemple de fichier manifeste d'application suivant montre comment ajouter l'élément de métadonnées:

<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>

Lorsque votre application désactive le VPN permanent, le système désactive les commandes d'interface utilisateur des options dans les paramètres.

VPN par application

Les applications VPN peuvent filtrer les applications installées qui sont autorisées à envoyer du trafic via la connexion VPN. Vous pouvez créer une liste d'autorisation ou une liste d'éléments non autorisés, mais pas les deux. Si vous ne créez pas de listes autorisées ou non autorisées, le système envoie tout le trafic réseau via le VPN.

Votre application VPN doit définir ces listes avant que la connexion ne soit établie. Si vous devez modifier ces listes, établissez une nouvelle connexion VPN. Une application doit être installée sur l'appareil lorsque vous l'ajoutez à une liste.

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

Applications autorisées

Pour ajouter une application à la liste des applications autorisées, appelez VpnService.Builder.addAllowedApplication(). Si la liste comprend une ou plusieurs applications, seules les applications qu'elle contient utilisent le VPN. Toutes les autres applications (qui ne figurent pas dans la liste) utilisent les réseaux système comme si le VPN n'était pas en cours d'exécution. Lorsque la liste des applications autorisées est vide, toutes les applications utilisent le VPN.

Applications non autorisées

Pour ajouter une application à la liste des applications non autorisées, appelez VpnService.Builder.addDisallowedApplication(). Les applications non autorisées utilisent la mise en réseau du système comme si le VPN n'était pas en cours d'exécution. Toutes les autres applications utilisent le VPN.

Contourner le VPN

Votre VPN peut autoriser des applications à contourner le VPN et à sélectionner leur propre réseau. Pour contourner le VPN, appelez VpnService.Builder.allowBypass() lors de l'établissement d'une interface VPN. Une fois le service VPN démarré, vous ne pouvez plus modifier cette valeur. Si une application ne lie pas son processus ou un socket à un réseau spécifique, le trafic réseau de l'application continue via le VPN.

Les applications qui sont associées à un réseau spécifique n'ont pas de connexion lorsqu'un utilisateur bloque le trafic qui ne passe pas par le VPN. Pour envoyer du trafic via un réseau spécifique, les applications appellent des méthodes, telles que ConnectivityManager.bindProcessToNetwork() ou Network.bindSocket(), avant de connecter le socket.

Exemple de code

Le projet Android Open Source comprend une application exemple appelée ToyVPN. Cette application explique comment configurer et connecter un service VPN.