La protection VpnService échoue quand un autre VPN est déjà actif
Android autorise une seule interface VPN par utilisateur. Exécutez les contrôles prepare, establish, protect et de propriété dans l’ordre, puis choisissez la révocation ou l’exclusion par application.
Votre application appelle VpnService.prepare, reçoit null, construit un tunnel, et le descripteur semble valide, mais rien n’atteint le serveur. Ou prepare renvoie un Intent, l’utilisateur l’approuve, et establish échoue encore. Les deux résultats signifient généralement la même chose : une autre application possède déjà l’interface VPN pour cet utilisateur, et vos appels protect marquent des sockets que votre tunnel ne peut pas utiliser. Parcourez les contrôles ci-dessous dans l’ordre et arrêtez-vous au premier qui échoue, car une étape ultérieure ne peut pas réparer une étape antérieure.
Vérifiez l’emplacement VPN avant de toucher à protect
Android accorde à chaque utilisateur exactement une interface VPN active. C’est une règle de la plateforme, pas une autorisation que votre application peut demander, donc deux clients qui veulent tous deux être le tunnel ne peuvent pas coexister.
Un VPN toujours actif configuré pour une autre application est le cas le plus difficile, car le système maintient cette application connectée et refuse une reprise. Un filtre de trafic comme AdGuard, un client d’entreprise ou un VPN grand public peut occuper l’emplacement alors que l’utilisateur a oublié qu’il est installé. Demandez à l’utilisateur d’ouvrir les Paramètres et de lire l’entrée listée sous VPN ; le nom qui s’y trouve est le propriétaire auquel vous vous heurtez. Tant que cette entrée n’est pas déconnectée ou que le réglage toujours actif n’est pas effacé, votre VpnService ne peut pas devenir l’interface.
Étape 1 : lisez ce que renvoie prepare
VpnService.prepare répond à une seule question : cette application peut-elle devenir le VPN ? Il renvoie null lorsque le consentement existe déjà, et un Intent lorsque l’utilisateur doit encore l’approuver, que vous devez lancer avec startActivityForResult. Traitez un retour null comme une autorisation de continuer, non comme la preuve que establish réussira.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Si le chemin Intent revient et que l’utilisateur a refusé, prepare continuera de renvoyer un Intent à la tentative suivante. Journalisez le code de résultat au lieu de réessayer en boucle, car un consentement refusé est une décision de l’utilisateur, pas une erreur passagère.
Étape 2 : confirmez que l’interface appartient à votre application
Après establish, le ParcelFileDescriptor que vous recevez est votre preuve de propriété, et il vaut la peine de le vérifier avant tout travail sur les sockets. Un descripteur null ou déjà fermé signifie que l’interface n’a jamais été créée, même si aucune exception n’est apparue. Comparez le nom d’interface du builder avec celui que le système rapporte pour le VPN actif, en utilisant dumpsys sur un appareil de test. S’ils diffèrent, le tunnel que vous avez construit n’est pas le tunnel qui transporte le trafic.
adb shell dumpsys connectivityLa même divergence explique une icône connectée avec un trafic mort. Le système affiche le tunnel de l’autre application, tandis que votre code détient un descripteur pour une interface qu’il ne possède pas.
Étape 3 : appelez protect avant connect
Une fois l’interface existante, les sockets que votre application ouvre sont routés vers le tunnel par défaut. Le transport externe ne peut pas suivre cette règle, sinon le paquet encapsulé reviendrait dans l’interface, c’est pourquoi VpnService protect marque un socket pour contourner le VPN et utiliser le réseau sous-jacent. L’appel renvoie un boolean, et l’ignorer est la façon de se retrouver avec un socket qui semble sain et ne transporte rien.
Appelez protect avant connect ou bind, jamais après. La protection doit être en place avant que le socket soit connecté ou lié, car un socket connecté a déjà choisi sa route et un protect ultérieur ne le déplace pas de façon fiable. Si protect renvoie false, arrêtez-vous et revérifiez la propriété : il échoue quand l’interface n’appartient pas à votre application, ce qui est exactement l’état que laisse un autre VPN. Un retour false est un signal pour revenir à l’étape 1, pas pour réessayer le même socket.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Étape 4 : liez un processus quand un socket est gênant
Certains transports ne sont pas un seul socket : une bibliothèque peut en ouvrir plusieurs, ou vous pouvez vouloir que chaque connexion d’un processus auxiliaire utilise la même sortie. ConnectivityManager.bindProcessToNetwork lie le processus à un réseau choisi, et l’ancien setProcessDefaultNetwork faisait de même avant d’être obsolète. Demandez la connexion sous-jacente avec NET_CAPABILITY_NOT_VPN afin que la liaison sélectionne le réseau sous votre tunnel plutôt que le tunnel lui-même.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Lier un processus n’est pas un second VPN. Cela choisit une sortie pour ce processus, ce qui est utile à votre transport et inutile pour faire tourner deux tunnels à la fois.
Réparez l’appareil de l’utilisateur, pas le chemin du code
Aucune API ne permet à deux applications de partager l’emplacement VPN, donc le correctif relève de l’état de l’appareil. Proposez à l’utilisateur deux voies concrètes : déconnectez ou effacez le toujours actif de l’autre application pour que votre prepare puisse prendre le relais, ou ouvrez les réglages par application de cette application et excluez la vôtre de son tunnel. L’exclusion maintient le filtre actif pour tout le reste et permet à votre application d’utiliser la connexion ordinaire, ce qui est souvent le résultat que l’utilisateur veut réellement.
Écrivez les deux voies dans votre documentation de support, car les utilisateurs savent rarement quelle application possède le VPN. Demandez-leur d’envoyer le nom du VPN actif depuis les Paramètres et les résultats de prepare, establish et protect depuis vos journaux, et vous pourrez dire en une minute quelle étape a échoué. Vérifiez ensuite avec du trafic réel : une résolution de nom, une connexion TCP et une requête à travers le tunnel, pas seulement une icône connectée. Un descripteur valide et un indicateur vert prouvent au mieux la propriété, jamais que le transport a survécu aux règles de routage.
Si les quatre contrôles passent et que le trafic meurt encore, la propriété n’est plus votre problème. Passez au routage et à l’accessibilité du serveur, et testez les mêmes identifiants depuis un appareil sans application VPN installée.