Grifon (
https://www.grifon.fr/ ) est un FAI associatif autour de Rennes. Hier, les techs ont rencontré un problème bien connu mais toujours aussi relou. Alarig a écrit une version synthétique, sans tous les rappels, voir
https://www.swordarmor.fr/le-mtu-et-le-mss.html .
Quel est le problème ? Certains sites web ne fonctionnent pas comme un téléchargement sur dovecot.org, une visite sur laposte.net ou bien encore une visite sur un site web hébergé chez aws. On constate également que des mails n'arrivent pas et que Postfix consigne ce qui suit dans son journal : « postfix/smtpd[9095]: timeout after DATA (0 bytes) from [...] ».
Que montre une capture réseau (tcpdump, wireshark, etc.) ? Que la poignée de main TCP se passe bien, le GET HTTP aussi mais qu'ensuite, plus rien ne transite, puis la connexion TCP est fermée. Ça, ça illustre typiquement un problème de MTU.
Qu'est-ce que la MTU (Maximum transmission unit) ? La taille maximale d'un paquet IP, entête compris. En effet, tout ce qui transite sur nos réseaux informatiques est découpé en paquets de taille $MTU octets. C'est ça, la fragmentation IP. La MTU standard est de 1500 octets. Pourquoi ? Parce que c'est celle qui découle de celle normalisée à l'IEEE pour les réseaux Ethernet (1518) et vu la suprématie des réseaux Ethernet, cette taille est devenue un standard de fait. Pourquoi l'IEEE a-t-elle choisi une trame de 1518 octets au niveau 2 ?
* Parce qu'à l'époque, on avait des réseaux en bus et/ou des hubs. Le support de communication était beaucoup plus partagé qu'aujourd'hui. Il fallait donc gérer les collisions (deux machines qui causent en même temps, ça va donner nawak au niveau électrique dans les câbles donc plus aucune donnée récupérable). Fixer une limite permet d'éviter qu'une machine émette en permanence et bloque le support de communication (chaque trame Ethernet doit être suivie de 12 octets de vide ). Une taille connue permet également aux machines de se synchroniser plus simplement pour émettre puisqu'on connaît le temps que prendra la trame pour être émise et progagée sur le support de communication.
* Il fallait également tenir compte de la vitesse de propagation de bout en bout dans les supports physiques de communication et les topologies de l'époque (genre le temps que le signal arrive à l'autre bout du bus…). « Large packets occupy a slow link for more time than a smaller packet, causing greater delays to subsequent packets, and increasing lag and minimum latency. For example, a 1500-byte packet, the largest allowed by Ethernet at the network layer (and hence over most of the Internet), ties up a 14.4k modem for about one second. » ( source :
https://en.wikipedia.org/wiki/Maximum_transmission_unit ). C'est avec une charge utile (au niveau 3) de 1500 octets que l'on arrivait à avoir une bonne efficacité (ratio charge utile / taille totale de la trame au niveau 2) et du coup, un débit décent. Et 1500 octets, c'était important pour l'époque.
* Enfin, il fallait tenir compte de la mémoire disponible dans les machines de l'époque car, le temps "d'avoir l'autorisation d'émettre" sur le réseau, il faut bien stocker l'information dans un espace temporaire, un buffer, que ça soit sur les commutateurs, les routeurs ou les ordis.
La MTU a du sens uniquement sur le réseau local c'est-à-dire entre deux routeurs. Internet étant un agglomérat de réseaux interconnectés les uns aux autres et causant IP, on imagine bien que, pour aller d'un point A à un point B d'Internet (genre de chez vous à Google), on croisera plusieurs routeurs… possiblement interconnectés entre eux avec des technologies bien différentes… donc avec des MTU différentes. Ce qu'on nomme la PMTU (Path MTU) est simplement la taille de paquet qui permet la communication d'un point A à un point B sans fragmentation IP. Elle est forcément égale à la plus petite MTU des liaisons physiques traversées. Genre, soit les MTU de 5 segments réseaux traversés pour aller d'un point A à un point B d'Internet : 1492 - 1500 - 1476 - 1500 - 1500, la PMTU sera de 1476.
Heu mais ça veut dire que sur un réseau mondial très hétérogène, la PMTU peut varier du tout au tout entre un point A et un un point B et entre le même point A et un point C ? Oui, c'est l'idée. Heu… Et du coup, comment le point A apprend que la PMTU pour joindre A c'est 1500 octets et que la MTU pour joindre C c'est 1476 octets ?
* Soit on s'accorde sur une PMTU minimale, que tout le monde doit savoir gérer. IPv4 normalise une taille de 68 octets, IPv6 une taille de 1280 octets. Au passage, on comprend que 1280 était une valeur de MTU raisonnable pour la fin des années 1990, quand IPv6 a été normalisé. Cela signifie donc qu'on était loin du tout Internet en 1500 octets à ce moment-là. ;)
Quel est le problème avec cette méthode de fixer une valeur plancher ? Elle n'est pas du tout optimale : la destination perd des ressources à re-assembler les paquets. On perd en efficacité (la proportion d'entêtes pour transmettre un même message utile augmente) et en débit (il faut transmettre beaucoup plus de paquets par seconde pour échanger la même quantité d'infos).
* Bah, c'est trop facile, le routeur qui doit émettre sur un lien qui dispose d'une MTU plus faible n'a qu'à fragmenter chaque paquet reçu, en plus petit. Ainsi, on est au plus près de la PMTU. Dans mon exemple précédent de 5 réseaux interconnectés avec les MTU suivantes 1492 - 1500 - 1476 - 1500 - 1500 , la machine source émettra avec une MTU = 1492 octets et le routeur qui fait le lien entre le réseau avec une MTU de 1500 octets et celui avec une MTU de 1476 aura qu'à fragmenter les paquets reçus (de taille 1492 donc) en paquets de taille 1476 + 16 octets.
Oui, mais la destination perdra toujours des ressources à re-assembler les paquets. Pire, les routeurs perdront des ressources énormes à fragmenter les paquets (car il faudra traiter les paquets de manière logicielle alors que leur transfert sans fragmentation se fait de manière matérielle, sur des circuits optimisés) et à transférer de petits paquets (la limite d'un routeur, ce n'est jamais le nombre d'octets transmis par seconde mais plutôt le nombre de paquets transférés par seconde). Sans compter qu'ils devront avoir un bufffer pour stocker temporairement les données reçues en attendant d'avoir émis tous les fragments sur le lien à la MTU plus faible. On peut remplacer le buffer par un mécanisme permettant de signaler à la destination qu'on est débordé, prière de ralentir le rythme d'envoi mais aucun des mécanismes de ce type normalisés n'a réussi à percer à grande échelle). Bref, mauvaise idée.
Notons qu'en IPv6, les routeurs n'ont plus le droit de fragmenter selon les RFC.
* Soit on adopte un processus permettant de déterminer dynamiquement la "bonne" PMTU pour chaque destination. Plusieurs méthodes co-existent aujourd'hui.
* La première a été normalisée dans TCP (
https://www.ietf.org/rfc/rfc793.txt ), en 1981. Elle se nomme Maximum Segment Size (MSS). Il s'agit d'une option de TCP. Oui, UDP peut aller se faire voir. UDP est minimaliste et ne permet pas d'implémenter cela. De plus, à l'époque, personne ne pouvait prévoir que quelques usages gourmands apparaitraient 25 ans plus tard comme DNSSEC. Cette option permet de dire : je ne suis pas en mesure de recevoir/émettre plus de telle quantité de données. Cette quantité exclut l'entête TCP. En gros, on a donc MSS = MTU - entête IP (20 octets en IPv4, 40 octets en IPv6, sans les options dont il ne faut pas tenir compte) - entête TCP (20 octets, sans les options dont il ne faut pas tenir compte, selon les RFC). Donc, pour une MTU de 1500 octets, on a une MSS de 1460 octets, par exemple. Cette option est échangée une seule fois, uniquement lors de la poignée de main TCP (dans le SYN et le SYN ACK). La source apprend donc la MSS de la destination et inversement. Elle est calculée automatiquement à partir de la MTU configurée sur l'interface réseau par laquelle le trafic sera émis.
Heu ? Cette méthode ne permet pas de trouver la PMTU mais uniquement de s'échanger la MTU des extrémités ?! Et si la MTU diminue subitement en plein milieu du chemin ? Hé bah oui, MSS ne permet pas de voir cela… À moins de ré-écrire sauvagement la valeur de l'option MSS de tous les paquets réseau amenés à circuler sur la liaison à la MTU limitée. C'est typiquement ce qui se passe dans nos box Internet : les données sont encapsulées en PPoE. Ce protocole consomme 8 octets d'entête. On ne peut donc plus stocker 1500 octets au niveau IP mais 1492 octets. Pourtant, entre nos ordinateurs et notre box, la MTU est bien de 1500 octets. Donc la valeur MSS transmise sera de 1460 car notre ordinateur ignore que la MTU sera inférieure derrière la box. La box réécrit ça en douce : MSS = 1456. Cette méthode se nomme TCP MSS clamping. Cette méthode est plutôt crade. D'abord parce qu'elle consomme des ressources sur les routeurs (ça ne se voit pas sur une ligne ADSL, j'en conviens). Ensuite parce qu'un opérateur réseau n'a pas à regarder au-delà d'IP (vous n'avez pas le contrôle de la box donc c'est bien un routeur qui obéit à votre FAI).
Dans le même genre, notons bien que la MSS est échangée uniquement à l'initialisation de la session TCP. Et si le routage change alors que la session TCP est encore ouverte ? La MSS n'est pas optimale (cas d'une PMTU qui augmente) voire le transfert s'interrompt (PMTU en baisse).
* La seconde, nommée PMTUD (PMTU discovery) a été normalisée en 1990 (
https://tools.ietf.org/html/rfc1191 ). Il s'agit de découvrir la PMTU. Comment ? L'émetteur émet au max de sa MTU en positionnant le flag Don't Fragment (DF) dans l'entête IP. Les routeurs n'ont ainsi pas l'autorisation de fragmenter le paquet. Si le paquet doit être fragmenté à moment donné sur le chemin, le routeur émet un message ICMP type 3 code 4 « Fragmentation needed » à destination de la source. La source doit adapter la taille des paquets qu'elle émet. Cette fois-ci, ça fonctionne pour TCP, UDP et n'importe quel autre protocole de couche 4 (SCTP, etc.) ! \o/
Notons que la PMTUD est la seule méthode utilisable en IPv6 puisque les routeurs ne doivent plus fragmenter même quand c'est nécessaire. Booon, y'a aussi la méthode de la taille minimale, en vrai, mais bon… :)
Enfin une méthode magique qui résout tous nos problèmes ?! Hé bah non. Beaucoup d'administrateurs-trices systèmes et réseaux sont incompétent-e-s et bloquent tout ICMP sur leur réseau et/ou leurs serveurs. Ainsi, leurs machines ne reçoivent pas le message ICMP « Fragmentation needed » et n'adaptent pas leur taille d'émission en conséquence. La PMTUD ne peut produire aucun résultat.
Pourquoi les administrateurs-trices font-ils cela ? Parce qu'ils-elles ont peur du Ping of Death (une vieille attaque par déni de service qui ne fonctionne plus aujourd'hui, qui consistait à envoyer des paquets IP dépassant la taille maximale normalisée (2^16 -1), en espérant un buffer oveflow dans l'implémentation, ce qui n'a rien de spécifique à ping/ICPM mais ping permettait de faire une démo rapide et efficace) ? Parce qu'ils-elles ont acheté une middlebox filtrante magique pour éviter d'avoir à réfléchir et que les codeurs-euses qui ont sorti c'te box n'y connaissent rien en réseaux informatiques parce que c'pas leur domaine ? Parce qu'ils-elles ont peur de certaines parties du protocole comme l'échange de timestamp ou le redirect (on peut désactiver ça simplement sur tous les systèmes sérieux…) ? Bref, avec tout pare-feu de ce nom, on peut laisser passer uniquement les messages ICMP en rapport avec une connexion existante (echo reply d'un request qu'on a lancé, ttl expired, destination unreachable, etc.) : iptables -A INPUT -p icmp -m state --state RELATED -j ACCEPT ; ip6tables -A INPUT -p icmp6 -m state --state RELATED -j ACCEPT . Il n'y a aucune raison valable de filtrer intégralement ICMP.
Une nouvelle méthode a été normalisée depuis 2007 (
https://www.bortzmeyer.org/4821.html ). « Notre RFC propose donc une alternative, ne dépendant pas de la réception des paquets ICMP et fonctionnant donc en présence de « trous noirs » qui absorbent tous ces paquets ICMP. Il suggère tout simplement de tenir compte des paquets perdus, en supposant que si seuls les plus gros se perdent, c'est probablement qu'ils étaient plus gros que la MTU. La nouvelle méthode est donc d'essayer des paquets de différentes tailles et de surveiller les pertes. ». Inutile de préciser que cette méthode n'est pas déployée.
Bref, la découverte de la PMTU c'est toujours la merde en 2016. Comme c'est la méthode privilégiée, les paquets IP se retrouvent avec le flag DF et donc, quand la PMTU foire, tout foire puisque les routeurs ne sont pas autorisés à fragmenter…
Hum, mais attend… si Ethernet est répandu partout, on devrait avoir 1500 octets partout donc osef de la découverte de PMTU ? Non. Cœur de réseau = 1500 octets. Fibre optique à la maison = PPoE = 1492. ADSL soit PPoE soit PPoA (1468 même si ça se fait plus trop), etc. Ensuite, il faut rajouter toute sorte de tunnels qui font notre quotidien : IPv6 over IPv4 pour avoir de la connectivité v6 quand on a un FAI qui n'en fournit pas, VPN pour contourner la censure et autres filtrages débiles, IPSec pour des liens sécurisés, etc. Donc non, les problèmes de MTU n'ont pas disparu, bien au contraire.
Revenons à Grifon : l'association a deux transitaires (voir ici pour une définition :
https://wiki.arn-fai.net/technique:routage#qu_est-ce_qu_un_transitaire ) : Cogent et ARN. Oui, ARN fournit de la connectivité à Grifon car Cogent ne fournit pas toutes les routes vers tous les réseaux qui composent Internet : il manque au moins Google et HE en IPv6. De plus, cela apporte de la redondance dans le cas où Cogent se vautre sur le routage mais que le routeur est encore en mesure de transférer des paquets (rigolez pas, ce cas s'est déjà produit suite à une maintenance).
On remarque que les destinations à problèmes passent toutes par ARN, partiellement (juste aller ou retour) ou en totalité (pour l'aller et le retour). Quelle est la particularité de l'interconnexion entre ARN et Grifon ? Comme les deux assos sont totalement à l'opposé d'un point de vue géographique (Rennes - Strasbourg :D ), on se doute bien qu'elles n'ont pas pu investir dans une liaison physique à plusieurs milliers d'euros/mois. Il s'agit donc d'un tunnel GRE. On perd donc 4 octets pour les entêtes GRE + 20 octets pour l'entête IP encapsulée. Sur la liaison entre ARN et Grifon, on a donc une MTU de 1476 octets.
Le premier réflexe quand on détecte un fail de MTU, c'est de diminuer la taille de la MTU des deux côtés de la liaison problématique. Mais c'est bien souvent une erreur. En l'occurrence, entre Grifon et ARN :
* La MTU configurée sur les interfaces GRE était 1476 donc elle correspondait à la théorie ( 1500 - entête GRE - entête IP) ;
* Un « ping <IP_pair> -s 1448 -M do » fonctionnait parfaitement. Cette commande envoie un paquet ICMP avec 1448 octets de bourrage. On ajoute l'entête ICMP (8 octets) et l'entête IP (20 octets). On arrive donc à 1476, notre MTU. La liaison était donc fonctionnelle ;
* Sur cette interconnexion, on avait une session BGP parfaitement fonctionnelle. Or, la table de routage complète d'Internet est beaucoup plus grosse que 1500 octets. En faisant une capture réseau, on voit bien que la MTU = 1476 et que la MSS = 1436.
BTW, pro-tips : dans notre cas, la length affichée par tcpdump est celle au niveau TCP, sans les entêtes. La length affichée par wireshark correspond à la totalité du paquet sauf que la libpcap ne lui file pas un header "valide" (aka que c'est une interface de niveau 3 mais pas enregistrée comme telle), comme GRE, il remplace par Linux cooked capture (
https://wiki.wireshark.org/SLL ) soit 16 octets qui n'existent pas. La technique imparable est de regarder la taille inscrite dans l'entête IP avec Wireshark.
Autre pro-tips : lors d'un changement de MTU sur une interface, on constate que l'ancienne valeur est mise en cache (on voit ça avec ip r g <IP> ou route show <IP> sous BSD). Il faut donc vider le cache (ip route flush cache <IP>) ou up/down l'interface réseau.
On retiendra donc ceci : quand un ping (avec du bourrage) dont la taille est égale à la MTU circule sur un lien, alors diminuer la MTU ne corrigera pas le problème. Par contre, cela ne met pas en évidence une MTU sous-optimale. ;)
Le deuxième réflexe, c'est de remonter la chaîne : on wget les sites web problématiques depuis le routeur d'ARN : ça fonctionne. On wget les sites web problématiques depuis le routeur de Grifon, ça fonctionne. On wget depuis toute autre machine (physique ou virtuelle) située derrière le routeur Grifon, ça ne fonctionne pas. Quelle est la différence entre une machine derrière le routeur et le routeur lui-même ? Ça ne peut pas être un problème de routage interne puisque c'est spécifique à l'interconnexion avec ARN et que si on vire temporairement cette interconnexion, les wget fonctionnent depuis les machines derrière le routeur Grifon.
La différence, c'est la MSS. Le routeur Grifon sait que le paquet va sortir par l'interface ARN. Il calcule donc la MSS (1436) par rapport à la MTU de cette interface (1476). Mais la machine derrière le routeur, elle calcule la MSS (1460) en fonction de son interface à elle (1500), celle qui donne sur le réseau Grifon. C'est ce qui fait que ça fonctionne sur le routeur mais pas plus loin.
Mais… la découverte auto de la PMTU n'est pas censée résoudre ce souci ? Si,si, mais pas quand la destination bloque tout ICMP… C'est clairement le cas ici et une capture réseau sur le routeur d'ARN le met en évidence : au premier paquet dépassant 1476 octets (la MTU sur le GRE entre ARN et Grifon), le routeur d'ARN émet un message ICMP « Fragmentation needed » mais la destination n'en tient pas compte. Le routeur d'ARN ne peut pas fragmenter lui-même puisque, pour que la PMTU fonctionne, il faut positionner le flag « Don't Fragment » d'IP, chose que fait la destination. On sent l'incompétence : laisser la PMTUD activée et virer tout ICMP… paie ta cohérence…
Quelles sont les solutions ?
* Grifon souscrit à une deuxième vraie prestation de transit IP qui ne lui sera pas livrée dans un tunnel donc la MTU sera égale à 1500 octets. Cette solution serait la meilleure (et pas que pour le problème de MTU) mais les finances de l'association ne permettent pas cela pour l'instant.
* Sur toutes les machines des abonné-e-s Grifon, on fixe la MTU à 1476 octets c'est-à-dire, la valeur minimale des MTU des interconnexions de Grifon avec le reste du monde. Ainsi, on fait une partie du job de la PMTUD et la MSS sera fixée à 1436 octets ce qui permettra d'accéder à des réseaux dans lesquels ICMP est bloqué. C'est chiant à faire (il faut prévenir les abonné-e-s, les relancer, s'assurer que la modif' a été faite, changer le template utilisé lors de la création des machines virtuelles,…) mais c'est jouable. Dans /etc/network/interfaces, il faut ajouter « mtu 1476 » dans une description d'interface static/manual.
* Sur le routeur de Grifon, on peut re-écrire la MSS, faire du MSS clamping, quoi. Avec Packet Filter (pf), ça se dit : « scrub out all max-mss 1436 ». Avec iptables, ça se prononce : « iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1476 » + la même chose avec ip6tables. Rapide et efficace.
Un côté pas cool c'est qu'il y a un routeur qui lit la couche 4 et qui la modifie. Ce n'est pas son rôle. BTW, ça ne passerait pas avec un IPSec configuré pour faire de l'authentification. ;)
Un autre aspect pas top, c'est que ça concerne uniquement TCP. Quid des applications au-dessus d'UDP ? Celles qui ne positionnent pas le flag DF verront leurs paquets fragmentés donc pas de soucis. Pour les autres, on retournera dans les travers de la PMTUD : ça ne fonctionnera pas avec des réseaux mal configurés. La question est : est-ce que les usages qui ont recours à des gros paquets + UDP + DF sont nombreux ? DNS ne semble pas positionner le flag DF (j'ai testé avec dig et unbound). La VOIP positionne le flag DF (pour RTP et STUN) mais les paquets ont une taille ridicule. En torrent, en revanche, l'usage du flag DF et de paquets > 1400 m'apparaît bien plus courant. Pour les VPN TLS (OpenVPN, par exemple), le flag DF est positionné et les paquets IP sont gros, mais, généralement, l'administrateur-trice du serveur VPN a prévu le coup (il-elle sait que son VPN sera utilisé sur des réseaux nazis… ) et demande à OpenVPN de fragmenter à une valeur qui passe partout comme 1300-1400 octets (paramètres de configuration « fragment » et « mssfix »).
* On pourrait imaginer une sorte de compromis genre fixer une MTU de 1476 sur l'interface interne du routeur Grifon et espérer que les machines des abonné-e-s l'utilise pour calculer la MSS. Mais ce n'est pas le cas : on n'utilise pas la PMTU pour calculer la MSS (
https://www.rfc-editor.org/rfc/rfc2923.txt , section 2.3 ) à cause du routage asymétrique et du fait que la MSS ne peut plus changer une fois la session TCP établie alors que le routage peut fluctuer, faisant ainsi changer le chemin.