Pourquoi Android VpnService capture une partie du trafic et pas tout
Les routes, les entrées DNS et la portée par application décident quels paquets atteignent l'appareil tun, pourquoi les appareils locaux disparaissent et ce que le constructeur refuse.
Rien dans VpnService n'intercepte le trafic de lui-même. Il demande au système une interface tun, remet à cette interface un ensemble d'adresses et de routes, et à partir de cet instant le noyau copie chaque paquet correspondant à ces routes dans un descripteur de fichier que l'application peut lire. Le code de l'application ne voit jamais les paquets que les routes n'ont pas sélectionnés, donc la portée d'un tunnel est une question de table de routage avant d'être une question de programmation. C'est pourquoi la même implémentation de service peut avaler chaque flux sur un téléphone et seulement une partie sur un autre, et pourquoi ajouter une route répare parfois un tunnel qui semblait cassé. Vous obtenez ce que vous avez déclaré, moins tout ce que le constructeur a refusé en silence.
Les routes décident quels paquets arrivent
Builder.addRoute(prefix, prefixLength) installe un préfixe dans la table de routage du réseau VPN. Le système traite ce réseau comme celui par défaut pour les applications de la portée, donc ses entrées sont consultées avant la route par défaut du réseau physique et un préfixe correspondant suffit à détourner un flux. Une route pour 0.0.0.0/0 prend donc IPv4 en entier, imprimante, cible de cast et page d'administration du routeur compris.
Les clients qui veulent cette couverture sans supprimer l'entrée par défaut physique utilisent plutôt la paire 0.0.0.0/1 et 128.0.0.0/1. Deux routes plus spécifiques l'emportent sur une route par défaut, ensemble elles couvrent tout l'espace d'adresses, et l'entrée par défaut du téléphone reste dans la table pour les sockets encore liées au réseau physique.
Les adresses et les entrées DNS viennent du même constructeur et répondent à une autre question. addAddress donne à l'interface une adresse avec une longueur de préfixe, et addDnsServer installe un résolveur que les applications de la portée interrogeront via l'interface. Les deux familles peuvent cohabiter sur une interface, donc un ensemble de routes IPv4 seul à côté d'une adresse IPv6 est une configuration qui se lit correctement et fuit quand même. Si vous routez une famille où l'interface n'a pas d'adresse, le noyau n'a pas d'adresse source pour ces paquets et ils n'apparaissent jamais au descripteur.
Ce que establish() renvoie
establish() renvoie un ParcelFileDescriptor, ou null quand l'application n'est pas autorisée. Null est un état de consentement et non une erreur réseau : VpnService.prepare() doit déjà avoir renvoyé null, ou l'utilisateur doit avoir approuvé la boîte de dialogue système avant l'appel. Le consentement peut être révoqué pendant que le service tourne, et l'interface se ferme sous vos pieds sans aucun rappel dans votre boucle de lecture.
Le descripteur est non bloquant sauf si vous dites le contraire, donc une lecture revient immédiatement sans données au lieu de parquer le thread. Une boucle écrite pour un socket bloquant brûle un cœur, et une boucle qui traite une lecture vide comme fatale déchire le tunnel pendant une seconde d'inactivité. Builder.setBlocking(true) change ce comportement, et le prix est un thread qui attend dans la lecture.
Les arguments de route et d'adresse sont validés comme valeurs LinkAddress, et les destinations multicast comme 224.0.0.0/4 sont rejetées d'emblée. Une interface tun point à point n'a pas non plus de domaine de diffusion, donc la découverte multicast à travers elle demande une autre conception plutôt qu'une route de plus. La référence VpnService.Builder est la liste à vérifier avant de conclure qu'un appel a été ignoré.
La portée par application rétrécit l'ensemble, pas le tunnel
Depuis l'API 21 le constructeur accepte addAllowedApplication et addDisallowedApplication, et les deux listes s'excluent l'une l'autre dans un même constructeur. Une liste d'autorisés fait exister le tunnel pour ces paquets pendant que tout le reste garde le réseau physique ; une liste d'exclus capture tout sauf les paquets nommés, ce qu'un client propose comme tunnel divisé. Avant l'API 21 il n'existe aucun mécanisme de plateforme, et le comportement par application venait de clients tiers qui filtraient par UID dans leur propre redirecteur. La distinction compte au débogage, car un tunnel par application avec une liste d'un seul paquet ne produit presque aucun trafic et c'est un comportement correct, pas une route cassée.
Quelle que soit la portée, la socket que votre application ouvre vers le serveur VPN ne doit pas passer par le tunnel qu'elle construit. VpnService.protect(socket) lie cette socket au réseau sous-jacent pour que ses paquets sautent l'appareil tun. Omettez l'appel et la poignée de main est avalée par l'interface censée la monter.
VpnService.setUnderlyingNetworks ferme la boucle en disant au système quel réseau physique porte le tunnel. Cette valeur détermine comment le VPN signale la connectivité aux autres applications, et une valeur null ou périmée est une cause courante d'un tunnel connecté que le reste du téléphone traite comme hors ligne. Mettez-la à jour quand le réseau sous-jacent change, pas seulement au démarrage.
Sommes de contrôle et MTU : là où une route correcte échoue encore
Une fois les paquets arrivés, deux détails au niveau paquet décident si quelque chose fonctionne de bout en bout. Le premier est l'adresse source : avec plusieurs adresses d'interface le noyau peut émettre des paquets portant la première, donc un redirecteur qui réécrit les en-têtes doit corriger l'adresse et la somme de contrôle qui la couvre.
Le second est la portée de la somme de contrôle. Quand l'application construit elle-même des paquets TCP ou UDP, la somme couvre un pseudo-en-tête contenant l'adresse source, l'adresse de destination, le numéro de protocole et la longueur de transport, et en omettre un seul produit des paquets qui arrivent et sont rejetés. Cette panne ressemble à un délai d'attente après une réponse qui n'arrive jamais, ce qui renvoie la plupart des gens vers les routes. setMtu fixe la MTU de l'interface, et chaque lecture renvoie exactement un paquet IP complet, donc un tampon écrit pour un flux d'octets fragmentera votre analyse, pas le paquet.
Une passe de vérification qui sépare les couches
Pour trouver où la portée a mal tourné, testez les couches dans l'ordre. Demandez à l'appareil quelles routes le réseau VPN détient réellement, puis vérifiez si un flux vers une adresse connue apparaît comme une lecture au descripteur, puis vérifiez si le paquet que votre redirecteur émet est bien formé. Un problème de route ne montre rien au descripteur, tandis qu'un problème de somme de contrôle montre un paquet qui arrive et disparaît.
Si rien n'atteint le descripteur, le suspect est l'ensemble des routes et non le lecteur : confirmez les préfixes, confirmez qu'il existe une adresse dans la même famille et confirmez que l'application est dans la portée que vous avez fixée. Si les paquets atteignent le descripteur mais que rien ne revient, inspectez protect() et la valeur du réseau sous-jacent avant de retoucher une route. Si le côté distant signale des paquets malformés, la somme de contrôle et la MTU sont les endroits à regarder. Une sonde par couche épargne un après-midi de suppositions.