Generación de tráfico con MoonGen + DPDK + Lua en la vista del artistaLa neutralización de los ataques DDoS en condiciones reales requiere pruebas preliminares y pruebas de varias técnicas. El equipo y el software de la red deben probarse en condiciones artificiales cercanas a las reales, con flujos de tráfico intensivos que simulan ataques. Sin tales experimentos, es extremadamente difícil obtener información confiable sobre las características y limitaciones específicas de cualquier herramienta compleja.
En este artículo, revelaremos algunos de los métodos de generación de tráfico utilizados en Qrator Labs.
ADVERTENCIARecomendamos encarecidamente que el lector no intente utilizar las herramientas mencionadas para atacar objetos de infraestructura real. La organización de los ataques DoS es punible por ley y puede conducir a sanciones severas. Qrator Labs realiza todas las pruebas en un entorno de laboratorio aislado.
Nivel técnico moderno
Una tarea importante en nuestra área es saturar la interfaz Ethernet 10G con paquetes pequeños, lo que implica el procesamiento de 14.88 Mpps (millones de paquetes por segundo). En lo sucesivo, consideramos los paquetes de red Ethernet más pequeños, 64 bytes, ya que nuestro interés principal es maximizar el número de paquetes transmitidos por unidad de tiempo. Un cálculo simple muestra que solo tenemos alrededor de 67 nanosegundos para procesar uno de esos paquetes.
Solo para comparar, esta vez está cerca de lo que un procesador moderno necesita para obtener un dato de la memoria si pierde el caché. Todo se vuelve aún más complicado cuando comenzamos a trabajar con interfaces Ethernet 40G y 100G e intentamos saturarlas completamente hasta la velocidad de línea (el máximo rendimiento declarado posible del dispositivo de red).
Dado que, en el caso habitual, el flujo de datos pasa a través de la aplicación en el espacio de usuario (espacio de usuario), luego a través del kernel, y finalmente ingresa al controlador de red (NIC), la idea primera y más directa es intentar configurar la generación de paquetes directamente en el kernel. Un ejemplo de
tal solución es el módulo nuclear
pktgen [2]. Este método le permite mejorar significativamente el rendimiento, pero no lo suficientemente flexible, ya que el más mínimo cambio en el código fuente en el kernel conduce a un largo ciclo de compilación, reinicio de los módulos del kernel o incluso todo el sistema y, de hecho, pruebas, lo que reduce la productividad general (es decir, requiere más tiempo del programador y esfuerzo).
Otro enfoque posible es obtener acceso directo desde el espacio de usuario a los búferes de memoria del controlador de red. Este camino es más complicado, pero vale la pena el esfuerzo para lograr una mayor productividad. Las desventajas incluyen alta complejidad y baja flexibilidad. Ejemplos de este enfoque son
netmap ,
PF_RING y
DPDK [4].
Otra forma efectiva, aunque muy costosa de lograr un alto rendimiento es utilizar equipos no universales, sino especializados. Ejemplo:
Ixia .
También hay soluciones basadas en DPDK que utilizan scripts, lo que aumenta la flexibilidad en el control de los parámetros del generador y también le permite variar el tipo de paquetes generados durante el inicio. A continuación describimos nuestra propia experiencia con una de estas herramientas: MoonGen.
Arquitectura MoonGen
Las características distintivas de MoonGen son:
- El procesamiento de datos DPDK en el espacio de usuario es la razón principal del aumento de rendimiento;
- Lua [ 5 ] stack con scripts simples en el nivel superior y enlaces a la biblioteca DPDK escrita en C, en la parte inferior;
- Gracias a la tecnología JIT (justo a tiempo), los scripts Lua funcionan lo suficientemente rápido, lo que contradice de alguna manera las ideas generalmente aceptadas sobre la efectividad de los lenguajes de scripting.
MoonGen puede considerarse como un envoltorio Lua alrededor de la biblioteca DPDK. Al menos las siguientes operaciones DPDK son visibles en el nivel de interfaz de usuario de Lua:
- Configurar controladores de red;
- Asignación y acceso directo a agrupaciones y memorias intermedias, que, para fines de optimización, deben asignarse en áreas alineadas continuas;
- Acceso directo a colas RSS de controladores de red;
- API para gestionar flujos computacionales, teniendo en cuenta la heterogeneidad del acceso a la memoria (afinidad NUMA y CPU) [ 12 ].

Arquitectura MoonGen, esquema del material [
1 ].
Moongen
MoonGen es un generador de paquetes de alta velocidad con script basado en la biblioteca DPDK. Las secuencias de comandos Lua controlan todo el proceso: la secuencia de comandos creada por el usuario es responsable de crear, modificar y enviar paquetes. Gracias a la muy rápida biblioteca de procesamiento de paquetes LuaJIT y DPDK, esta arquitectura le permite saturar una interfaz Ethernet de 10 gigabits con paquetes de 64 bytes utilizando solo un núcleo de la CPU. MoonGen le permite alcanzar esta velocidad incluso cuando el script Lua modifica cada paquete. No utiliza trucos como reutilizar el mismo búfer del controlador de red.
MoonGen también puede recibir paquetes, es decir, verificar qué paquetes fueron descartados por el sistema bajo prueba. Dado que la recepción de paquetes está controlada exclusivamente por un script Lua personalizado, también se puede usar para crear scripts de prueba más complejos. Por ejemplo, puede usar dos instancias de MoonGen para establecer una conexión entre sí. Dicha configuración se puede utilizar, en particular, para probar las llamadas cajas intermedias (equipo entre el punto de envío y recepción de tráfico), por ejemplo, firewalls. MoonGen se centra en cuatro áreas principales:
- Alto rendimiento y escalamiento de múltiples núcleos: más de 20 millones de paquetes por segundo en un solo núcleo de CPU;
- Flexibilidad: cada paquete se genera en tiempo real basado en un script Lua creado por el usuario;
- Marcas de tiempo exactas: en hardware ordinario (de productos básicos), la marca de tiempo se realiza con una precisión de milisegundos;
- Control exacto de los intervalos entre paquetes enviados: generación confiable de los patrones y tipos de tráfico requeridos en el hardware ordinario.
DPDK
DPDK significa Data Plane Development Kit y consta de bibliotecas cuyas funciones principales son aumentar el rendimiento de generación de paquetes de red en una amplia variedad de arquitecturas de procesador central.
En un mundo donde las redes de computadoras se están convirtiendo en la base de la comunicación humana, el rendimiento, el ancho de banda y la latencia se están convirtiendo en parámetros cada vez más críticos para sistemas como las redes inalámbricas y la infraestructura de cable, incluidos todos sus componentes individuales: enrutadores, equilibradores de carga, firewalls; así como áreas de aplicación: transferencia de medios (transmisión), VoIP, etc.
DPDK es una forma ligera y conveniente de crear pruebas y scripts. La transferencia de datos dentro del espacio de usuario es algo que no observamos con tanta frecuencia, principalmente porque la mayoría de las aplicaciones se comunican con el equipo de red a través del sistema operativo y la pila del núcleo, que es lo opuesto al modelo DPDK.
Lua
El objetivo principal de la existencia de Lua es proporcionar herramientas expresivas simples y flexibles que sean expandibles para tareas actuales específicas, en lugar de un conjunto de primitivas aplicables en un solo paradigma de programación. Como resultado, el lenguaje base es muy ligero: el intérprete completo solo requiere
180 kB en forma compilada y se adapta fácilmente a una amplia gama de posibles implementaciones.
Lua es un lenguaje dinámico. Es tan compacto que se puede colocar en casi cualquier dispositivo. Lua admite un pequeño conjunto de tipos: valores booleanos, números (coma flotante de precisión doble) y cadenas. Las estructuras de datos convencionales, como las matrices, los conjuntos y las listas, se pueden representar mediante la única estructura de datos incorporada en Lua: una tabla, que es una matriz asociativa heterogénea.
Lua utiliza la compilación JIT (justo a tiempo), por lo tanto, al ser un lenguaje de script, muestra un rendimiento comparable a los lenguajes compilados como C [
10 ].
¿Por qué Moongen?
Como empresa especializada en neutralizar los ataques DDoS, Qrator Labs necesita una forma confiable de crear, actualizar y probar sus propias soluciones de seguridad. Es para la última prueba, que se necesitan varios métodos para generar tráfico que simulan ataques reales. Sin embargo, no es tan fácil simular un ataque de inundación peligroso, pero directo, en los niveles 2-3 del modelo OSI, principalmente debido a las dificultades para lograr un alto rendimiento en la generación de paquetes.
En otras palabras, para una empresa dedicada a la disponibilidad continua y la neutralización de DDoS, simular varios ataques DoS en un entorno de laboratorio aislado es una forma de entender cómo se comportarán en realidad los diversos equipos que forman parte de los sistemas de hardware de la empresa.
MoonGen es una buena forma de generar valores de tráfico cercanos al límite para el controlador de red con un mínimo de núcleos de CPU. La transferencia de datos dentro del espacio de usuario mejora significativamente el rendimiento de la pila en cuestión (MoonGen + DPDK), en comparación con muchas otras opciones para generar altos valores de tráfico. El uso de DPDK puro requiere mucho más esfuerzo, por lo que no debería sorprenderse de nuestro deseo de optimizar el rendimiento. También admitimos un clon [
7 ] del repositorio original MoonGen para expandir la funcionalidad e implementación de nuestras propias pruebas.
Para lograr la máxima flexibilidad, el usuario establece la lógica para generar paquetes utilizando el script Lua, que es una de las principales características de MoonGen. En el caso del procesamiento de paquetes relativamente simple, esta solución funciona lo suficientemente rápido como para saturar la interfaz 10G en un solo núcleo de CPU. Una forma típica de modificar paquetes entrantes y crear nuevos es trabajar con paquetes del mismo tipo, en los que solo cambian algunos de los campos.
Un ejemplo es la prueba l3-tcp-syn-ack-flood, que se describe a continuación. Tenga en cuenta que cualquier modificación del paquete puede realizarse en el mismo búfer, donde resultó ser el paquete generado o recibido en el paso anterior. De hecho, tales conversiones de paquetes se realizan muy rápidamente, ya que no implican operaciones costosas, como llamadas al sistema, acceso a secciones de memoria potencialmente no almacenadas en caché y similares.
Pruebas en hardware de Qrator Labs
Qrator Labs realiza todas las pruebas en el laboratorio en diversos equipos. En este caso, utilizamos los siguientes controladores de interfaz de red:
- Intel 82599ES 10G
- Mellanox ConnectX-4 40G
- Mellanox ConnectX-5 100G
Observamos por separado que cuando se trabaja con controladores de red que funcionan con estándares superiores a 10G, el problema de rendimiento se agudiza. Hoy en día no es posible saturar la interfaz 40G con un núcleo, aunque con un pequeño número de núcleos esto ya es realista.
En el caso de los controladores de red fabricados por Mellanox, es posible cambiar algunos parámetros y configuraciones del dispositivo utilizando la guía de ajuste [
3 ] proporcionada por el fabricante. Esto le permite aumentar el rendimiento y, en algunos casos especiales, profundizar el comportamiento de la NIC. Otros fabricantes pueden tener documentos similares para sus propios dispositivos de alto rendimiento destinados a uso profesional. Incluso si no puede encontrar dicho documento en el dominio público, siempre tiene sentido ponerse en contacto directamente con el fabricante. En nuestro caso, los representantes de la compañía Mellanox fueron muy amables y, además de proporcionar documentación, respondieron rápidamente a nuestras preguntas, por lo que logramos lograr el 100% de utilización de la tira, lo cual fue muy importante para nosotros.
Prueba de inundación TCP SYN
L3-tcp-syn-ack-flood es un ejemplo de simulación de un ataque como SYN flood [
6 ]. Esta es una versión extendida de Qrator Labs de la prueba l3-tcp-syn-flood del repositorio principal de MoonGen, que se almacena en nuestro clon de repositorio.
Nuestra prueba puede ejecutar tres tipos de procesos:
- Genere flujo de paquetes TCP SYN desde cero, variando los campos requeridos, como la dirección IP de origen, el número de puerto de origen, etc.
- Cree una respuesta ACK válida para cada paquete SYN recibido de acuerdo con TCP;
- Cree una respuesta SYN-ACK válida para cada paquete ACK recibido de acuerdo con el protocolo TCP.
Por ejemplo, el bucle de código interno (respectivamente, el "más caliente") para crear respuestas ACK es el siguiente:
local tx = 0 local rx = rxQ:recv(rxBufs) for i = 1, rx do local buf = rxBufs[i] local pkt = buf:getTcpPacket(ipv4) if pkt.ip4:getProtocol() == ip4.PROTO_TCP and pkt.tcp:getSyn() and (pkt.tcp:getAck() or synack) then local seq = pkt.tcp:getSeqNumber() local ack = pkt.tcp:getAckNumber() pkt.tcp:unsetSyn() pkt.tcp:setAckNumber(seq+1) pkt.tcp:setSeqNumber(ack) local tmp = pkt.ip4.src:get() pkt.ip4.src:set(pkt.ip4.dst:get()) pkt.ip4.dst:set(tmp) …
La idea general de crear un paquete de respuesta es la siguiente. Primero, debe eliminar el paquete de la cola RX, luego verificar si el tipo de paquete coincide con el esperado. En caso de coincidencia, prepare una respuesta modificando algunos campos del paquete original. Finalmente, coloque el paquete creado en la cola TX utilizando el mismo búfer. Para mejorar el rendimiento, en lugar de tomar paquetes uno por uno y modificarlos uno por uno, los agregamos, extraemos todos los paquetes disponibles de la cola RX, creamos las respuestas correspondientes y los ponemos todos en la cola TX. A pesar de un número bastante grande de manipulaciones en un paquete, el rendimiento sigue siendo alto, principalmente debido al hecho de que Lua JIT compila todas estas operaciones en un pequeño número de instrucciones del procesador. Muchas otras pruebas, no solo TCP SYN / ACK, funcionan con el mismo principio.
La siguiente tabla muestra los resultados de la prueba de inundación SYN (generación SYN sin intentos de respuesta) utilizando Mellanox ConnectX-4. Esta NIC tiene dos puertos 40G con un techo de rendimiento teórico de 59.52 Mpps en un puerto y 2 * 50 Mpps para dos puertos. La implementación específica de conectar la NIC a PCIe limita un poco el ancho de banda (dando 2 * 50 en lugar de los 2 * 59.52 esperados).
núcleos por puerto | 1 puerto, Mpps | 2 puertos, Mpps por cada puerto |
1 | 20 | 19 |
2 | 38 | 36 |
3 | 56,5 | 47 |
4 4 | 59,5 | 50 |
Prueba de inundación SYN; NIC: Mellanox Technologies MT27700 Family (ConnectX-4), puerto dual 40G; CPU: CPU Intel® Xeon® Silver 4114 @ 2.20GHzLa siguiente tabla muestra los resultados de la misma prueba de inundación SYN realizada en un Mellanox ConnectX-5 con un puerto de 100G.
núcleos | Mpps |
1 | 35 |
2 | 69 |
3 | 104 |
4 4 | 127 |
5 5 | 120 |
6 6 | 131 |
7 7 | 132 |
8 | 144 |
Prueba de inundación SYN; NIC: Mellanox Technologies MT27800 Family (ConnectX-5), solo puerto 100G; CPU: CPU Intel® Xeon® Silver 4114 @ 2.20GHzTenga en cuenta que en todos los casos alcanzamos más del 96% del techo de rendimiento teórico en un pequeño número de núcleos de procesador.
Capture el tráfico entrante y guárdelo en archivos PCAP
Otro ejemplo de la prueba es rx-to-pcap, que intenta capturar todo el tráfico entrante y guardarlo en un cierto número de archivos PCAP [
8 ]. Aunque esta prueba no se refiere específicamente a la generación de paquetes como tal, sirve como una demostración del hecho de que el eslabón más débil para organizar la transferencia de datos a través del espacio de usuario es el sistema de archivos. Incluso el sistema de archivos virtual tmpfs ralentiza significativamente la transmisión. En este caso, se necesitan 8 núcleos del procesador central para la utilización de 14.88 Mpps, mientras que solo un núcleo es suficiente para recibir (y restablecer o redirigir) la misma cantidad de tráfico.
La siguiente tabla muestra la cantidad de tráfico (en Mpps) que se recibió y se guardó en los archivos PCAP ubicados en el sistema de archivos ext2 en el SSD (segunda columna) o en el sistema de archivos tmpfs (tercera columna).
núcleos | en SSD, Mpps | en tmpfs, Mpps |
1 | 1,48 | 1,62 |
2 | 4 4 | 4.6 |
3 | 6,94 | 8.1 |
4 4 | 9,75 | 11,65 |
5 5 | 12,1 | 13,8 |
6 6 | 13,38 | 14,47 |
7 7 | 14,4 | 14,86 |
8 | 14,88 | 14,88 |
Prueba de Rx a pcap; NIC: Intel 82599ES de 10 Gigabits; CPU: CPU Intel® Xeon® E5-2683 v4 @ 2.10GHzModificación de MoonGen: tman Task Manager
También nos gustaría presentarle al lector nuestra propia extensión de la funcionalidad MoonGen, que proporciona otra forma de lanzar un grupo de tareas para realizar pruebas. La idea principal aquí es separar la configuración general y la configuración específica de cada tarea, lo que le permite ejecutar un número arbitrario de tareas diferentes (es decir, scripts Lua) al mismo tiempo. En nuestro clon del repositorio MoonGen, se presenta la implementación de MoonGen con el administrador de tareas [
9 ], aquí solo enumeraremos brevemente sus funciones principales.
La nueva interfaz de línea de comandos le permite ejecutar múltiples tareas de diferentes tipos simultáneamente. El escenario básico es el siguiente:
./build/tman [tman options...] [-- <task1-file> [task1 options...]] [-- <task2-file> [task2 options...]] [-- ...]
Además ./build/tman -h proporciona ayuda detallada.
Sin embargo, hay una limitación: los archivos de trabajo de Lua normales no son compatibles con la interfaz
tman . El
archivo de trabajo de
tman debe definir claramente los siguientes objetos:
- La función de configuración (analizador) que describe los parámetros del trabajo;
- La función de tarea (taskNum, txInfo, rxInfo, args), que describe el proceso de la tarea real. Aquí txInfo y rxInfo son matrices de colas RX y TX, respectivamente; args contiene los parámetros del administrador de tareas y la tarea misma.
- Se pueden encontrar ejemplos en examples / tman.
El uso del administrador de tareas le brinda más flexibilidad para ejecutar pruebas heterogéneas.
Conclusiones
El método que ofrece MoonGen resultó ser muy adecuado para nuestros objetivos y satisfizo a los empleados con los resultados. Obtuvimos una herramienta con alto rendimiento, manteniendo el entorno de prueba y el lenguaje bastante simple. El alto rendimiento de esta configuración se logra gracias a dos características principales: acceso directo a las memorias intermedias del controlador de interfaz de red y la técnica de compilación Just-In-Time en Lua.
Como regla, lograr un techo teórico para el rendimiento de un controlador de interfaz de red es una tarea factible. Como hemos demostrado, un solo núcleo puede ser suficiente para saturar un puerto 10G, mientras que una carga completa de un puerto 100G no presenta un problema particular con un mayor número de núcleos.
Estamos especialmente agradecidos al equipo de Mellanox por su ayuda con su equipo y al equipo de MoonGen por su reacción a la corrección de los errores.
Materiales
- MoonGen: un generador de paquetes de alta velocidad programable - Paul Emmerich et al., Internet Measurement Conference 2015 (IMC'15), 2015
- Pktgen
- Guía de afinación de Mellanox
- Kit de desarrollo de plano de datos
- Lua
- Inundación Syn
- El clon de Qrator Labs del repositorio MoonGen
- Formato de archivo PCAP
- Administrador de tareas
- Lua performance
- Informe de virtualización de funciones de red
- NUMA, acceso a memoria no uniforme