Filtrado de alto rendimiento con Linux

Me llamo Javier de Miguel Rodríguez, de la asociación SUGUS GNU/Linux de la ETSII.

En esta presentación se resumirán las nociones técnicas necesarias para la elección de hardware para cortafuegos de filtrado multigigabit, centrándonos en la plataforma x86 / Opteron al ser la más difundida y ofrecer una relación precio/rendimiento muy elevada.

Caudal vs Latencia

Caudal: cantidad de información que se transmite por unidad de tiempo, por ejemplo, megabytes por segundo. Protocolos típicos que requieren mucho caudal: ftp,servidores de ficheros,bittorrent...

Latencia: Tiempo transcurrido desde que se envía un paquete hasta que se recibe, se suele medir en milisegundos. Protocolos típicos donde importa la latencia: videoconferencia, telefonía ip, videojuegos en red...

Tipo de tráfico

No es igual filtrar 10 megabytes por segundo de tráfico ftp en una LAN que 10 megabytes por segundo de tráfico p2p en Internet

En el primer caso podemos encontrarnos con una sola ip fuente y una sola ip destino, con un único inicio de comunicación, con pocos puertos involucrados

En el segundo caso tenemos comunicaciones “a ráfagas”, con muchas comunicaciones individuales

Breve introducción a netfilter

¿Cómo funciona internamente netfilter?

Se decide que hacer con un paquete de acuerdo haciendo comparaciones con algunos o todos estos parámetros (esto es un resumen, hay más):

Protocolo

Interfaz de entrada y/o de salida

Dirección(es) IP fuente(s) y/o destino(s)

Puerto(s) de entrada y/o salida

Estado (lo veremos más adelante)

Negación de alguno o todos los anteriores

Netfilter

El principal problema de netfilter es la escalabilidad. Las reglas se evalúan secuencialmente, por lo que hay muchísimas comparaciones, es una estructura de datos no funciona bien con muchas reglas en la misma cadena.

La tabla de estados de connection tracking intenta mitigar este problema usando una estructura de datos mucho más rápida, que veremos a continuación.

La tabla de estados (I)

La tabla de estados se crea cada vez que se inicia una comunicación, sea tcp o udp.

Se crea una máquina de estados por cada comunicación, empleando la tupla de protocolo, fuente, destino, puertos involucrados , por lo que un paquete de ese flujo de datos puede tener cuatro estados posibles:

NEW (inicio de comunicación)

ESTABLISHED y RELATED (pertenece a un flujo de datos ya iniciado)

INVALID (no tiene que ver con este flujo)

La tabla de estados (II)

Así, una transferencia de 10 megabytes tendría un paquete con el estado NEW y los subsiguientes en dicho flujo (el 99% restante) tendrían el estado ESTABLISHED o RELATED.

Internamente se emplea una tabla hash con colisiones como estructura de datos para mantener la tabla de conexiones. Veamos gráficamente como funciona.

La tabla de estados (III)

1º Llega un paquete

2º Se le aplica una función hash

respecto a la tupla de fuente,

destino,protocolo y puertos

3º Ese hash se emplea como índice

para acceder a la tabla de hashs

tabla [i]

4º Se recorre una lista enlazada

con las entradas de conntrack

Requerimientos de memoria (I)

Veamos el tamaño por defecto del número de entradas de connection tracking (“sesiones”) para una máquina x86 con 512 megabytes de ram:

CONNTRACK_MAX = RAMSIZE (in bytes) / 16384 / (x / 32)

Donde RAMSIZE se mide en bytes

X es el tamaño de un puntero (32 o 64 bits)

Luego un x86 de 32 bits con 512 megabytes de ram

puede almacenar 32768 entradas de conntrack por defecto

Requerimientos de memoria (II)

Veamos el tamaño por defecto del tamaño de la tabla de hash para la misma máquina x86 con 512 megabytes de ram:

HASHSIZE = CONNTRACK_MAX / 8

Luego un x86 de 32 bits con 512 megabytes de ram

tiene un tamaño de tabla de hash de 4096 y en tabla[i]

hay como máximo 8 entradas de conntrack

Requerimientos de memoria (III)

Puesto que el cálculo de la función hash de un paquete es una operación de tiempo fijo, mientras que recorrer una lista enlazada tarda un tiempo variable (depende de la longitud de la lista), lo ideal es que la tabla de hash tenga el mismo tamaño que el numero de entradas de conntrack

El cuadrado blanco es una entrada

de conntrack (lista de un elemento)

Requerimientos de memoria (IV)

Una entrada de conntrack ocupa alrededor de 308 bytes de memoria ram no intercambiable (no puede estar en swap). Para obtener el máximo rendimiento queremos que HASHSIZE sea igual al número de entradas de conntrack, por lo que una máquina puramente cortafuegos el cálculo sería el siguiente:

Tamaño de memoria en el kernel =

CONNTRACK_MAX * sizeof(struct ip_conntrack) +HASHSIZE * sizeof(2*tamaño_puntero)

Requerimientos de memoria (V)

Luego una máquina x86 con 512 megas de ram que queremos dedicar la mayoría de la memoria a cortafuegos (dejando 128 megabytes para el resto, o sea, kernel, syslog y sshd) haríamos este cálculo:

Comparemos con los valores por defecto de 32768 entradas de conntrack y tamaño de tabla hash de 4096

CONNTRACK_MAX * sizeof(struct ip_conntrack) +

HASHSIZE * sizeof(2*tamaño_puntero)

(512 - 128) * 1024^2 / 308 =~ 1307315

para HASHSIZE y el número de entradas de conntrack

Requerimientos de memoria (VI)

Hay que tener en cuenta que el tamaño de la estructura de una entrada de conntrack depende de la versión de kernel, en el 2.6.10-5-686 de esta Ubuntu lo veo así:

1º Activo en conntrack en el kernel con modprobe

2º En dmesg busco con grep la entrada adecuada

ip_conntrack version 2.1 (3963 buckets, 31704 max) - 336 bytes per conntrack

Requerimientos de memoria (VII)

Por tanto, es necesaria suficiente RAM para obtener las siguientes mejoras:

Poder manejar más conexiones simultaneamente (número de entradas de connection tracking)

Hacer que las búsquedas tarden un tiempo fijo (que la tabla de hash este compuesta de listas de conntrack de un sólo elemento)

Al menos 1 o 2 gigabytes de ram son un requerimiento imperativo para manejar un número elevado de conexiones a alta velocidad.

Escalabilidad de iptables (I)

Tradicionalmente se escribe que la primera regla de iptables sea la siguiente:

Las reglas subsiguientes aplican el estado NEW a las comunicaciones que deseamos aceptar para que el resto de las tramas de ese flujo entren directamente en la búsqueda de hashes (con estados RELATED o ESTABLISHED)

iptables -A FORWARD -m state

–state RELATED,ESTABLISHED -j ACCEPT

Escalabilidad de iptables (II)

La tabla hash de conntrack tiene limitado el rendimiento al tiempo que se tarda en calcular el hash de cada paquete y acceder a la tabla de hashes.

El principal problema de iptables es que para determinadas comunicaciones (ej: tráfico P2P, videoconferencia, voz IP, videojuegos en red) se inician y se cierran muchas comunicaciones en corto espacio de tiempo, por lo que han de evaluarse muchos paquetes con estado NEW

Escalabilidad de iptables (III)

Se está trabajando para solucionar el principal problema de iptables: que la evaluación de reglas se realiza de forma secuencial, algo que implica muchas comparaciones.

Iptables ejecutándose en una máquina dual Xeon a 2.4 ghz, con 2 gb de ram DDR y tarjetas gigabit e1000 no puede saturar la línea con más de 300 reglas con una MTU pequeña (más detalles en las siguientes diapositivas)

Propuestas de mejoras de escalabilidad (I)

Está claro que la evaluación secuencial de reglas es un cuello de botella. Existen múltiples proyectos tales como nf-hipac o ipset (que se van a presentar en estas jornadas) modifican el algoritmo de búsqueda para conseguir mayor eficiencia

Por ejemplo, el proyecto nf-hipac http://www.hipac.org cambia la búsqueda secuencial por árboles binarios, consiguiendo un espectacular incremento de rendimiento

Propuestas de mejoras de escalabilidad (II)

Comparativa de iptables vs nf-hipac en un dual Intel Xeon 2.4 ghz, 2 gb de ram ddr y tarjetas ethernet gigabit e1000, kernel 2.6.11, con diferentes tamaños de tramas:

Tramas de 128 bytes (ej: juegos en red)

Tramas de 512 bytes (ej: voz ip, videoconferencias)

Tramas de 1518 bytes (ej: tráfico http, ftp, correo)

Elección del procesador (I)

Un kernel de 32 bits ofrece mejor rendimiento que un kernel de 64 bits en la misma arquitectura

En Linux, cuando se recibe un paquete a través de un interfaz de red, se copia a un buffer del kernel llamado skb y a partir de ahí se trabaja sólo con el buffer . Con punteros de 64 bits este buffer ocupa una línea más de caché, lo cual perjudica el rendimiento

Con 32 bits direccionamos 2^32 bits que son 4 gigabytes de ram, suficientes para cualquier cortafuegos con utilidad práctica, no hacen falta 64 bits.

Elección del procesador (II)

La pila tcp/ip de linux muestra una escasa escalabilidad en sistemas multiprocesador (sólo se mejora alrededor de un 20% el rendimiento al pasar de 1 a 2 procesadores).

Podemos mejorar un 30% adicional el rendimiento empleando afinidad de procesos e interrupciones a procesadores específicos.

Tradicionalmente se ha recomendado un sistema biprocesador: un micro dedicado en exclusiva a iptables/routing y el otro procesador con el resto de procesos: kernel, syslog, sshd....

Elección del procesador (III)

Debido a su arquitectura de interconexión HyperTransport (memoria y entre procesadores) los procesadores AMD Opteron, obtienen un 40% de más rendimiento de filtrado en configuraciones SMP respecto a los procesadores Intel Xeon (el FSB es un bus compartido y el rendimiento decrece)

FSB, bus compartido

RAM

RAM

Conmutador

HyperTransport

Acerca de los buses PCI

El bus PCI típico de un PC funciona a 33 Mhz y a 32 bits. Es capaz de soportar hasta 132 megabytes por segundo de tráfico

Este valor es claramente insuficiente para tarjetas de red de más de 1 gigabit (que son 128 megabytes/segundo)

El ancho de banda típico del bus de sistema de una máquina x86 (FSB 400 Mhz) es de 25 gigabits/segundo. Comparativa de rendimiento:

http://www.acme.com/build_a_pc/bandwidth.html

Buses PCI-X y PCI Express (I)

Mientras que un bus PCI típico funciona a 133 Mbytes/segundos, se han creado alternativas de más alto rendimiento:

PCI-X: 133 Mhz, 64 bits, 1.06 Gigabytes por segundo de capacidad

PCI Express x4, 2.5 ghz, 1 Gigabyte por segundo de capacidad

PCI-X 266, 266 Mhz, 64 bits, 2.1 gigabytes por segundo

PCI Express x8, 2.5 ghz, 2 gigabytes por segundo

Buses PCI-X y PCI Express (II)

Estos buses avanzados PCI-X y PCI Express permiten emplear tarjetas ethernet de alto rendimiento como las quad gigabit ethernet. Es necesario un bus PCI-X o PCI-Express de última generación para soportar tarjetas 10 gigabits ethernet (1.2 gigabytes por segundo)

Quad Gigabit

Ethernet

10 Gigabits

Ethernet

Puentes PCI y compartición de IRQs (I)

Para obtener el máximo rendimiento hemos de procurar que los interfaces de red NO compartan puentes pci-pci ni que múltiples tarjetas compartan interrupciones

En el siguiente ejemplo podemos ver una máquina hp proliant dl380g3 biprocesadora con 14 bocas gigabit ethernet repartidas en 2 tarjetas tg3 integradas en placa base y 3 tarjetas quad gigabit ethernet e1000 en bus pci-x

Puentes PCI y compartición de IRQs (II)

Puentes PCI y compartición de IRQs (III)

Elección de NICs (I)

A la hora de elegir la conectividad para nuestro cortafuegos de alto rendimiento hemos de observar tanto la calidad de las tarjetas como el soporte que tiene linux de dichos interfaces.

Las siguientes diapositivas aclaran 4 conceptos básicos para obtener alto rendimiento:

Hardware checksums

Zero Copy Networking

DMA

NAPI

Hardware cheksum

Casi todas las tarjetas ethernet son capaces de calcular por si mismas la suma de comprobación de tramas de nivel 2

Algunas tarjetas de red de gama alta poseen un procesador propio capaz de calcular el crc de los paquetes a nivel IP (o incluso TCP). Se debe deshabilitar para estos interfaces que el sistema operativo los vuelva a calcular para así lograr el incremento de rendimiento deseado.

Ejemplos de drivers: nvnet, e1000, 3c905c,e10000

Zero Copy Networking y DMA

El uso de DMA redunda en un importante incremento de rendimiento, y es lo habitual en cualquier tarjeta moderna.

Cuando se envía o se recibe tráfico de red los datos son copiados de/hacia la aplicación a espacio de memoria del kernel, y de allí a la memoria de la tarjeta.

Algunas tarjetas son capaces de leer directamente los datos de/hacia el nivel de aplicación, por lo cual se evitan esas copias intermedias, lo cual incrementa el rendimiento (zero copy networking)

.

NAPI

Tradicionalmente cuando se recibe tráfico de red se genera una interrupción para que el procesador la atienda y se procese el paquete.

En ambientes con un elevado número de tramas por segundo la ejecución de todas esas rutinas de interrupción decrementa el rendimiento.

La técnica NAPI consiste en que no se genere una interrupción por cada paquete, sino cuando se posean un número determinado almacenados en un buffer o bien si no se llega hasta ese número, cuando se supere un timeout.

Bibliografía

Documento de Intel acerca del rendimiento de la pila tcp/ip de Linux en sistemas SMP:

http://www.intel.com/cd/ids/developer/asmo-na/eng/188935.htm

Netfilter Perfomance Test (pdf, muy recomendable)

http://people.netfilter.org/kadlec/nftest.pdf

Apuntes acerca del rendimiento de connection tracking:

http://www.wallfire.org/misc/netfilter_conntrack_perf.txt

¿Dudas?

¿Tan mal me explico que

nadie tiene dudas?

Contacto y licencia

Correo de contacto: javier@sugus.eii.us.es

Web: http://sugus.eii.us.es

Esta presentación se puede descargar de http://talika.eii.us.es/~javier/netfilter.sxi

Esta presentación se encuentra bajo la licencia Creative Commons “Reconocimiento-NoComercial-CompartirIgual 2.5 España”

La licencia completa puede ser consultada en:

http://creativecommons.org/licenses/by-nc-sa/2.5/es/

Volver ()

Volver ()

Volver ()

2005-09-05T20:26:37