Deux symptômes apparaissent dans la même boucle VpnService et sont mis sur le compte de la même chose : un lecteur qui ne revient jamais, et une écriture qui échoue avec ENOBUFS. Ce ne sont pas des variantes d'un même bug. Une lecture bloquée est un désaccord sur le contrôle de flux avec le pair ; une écriture échouée est un paquet que l'interface n'accepte pas. Si vous les traitez comme un seul problème, vous passerez une journée à régler des tampons qui n'ont jamais été en cause. La séparation ci-dessous est ma façon de décider quelle moitié de la boucle instrumenter d'abord.

Une boucle, deux points de défaillance indépendants

Votre service lit un paquet IP depuis le descripteur de fichier TUN, l'inspecte et écrit une réponse dans le même descripteur. Tout ce qui casse sur ce chemin se manifeste à l'un de ces deux appels, et c'est pourquoi les deux sont signalés comme un tunnel cassé. Les appels ont des contrats différents. Une lecture attend que le noyau vous remette un paquet, et une écriture remet au noyau un paquet qu'il a le droit de refuser.

Les lectures sont la moitié facile à raisonner. Une lecture qui ne renvoie rien attend soit un trafic qui n'arrive jamais, soit parce que le pair a cessé d'envoyer.

Une lecture bloquée est un désaccord de contrôle de flux

La lecture d'un périphérique TUN bloque jusqu'à ce qu'un paquet soit disponible. C'est un comportement voulu, et c'est pourquoi un thread garé dans read semble normal sur un lien inactif. Cela devient un blocage quand votre propre pile TCP continue d'accepter des données de l'application locale mais n'annonce jamais une fenêtre suffisante pour que le pair distant en envoie davantage. Le pair remplit la fenêtre que vous avez annoncée, attend un ACK et une mise à jour de fenêtre, puis s'arrête. Votre lecteur n'a alors rien à renvoyer, et l'application voit une connexion ouverte et silencieuse.

Les causes courantes sont une pile qui écrit dans la socket sans suivre la fenêtre annoncée, et une autre qui oublie d'envoyer une mise à jour de fenêtre après avoir vidé son tampon. De l'extérieur, elles se ressemblent : des octets sortent, rien ne revient, aucune erreur n'est levée. Bloquer le thread de lecture sur l'analyse, la résolution DNS ou les E/S disque produit la même forme de symptôme pour une raison sans rapport, alors confirmez que le thread est bien dans read avant d'accuser le contrôle de flux.

Vérifiez la file d'envoi du pair et la fenêtre qu'il a vue en dernier. Si le pair retient des données parce que votre fenêtre est nulle, le correctif est dans votre chemin ACK, pas dans la lecture TUN.

ENOBUFS est un problème de taille, pas un manque de tampon

Une écriture qui renvoie ENOBUFS sur une interface TUN rejette généralement la longueur du paquet. L'interface a une MTU, un paquet plus grand ne peut pas être mis en file, et réessayer le même paquet échoue de la même façon à chaque fois. Ce déterminisme est l'indice. Un vrai manque de tampon disparaît quand la pression baisse, alors qu'un rejet de taille ne se soucie pas de votre temps d'attente.

TCP a un mécanisme exactement pour cela, et c'est l'étape que les gens sautent : le clamp MSS. Quand vous synthétisez des segments en espace utilisateur, vous choisissez la taille de la charge utile, et la prendre depuis la MTU de l'interface locale au lieu de celle du tunnel émet des segments que le tunnel ne peut pas transporter. Limitez à la plus petite des deux. Le clamp ne couvre que TCP, donc un datagramme UDP trop grand doit être fragmenté ou abandonné par votre code.

Journalisez la longueur de chaque paquet que vous écrivez pendant le débogage. La longueur qui échoue est la réponse, et elle ne changera pas entre les tentatives.

Trois mesures qui séparent les cas

Vous n'avez pas besoin d'un débogueur pour l'une ou l'autre des défaillances. Trois observations suffisent à vous orienter vers la bonne section, et elles prennent moins de temps qu'une recompilation.

What you seeWhere to look
Reader parked, peer send queue empty, last advertised window zeroWindow updates in your ACK path
Write fails on one length and succeeds on a smaller oneMSS clamp and tunnel MTU
Both directions look fine, app still sees no dataProtected socket and routing loop

Si la troisième ligne correspond, la boucle elle-même est saine et le problème se situe là où le tunnel ne peut pas le voir : une socket de transport non protégée qui ramène votre propre trafic VPN dans le tunnel, ce qui se présente généralement comme une connexion qui réussit puis ne livre rien.

Réparez la plomberie dont les deux chemins dépendent

Aucun symptôme ne vaut la peine d'être poursuivi tant que l'interface n'est pas correctement construite. Les appels de builder qui comptent sont l'adresse, la route, la MTU et le consentement, dans cet ordre :

Builder()
  .addAddress(10.8.0.2, 32)
  .addRoute(0.0.0.0, 0)
  .setMtu(1400)
  .establish()

Réglez la MTU sur la valeur réelle du tunnel au lieu de la laisser sur celle qu'utilisait l'exemple. Tout ce que vous écrirez ensuite est borné par ce nombre, et un écart entre la MTU que vous fixez et le MSS auquel vous vous limitez est le moyen le plus courant de réintroduire l'échec d'écriture après un correctif. Protégez ensuite la socket qui transporte le trafic vers le serveur VPN lui-même. Sans protect, ces paquets sont renvoyés dans votre propre tunnel et la connexion ne s'établit jamais, ce qui ressemble à une lecture bloquée mais est une boucle de routage.

Le consentement ne peut pas être contourné. Un service démarré sans que prepare renvoie null n'obtient pas d'interface, et establish renvoie null au lieu de lever une exception, alors vérifiez le descripteur avant de lancer votre boucle. La référence VpnService est l'endroit pour confirmer le contrat du builder au lieu de copier un exemple.

Vérifiez un sens à la fois

Envoyez un écho UDP dans le tunnel et observez les longueurs de paquets avant de toucher à TCP. Ouvrez ensuite une poignée de main et confirmez que les segments restent sous le MSS que vous avez calculé. Comptez les paquets plutôt que les connexions, pour qu'un blocage apparaisse comme un trou dans le compteur plutôt qu'une socket suspendue. Si les lectures s'arrêtent après une rafale d'écritures, la fenêtre est suspecte ; si les écritures échouent immédiatement, c'est la longueur.

Redémarrez ensuite l'appareil et répétez une fois, car un VpnService qui ne fonctionne que jusqu'au premier changement de réseau est un problème de routage et non de contrôle de flux.

Articles similaires