Une application Android résout les noms hors du VPN : une liste de vérifications
Android peut résoudre via le réseau par défaut alors qu’un VPN est actif. Rattachez le processus au réseau VPN et vérifiez quel résolveur l’application a réellement utilisé.
Une application Android n’arrive pas à résoudre un nom interne alors que le navigateur du client VPN y accède, et la même compilation fonctionne sur un autre téléphone. L’URL n’est pas la variable. La variable, c’est le réseau que le processus de l’application est autorisé à utiliser pour la résolution, et Android ne garantit pas que le VPN actif soit celui que vos sockets utilisent. Déroulez ces vérifications dans l’ordre et arrêtez-vous dès que l’instantané du résolveur contredit votre hypothèse.
Étape 1 : prouver sur quel réseau se trouve l’application
Lisez la vue de l’application elle-même plutôt que la barre d’état. Depuis ConnectivityManager, récupérez le réseau actif, ses LinkProperties, la liste des serveurs DNS et le réglage DNS privé. Si la liste contient le résolveur du VPN, l’application dispose d’un chemin vers lui ; si elle contient le résolveur du Wi-Fi, le tunnel n’est pas le réseau par défaut de ce processus.
Une icône VPN indique seulement qu’un tunnel existe. Elle ne dit pas que le processus de l’application y a été rattaché, et Android ne route pas toujours la résolution de noms par le réseau qui semble actif. Les applications VPN locales qui transfèrent vers un autre appareil, les réseaux Wi-Fi sans internet et les compilations constructeur peuvent toutes laisser une application interroger le résolveur par défaut au lieu des serveurs du tunnel.
Étape 2 : demander le transport VPN et rattacher le processus
Quand l’application doit utiliser le résolveur du tunnel, demandez un réseau avec le transport VPN et rattachez le processus dans le rappel, afin que le code ne devine pas quand le réseau apparaît :
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.build()
connectivityManager.requestNetwork(request, object : NetworkCallback() {
override fun onAvailable(network: Network) {
val bound = connectivityManager.bindProcessToNetwork(network)
Log.i(TAG, "bound to VPN network: " + bound)
}
}) L’appel change le réseau par défaut de tout le processus, donc chaque socket créé ensuite l’utilise. Les sockets déjà existants conservent le réseau sur lequel ils ont été créés : c’est pourquoi un pool de connexions ouvert avant le rattachement peut continuer à résoudre par l’ancien chemin. La valeur de retour est un booléen, et un résultat false signifie que le rattachement n’a pas eu lieu ; réessayez après un court délai et journalisez les deux tentatives.
Étape 3 : limiter le rattachement à ce qui en a besoin
Le rattachement à l’échelle du processus est brutal. La télémétrie, les vérifications de mise à jour et les SDK tiers passent eux aussi par le tunnel, et si le tunnel n’a pas internet ils se mettent à échouer. Pour une seule requête, utilisez la fabrique de sockets du réseau et gardez la valeur par défaut de l’appareil pour tout le reste. Choisissez un modèle délibérément : un rattachement étroit par connexion, ou un rattachement de processus avec un détachement explicite quand le VPN disparaît.
Deux fonctions du résolveur peuvent défaire le rattachement sans aucune erreur. Le DNS privé en mode strict envoie les requêtes à un serveur DoT nommé au lieu du résolveur du réseau, et une application qui embarque son propre client DNS-over-HTTPS contourne entièrement le résolveur système. Vérifiez les deux avant de conclure que le rattachement a échoué. Une requête qui n’atteint jamais le serveur DNS du VPN n’apparaîtra dans aucun journal côté tunnel.
Étape 4 : ne pas faire confiance à une contrainte CONNECTED
Le NetworkType.CONNECTED de WorkManager signifie qu’un réseau est connecté, et le transport VPN le satisfait même quand la liaison Wi-Fi ou cellulaire sous-jacente a disparu. La tâche s’exécute, chaque requête échoue, et la politique de nouvelle tentative en fait une tempête qui ressemble à un bug de planification. Le tunnel est un réseau ; la contrainte n’a jamais été un test d’internet.
Vérifiez la capacité dont le travail a réellement besoin, comme un accès internet validé, et gardez une politique de nouvelle tentative bornée avec backoff. Si la tâche a besoin d’un nom privé, résolvez ce nom dans le réseau sélectionné avant d’effectuer le travail, et échouez vite avec une erreur claire au lieu de réessayer une requête qui ne peut pas réussir.
Étape 5 : consigner les preuves et retester la gamme
Journalisez les types de transport du réseau sélectionné, les serveurs DNS issus de LinkProperties, le fait que le rattachement ait renvoyé true, et l’échec exact de la première requête. Lancez ensuite un nom privé et un nom public sous le même rattachement. Cette paire montre si l’application a utilisé le résolveur du tunnel ou si elle est retombée silencieusement sur la valeur par défaut de l’appareil, ce qui est la différence que l’utilisateur perçoit.
Répétez sur la plus ancienne version d’Android que vous prenez en charge et sur une version actuelle. Le comportement du résolveur qui fait paraître correct le chemin par défaut est justement là où les appareils et les versions diffèrent, et une compilation qui ne passe que sur le plus récent appareil de test n’a pas été vérifiée pour la gamme que vous livrez. adb shell dumpsys connectivity est un moyen rapide de confirmer quels réseaux et capacités l’appareil signale pendant que vous reproduisez la panne.