Liste d'autorisation Android VpnService : pourquoi les pages autorisées restent bloquées
Votre liste d'autorisation est probablement correcte. Les pages restent bloquées à cause de la réponse DNS, de la table de routes, du socket du résolveur ou du MTU. Voici l'ordre de vérification.
Vous avez ajouté un domaine à la liste d'autorisation, la requête atteint votre application, le comparateur répond autorisé, et la page ne se charge toujours jamais. À ce stade, la plupart des gens recommencent à réécrire le comparateur et à ajouter des comparaisons avec jokers. D'après mon expérience, le comparateur est généralement correct et la panne se situe une couche plus bas : dans la réponse DNS que vous avez synthétisée, dans la route que vous avez installée ou dans le socket que vous avez utilisé pour transmettre la requête. La liste est la partie facile ; le chemin du paquet est l'endroit où les listes d'autorisation cassent.
Le comparateur est rarement le coupable
La première chose à distinguer, c'est bloqué de cassé. Un nom bloqué doit échouer vite et visiblement, car le navigateur reçoit une réponse indiquant que le nom n'existe pas et s'arrête. Un nom cassé reste suspendu, réessaie en IPv6, bascule vers un autre transport et vous laisse avec un indicateur qui tourne. Si vous voyez un indicateur qui tourne, votre liste d'autorisation répond probablement quelque chose dont le client ne peut rien faire.
L'enregistrement A vide est l'erreur classique. Renvoyer NOERROR avec zéro réponse ressemble à une réponse valide, donc le résolveur stub l'accepte, le navigateur réessaie, essaie AAAA, essaie un autre résolveur et abandonne bien plus tard. Un code de réponse 3, NXDOMAIN, avec la section question répétée et l'identifiant de transaction recopié, met fin à la conversation immédiatement. Utilisez-le pour chaque nom que vous refusez délibérément.
IPv6 mérite la même décision et non une valeur par défaut. Si vous répondez NXDOMAIN pour A et laissez passer AAAA, le client peut atteindre le nom bloqué en IPv6 et le filtre fuit. Certaines implémentations répondent aux requêtes AAAA bloquées par une adresse de loopback pour que la connexion échoue aussitôt au lieu de basculer. Choisissez une politique, appliquez-la aux deux types d'enregistrement et journalisez les deux.
Une route qui capture tout possède tout
Une conception DNS seule n'ajoute jamais addRoute("0.0.0.0", 0), donc seules les requêtes entrent dans l'interface TUN et tout le reste continue d'utiliser le réseau sous-jacent. C'est la forme sûre, et cela explique aussi pourquoi certaines listes d'autorisation semblent fonctionner en Wi-Fi et échouer sur le réseau mobile.
Dès qu'une route par défaut existe, chaque flux est le vôtre. Un paquet TCP qui arrive à l'interface et n'est ni transmis ni traité reste dans la file jusqu'à ce que le client expire, et il n'y a aucune erreur à journaliser parce que rien n'a échoué. J'ai vu quelqu'un passer un après-midi sur une liste d'autorisation cassée qui était en réalité un transmetteur manquant pour le port 443. Journalisez le protocole et la destination de chaque paquet que vous décidez de supprimer.
Le socket du résolveur doit sortir du tunnel
Le transfert est l'endroit du piège suivant. Les résolveurs des opérateurs vivent souvent sur une adresse privée que le tunnel n'atteint pas, donc la requête que vous transmettez ne va nulle part et l'application signale un délai de lecture dépassé. Envoyez les requêtes approuvées à un résolveur que vous savez joignable, et passez d'abord ce socket à VpnService.protect(). Un socket non protégé renvoie la requête dans votre propre tunnel, où elle arrive de nouveau à l'application et tourne jusqu'à ce que le délai expire.
La panne ressemble exactement à une coupure réseau, et c'est pourquoi elle survit si longtemps dans les rapports de bogues. Tenez des compteurs de requêtes envoyées et de réponses reçues : si le premier nombre monte et que le second reste à zéro, vous regardez le socket, pas la liste.
La taille et les sommes de contrôle échouent sans message
Les suppressions silencieuses viennent aussi de la taille. Une réponse DNS avec un grand ensemble de réponses, ou une poignée de main TCP avec des options généreuses, peut dépasser un MTU de chemin que vous n'avez jamais fixé explicitement ; le fragment disparaît en amont et le client soit réessaie plus petit, soit cesse d'essayer. Fixer le MTU de l'interface à 1280 est un choix prudent qui survit à la plupart des chemins mobiles, et être explicite vaut mieux qu'hériter d'une valeur par défaut que personne n'a mesurée.
Si vous construisez les paquets vous-même, la somme de contrôle est l'autre tueur silencieux. Une somme de contrôle UDP calculée sur un pseudo-en-tête qui omet la longueur UDP ou le champ protocole produit un paquet qui semble bien formé de votre côté et que le destinataire rejette sans trace. Comparez votre pseudo-en-tête à la spécification champ par champ, et imprimez la somme calculée pour un flux connu pendant le débogage.
Ce qu'un VpnService ne fera pas pour vous
Deux limites valent d'être connues avant de concevoir autour d'elles. Aucune application ordinaire ne peut installer un proxy ou un fichier PAC à l'échelle du système hors Wi-Fi, et cette partie n'a pas changé. Le chemin par application, si : avant l'API 29, VpnService.Builder ne pouvait pas porter de proxy du tout, et depuis l'API 29, setHttpProxy(ProxyInfo.buildPacProxy(uri)) remet à la plateforme un script PAC à évaluer pour le réseau VPN. Évaluez les règles dans votre propre code lorsque vous ciblez des API plus anciennes ou lorsque le modèle PAC ne peut pas exprimer la règle. UDP n'est pas gratuit non plus : les navigateurs n'envoient que du TCP via un proxy SOCKS, et la plupart des clients SOCKS5 n'implémentent jamais UDP ASSOCIATE, donc une conception à base de proxy supprime ou fuit l'UDP pendant que le TCP semble sain.
Cette seconde limite est la raison pour laquelle un protocole à base d'UDP peut fonctionner sur un réseau ordinaire et échouer dès que votre VPN se connecte. Si le tunnel doit transporter de l'UDP, le transmetteur doit implémenter le chemin de transfert pour cela, pas seulement le chemin TCP avec un autre type de socket.
Vérifiez dans cet ordre
Parcourez la liste dans l'ordre et arrêtez-vous à la première étape qui échoue, car toute vérification ultérieure n'est que du bruit tant que la précédente ne tient pas.
- Listez les routes installées et confirmez si les paquets autres que DNS entrent réellement dans l'interface TUN.
- Journalisez le nom de la requête, la décision et le code de réponse pour un nom bloqué et un nom approuvé.
- Confirmez que le socket du résolveur est protégé et que le résolveur choisi répond depuis l'intérieur du tunnel.
- Confirmez le MTU de l'interface et la somme de contrôle d'une réponse construite à la main.
- Répétez la même page en Wi-Fi et sur le réseau mobile, car les résolveurs de l'opérateur et le MTU du chemin diffèrent.
Si les cinq tiennent et que la page reste bloquée, le suspect suivant est le transmetteur lui-même : comparez une requête capturée à une réponse capturée et cherchez un nombre d'octets manquant ou un socket à moitié fermé. La référence VpnService.Builder couvre les contrats de route, de DNS et de MTU, et protect() est un appel VpnService distinct et non une option du builder ; une interface connectée signifie seulement que le premier contrat est rempli.