afinar u optimizar TCP

Afinar u Optimizar TCP y la Red de tu Linux

Llegamos a la séptima parte de este tutorial en el que profundizamos la afinación de nuestra máquina. Como este servidor es una unificación de seguridad informática que incluye Proxy, Firewall, Router y VPN, los paquetes de red que lo atraviesan siguen cierto flujo técnico, que podemos optimizar. Por supuesto que este servidor goza de buenos recursos de hardware para funcionar sobradamente. Sin embargo, como esto es GNU/Linux, nos permitimos optimizar con precisión sus recursos (a esto se le llama Tuning). Veamos entonces cómo afinar u optimizar TCP y la red de tu Linux.

Cuando los paquetes de red llegan a este servidor, circulan a través de sus tarjetas de red y de ahí se entregan al sistema operativo, para elevarlos a las siguientes capas (siguiendo el modelo OSI). En este blog puedes aprender más sobre el MODELO OSI.

Es importante verificar si en este flujo interviene NAPI, pero bueno, de todo este tema de flujos de paquetes y su manipulación técnica a nivel de kernel, se encarga el Subsistema de Red de Linux.

Afinar u optimizar TCP y la Red de tu Linux – Tarjetas de red (affinity)


Una forma práctica de verificar si nuestro servidor está trabajando con NAPI, es verificar la existencia de SOFTIRQs, en lugar de las IRQs. Como se supone que esto es un router y firewall, que le da servicio de pasarela a más de 300 clientes para que tengan Internet, es obvio que tiene un uso exhaustivo de las tarjetas de red. De manera que por este lado debe tener una estadística apreciable de solicitudes de interrupción.

Podemos verificarlo con el siguiente comando:

Otra forma de verlo gráficamente es generando una estadística con Monitorix:

afinar u optimizar TCP

El resultado con Monitorix evidencia una nula actividad de IRQ, mientras que sí un porcentaje de SOFTIRQs en el núcelo 1. Lo anterior quiere decir, que nuestro servidor sí está trabajando con NAPI.

NAPI ¿es malo o bueno?


Si usar NAPI es malo o bueno, es relativo. Si esta máquina fuera una estación de trabajo, o sí corriera alguna plataforma exclusivamente para capa de aplicación (tal como una Base de Datos), que recibiera mucho tráfico, es bueno. Pero para la función de nuestro servidor, usar NAPI puede ser un poco inconveniente, porque NAPI le adiciona latencia a los paquetes que circulan. Sin embargo, el espacio en memoria que usa NAPI en este caso es pequeño, por lo que no afecta, es más, nos puede ayudar a reducir pérdidas de paquetes.

Ajustar tamaño del Buffer de Transmisión – (TX)


El buffer de salida, o de transmisión, es una cola por donde salen los paquetes de nuestro servidor. El valor de éste parámetro depende del flujo de paquetes salientes, la velocidad del sistema y de sus tarjetas de red.

Si la tarjeta de red es más lenta que el resto del sistema (unos 100 Mbps), ajustemole un valor de 5000 (es un buen punto de partida). Si la tarjeta de red opera, o puede operar a 1 Gbps, podemos mermarla a 2000, así ocupamos menos RAM. El comando relacionado de esto es:

_# ifconfig ethX txqueuelen 5000

Si necesitas que este ajuste sea permanente, colóca el comando en el archivo /etc/rc.local

Cola de Recepción de Paquetes del Kernel


La Cola de Recepción de Paquetes del Kernel no es otra cosa que el DMA Buffer (Subsistema de Red de Linux). Como en este contexto estamos hablando de paquetes, por eso no lo llamamos buffer.

Este componente del kernel puede ser ajustado por estas variables:

net.core.netdev_max_backlog
net.core.dev_weight
net.core.netdev_budget_usecs

El parámetro net.core.netdev_max_backlog determina el tamaño máximo de la cola de recepción de paquetes. Esta cola existe porque a veces la NIC entrega los paquetes a una  velocidad mayor a la que el kernel puede procesarlos. Su valor por defecto es 1000, el cual podemos verificar en nuestros sistemas con el siguiente comando:

Con net.core.netdev_budget_usecs se define el presupuesto de tiempo (en microsegundos), que Linux tiene para tomar los paquetes de la cola de recepción. Esta variable se encuentra a partir de la versión 4.12 del kernel (en kernels más viejos su valor está fijado a 2 jiffies).

Y el parámetro net.core.dev_weight representa el número de paquetes que Linux debe tomar en una interrupción NAPI, dentro del presupuesto net.core.netdev_budget_usecs.

Podemos fijar la variable net.core.netdev_max_backlog a 4000, es un buen valor para comenzar afinando nuestro servidor. Adicionamos este parámetro en el archivo de configuración /etc/sysctl.conf, así: net.core.netdev_max_backlog = 4000.

Luego, aplicamos el cambio con el comando sysctl -p.

También podemos activarlo de un solo golpe con el siguiente comando:

Este último ajuste va perfecto en un servidor que transmita grandes volúmenes de datos, tal como backups, FTP de archivos, etc.

Resumen gráfico:

Bueno, hasta aquí los asociado a la Cola de Recepción de Paquetes. Ahora, vamos a revisar su homólogo, el Buffer de Recepción, ó Buffer de la Ventana TCP, pues ambos están relacionados.

Parámetros adicionales para definir tamaño de buffer de la ventana TCP


Linux tiene los siguiente parámetros relacionados con la asignación de buffer para los segmentos TCP:

net.ipv4.tcp_rmem – Espacio de memoria para la recepción de segmentos TCP
net.ipv4.tcp_wmem – Espacio de memoria para la transmisión de segmentos TCP

Estos los ajusta el kernel al valor más recomendado dependiendo de la memoria RAM instalada. Sin embargo, viene con valores neutrales.

Todos los sistemas que transmiten datos, reciben de sus receptores un mensaje que les índica la cantidad de bytes que deben enviar en determinado momento. Eso se hace para que el host que transmite no desborde al receptor. A eso se le llama Ventana Deslizante, y la determina la memoria disponible y la velocidad del enlace con su retardo.

Con los siguientes comandos podemos visualizar sus valores activos:

 

Para mejorar el flujo de paquetes de un host a otro, por ejemplo, aprovechando que están en una red Gigabit, podemos aumentar el tamaño del buffer asignado a la ventana (no al socket TCP).

La mayoría de la literatura que aborda este tema hablan de la siguiente fórmula para calcularlo:

Buffer = BDP / 8

Buffer = (velocidad de enlace * latencia de la red) / 8

La variable BDP en resumidas cuentas es el tamaño de la ventana (por ende el buffer que la aloja). La latencia de la red (la de los paquetes) es su valor de ida y vuelta (RTT).

Laboratorio Personal


He realizado este ejercicio con mis servidores, específicamente uno donde que almacena los backups en ambiente productivo, y esta es la conclusión:

Entre más bajo el RTT con la misma velocidad, menos buffer de ventana resulta. A más velocidad (bps) más buffer de ventana se necesita.

Atención a lo siguiente:

En el siguiente artículo de Google Cloud, sobre optimización de TCP, puedes observar el mismo ejercicio, teniendo como datos un enlace de 10 Gbps con latencia de 0,030segundos, lo que les da una ventana de 37,5 Mbytes. Pero en mi ejercicio real tengo un enlace de 1 Gbps ethernet con latencia de 0,00017 segundos en promedio!

O sea que según la fórmula Google, o de la literatura general, mi ventana quedaría en 20 KBytes. Absurdo (lo hice, y la tasa  de transferencia se me fue cuesta abajo). Más absurdo es tener un enlace de 10 Gbps con una latencia de 0,030 segundos en un ambiente controlado y académico.

Lo que sucede con el cáculo del buffer que reza la literatura general, es que está basado en una ventana deslizante de 65535 bytes (que era lo máximo que manejaba TCP hasta principios de los años 90 del siglo XX). Por eso se iba en picada mi tasa de transferencia, porque 20 KBytes es el 30% de una ventana de 65535.

Hoy todos los sistemas vienen con un parámetro llamado Opción de Escala de Ventana activado, por lo que podemos aumentar el buffer hasta un factor de 14 (214). El factor de mí ventana es 9:

Buffer = ((velocidad de enlace * latencia de la red) / 8) * xn

Buffer = ((1000000000 * 0,00017) / 8) x 29 = 10880000 bytes

Entonces, para un enlace de 1000 Mbps (1 Gbps), con latencia de 0,00017 segundos, el buffer de una ventana TCP se puede fijar a ≈ 10,8 MB.

Quiere decir esto que si la velocidad del enlace aumenta, debemos recalcular el tamaño del buffer para que el host transmisor no desborde al receptor. Pero si pasa lo contrario, podemos mantener el buffer a 10,8 MB.

El comando:

Sin embargo, existen otros parámetros que pueden afectar el tamaño de nuestra ventaba TCP:

net.ipv4.tcp_adv_win_scale
net.ipv4.tcp_window_scaling
net.ipv4.tcp_workaround_signed_windows

Esos 10,8 MB cubren muy bien la Cola de Recepción de Paquetes, porque ese espacio que definimos de 4000 paquetes, es aproximadamente equivalente a 5,9 MBytes ¿Por qué? Por la siguiente ecuación:

KBytes = ((1480 bytes / 1000 ) x paquetes)

La longitud de un paquete en este punto es la MSS + la cabecera TCP = 1480 bytes.

De manera que el espacio del Buffer de la Ventana de Recepción, debe ser mayor al valor de la Cola de Recepción de Paquetes, para evitar pérdida de paquetes (dropped).

Los comandos que nos ayudan a saber si tenemos pérdidas de paquetes en nuestro servidor, son los siguiente:

Ver paquetes perdidos en la tarjeta de red:

Ver paquetes perdidos en la cola de recepción de paquetes

La primera columna nos muestra el total de paquetes recibidos por cada núcelo de la CPU (cada fila es un núcleo). La segunda columna nos muestra si netdev_max_backlog ha sido sobrecargado (y por ende pérdida de paquetes). Y la tercera muestra si Softirq no ha alcanzado a tomar todos los paquetes de la NIC. En fin, esta es la identificación de cada una de esas columnas:

Otra forma de ver paquetes perdidos:

Si netstat muestra paquetes perdidos, pero no así /proc/net/softnet_stat, es porque la pérdida está ocurriedno fuera de nuestro servidor.

Aunque este proceso de optimización ayuda bastante, siempre vamos a tener algunas pérdidas de paquetes en su recepción, debido a los Algoritmos de Control de Congestión. Éstos algoritmos no son malos, pero la pérdida de paquetes que suponen es un pequeño precio que se paga.

Visualizar el algoritmo de congestión activado e el kernel:

En el moderno kernel de Linux contamos con el Algoritmo de Control de Congestión BBR. Este fue desarrollado por Google y es lo último en «güarachas» (lo mejor). Por ejemplo, con BBR, Google pudo mejorar el flujo de paquetes de Youtube hasta en un 14%, con relación a otros algorimos. Para activarlo:

Con el siguiente comando podemos visualizar los Algoritmos de Control de Congestión disponibles en nuestro kernel:

Tenemos otra variable llamada net.core.rmem_max, que define el tamaño máximo del buffer que una aplicación del servidor puede solicitarle al kernel. Podemos ajustarlo al siguiente valor:

Gestión de Colas


Todos los dispositivos activos de red tiene un algoritmo de gestión de colas. Estos procesan el orden de llegada y salida de los paquetes de red. El buffer más básico es un FIFO, pero tiene sus limitaciones para ingentes cantidades de tráfico, pues producen un efecto llamado BufferBloat. Hoy contamos con otros buffers que nos ayudan a mitigar ese efecto.

Para tal propósito, podemos activar en Linux cualquiera de estos:

pfifo_fast: para routers
fq_codel: para routers y propósito general (mejor que pfifo_fast)
fq: para servidores de carga pesada
sch_cake: La más reciente y con mejor rendimiento (para activarla debe compilarse el kernel)

Podemos visualizar el que está activo, así:

Ajustar direccionamiento del Buffer Ring (RX) en la NIC


No es mucho el espacio que puede direccionar una NIC en la RAM, para un buffer ring. Con el comando ethtool -g interfaz, podemos verificar el tamaño del buffer ring gestionado por esa NIC, y su límite máximo:

El parámetro Pre-set maximus es el valor máximo al que se puede ajustar el buffer ring. Con el comando ethtool -G interfaz rx 16384 tx 16384, podemos aumentarlo (su valor es en bytes):

Afinamiento de los Buffer Rings desde la CPU


La identificación de una interrupción SOFTIRQ (NAPI), siempre está asociada a un núcleo de la CPU. El núcleo de la CPU que reciba la solicitud de interrupción desde NAPI, será el que recupere los datos almacenados en el buffer ring.

Podemos instalar irqbalance en Linux para que este demonio haga un balanceo más equitativo de las interrupciones del sistema, entre los núcleos de la CPU. Sin embargo, casi siempre las solicitudes de interrupción de los dispositivos de red, quedan concentradas en un solo núcleo.

Para balancear este asunto, primero debemos tener identificadas las interfaces de red y sus interrupciones asociadas. Con la comando cat /proc/interrups podemos identificarlas:

En este caso tenemos que todas las interrupciones de las interfaces de red (enp0s8, enp0s9, enp6s4f1 y enp6s4f0) están concentradas en el núcelo 1 de la CPU. Para nuestro caso, nos interesa fijar las interrupciones de la interfaz enp0s8 (asociada la interrupción 25) al núcelo 0.

Entonces, confirmamos que efectivamente la interrupción 25 está asociada por defecto al núcleo 1 de la CPU. Con el comando cat /proc/irq/25/smp_affinity confirmamos:

El anterior comando nos da como resultado 02, porque los núcleos de la CPU se identifican en formato hexadecimal. Entonces 0x1 para núcelo 0 y 0x2 para núcelo 1.

CPUBinarioHexadecimal
000010x1
100100x2
201000x4
310000x8

Debemos indicarle al demonio irqbalance que no haga afinidad a la interrupción 25:

Para que el anterior ajuste sea permanente, debemos indicarlo en su archivo de configuración:

_$ sudo nano /etc/default/irqbalance

Al final del archivo de configuración ingresamos este texto:

OPTIONS="--banirq=25"

Luego, procedemos a trasladar la interrupción 25 al núcelo 0, con el siguiente comando:

_# echo 1 > /proc/irq/25/smp_affinity

Si queremos que este ajuste persista, debemos acomodarlo en el archvio /etc/rc.local

Después volvemos a verificar con el comando cat /proc/irq/25/smp_affinity

Esquema básico resultante de lo que acabamos de hacer:

Finalmente evidenciamos que los SOFTIRQ del dispositivo de red enp0s8, se han trasladado al núcelo 0:

Primer día. En este día podemos ver que antes de las 12 horas, el núcelo 0 estaba de holgazán, bien frescote atendiendo pocas interrupciones. Después de las 12 horas (momento en que hicimos el ajuste) lo pusimos a trabajar más (franja anaranjada). Le hemos mermado carga al núcleo 1.

afinar u optimizar TCP

Segundo día del ajuste.

Balanceo de Ciclos de Procesamiento


Si recordamos al Modelo OSI, damos por sabido que la integridad de los paquetes de red debe ser verificada por medio de una suma de verificación. También sabemos que cuando la capa de Aplicación produce datos de gran tamaño, estos son segmentados por la capa de Transporte (los divide en bloques pequeños) para que quepan en el formato de un paquete.

Ambas actividades, la de verificar y segmentar, las realiza el kernel, pero también las puede hacer la tarjeta de red (aúnque no todas soportan esta función). A lo que me refiero es que podemos ahorrarle a la CPU esos ciclos de procesamiento que representa ambas actividades, trasladándolas al procesador de la tarjeta de red.

Miremos en cuáles recursos de hardware nuestros paquetes están siendo verificados y segmentados, con el siguiente comando:

_# ethtool -k enp7s0

Como vemos, ni la verificación ni la segmentación, las está ejecutando la tarjeta de red. Con esto inferimos que ambas actividades las está haciendo el kernel. Pongamos a la tarjeta de red a hacer ambas actividades, con los siguientes comandos:

Para que la NIC ejecute la verificación de paquetes:

Para que la NIC ejecute la segmentación:

ó

Si en alguno de los dos comandos te resulta algo como en la siguiente imagen, es por que esa NIC no soporta dicha función:

Bueno ¿y para qué queremos que nuestro router segmente datos si se supone que estos artefactos ya direccionan paquetes segmentados? Porque nuestro router también produce datos. Recordemos que aquí también funciona un Proxy Caché, además tenemos aplicaciones que nos generan informes de uso, etc.

Cómo monitorear la red en tu Linux, aquí

Para que los ajustes con ethtool sean persistentes, debemos acomodar el mismo comando al final del archivo de configuración de red /etc/network/interfaces, así, por ejemplo:

post-up ethtool -K enp7s0 tso on

(Para Red Hat el archivo es /etc/sysconfig/network-scripts/ifcfg-ethX)

Vaya! esto de afinar u optimizar TCP y la red tu Linux es un asunto bastante entretenido.

Receive Packet Steering (RPS)


El RPS se puede traducir a algo como «Direccionamiento de Paquetes de Llegada». Es un método de optimización de colas diferente al Afinamiento de los Buffer Rings desde la CPU, que vimos más arriba. La diferencia es que con RPS no «amarramos» una cola (o varias) a una CPU, sino que una cola puede asignarse dinámicamente a multiples CPUs. Podemos implementar RPS si la tarjeta de red nuestro servidor carece de Recibo Escalado lateral (RSS), y si nuestro procesador cuenta con varios núcleos.

Con una tarjerta de red

Con dos tarjetas de red

Por ejemplo, la tarjeta de red Intel 82575EB soporta RSS y no es necesario ajustar el kernel para activar esta función. Podemos ver la estructura interna de dicha tarjeta, y verificar la distribución de sus colas de recepción y transmisión entre los núcleos del procesador, con el comando cat /proc/interrupts | grep «interfaz», a continuación:

Si no tenemos una tarjeta de red con RSS, podemos activar RPS para «emular» a RSS usando los núcleos de la CPU como colas.

Verificamos si RPS esta o no activado:

_$ cat /sys/class/net/enp4s0f0/queues/rx-0/rps_cpus

Si el resultado es 0, es porque no está activado.

Si queremos que RPS distribuya la llegada de los paquetes que viene por la interfaz enp4s0f0, entre los cuatro núcleos de nuestro procesador, lo activamos así:

_$ sudo echo f > /sys/class/net/enp4s0f0/queues/rx-0/rps_cpus

Si no funciona, es porque el kernel no está compilado con CONFIG_RPS (https://github.com/torvalds/linux/blob/v3.13/Documentation/networking/scaling.txt#L160-L164)

Para este caso (RPS), la variable net.core.dev_weight no nos servirá de a mucho para optimizar. En su lugar, podemos optimizar con la variable net.core.netdev_budget, que define el número máximo de paquetes que se pueden tomar de todas las interfaces en un ciclo de sondeo (NAPI). Igualmente dicho ciclo está limitado por netdev_budget_usecs, incluso si netdev_budget no alcanza a tomar todos los paquetes en su correspondiente ciclo.

TCP Fast Open


No está demás acelerar el inicio de las conexiones TCP con la extensión de este protocolo, llamado TCP Fast Open. Lo activamos:

Más Colas y más Tiempos de Espera


TCP Window Scale es una opción del protocolo TCP que aumenta la cantidad de bytes manejados por la ventana de recepción. Si esta opción no se encuentra activa, la ventana máxima es de 65535 bytes. Si está activa se puede alcanzar una ventana máxima de 1GB (65535 * 2^14). Por defecto es 1:

 

net.ipv4.tcp_fin_timeout

Es el tiempo que permance un socket activo después de cerrarse una conexión (TIME_WAIT). Algunos le dicen «conexión huérfana», y Linux puede «reutilizar» (junto con sus recursos de socket, cada huérfana representa 64 KB de RAM) mientras dure tcp_fin_timeout (cuesta menos que abrir un nuevo socket). Claro, el parámetro net.ipv4.tcp_tw_reuse debe estar activo para que Linux pueda reasignar una conexión huérfana.

 Al reducir el valor de esta entrada, TCP/IP puede liberar conexiones cerradas más rápidamente, haciendo que haya más recursos disponibles para nuevas conexiones.

Para un servidor se recomienda un valor de 30 segundos.

 

net.ipv4.tcp_max_orphans

Número máximo de conexiones huérfanas (TIME_WAIT).

 

Puertos efímeros y su tiempo de estado


Linux mantiene un rango de puertos efímeros para entablar una o varias conexiones con los servicios de red. Su rango por defecto es 32768:60999, definido en la variable net.ipv4.ip_local_port_range.

El tiempo en que un puerto efímero se mantiene «vivo» después de usarse, depende la variable net.ipv4.tcp_fin_timeout, y su valor por defecto es de 60 segundos.

De manera que la cantidad de puertos efímeros que Linux puede manter activos, durante tcp_fin_timeout, es:

puertos efímeros activos  = (60999 – 32768) / 60 = 470 puertos efímeros/minuto

Si tenemos un servidor Proxy con bastante carga de solicitudes, podemos aumentar el número de puertos efímeros a 46000, definiendo el rango así:

14999:60999

Por otro lado, tener en espera un socket sin uso durante 60 segundos, es un derroche de recursos para otras conexiones que sí lo necesitan, por eso rebajamos tcp_fin_timeout a 30 segundos. Así Linux podrá sostener el doble de puertos efímeros por minuto.

Cola de espera para solicitudes de conexión


Cuando un cliente necesita conectarser con algún servicio de red, por ejemplo, Squid Proxy, Apache Web Server ó un web socket construido con PHP, debe ejecutar un proceso de conexión de tres pasos.

La solicitud de conexión comienza cuando el cliente envía al servidor un paquete llamado SYN. El servidor le responde con un paquete SYN-ACK. Finalmente el cliente completa el proceso con un ACK en el tercer paso.

Una solicitud de conexión respondida con un SYN-ACK desde el servidor, pasa a ser un estado de espera denominado SYN-RECV. Linux encola estos estados SYN-RECV porque están a la espera del tercer paso del cliente. El parámetro net.ipv4.tcp_max_syn_backlog (SYN_BACKLOG) define el tamaño de esa cola de espera.

Cada elemento SYN en la cola de espera usa 256 bytes de memoria en el kernel 4.14.

Por otra parte, el parámetro net.core.somaxconn (SOMAXCONN) representa el límite de solicitudes de conexión que un socket (el puerto TCP)  debe colocar en SYN_BACKLOG. Es una secuencia. Su valor por defecto es de 128, pero se puede aumentar a 1024 ó más, claro, sí nuestro servidor es  lo suficientemente potente.

La secuencia de SOMAXCONN -> SYN_BACKLOG produce el sigueinte efecto:

El límite de SYN_BACKLOG trunca a SOMAXCONN.

SOMAXCONN límita al proceso dueño del socket la cantidad de solicitudes de conexión, SYN-RECV, que debe poner en SYN_BACKLOG.

Por ejemplo, si SOMAXCONN está fijado a 20, y SYN_BACKLOG a 10, solo 10 solicitudes de conexión (SYN-RECV) se pondrán en la cola de espera.

De acuerdo al ejemplo anterior, si tenemos a funcionando a Apache Server y a Squid Proxy, se le truncarán a cada servicio 10 solicitudes de conexión en espera por un ACK. A como les lleguen los ACK pendientes del tercer paso, las conexiones se establecerán normalmente..

Recuerda, SYN_BACKLOG trunca a SOMAXCONN.

 

Hagamos esto más agradable con una Analogía Gastronómica


Haré una analogía más o menos parecida con esto de los paquetes, el buffer de recepción y lo que acabamos de ver.

Pensemos en tcp_max_syn_backlog junto con net.core.somaxconn, como si fueran un restaurante que acaba de crear un nuevo, delicioso y famoso platillo de comida mediterránea. Este resturante tiene su debido administrador. El comedor es relativamente grande, pero límitado a un tope de mesas, y un pasillo que lo conecta desde la entrada, por donde circulan los clientes.

Tcp_max_syn_backlog viene siendo el pasillo y net.core.somaxconn la sala con las mesas. Entonces, llegan los clientes a las mesas y se alistan para recibir el menú (esperan el ACK). Finalmente llegan sus platillos y bebidas para disfrutar, claro, cada comensal lo pide grande o pequeño, según su voracidad y capacidad (net.ipv4.tcp_rmem). Como todos en la calle quiere entrar para probar tal maravilla, cada vez más habrán más clientes esperando en pasillo.

Conforme va desocupándose las mesas, estas se «reutilizan» tal como están, para desalojar el pasillo. Al reutilizar las mesas en ese estado, se ahorra tiempo y esfuerzo en acomodar sillas y cambiar de mantel (suponiendo que quedaron en condiciones aceptables). Si nadie en un tiempo determinado viene a ocupar las mesas desocupadas, Linux (nuestro andministrador) las ordenará nuevamente para un próximo comensal.

Laboratorio de SOMAXCONN


Podemos verificar el funcionamiento de SOMAXCONN y SYN_BACKLOG, por ejemplo, con un servidor web junto con netcat. A estos dos servicios les lanzarémos una serie de solicitudes de conexión por medio de un script desde otro computador.

Antes que nada, debemos desactivar temporalmente la prevención de Syn Flood, así:

Ahora sí podemos continuar. Al script lo he llamado spoofer.sh (un humilde «spoofeador» de direcciones IP):

Ya tenemos a Apache Web Server funcionando por el puerto 80. Ahora ejecutamos a netcat una vez para que escuche por el puerto 81, y otra vez para que escuche por el puerto 82:

Básicamente el script se encarga de generar un rango de 90 direcciones IP (192.168.140.10 – 100). Luego, las pasamos netcat como «IP origen» para que las lanze al servidor web con 90 solicitudes de conexión TCP. Así mismo para el puerto 81 y 82. También debemos garantizar que el servidor web NO tenga una ruta, conectividad o una entrada en su tabla ARP hacía nuestro spoofer. Esto con el fin de poder ver el efecto en la cola de espera.

Luego, vamos al servidor web y le bajamos el tope de SOMAXCONN a 20 y de SYN_BACKLOG a 10, para hacerlo más entendible:

Desde el otro computador, ejecutamos el spoofer.sh.

Volvemos a nuestro servidor web y verificamos las solicitudes de conexión con el siguiente comando:

 

Entonces en la imagen vemos 9 solicitudes de conexión encoladas por cada socket, a la espera del ACK, pues el servidor web no tiene una ruta hacía la direcciones IP «spoofeadas».

Adicionándole al servidor una ruta hacía las direcciones IP spoofeadas, podremos ver pues, como se van entablando las conexiones (ESTABLISHED).

La siguiente tabla muestra el efecto producido sobre los SYN-RECV, debido a la alternancia de entre SYN_BACKLOG y SOMAXCONN:

Recordemos activar nuevamente la protección contra inundaciones SYN:

Un ataque por inundación SYN (SYN flood) es un tipo de ataque por Denegación de Servicio. Consiste en disparar una ráfaga de solicitudes de conexión sin intención de devolver sus correspondientes ACK. Entonces la cola de espera se llena y Linux queda sin poder atender más solicitudes. 

La protección contra SYN flood es un frente que tiene Linux para mitigar ataques DoS. En este caso Linux responde a los paquetes SYN con un SYN-ACK más una cookie, que identifica la solicitud de conexión. Con este método se elimina el encolamiento de SYN-RECV. Si la solicitud de conexión es genuina, el cliente responderá con un ACK más la cookie que lo identifica. Y listo, conexión establecida.

Fragmentación del datagrama IP


Bueno, puede que de vez en cuando nos encontremos con algunos paquetes IP (datagramas, capa 3) fragmentados. Eso ocurre cuando los datos son producidos en una máquina con tecnología FDDI, Tocke Ring ó ATM, pues manejan una MTU superior a la de ethernet. En esas tecnologías un paquete IP puede superar hasta 8 veces el tamaño de una trama ethernet. De ahí es que sale el tema de la fragmentación del datagrama.

Linux reserva un límite de paquetes y un espacio en memoria para que su pila TCP/IP reensamble paquetes IP fragmentados. Sus variables con valores predeterminados, son estos:

net.ipv4.ipfrag_high_thresh = 4194304
net.ipv4.ipfrag_low_thresh = 3145728

Pero en la actualidad es inusual encontrar paquetes IP fragmentados, debido a un algoritmo llamado Path MTU Discovery. Pero como somos bien curiosos, vamos a echarle mano a TCPDUMP para saber si a nuestro servidor le llegan paquetes IP fragmentados.

A la Caza de Paquetes IP (v4) Fragmentados


Para salir de caza con nuestro GNU/Linux, debemos hacer dos cosas: armarnos con TCPDUM y recordar la «morfología» de nuestra «presa»:

Para esta cacería nos interesa conocer dos «órganos» del datagrama IP. Se trata de los siguientes campos: Flags y Fragment Offset.

Estos son los bits que compone el campo Flags:

Bit 0: reserved, must be zero
Bit 1: (DF) 0 = May Fragment, 1 = Don’t Fragment.
Bit 2: (MF) 0 = Last Fragment, 1 = More Fragments.

 

Los dos campos:

   1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 <- 2 bytes
+-----------------+---------------+
  Flags |    Fragment Offset
+-------+-------------------------+

Tengamos en cuenta los 16 bits distribuidos entre los campos Flags y Fragments Offset. Los dos juntos componen 2 Bytes. El campo Flags trabaja con 3 bits del primer Byte (en azul), que comparte con Fragment Offset.

De los tres bits del campo Flags, el dos (2) índica si el paquete IP ha sido fragmentado o no. En caso de que sí, el campo Fragment Offset le informa a la pila TCP/IP sobre los bytes que le hacen falta para completar el reensamblado del datagrama IP.

Si hacemos un recuento de bytes del datagrama, desde el campo Version hasta el byte compartido por Flags y Fragment, tenemos que hay 7 bytes (de 0 a 6, byte 6 en azul).

Después de este detallado reconocimiento de nuestro objetivo, despleguamos el operativo:

En una consola, ejecutamos a TCPDUMP índicandole la interfaz por donde creemos que vienen los paquetes IP fragmentados.

Le hemos dicho a TCPDUMP lo siguiente:

Que capture los paquetes IP que tengan información (>0) a partir del bit 2 del byte 6, o sea, el campo Fragment Offset, pero que no capture paquetes con el flag Don’t Fragment activado (64, o sea, el byte 6 – 01000000 – en décimal), pues no nos interesa capturar paquetes que no deben ser fragmentados.

Además, los paquete capturados los guardamos en el archivo volcado1.pcap para analizarlos posteriormente con Wireshark.

Seguro pasará mucho tiempo para ver un paquete fragmentado circulando por nuestra red. Pero si estamos ansiosos por ver uno, podemos crearlos con el comando ping, así:

Lo que hacemos con ping es enviar a nuestro Linux un (1) paquete echo con 6000 bytes de datos. Como el protocolo ICMP no fragmenta o segmenta datos grandes como lo hace TCP, le toca al mismo protocolo IP hacerlo.

Abrimos el archivo volcado1.pcap con Wireshark:

Entonces vemos que la secuencia funciona así:

  • El primer fragmento le informa al protocolo IP que el TOTAL de bytes fragmentados es 5920, eso incluye los 1480 bytes que él lleva.
  • Luego el segundo paquete informa que restan 4440 bytes (5920 bytes del TOTAL – 1480 bytes del primero que ya fueron entregados).
  • Después el tercero avisa que restan 2960 bytes por ensamblar, o sea, los 1480 bytes que él lleva y más 1480 del cuarto paquete.
  • Finalmente el cuarto paquete lleva los últimos 1480 bytes y su Fragment Offset informa que no hay más bytes por ensamblar (0).

 

Comandos adicionales


Los siguientes comandos son útiles para verificar y consultar información acerca de los drivers de las tarjetas de red, y algunos de sus parámetros habilitados:

lshw -C network (con éste podemos averiguar el driver asociado a una NIC)

modinfo ‘nombre del driver’

systtool -vm ‘nombre del driver’ (instalar con apt install sysfsutils)

Visualización de estado de sockets:

ss -inm ‘src *:8080’ | more

En el siguiente hipervínculo puedes conocer más sobre el comando ss.


Estos otros comandos son útiles para consultar más información acerca de los dispositivos de red y sus informes sobre paquetes de red procesados, informes que también pueden ser valiosos para identificar problemas de red con este equipo:

ethtool ethX

Informes sobre velocidades soportadas, autonegociación, etc.

ethtool -i ethX

Información como nombre del driver de la interfaz, acceso a la EEPROM, etc.

ethtool -S ethX

Cuantificación de paquetes enviados, recibidos, pérdidos, colisiones, así como la cuatificación de datos enviados y leídos hacía su memoria EEPROM por medio SMBus, etc.

ethtool -a ethX

Verifica si tiene activada la auto-negociación.

ethtool -p ethX

Hace titilar los LEDs de la NIC para que la podamos ubicar físicamente.

ethtool -s eth0 autoneg off ó ethtool -s eth0 autoneg on

Deshabilita o habilita la autonegociación.

netstat -s | grep segments

netstat -s | grep retransmitted

Mirar la estadística de paquetes enviados, recibidos, perdidos, retransmitidos, etc.

 

En la página web de Ubunlog podemos conocer a Netutils-linux, una batería de comandos Python para monitorear el rendimiento de los paquetes,  en los recursos de hardware asociados.

 

El el siguiente artículo explicamos cómo configurar una VLAN en una tarjeta de red, de nuestro Linux.


< Sexta Parte    –    Octava Parte – VLAN en Linux >


Valora este artículo:

Afinación de un servidor Proxy con firewall
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 1 reviews.

 

Name
Email
Review Title
Rating
Review Content

 

Está genial

★★★★★
“ Usted es un master”
- Ruben Ballesteros
Comparte esto en
Publicado en GNU Linux.