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)
listen(sfd, 1024)
Remarque : Dans les noyaux antérieurs à 4.3, la longueur de la file d'attente SYN était comptée différemment.
$ sysctl net.core.somaxconn
net.core.somaxconn = 16384
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 :
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 paquetsACK
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'étatSYN-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 typeSYN
, 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’attenteSYN
nécessite 256 octets de mémoire sur le noyau 4.14.
$ ss -n state syn-recv sport = :80 | wc -l
119
$ ss -n state syn-recv sport = :443 | wc -l
78
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.
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'attenteSYN
sont supprimés.Les paquets
ACK
entrants dans la file d'attenteSYN
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.
$ nstat -az TcpExtListenDrops
TcpExtListenDrops 49199 0.0
Vous pouvez suivre les statistiques de débordement de la file d’attente Accepter en consultant les compteurs nstat :
$ ss -plnt sport = :6443|cat
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 1024 *:6443 *:*
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 :
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.
$ 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
...
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 :
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
+----------+--------+-------------------+
| 6 bits | 2 bits | 24 bits |
| t mod 32 | MSS | hash(ip, port, t) |
+----------+--------+-------------------+
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 :
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.
+-----------+-------+-------+--------+
| 26 bits | 1 bit | 1 bit | 4 bits |
| Timestamp | ECN | SACK | WScale |
+-----------+-------+-------+--------+
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 :
$ sysctl net.ipv4.tcp_timestamps
net.ipv4.tcp_timestamps = 1
Les horodatages TCP doivent être activés par défaut pour vérifier Voir le sysctl :
Historiquement, l’utilité des horodatages TCP a fait l’objet de nombreuses discussions.
Par le passé, les horodatages ont gaspillé le temps de disponibilité du serveur (savoir si cela compte est un autre débat). Cela a été corrigé il y a 8 mois.
Les horodatages TCP utilisent une quantité non négligeable de bande passante, 12 octets sur chaque paquet.
Ils peuvent ajouter un caractère aléatoire supplémentaire aux sommes de contrôle des paquets, ce qui peut aider avec certains matériels défectueux.
Comme mentionné ci-dessus, les horodatages TCP peuvent améliorer les performances des connexions TCP si les cookies SYN sont activés.
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](https://lwn.net/Articles/645128/), 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.
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. ↩︎
Si TCP_DEFER_ACCEPT est nouveau pour vous, vérifiez bien la version de FreeBSD, acceptez les filtres. ↩︎