Chez Cloudflare, nous avons beaucoup d’expérience dans l’exploitation de serveurs dans le monde sauvage d'Internet. Mais nous améliorons toujours notre maîtrise de cette magie noire. Sur ce blog même, nous avons abordé plusieurs angles sombres des protocoles Internet : comme comprendre FIN-WAIT-2 ou régler le tampon de réception.

Image CC BY 2.0 par Isaí Moreno

Un sujet, à-savoir, les attaques de type SYN, n'a cependant pas eu assez d'attention. Nous utilisons Linux et il s’avère que la gestion des paquets SYN sous Linux est vraiment complexe. Dans cet article, nous tenterons de répondre à la question.

L'histoire de deux files d'attente

Premièrement, nous devons comprendre que chaque socket lié, dans l'état TCP « ÉCOUTE », a deux files d'attente distinctes :

  • La file d'attente SYN
  • La file d'attente Accepter

Dans la littérature, ces files d'attente portent souvent d'autres noms, tels que « reqsk_queue », « Accumulation ACK », « Accumulation d’écoute » ou même « Accumulation TCP ». Mais je vais m'en tenir aux noms ci-dessus pour éviter toute confusion.

File d'attente SYN

La file d'attente SYN stocke les paquets SYN entrants [1] (spécifiquement : struct inet_request_sock). Elle est responsable de l'envoi des paquets SYN + ACK et de réessayer après l'expiration du délai. Sur Linux, le nombre de tentatives est configuré avec :

$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5

Les documents décrivent cette bascule :

tcp_synack_retries - INTEGER

	Number of times SYNACKs for a passive TCP connection attempt
	will be retransmitted. Should not be higher than 255. Default
	value is 5, which corresponds to 31 seconds till the last
	retransmission with the current initial RTO of 1second. With
	this the final timeout for a passive TCP connection will
	happen after 63 seconds.

Après la transmission du SYN + ACK, la file d’attente SYN attend un paquet ACK du client, le dernier paquet de l’établissement de liaison en 3 phases. Tous les paquets ACK reçus doivent d'abord être mis en correspondance avec la table de connexion entièrement établie, puis seulement avec les données de la file d'attente SYN appropriée. En cas de correspondance avec la file d'attente SYN, le noyau supprime l'élément de la file d'attente SYN, crée allégrement une connexion à part entière (en particulier : struct inet_sock) et l'ajoute à la file d'attente Accepter.

File d'attente Accepter

La File d'attente Accepter contient des connexions entièrement établies : prêtes à être récupérées par l'application. Lorsqu'un processus appelle Accepter (), les sockets sont retirés de la file d'attente et transmis à l'application.

Ceci est une vue plutôt simplifiée de la gestion des paquets SYN sous Linux. Avec les bascules de socket comme TCP_DEFER_ACCEPT [2] etTCP_FASTOPEN, les choses fonctionnent un peu différemment.

Limites de taille de la file d'attente

La longueur maximale autorisée des files d'attente Accepter et SYN est tirée du paramètre d’accumulation transmis à l'appel système listen (2) par l'application. Par exemple, cela définit les tailles de la file d'attente Accepter et SYN à 1 024 :

listen(sfd, 1024)

Remarque : Dans les noyaux antérieurs à 4.3, la longueur de la file d'attente SYN était comptée différemment.

Par le passé, cette limite de file d'attente SYN était configurée par la bascule net.ipv4.tcp_max_syn_backlog, mais ce n'est plus le cas. De nos jours, net.core.somaxconn limite les deux tailles de file d'attente. Sur nos serveurs, nous l'avons réglé à 16k :

$ sysctl net.core.somaxconn
net.core.somaxconn = 16384

Valeur parfaite d’accumulation

Sachant tout cela, nous pourrions nous poser la question suivante : quelle est la valeur idéale du paramètre d’accumulation ?

La réponse est : ça dépend. Pour la majorité des serveurs TCP triviaux, cela n'a pas vraiment d'importance. Par exemple, avant la version 1.11, il est notoire que Golang ne supportait pas la personnalisation de la valeur d’accumulation. Toutefois, Il y a des raisons valables d'augmenter cette valeur :

  • Lorsque le taux de connexions entrantes est très élevé, même avec une application performante, la file d'attente SYN entrante peut nécessiter un plus grand nombre de créneaux.
  • La valeur d’accumulation contrôle la taille de la file d'attente SYN. Cela peut effectivement être lu comme « des paquets ACK en vol ». Plus le temps moyen d'aller-retour est long pour le client, plus le nombre de créneaux utilisés sera important. Dans le cas de nombreux clients éloignés du serveur, à des centaines de millisecondes, il est logique d'augmenter la valeur d'accumulation.
  • L'option TCP_DEFER_ACCEPT fait que les sockets restent plus longtemps dans l'état SYN-RECV et contribuent aux limites de la file d'attente.

Dépasser l'accumulation est également mauvais :

  • Chaque créneau de fil d’attente SYN utilise de la mémoire. Pendant une attaque de type SYN, cela n’a aucun sens de gaspiller des ressources pour stocker des paquets d’attaque. Chaque entrée de struct inet_request_sock dans la file d’attente SYN nécessite 256 octets de mémoire sur le noyau 4.14.

Pour jeter un coup d'œil dans la file d'attente SYN sous Linux, nous pouvons utiliser la commande ss et rechercher les sockets SYN-RECV. Par exemple, sur l'un des serveurs de Cloudflare, nous pouvons voir 119 créneaux utilisés dans la file d’attente tcp/80 SYN et 78 sur tcp/443.

$ ss -n state syn-recv sport = :80 | wc -l
119
$ ss -n state syn-recv sport = :443 | wc -l
78

Des données similaires peuvent être affichées avec notre script sur-stratifié SystemTap : resq.stp.

Application lente

Que se passe-t-il si l'application ne parvient pas à suivre l'appel avec Accepter () assez rapidement ?

C'est à ce moment que la magie opère ! Lorsque la file d'attente Accepter est pleine (sa taille est d’accumulation +1), puis :

  • Les paquets SYN entrants dans la file d'attente SYN sont supprimés.
  • Les paquets ACK entrants dans la file d'attente SYN sont supprimés.
  • Le compteur TcpExtListenOverflows / LINUX_MIB_LISTENOVERFLOWS est incrémenté.
  • Le compteur TcpExtListenDrops / LINUX_MIB_LISTENDROPS est incrémenté.

Il y a de bonnes raisons de laisser tomber les paquets entrants : il s’agit d’un mécanisme de refoulement. L’autre partie renverra tôt ou tard les paquets SYN ou ACK au stade où, on l’espère, l’application lente aura récupéré.

C'est un comportement souhaitable pour presque tous les serveurs. Pour être complet : il peut être ajusté avec la bascule globale net.ipv4.tcp_abort_on_overflow, mais il est préférable de ne pas y toucher.

Si votre serveur a besoin de gérer un grand nombre de connexions entrantes et a du mal à obtenir le débit Accepter (), lisez plutôt notre article sur la distribution de travail Nginx tuning / Epoll et un suivi présentant des scripts SystemTap utiles.

Vous pouvez suivre les statistiques de débordement de la file d’attente Accepter en consultant les compteurs nstat :

$ nstat -az TcpExtListenDrops
TcpExtListenDrops     49199     0.0

Il s'agit d'un compteur global. Ce n'est pas l'idéal. Nous avons parfois constaté une augmentation, alors que toutes les applications semblaient en bon état ! La première étape doit toujours être d’imprimer les tailles de la file d’attente Accepter avec ss :

$ ss -plnt sport = :6443|cat
State   Recv-Q Send-Q  Local Address:Port  Peer Address:Port
LISTEN  0      1024                *:6443             *:*

La colonne Recv-Q indique le nombre de sockets dans la file d'attente Accepter et Send-Q indique le paramètre d’accumulation. Dans ce cas, nous voyons qu'il n'y a pas de sockets en attente à Accepter (), mais nous avons tout de même vu le compteur ListenDrops augmenter.

Il s'avère que notre application est restée bloquée pendant une fraction de seconde. Cela était suffisant pour laisser la file d'attente Accepter déborder pendant une très brève période. Il s’est remis quelques instants plus tard. Les cas comme celui-ci sont difficiles à déboguer avec ss. Nous avons donc écrit un script acceptq.stp SystemTap pour nous aider. Il s'accroche au noyau et imprime les paquets SYN en cours de suppression :

$ sudo stap -v acceptq.stp
time (us)        acceptq qmax  local addr    remote_addr
1495634198449075  1025   1024  0.0.0.0:6443  10.0.1.92:28585
1495634198449253  1025   1024  0.0.0.0:6443  10.0.1.92:50500
1495634198450062  1025   1024  0.0.0.0:6443  10.0.1.92:65434
...

Ici, vous pouvez voir précisément quels paquets SYN ont été affectés par ListenDrops. Avec ce script, il est facile de comprendre quelle application élimine des connexions.

image CC BY 2.0 parinternets_dairy

Attaques de type SYN

S'il est possible de déborder la file d'attente Accepter, il doit également être possible de déborder de la file d'attente SYN. Que se passe-t-il dans ce cas ?

C'est ce que sont les attaques de type SYN. Dans le passé, attaquer la file d'attente SYN avec de faux paquets SYN usurpés était un réel problème. Avant 1996, il était possible de refuser le service de presque tous les serveurs TCP avec très peu de bande passante, simplement en remplissant les files d'attente SYN.

La solution est Cookies SYN. Les cookies SYN sont une construction qui permet de générer le SYN + ACK sans état, sans enregistrer réellement le SYN entrant ni gaspiller la mémoire système. Les cookies SYN ne brisent pas le trafic légitime. Lorsque l’autre partie est réelle, elle répond avec un paquet ACK valide comprenant le numéro de séquence reflété, qui peut être vérifié par cryptographie.

Par défaut, les cookies SYN sont activés lorsque cela est nécessaire pour les sockets ayant une file d'attente SYN remplie. Linux met à jour quelques compteurs sur les cookies SYN. Lorsqu'un cookie SYN est envoyé :

  • TcpExtTCPReqQFullDoCookies / LINUX_MIB_TCPREQQFULLDOCOOKIES est incrémenté.
  • TcpExtSyncookiesSent / LINUX_MIB_SYNCOOKIESSENT est incrémenté.
  • Linux avait l'habitude d'incrémenter TcpExtListenDrops mais pas à partir du noyau 4.7.

Lorsqu'un ACK entrant se dirige vers la file d'attente SYN avec les cookies SYN engagés :

  • TcpExtSyncookiesRecv / LINUX_MIB_SYNCOOKIESRECV est incrémenté lorsque la validation cryptographique réussit.
  • TcpExtSyncookiesFailed / LINUX_MIB_SYNCOOKIESFAILED est incrémenté si la cryptographie échoue.

Un sysctl net.ipv4.tcp_syncookies peut désactiver les cookies SYN ou les activer par la force. La valeur par défaut est bonne, ne la changez pas.

Cookies SYN et horodatages TCP

La magie Cookies SYN fonctionne, mais n’est pas sans inconvénients. Le principal problème est qu’il existe très peu de données pouvant être sauvegardées dans un cookie SYN. Plus précisément, seuls 32 bits du numéro de séquence sont renvoyés dans l’ACK. Ces bits sont utilisés comme suit :

+----------+--------+-------------------+
|  6 bits  | 2 bits |     24 bits       |
| t mod 32 |  MSS   | hash(ip, port, t) |
+----------+--------+-------------------+

Avec le paramètre MSS tronqué à seulement 4 valeurs distinctes, Linux ne connaît aucun paramètre TCP optionnel de l'autre partie. Les informations relatives à l'horodatage, l’ECN, l’ACK sélectif ou à l'échelonnage des fenêtres sont perdues et peuvent entraîner une dégradation des performances de la session TCP.

Heureusement, Linux a un moyen de contourner le problème. Si les horodatages TCP sont activés, le noyau peut réutiliser un autre créneau de 32 bits dans le champ Horodatage. Il contient :

+-----------+-------+-------+--------+
|  26 bits  | 1 bit | 1 bit | 4 bits |
| Timestamp |  ECN  | SACK  | WScale |
+-----------+-------+-------+--------+

Les horodatages TCP doivent être activés par défaut pour vérifier Voir le sysctl :

$ sysctl net.ipv4.tcp_timestamps
net.ipv4.tcp_timestamps = 1

Historiquement, l’utilité des horodatages TCP a fait l’objet de nombreuses discussions.

Actuellement, chez Cloudflare, les horodatages TCP sont désactivés.

Enfin, avec les cookies SYN activés, certaines fonctionnalités intéressantes ne répondront pas, comme TCP_SAVED_SYN, TCP_DEFER_ACCEPT ou TCP_FAST_OPEN.

Attaques de type SYN à l'échelle de Cloudflare

Les cookies SYN sont une excellente invention et résolvent le problème des attaques de type SYN plus petites. Toutefois, chez Cloudflare, nous essayons si possible de les éviter. Bien que l'envoi de quelques milliers de paquets SYN + ACK vérifiables par cryptographie soit gérable, nous constatons des attaques de plus de 200 millions de paquets par seconde. À cette échelle, nos réponses SYN + ACK ne feraient que dégrader Internet et n'apporteraient aucun avantage.

Au lieu de cela, nous essayons de supprimer les paquets SYN malveillants sur la couche de pare-feu. Nous utilisons les empreintes digitales p0f SYN compilées pour BPF. Plus d'informations dans cet article de blog Présentation du compilateur BPF p0f. Pour détecter et déployer les mesures d'atténuation, nous avons développé un système d'automatisation appelé « Gatebot ». Nous avons décrit cela ici Consultez Gatebot, le robot qui nous donne du repos.

Paysage changeant

Pour plus de données légèrement dépassées sur le sujet, lisez une excellente explication d’Andreas Veithen de 2015 et un article complet de Gerald W. Gordon de 2013.

Le paysage de la gestion des paquets SYN Linux évolue constamment. Jusqu'à récemment, les cookies SYN étaient lents, en raison d'un verrou à l'ancienne dans le noyau. Ce problème a été résolu dans la version 4.4. Vous pouvez désormais compter sur le noyau pour pouvoir envoyer des millions de cookies SYN par seconde, ce qui résout pratiquement le problème des attaques de type SYN pour la plupart des utilisateurs. Avec un réglage approprié, il est possible d'atténuer les attaques de type SYN les plus gênants, sans affecter les performances des connexions légitimes.

Les performances des applications suscitent également une attention particulière. Des idées récentes telles que SO_ATTACH_REUSEPORT_EBPF introduisent une toute nouvelle couche de programmabilité dans la pile réseau.

C’est formidable de voir les innovations et les idées nouvelles s’enchaîner dans le domaine des piles réseaux, dans le monde stagnant des systèmes d’exploitation.

Merci à Binh Le pour son aide avec cet article.


Partager les dossiers internes de Linux et NGINX vous semble intéressant ? Rejoignez notre équipe à la renommée mondiale à Londres, Austin, San Francisco et notre filiale d'élite à Varsovie, en Pologne.


  1. En peu de mots, la file d'attente SYN stocke des connexions pas encore ÉTABLIES et non des paquets SYN eux-mêmes sur le plan technique. Avec TCP_SAVE_SYN, cela devient assez proche. ↩︎
  2. Si TCP_DEFER_ACCEPT est nouveau pour vous, vérifiez bien la version de FreeBSD, acceptez les filtres. ↩︎