Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Ève

Régler les problèmes de MTU et de MSS

Première rédaction de cet article le 27 juin 2007
Dernière mise à jour le 3 septembre 2007


Les liens Internet dont la MTU est plus faible que les traditionnels 1500 octets rencontrent souvent de nombreux problèmes, qui frappent particulièrement les transferts TCP. Quelles sont les solutions pratiques ?

Le lien « standard » sur Internet a une MTU de 1500 octets, héritée d'Ethernet. Si, de bout en bout, tous les liens ont cette MTU, la machine émettrice peut fabriquer des paquets de 1500 octets et ils arriveront intacts. Mais il y a souvent sur le trajet un lien dont la MTU est plus faible, par exemple un tunnel d'IPv6 dans IPv4 ou bien un tunnel GRE ou encore une connexion PPPoE sur de l'ADSL. On observe alors souvent un phénomène désagréable : les paquets de petite taille, tels que ceux fabriqués par ping ou traceroute passent mais les gros paquets, par exemple les transferts de fichier avec HTTP bloquent mystérieusement. Le déboguage est donc difficile. Une bonne technique est de tester la connectivité, non pas avec ping AUTRE-MACHINE tout court mais avec ping -s 1480 AUTRE-MACHINE pour forcer l'usage de paquets de 1480 octets. Si le premier ping fonctionne mais pas le second, vous avez un problème de MTU.

Si tous les ping fonctionnent, ainsi que des essais avec UDP, mais que TCP n'y arrive pas, cela peut être parce que les implémentations d'IP ne mettent souvent le bit DF (Don't Fragment, un bit dans l'en-tête IP qui indique aux routeurs de ne pas fragmenter le paquet) que pour TCP. Sur Linux, mettre l'option net.ipv4.ip_no_pmtu_disc du noyau à 1 pourrait permettre de tester cette hypothèse (je n'ai pas essayé).

En théorie, ces problèmes de MTU ne devraient jamais arriver. En IPv4, le routeur qui connecte au lien « trop étroit » devrait fragmenter les paquets, en IPv6, la découverte de la MTU du chemin, décrite dans le RFC 1981, devrait empêcher la machine émettrice d'envoyer des paquets trop gros. (Cette découverte de la MTU du chemin peut aussi se faire en IPv4, cf. le RFC 1191, pour éviter la chute de performance provoquée par la fragmentation.)

Mais, en pratique, cela marche mal (le RFC 2923 a été le premier à expliquer ce problème, le RFC 4459 le détaillant ensuite et le RFC 4821 finissant par proposer d'abandonner l'ancien mécanisme et d'en utiliser un tout nouvau). En effet, la découverte de la MTU du chemin dépend de la bonne réception des messages ICMP Packet too big. Ceux-ci sont malheureusement souvent filtrés par des coupe-feux mal configurés par des administrateurs ignorants qui croient améliorer la sécurité en bloquant tout ICMP. On constate alors qu'on peut transférer des fichiers avec certains sites et pas d'autres, selon la configuration des coupe-feux situés sur le chemin. (Un excellent article très complet sur la question est A Tale of Two Protocols: IPv4, IPv6, MTUs and Fragmentation.)

Il existe alors plusieurs solutions. On peut se passer de tunnel, on peut tricher un petit peu pour remonter la MTU de PPPoE (le RFC 4638 explique comment), on peut attendre le déploiement de la nouvelle technique de découverte de MTU du chemin, normalisée dans le RFC 4821 mais il existe aussi des solutions plus raisonnables.

Une classique est est d'abaisser manuellement la MTU sur toutes les machines du réseau local (sur Unix, cela se fait avec la commande ifconfig). C'est pénible car il ne faut pas oublier de machine et cela abaisse la MTU (donc les performances) même pour les transferts purement locaux.

Cela peut se faire spécifiquement pour IPv6 si on utilise le RA (Router Advertisement, autoconfiguration IPv6 sans état). Une option permet en effet de fixer la MTU du lien. Plus besoin alors de courir sur toutes les machines. Si on utilise le démon radvd, c'est l'option AdvLinkMTU. Par exemple :

interface eth0
{
...
   AdvLinkMTU 1460;
...
};  

Une autre approche est de partir du fait que le problème touche surtout TCP, plus souvent impliqué dans les gros transferts et elle est donc spécifique à TCP (le test avec ping échouera donc toujours puisque ping utilise ICMP). Elle consiste à modifier la MSS, la taille maximale des paquets qu'annonce une machine qui fait du TCP à son partenaire. On voit cette annonce avec tcpdump :

13:35:32.703105 2001:7a8:7509:0:216:3eff:fe78:b525.65513 > 2001:660:3003:2::4:20.80: \
         S 4227314701:4227314701(0) win 32768 \
         <mss 1400,nop,wscale 0,sackOK,nop,nop,nop,nop,timestamp 0[|tcp]> \
         [flowlabel 0xbbb6b]

On voit ici la machine 2001:7a8:7509:0:216:3eff:fe78:b525 qui annonce, dans le paquet d'établissement de connexion TCP (paquet S pour SYN) une MSS de 1400 octets.

Pour modifier cette valeur, on peut bien sûr intervenir sur toutes les machines du réseau local mais c'est un travail pénible pour l'administrateur. Mieux vaut modifier de force ce réglage sur le routeur, ce qui se nomme le MSS clamping. Cela peut se faire dans le démon pppoe avec l'option -m, par exemple, dans /etc/ppp/peers/MON-FAI, on met pty "pppoe -I eth1 -T 80 -m 1412". À noter que, sur Linux, ce clamping ne fonctionne apparemment que pour IPv4, obligeant à trouver une autre solution (comme l'annonce d'une MTU par RA) pour IPv6. Le clamping existe aussi sur d'autres systèmes ; par exemple, sur IOS, c'est l'option ip tcp adjust-mss (profitons-en pour signaler que Cisco a une excellente documentation sur le sujet).

On peut aussi créer une règle iptables ou ip6tables avec la cible TCPMSS, mais celle-ci n'est disponible en IPv6 que depuis la version 2.6.21 du noyau Linux, et n'est acceptée que par des versions très récentes d'iptables. Le principe (je n'ai pas testé moi-même, je copie Pascal Hambourg) :

iptables -t mangle -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --clamp-mss-to-pmtu

On peut aussi remplacer --clamp-mss-to-pmtu, qui force (to clamp) la MSS à la valeur de la MTU par --set-mss XXX qui permet d'indiquer une valeur explicite.

Notons enfin que le fait de fixer la MTU, quel que soit le moyen utilisé, ne va pas forcément influer directement sur le MSS. Par exemple, avec NetBSD, par défaut, la MSS est la MTU de l'interface réseau non-locale ayant la plus grande MTU, ce qui peut être déroutant si on a une interface virtuelle ayant une MTU de plusieurs dizaines de milliers d'octets. Il peut donc être prudent de mettre les paramètres sysctl net.inet6.tcp6.mss_ifmtu et net.inet.tcp.mss_ifmtu à 1, pour que la MSS soit celle de la MTU de l'interface utilisée.

Un grand merci à Pascal Hambourg pour son aide pour déboguer ces problèmes et les documenter. Et merci aussi à Christophe Wolfhugel pour des détails érudits.

Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)

Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)