Linux et le cas étrange des trous noirs TCP/IP

Sur Internet, certains sites peuvent être mystérieusement inaccessibles, alors que le serveur fonctionne parfaitement. Quelle est la cause de ces étranges trous noirs ?

Lors de l’établissement d’une connexion TCP, les 2 machines négocient la taille maximale des données échangés (le MSS). Cependant, elles ignorent les contraintes propres au chemin suivi.

À partir de ce MSS, le système déduit le MTU, la taille maximale des paquets échangés. Le MTU est toujours égal à la taille du MSS plus 40 (la taille totale des en-têtes IP et TCP).

MTU = MSS + 40

Normalement, si un paquet envoyé est trop gros, le routeur concerné le signale, via un message ICMP, et la machine émettrice ajuste sa taille de paquets1.

Cependant, ces messages ICMP sont parfois bloqués par des pare-feu paranoïaques. C’est là que les ennuis commencent. Le routeur jette le paquet et signale le problème avec un message ICMP. Ce message est bloqué par le pare-feu. C’est le trou noir. Les paquets continuent à être envoyées, mais sont perdus à chaque fois.

Une solution à ce problème est décrite par le RFC 4821. Afin de déterminer la taille optimale des paquets, l’émetteur va envoyer des sondes, c’est-à-dire des paquets un peu plus gros que les autres, afin de rechercher la taille idéale. Si la sonde ne passe pas, mais que le reste du trafic passe, c’est que la taille de la sonde est trop grosse. Si elle passe, sa taille servira de base aux paquets suivants.

Linux dispose de ce mécanisme depuis le noyau 2.6.17, activable via l’option tcp_mtu_probing. Cependant, cette première mise en œuvre n’est pas très subtile.

Lorsque la détection démarre, on bascule à une taille de segment par défaut (tcp_base_mss2). Si rien ne passe, on divise cette taille par 2. Si les paquets passent, on essaie d’envoyer des sondes en doublant la taille des segments (MSS).

Avec l’option tcp_mtu_probing=1, on ne déclenche ce mécanisme que si l’on détecte un trou noir (un blocage du trafic).

Par exemple, avec une taille de segment négociée de 1460, on détecte un trou noir. La taille des segments est forcée à 512 (ça passe), puis on envoie une sonde à 1024 (ça passe encore), la taille des segments de la connexion passe à 1024. On devrait envoyer une sonde à 2048, mais cela dépasse le MSS négocié en début de connexion. Fin de la recherche. Le trafic reste sur une taille de segments de 1024. Même si la taille de segment optimale était de 1280.

Cela dit, c’est un compromis acceptable pour éviter les blocages. Mieux vaut un débit un peu dégradé qu’un blocage du trafic.

On peut paramétrer ce mécanisme pour être utilisé en permanence avec l’option tcp_mtu_probing=2.

Inutile de le dire, dans ce cas là, c’est une très très mauvaise idée.

Dans ce second cas, dès l’établissement du flux, on bascule en mode détection de MTU. Donc, on commence à la taille de segment définie par tcp_base_mss, puis on envoie des sondes en doublant la taille de segment à chaque fois. Pour un réseau Ethernet, on va se retrouver systématiquement avec une taille de segment de 1024 (au lieu de 1460).

Avec le noyau Linux 4.1, ce mécanisme a été sérieusement amélioré. La taille n’est plus doublée, mais les sondes sont utilisée pour réaliser une recherche par dichotomie de la taille idéale, ce qui permet de se rapprocher assez rapidement de la taille idéale. De plus, la taille de segment de base (tcp_base_mss) passe à 1024.

Quel paramétrage adopter ?

  • Activez systématiquement ce mécanisme en mode détection de trou noir (tcp_mtu_probing=1).

  • Pour les noyaux < 4.1, passez la taille de base (tcp_base_mss) à 1024.

Avec les noyau 4.1 et plus, le mode tcp_mtu_probing=2 est beaucoup moins pénalisant, mais pas forcément recommandé (il y a une dégradation de la taille des paquets en début de connexion). Dans tous les cas, le mode tcp_mtu_probing=1 permet de se protéger des trous noirs sans prendre de risque.


  1. C’est le mécanisme de découverte automatique du MTU du chemin (Path MTU discovery), décrit par le RFC 1191. Ce mécanisme est activé par défaut sous Linux. ↩︎

  2. La valeur de tcp_base_mss est 512 par défaut. Cette valeur passe à 1024 avec le noyau 4.1. Une valeur de tcp_base_mss de 1024 devrait vous donner un MTU initial de 1064 (1024 + 20 pour l’en-tête IP + 20 pour l’en-tête TCP). Comme rien n’est simple, sur les anciens noyaux Linux, il faut également y ajouter la taille des options TCP. ↩︎