L’hypothèse erronée est qu’une application VpnService bascule un interrupteur système et qu’à partir de là le téléphone est protégé. Ce qui se passe réellement est plus étroit : l’application demande le consentement, construit une interface, lit et écrit des paquets via un descripteur de fichier, et rend l’interface quand elle s’arrête. Un seul tunnel de ce type peut être vivant à la fois, et le système peut le retirer sans demander. L’essentiel du comportement déroutant que les gens imputent au protocole vient de ce modèle de propriété.

Traitez chaque transition du cycle de vie comme une partie de la conception, non comme un cas limite.

Mythe : l’application active le VPN du téléphone

Le flux commence par VpnService.prepare(), qui renvoie un Intent que l’utilisateur doit accepter, et se poursuit par establish(), qui peut encore échouer ensuite pour des raisons que l’application ne contrôle pas. Établir un second tunnel désactive la première interface et remplace la notification système. Nulle part dans cette séquence l’application ne possède un interrupteur pour tout l’appareil ; elle possède le descripteur qui lui a été accordé et les paquets qui y arrivent. Même la notification persistante appartient au système, pas à la conception de l’application.

Comme l’interface est accordée plutôt que saisie, un autre client peut prendre la place à tout moment. L’application l’apprend par un rappel, non par une valeur de retour.

L’arrêt forcé de l’application est visible dans le volet des notifications pour la même raison : le système signale ce qu’il a accordé, non ce que l’application a choisi d’afficher.

Mythe : le consentement est donné une fois

Quand le système remet le tunnel à un autre client, il envoie une transaction de révocation sur le binder que la classe de base renvoie depuis onBind(). Une application qui remplace ce binder par le sien doit transmettre elle-même la transaction de révocation, sinon VpnService.onRevoke() ne se déclenche jamais et le service continue d’écrire dans un descripteur qui n’existe plus. L’approbation peut aussi être retirée par l’utilisateur, et le système peut arrêter le service lors d’un redémarrage ou d’une reconnexion toujours active.

Le gérer est un travail concret et non une ligne de journal : cessez de lire le descripteur, fermez-le, effacez l’indicateur de connexion et dites à l’utilisateur dans quel état il se trouve. Décidez ensuite si l’application doit rétablir, redemander ou attendre, car dans une configuration toujours active le système peut ramener le service de lui-même.

Une application qui traite la révocation comme une exception et non comme un état signalera une connexion alors que chaque paquet qu’elle écrit est rejeté, ce qui est le pire mode de défaillance possible pour un outil de confidentialité.

Mythe : l’interface filtre le trafic pour vous

Ce qui arrive est de l’IP brut. Capturer ou filtrer signifie analyser les en-têtes IP, TCP, UDP et IPv6, suivre les flux et réécrire les réponses vers le descripteur, ce qui fait d’une application de capture une petite pile réseau et non une facilité du système. protect() existe pour que la socket de transport de l’application contourne son tunnel au lieu de se capturer elle-même, et elle renvoie un booléen qui mérite une vérification sur chaque socket créée après la mise en place du tunnel.

Les listes d’applications autorisées et interdites du constructeur décident de qui entre dans l’interface. Elles ne donnent pas accès à la charge utile au-delà des paquets IP que l’interface transporte, et aucune API n’expose le tunnel d’un autre client ni n’énumère les profils VPN du système. Les capacités réseau peuvent dire qu’un réseau est un transport VPN, mais pas à qui appartient le tunnel.

C’est pourquoi les questions d’état sur les autres applications obtiennent des réponses contradictoires : l’information n’est tout simplement pas exposée à votre processus.

Ce que le cycle de vie exige réellement

Suivez les états que vous contrôlez : préparé, établi, révoqué, arrêté. La reconnexion devrait être un démontage suivi d’un établissement, avec un seul propriétaire du descripteur et de la socket de transport, car deux composants qui se disputent l’établissement se battront pour la seule place disponible. Les démarrages en arrière-plan sont restreints sur Android moderne, donc une diffusion au démarrage n’est pas une garantie ; utilisez un service de premier plan avec sa notification là où le niveau d’API le permet.

Journalisez la transition et pas seulement l’état final, et gardez la machine à états assez petite pour tester chaque chemin. Un état que vous ne pouvez pas atteindre en test est un état que vous rencontrerez sur le terrain sans journal.

Testez le cycle de vie sur un appareil

Les émulateurs peuvent simuler la connectivité mais pas le chemin de consentement et de révocation de manière convaincante, donc exécutez ces tests sur un appareil réel avec un second client VPN installé pour voler la place.

adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp

Démarrez le tunnel, confirmez la notification et l’interface, puis connectez l’autre client et observez si le rappel de révocation s’exécute et si l’état se vide. Arrêtez l’application de force et vérifiez si quelque chose signale encore un descripteur vivant.

Répétez la séquence après un redémarrage toujours actif, car ce chemin diffère d’une connexion lancée par l’utilisateur. Ce que vous cherchez n’est pas une icône verte mais la preuve que l’application a cessé d’utiliser un descripteur qu’elle ne possédait plus.

Articles similaires