En Cloudflare, tenemos mucha experiencia en la operación de servidores en la internet de libre circulación. Sin embargo, siempre tratamos de optimizar nuestro dominio de este arte negro. En este mismo blog hemos tratado varios puntos oscuros de los protocolos de internet: como comprensión de FIN-WAIT-2 o recepción del ajuste de la memoria intermedia.
Imagen CC BY 2.0 por Isaí Moreno
Sin embargo, no se le ha prestado la atención suficiente a un tema: las inundaciones SYN. Utilizamos Linux y resulta que el manejo de paquetes SYN en Linux es verdaderamente complejo. En esta publicación revelaremos información para aclarar este tema.
La historia de dos colas
En primer lugar, debemos entender que cada socket enlazado, en el estado TCP de “ESCUCHA” tiene dos colas separadas:
La cola SYN
La cola de aceptación
En los textos, a estas colas se les suelen dar otros nombres como “reqsk_queue”, “registro de ACK”, “registro de escucha” o incluso “registro de TCP”, pero usaré los nombres anteriores para evitar confusiones.
Cola SYN
La cola SYN almacena los paquete SYN entrantes[1] (en concreto: struct inet_request_sock). Se encarga de enviar paquetes SYN+ACK y de reintentar el envío en el tiempo de espera. En Linux, el número de reintentos se configura con lo siguiente:
$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5
Los documentos describen esta alternancia:
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.
Después de transmitir el SYN+ACK, la cola SYN espera un paquete ACK del cliente: el último paquete en el protocolo de enlace de tres vías. Todos los paquetes ACK que se reciben primero se deben hacer coincidir con la tabla de conexiones totalmente establecida, y solo después con los datos en la cola SYN correspondiente. En la coincidencia de cola SYN, el núcleo elimina el elemento de la cola SYN, establece una conexión completa (en concreto: struct inet_sock), y lo agrega a la cola de aceptación.
Cola de aceptación
La cola de aceptación tiene conexiones completamente establecidas: listas para que la aplicación las tome. Cuando un proceso anuncia aceptar(), los sockets se eliminan de la cola y pasan a la aplicación.
Esta es una visión bastante simplificada del manejo de paquetes SYN en Linux. Con la alternancia de sockets como TCP_DEFER_ACCEPT[2] y TCP_FASTOPEN, las cosas funcionan un poco diferente.
Límites de tamaño de cola
La longitud máxima permitida de ambas colas Accept y SYN se toma del parámetro registro que la aplicación transmite a la llamada del sistema escucha(2). Por ejemplo, esto determina los tamaños de la cola Accept y SYN en 1,024:
listen(sfd, 1024)
listen(sfd, 1024)
Importante: en los núcleos anteriores a 4.3, la longitud de la cola SYN se consideró de manera diferente.
$ sysctl net.core.somaxconn
net.core.somaxconn = 16384
Este límite de cola SYN solía ser configurado por la alternancia net.ipv4.tcp_max_syn_backlog,, pero ya no se hace de este modo. Actualmente net.core.somaxconn limita ambos tamaños de cola. En nuestros servidores lo establecemos en 16k:
Valor de registro perfecto
Teniendo en cuenta todo eso, podríamos hacer la siguiente pregunta: ¿cuál es el valor ideal del parámetro de registro?
La respuesta es la siguiente: depende. Para la mayoría de los servidores TCP triviales, realmente no es importante. Por ejemplo, antes de la versión 1.11 Golang no respaldó, como es de público conocimiento, la personalización del registro. Sin embargo, hay razones válidas para incrementar este valor:
Cuando el ritmo de las conexiones entrantes es realmente importante, incluso con una aplicación de rendimiento, es posible que la cola SYN entrante necesite un mayor número de espacios.
El valor del registro controla el tamaño de la cola SYN. Esto efectivamente se puede leer como “paquetes ACK en proceso”. Cuanto mayor sea el tiempo promedio de ida y vuelta al cliente, más espacios se utilizarán. En el caso de muchos clientes que están lejos del servidor, a cientos de milisegundos de distancia, tiene sentido aumentar el valor del registro.
La opción TCP_DEFER_ACCEPT hace que los sockets permanezcan en el estado SYN-RECV por más tiempo y contribuyan con los límites de cola.
Exceder el registro también es malo:
Cada espacio en la cola SYN utiliza cierta memoria. Durante una inundación SYN no tiene sentido desperdiciar recursos en el almacenamiento de paquetes de ataque. Cada entrada struct inet_request_sock en la cola SYN utiliza 256 bytes de memoria en el núcleo 4.14.
$ ss -n state syn-recv sport = :80 | wc -l
119
$ ss -n state syn-recv sport = :443 | wc -l
78
Para dar un vistazo a la cola SYN en Linux, podemos utilizar el comando ss y buscar los sockets SYN-RECV. Por ejemplo, en uno de los servidores de Cloudflare podemos ver 119 espacios que se utilizan en la cola tcp/80 SYN y 78 en tcp/443.
Se pueden mostrar datos similares con nuestro overenginered SystemTap script: resq.stp.
Aplicación lenta
¿Qué sucede si la aplicación no puede mantener el ritmo rápido de la llamada de aceptación()?
¡Aquí es cuando sucede la magia! Cuando la cola de aceptación está completa (tiene un tamaño de registro+1) entonces:
Los paquetes SYN entrantes a la cola SYN se caen.
Los paquetes ACK entrantes a la cola SYN se caen.
El contador TcpExtListenOverflows /
LINUX_MIB_LISTENOVERFLOWS
se incrementa.El contador TcpExtListenDrops /
LINUX_MIB_LISTENDROPS
se incrementa.
Hay una sólida razón para descartar los paquetes entrantes: es un mecanismo de rechazo. La otra parte, tarde o temprano, reenviará los paquetes SYN o ACK, y para ese entonces se espera que la aplicación lenta se haya recuperado.
Este es el comportamiento que se desea para la mayoría de los servidores. Para completar: se puede ajustar con la alternancia global net.ipv4.tcp_abort_on_overflow, pero es mejor no tocarlo.
Si su servidor necesita manejar una gran cantidad de conexiones entrantes y tiene problemas con la aceptación() del rendimiento, lea nuestra publicación Ajuste de Nginx / Distribución de trabajo de Epoll y un seguimiento que muestre scripts útiles de SystemTap.
$ nstat -az TcpExtListenDrops
TcpExtListenDrops 49199 0.0
Puede rastrear las estadísticas de desbordamiento de la cola de aceptación mediante el análisis de los contadoresnstat:
$ ss -plnt sport = :6443|cat
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 1024 *:6443 *:*
Este es un contador global. No es lo ideal: a veces veíamos que aumentaba cuando el estado de todas las aplicaciones no tenía problemas. El primer paso siempre debe ser imprimir los tamaños de la cola de aceptación con ss:
La columna Recv-Q muestra la cantidad de sockets en la cola de aceptación, y Send-Q muestra el parámetro de registro. En este caso, vemos que no hay sockets pendientes de aceptación(), pero aún vemos el incremento del contador ListenDrops.
$ 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
...
Resulta que nuestra aplicación se atascó por fracciones de segundo. Esto fue suficiente para que la cola de aceptación se desbordara por un período de tiempo muy breve. Momentos después se recuperaría. Los casos como este son difíciles de solucionar con ss, así que escribimos un acceptq.stp SystemTap script a modo de ayuda. Se enlaza en el núcleo e imprime los paquetes SYN que se están cayendo:
Aquí usted puede ver con precisión qué paquetes SYN se vieron afectados por ListenDrops. Con este script, no tiene importancia entender qué aplicación está eliminando conexiones.
Imagen CC BY 2.0 por internets_dairy
Inundación SYN
Si es posible que se desborde la cola de aceptación, también puede ser posible que se desborde la cola SYN. ¿Qué sucede en ese caso?
De esto se tratan los ataques de inundación SYN. En la última inundación, la cola SYN con paquetes SYN falsificados fue un verdadero problema. Antes de 1996, se podía denegar el servicio de prácticamente cualquier servidor TCP con muy poco ancho de banda, solo con completar las colas SYN.
La solución es Cookies SYN. Las cookies SYN son una construcción que permite que SYN+ACK se genere sin estado, sin guardar en realidad el SYN entrante ni desperdiciar la memoria del sistema. Las cookies SYN no interrumpen el tráfico legítimo. Cuando la otra parte es real, responderá con un paquete ACK válido que incluye el número de secuencia reflejado, que se puede verificara nivel criptográfico.
Las cookies SYN se activan de manera predeterminada cuando es necesario - para sockets con una cola SYN completa. Linux actualiza un par de contadores en las cookies SYN. Cuando se envía una cookie SYN:
TcpExtTCPReqQFullDoCookies /
LINUX_MIB_TCPREQQFULLDOCOOKIES
se incrementa.TcpExtSyncookiesSent /
LINUX_MIB_SYNCOOKIESSENT
se incrementa.Linux solía incrementar TcpExtListenDrops pero eso no sucede desde el núcleo 4.7.
Cuando un ACK entrante se dirige a la cola SYN con cookies SYN activadas:
TcpExtSyncookiesRecv /
LINUX_MIB_SYNCOOKIESRECV
se incrementa cuando la validación de criptografía es correcta.TcpExtSyncookiesFailed /
LINUX_MIB_SYNCOOKIESFAILED
se incrementa cuando se produce un error en la criptografía.
Un sysctl net.ipv4.tcp_syncookies puede desactivar las cookies SYN o forzar las activaciones. El valor predeterminado es bueno, no lo cambie.
Cookies SYN y marcas de tiempo TCP
+----------+--------+-------------------+
| 6 bits | 2 bits | 24 bits |
| t mod 32 | MSS | hash(ip, port, t) |
+----------+--------+-------------------+
La magia de las cookies SYN funciona, pero existen ciertas desventajas. El principal problema es que se pueden guardar muy pocos datos en una cookie SYN. En concreto, solo 32 bits del número de secuencia se devuelven en el ACK. Estos bits se utilizan de la siguiente manera:
Con la configuración de MSS reducida a solo 4 valores distintos, Linux no conoce ningún parámetro TCP opcional de la otra parte. La información sobre marcas de tiempo, ECN, ACK selectivos o escalado de ventana se pierde y puede disminuir el rendimiento de la sesión de TCP.
+-----------+-------+-------+--------+
| 26 bits | 1 bit | 1 bit | 4 bits |
| Timestamp | ECN | SACK | WScale |
+-----------+-------+-------+--------+
f, Linux ha trabajado en este tema. Si las marcas de tiempo de TCP están activadas, el núcleo puede reutilizar otro espacio de 32 bits en el campo de marcas de tiempo. Contiene:
$ sysctl net.ipv4.tcp_timestamps
net.ipv4.tcp_timestamps = 1
Las marcas de tiempo de TCP deben estar activadas de manera predeterminada, para verificar el sysctl:
Históricamente, se ha debatido mucho sobre la utilidad de las marcas de tiempo de TCP.
Anteriormente, las marcas de tiempo generaban pérdidas del tiempo activo del servidor (si eso es importante sería tema d). Esto se solucionó hace 8 meses.
Las marcas de tiempo de TCP utilizan una cantidad insignificante de ancho de banda - 12 bytes en cada paquete.
Pueden agregar aleatoriedad a las sumas de comprobación del paquete, lo que puede ayudar con cierto hardware dañado.
Como se mencionó anteriormente, las marcas de tiempo de TCP pueden aumentar el rendimiento de las conexiones TCP si se activan las cookies SYN.
Actualmente, en Cloudflare las marcas de tiempo de TCP están desactivadas.
Por último, con las cookies SYN activadas, algunas características interesantes no funcionarán, como por ejemplo [TCP_SAVED_SYN](https://lwn.net/Articles/645128/), TCP_DEFER_ACCEPT oTCP_FAST_OPEN
.
Inundaciones SYN en proporción a la necesidad de Cloudflare
Las cookies SYN son un excelente invento y resuelven el problema de las inundaciones SYN de menor magnitud. Sin embargo, en Cloudflare, tratamos de evitarlas en la medida de lo posible. Si bien el envío de un par de miles de paquetes SYN+ACK verificables a nivel criptográfico por segundo se puede hacer correctamente, vemos ataques de más de 200 millones de paquetes por segundo. En esta proporción, nuestras respuestas SYN+ACK simplemente llenarían la internet de basura, sin generar ningún beneficio.
En lugar de hacer esto, tratamos de eliminar los paquetes SYN maliciosos en la capa de firewall. Usamos las huellas digitales SYN p0f compiladas para BPF. Puede obtener más información en esta publicación del blog Introducción al compilador p0f BPF. Para detectar e implementar las mitigaciones, hemos desarrollado un sistema de automatización que denominamos “Gatebot”. Lo describimos aquí Conoce a Gatebot - el bot que nos permite dormir
Panorama en evolución
Para obtener más información, un poco desactualizada, sobre el tema, lea una excelente explicación de Andreas Veithen de 2015 y un documento exhaustivo de Gerald W. Gordon de 2013.
El panorama del manejo de paquetes SYN de Linux evoluciona constantemente. Hasta hace poco, las cookies SYN eran lentas debido a un bloqueo obsoleto en el núcleo. Esto se solucionó en 4.4 y ahora usted puede confiar en el núcleo para enviar millones de cookies SYN por segundo, y resolver prácticamente el problema de inundación SYN de la mayoría de los usuarios. Con el ajuste adecuado, es posible mitigar incluso las inundaciones SYN más fastidiosas sin afectar el rendimiento de las conexiones legítimas.
El rendimiento de las aplicaciones también está recibiendo una atención considerable. Ideas recientes como SO_ATTACH_REUSEPORT_EBPF
introducen una capa totalmente nueva de programabilidad en la pila de red.
Resulta extraordinario ver cómo las innovaciones y los pensamientos renovados se canalizan en la pila de redes en un mundo de sistemas operativos que de otra manera estaría estancado.
Agradecemos a Binh Le por colaborar con esta publicación.
¿Está tratando de que los aspectos internos de Linux y NGINX suenen interesantes? Únase a nuestro equipo de reconocimiento internacionalen Londres, Austin, San Francisco y nuestra selecta oficina en Varsovia, Polonia.
Estoy simplificando, técnicamente hablando, la cola SYN almacena las conexiones aún no ESTABLECIDAS, no paquetes SYN sí. Aunque con TCP_SAVE_SYN se acerca lo suficiente. ↩︎
Si TCP_DEFER_ACCEPT es nuevo para usted, definitivamente verifique la versión de FreeBSD - acepta filtros. ↩︎SYN TCP Programación