mtu, pmtu, mss…
Le mauvais sort du réseau a encore frappé ! Pour une raison mystérieuse, Outlook sur les postes clients a perdu la connexion avec le réseau Exchange. Il n’y a eu aucune intervention sur les routeurs ou switches, les câbles réseaux fonctionnent, et même les serveurs Exchange sont opérationnels. C’est mystique, vraiment. Le seul point sur lequel je n’ai pas la main concerne le transit entre les postes clients et les serveurs Exchange.
Position relative des postes de travail et des serveurs Exchange
Une analyse préliminaire sur mon poste de travail avec Process Explorer n’indique également rien d’anormal. On voit clairement qu’Outlook parvient à établir avec succès une connexion HTTPS vers les serveurs Exchange.

Le ping également passe !

Ce genre de connexion bizarre est généralement dû à un problème de MTU, c’est à dire que des paquets sont dropés sans que l’émetteur desdits paquets n’en ait conscience, et pendant ce temps, le destinataire attend…
Le MTU, késaco
Revenons un peu à la théorie afin de comprendre ce qu’il se passe. Un périphérique connecté sur un réseau Ethernet peut envoyer des paquets sur ce réseau. Cependant ces paquets ne peuvent excéder le MTU ou Maximum Transfert Unit. Il s’agit de la taille maximale du datagramme, exprimée généralement en octet du paquet à partir de la couche 3 (en-tête IP inclus).
En cas de dépassement, il existe plusieurs possibilités :
- le paquet n’a pas encore quitté l’émetteur originel
- il sera fragmenté
- le paquet est en transit sur le réseau
- le paquet arrive à un switch : ceux-ci gèrent aujourd’hui nativement les paquets faisant jusqu’à 9000 octets (si configuré ainsi)
- le paquet arrive à un routeur
- le routeur fragmente le paquet. Ce cas est très rare car il demande une charge de travail supplémentaire au niveau de l’équipement réseau
- le routeur drop le paquet et émet une notification ICMP de type 2 (Paquet Too Big) afin que l’émetteur ajuste en renvoyant un paquet de taille plus modeste
Le problème qui arrive souvent est qu’un paquet est émis mais est trop gros lorsqu’il arrive à un routeur. Le routeur drop donc le paquet et envoie la notification, malheureusement cette dernière n’est parfois jamais reçue, parce qu’un routeur sur le chemin de retour a été abusivement configuré pour bloquer la plupart des paquets de type ICMP. L’émetteur du gros paquet n’a donc pas conscience que ce paquet n’a jamais été reçu et logiquement le renvoi, mais il est toujours trop imposant.
Le MTU est défini localement sur chaque périphérique et doit donc être identique à tous les périphériques du réseau local afin d’éviter tout drop de paquet. Sa valeur est généralement de 1500, que cela soit sur Internet, au niveau des serveurs dédiés ou en réseau local (home ou professionnel). Mais il existe des cas où sa valeur est moindre :
- les connexions ADSL ou Fibre non-pro s’initient généralement en PPPoE. Ce protocole encapsule tout le traffic avec un coût de 8 octets, ce qui réduit le MTU à 1492 sur l’interface “Internet”.
- les tunnels SIT (IPv6-in-IPv4 par exemple) ajoutent un overhead de 20 octets si le transport se fait en IPv4.
- les tunnels IPSec ajoutent un overhead d’environ 54 octets, la valeur exacte dépendant des algorithmes utilisés
Explications
MTU sur une connexion Internet grand publique
Ci-dessus j’ai représenté un cas classique de la manière dont le grand publique est connecté à Internet, que cela soit en ADSL/VDSL mais également en fibre où certains FAI continuent encore à utiliser une encapsulation PPPoE pour effectuer l’authentification, comme Orange.
On se pose dans le cas d’un modem stupide mais respectant les spécifications réseaux et où tous les liens ont un MTU à 1500 sauf le lien modem⟷Internet (cas assez réaliste).
Si c’est le poste client qui envoie un paquet trop gros, par exemple une trame Ethernet faisant exactement 1500 octets. On peut voir que ce paquet pourra arriver jusqu’au modem, mais ce dernier ne pourra pas le faire transiter sur Internet car son lien avec Internet lui impose une limite à 1492 octets. Le modem va donc droper le paquet et envoyer un “Paquet Too Large” au poste client en ICMP. Le poste client va donc ré-effectuer son envoi mais en réduisant ses trames afin que cela puisse passer.
Si en revanche c’est le serveur qui envoie un paquet trop gros, son paquet va transiter de réseau en réseau à travers Internet jusqu’à arriver au dernier commutateur du FAI. Comme précédemment, le paquet ne peut aller plus loin en raison du MTU plus faible sur le prochain lien. Comme précédemment, le paquet est dropé, et une notification est renvoyé au serveur. La pratique montre cependant que le serveur ne reçoit pas toujours la notification et donc retente régulièrement l’envoi de son gros paquet jusqu’à ce qu’un des deux parties, le poste client ou le serveur, abandonne par timeout. La difficulté de cette situation est l’absence d’indice permettant d’identifier la problématique, en effet les connexions TCP sont bien établies au niveau du poste client et du serveur. Cela indique que le réseau “fonctionne” entre les deux partenaires. Cela s’explique simplement par le fait que l’établissement d’une connexion TCP ne nécessite que des paquets de très petites tailles, donc bien en-dessous de la limite du MTU.
Solutionner le cas “PPPoE” sur un router Linux
Afin de palier à ces routeurs intermédiaires qui dropent l’ICMP, il existe sous Linux une extension à iptables permettant de modifier le MSS d’un paquet. Le MSS ou Maximum Segment Size est un paramètre spécifique à TCP, et demande à ce que le partenaire de la connexion limite sa charge utile à la valeur indiquée par le MSS. La charge utile ici est hors en-tête TCP.
Décomposition d’une frame Ethernet et délimitation du MTU et du MSS
Le MSS est spécifique au TCP, cela signifie que le problème demeure pour les autres protocoles de la couche IP. Cependant la limite posée par le MTU n’est que très rarement dépassée sur les autres protocoles communs que sont l’ICMP et l’UDP et ne pose donc aucun problème en pratique.
Au niveau du modem ou du routeur qui se trouve juste devant, il suffira de modifier le paquet avec cette règle iptables. De cette manière, toutes les connexions TCP sortantes en demande d’ouvertures contiendront une information, le MSS, qui permettra au partenaire de ne pas envoyer des paquets plus gros que ce qu’aura déterminé le PMTU. Etant donné que le problème se trouve généralement côté client avec son MTU réduit, le PMTU devrait fonctionner.
iptables -t mangle -A POSTROUTING -o ppp0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
IPSec
Le même problème de connexion instable se pose sur les tunnels IPSec. Comme on peut le voir ci-dessous, un paquet qui passe dans un tunnel a beaucoup plus de chance de dépasser le MTU en raison de la surcharge imposée par le tunneling IPSec. En effet à la trame initiale s’ajoute un en-tête IP complet, un en-tête ESP, et le payload qui sera très légèrement plus grand en raison du padding utilisé avec l’AES, algorithme courant pour l’IPSec.

L’utilisation de --clamp-mss-to-pmtu n’a pas eu l’effet escompté sur les connexions tunnelées. Cela indique que le PMTU est défaillant et qu’il faut dans ce cas spécifier manuellement le MSS et ou le MTU. Pour ma part, ce n’est peut-être pas optimal car j’ai fait les deux mais ça marche bien.
Afin de déterminer le MTU, il suffit d’envoyer un ping vers la destination (un hôte de l’autre côté du tunnel) en bloquant la fragmentation. Il suffit par essai successifs de trouver la valeur seuil à partir de laquelle le ping ne part plus. On pourra procéder par dichotomie afin de minimiser le nombre de tentatives.
ping -f -l 1392 -n 1 192.168.14.34
Une fois la valeur déterminée et stabilisée (faites plusieurs ping pour confirmer que tous les echo passent avant de valider la valeur), il suffira d’y ajouter 28 pour obtenir le MTU max sur un lien IPv4. 28 s’obtenant par 20 octets d’en-tête IPv4 et 8 octets d’en-tête ICMP.
Pour obtenir le MSS en IPv4, il faudra soustraire au MTU 40 octets, soit 20 octets d’en-tête IPv4 et 20 octets d’en-tête TCP. Pour obtenir le MSS en IPv6, il faudra soustraire au MTU 60 octets, soit 40 octets d’en-tête IPv6 et 20 octets d’en-tête TCP.
Une fois en possession de ces valeurs, nous pouvons les fixer pour stabiliser les connexions qui passent par le VPN. Dans mon cas, j’ai déterminé que le MSS à appliquer était de 1398. Afin de ne pas détériorer les connexions qui auraient déjà paramétré un MSS plus petit pour des raisons de réactivité, la règle inclus un filtre ignorant ces paquets. L’extension policy permet de différencier les connexions qui vont dans un tunnel IPSec.
iptables -A POSTROUTING -o ppp0 -p tcp -m policy --dir out --pol ipsec -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1399:1500 -j TCPMSS --set-mss 1398
Strongswan permet en outre de fixer un MTU pour tout ce qui passe dans un tunnel. Cependant ce MTU est global à toutes les connexions. Il faudra y prêter une attention particulière car le MTU de la connexion chiffrée dépend de la surcharge IPSec, et celle-ci varie si le lien se fait en IPv4 ou en IPv6 ! Exemple chez moi :
kernel-netlink {
# MSS to set on installed routes, 0 to disable.
# mss = 0
# MTU to set on installed routes, 0 to disable.
mtu = 1425
}