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)

Importante: en los núcleos anteriores a 4.3, la longitud de la cola SYN se consideró de manera diferente.

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:

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

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.

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.

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

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.

Puede rastrear las estadísticas de desbordamiento de la cola de aceptación mediante el análisis de los contadoresnstat:

$ nstat -az TcpExtListenDrops
TcpExtListenDrops     49199     0.0

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:

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

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.

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:

$ 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
...

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

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:

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

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.

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:

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

Las marcas de tiempo de TCP deben estar activadas de manera predeterminada, para verificar el sysctl:

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

Históricamente, se ha debatido mucho sobre la utilidad de las marcas de tiempo de TCP.

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, 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.


  1. 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. ↩︎
  2. Si TCP_DEFER_ACCEPT es nuevo para usted, definitivamente verifique la versión de FreeBSD - acepta filtros. ↩︎SYN TCP Programación