Escribimos protección contra ataques DDoS en XDP. Parte nuclear

La tecnología EXpress Data Path (XDP) permite el procesamiento arbitrario del tráfico en las interfaces de Linux antes de que los paquetes lleguen a la pila de red del núcleo. Aplicación de XDP: protección contra ataques DDoS (CloudFlare), filtros sofisticados, recopilación de estadísticas (Netflix). Los programas XDP son ejecutados por la máquina virtual eBPF, por lo tanto, tienen restricciones tanto en su código como en las funciones de kernel disponibles, dependiendo del tipo de filtro.


El artículo está destinado a llenar las deficiencias de numerosos materiales XDP. En primer lugar, proporcionan un código listo para usar que omite inmediatamente las características de XDP: preparado para verificación o demasiado simple para causar problemas. Cuando intenta escribir su código desde cero, no se comprende qué hacer con los errores típicos. En segundo lugar, los métodos para probar localmente XDP sin máquinas virtuales y hardware no están cubiertos, a pesar de que tienen sus propias dificultades. El texto está dirigido a programadores familiarizados con redes y Linux que estén interesados ​​en XDP y eBPF.


En esta parte, examinaremos en detalle cómo se ensambla el filtro XDP y cómo probarlo, luego escribiremos una versión simple del conocido mecanismo de cookies SYN a nivel de procesamiento de paquetes. Si bien no formaremos una "lista blanca"
clientes verificados, mantener contadores y administrar el filtro: registros suficientes.


Escribiremos en C: esto no está de moda, sino práctico. Todo el código está disponible en GitHub a través del enlace al final y se divide en confirmaciones de acuerdo con las etapas descritas en el artículo.


Descargo de responsabilidad. Durante el artículo, se desarrollará una mini solución para repeler los ataques DDoS, porque esta es una tarea realista para XDP y mi área. Sin embargo, el objetivo principal es lidiar con la tecnología, esto no es una guía para crear una protección preparada. El código de entrenamiento no está optimizado y omite algunos matices.


XDP de un vistazo


Solo describiré los puntos clave para no duplicar la documentación y los artículos existentes.


Entonces, el código del filtro se carga en el núcleo. Los paquetes entrantes se envían al filtro. Como resultado, el filtro debe tomar una decisión: omitir el paquete al núcleo ( XDP_PASS ), descartar el paquete ( XDP_DROP ) o enviarlo de regreso ( XDP_TX ). El filtro puede cambiar el paquete, esto es especialmente cierto para XDP_TX . También puede bloquear el programa ( XDP_ABORTED ) y descartar el paquete, pero esto es un análogo de assert(0) para la depuración.


La máquina virtual eBPF (filtro de paquetes Berkley extendido) está especialmente simplificada para que el núcleo pueda verificar que el código no se repita y no dañe la memoria de otras personas. Restricciones y controles agregados:


  • Ciclos prohibidos (saltar hacia atrás).
  • Hay una pila de datos, pero no funciones (todas las funciones de C deben estar en línea).
  • El acceso a la memoria fuera de la pila y el búfer de paquetes está prohibido.
  • El tamaño del código es limitado, pero en la práctica esto no es muy significativo.
  • Las llamadas solo están permitidas para funciones especiales del núcleo (ayudantes eBPF).

El diseño e instalación del filtro se ve así:


  1. El código fuente (por ejemplo, kernel.c ) se compila en el objeto ( kernel.o ) bajo la arquitectura de la máquina virtual eBPF. A partir de octubre de 2019, la compilación en eBPF es compatible con Clang y prometida en GCC 10.1.
  2. Si en este código de objeto hay llamadas a estructuras de kernel (por ejemplo, tablas y contadores), se usan ceros en lugar de sus ID, es decir, dicho código no se puede ejecutar. Antes de cargar en el kernel, debe reemplazar estos ceros con la ID de objetos específicos creados a través de llamadas al kernel (código de enlace). Puede hacer esto con utilidades externas, o puede escribir un programa que vinculará y cargará un filtro específico.
  3. El núcleo verifica el programa cargado. Se verifica la ausencia de bucles y ausentismo más allá de los límites del paquete y la pila. Si el verificador no puede probar que el código es correcto, se rechaza el programa; debe poder complacerlo.
  4. Después de una verificación exitosa, el núcleo compila el código objeto de la arquitectura eBPF en el código máquina de la arquitectura del sistema (justo a tiempo).
  5. El programa se conecta a la interfaz y comienza a procesar paquetes.

Como XDP funciona en el núcleo, la depuración se realiza mediante registros de rastreo y, de hecho, mediante paquetes que el programa filtra o genera. Sin embargo, eBPF proporciona seguridad para el código cargado del sistema, por lo que puede experimentar con XDP directamente en Linux local.


Preparación del medio ambiente


Asamblea


Clang no puede emitir directamente código objeto para la arquitectura eBPF, por lo que el proceso consta de dos pasos:


  1. Compile el código C en el código de clang -emit-llvm LLVM ( clang -emit-llvm ).
  2. Convierta bytecode a código de objeto eBPF ( llc -march=bpf -filetype=obj ).

Al escribir un filtro, un par de archivos con funciones auxiliares y macros de las pruebas del núcleo son útiles. Es importante que coincidan con la versión del kernel ( KVER ). Descárguelos en helpers/ :


 export KVER=v5.3.7 export BASE=https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/tools/testing/selftests/bpf wget -P helpers --content-disposition "${BASE}/bpf_helpers.h?h=${KVER}" "${BASE}/bpf_endian.h?h=${KVER}" unset KVER BASE 

Makefile para Arch Linux (kernel 5.3.7):


 CLANG ?= clang LLC ?= llc KDIR ?= /lib/modules/$(shell uname -r)/build ARCH ?= $(subst x86_64,x86,$(shell uname -m)) CFLAGS = \ -Ihelpers \ \ -I$(KDIR)/include \ -I$(KDIR)/include/uapi \ -I$(KDIR)/include/generated/uapi \ -I$(KDIR)/arch/$(ARCH)/include \ -I$(KDIR)/arch/$(ARCH)/include/generated \ -I$(KDIR)/arch/$(ARCH)/include/uapi \ -I$(KDIR)/arch/$(ARCH)/include/generated/uapi \ -D__KERNEL__ \ \ -fno-stack-protector -O2 -g xdp_%.o: xdp_%.c Makefile $(CLANG) -c -emit-llvm $(CFLAGS) $< -o - | \ $(LLC) -march=bpf -filetype=obj -o $@ .PHONY: all clean all: xdp_filter.o clean: rm -f ./*.o 

KDIR contiene la ruta a los encabezados del núcleo, ARCH , la arquitectura del sistema. Las rutas y herramientas pueden variar ligeramente entre las distribuciones.


Ejemplo de diferencia para Debian 10 (kernel 4.19.67)
 #   CLANG ?= clang LLC ?= llc-7 #   KDIR ?= /usr/src/linux-headers-$(shell uname -r) ARCH ?= $(subst x86_64,x86,$(shell uname -m)) #    -I CFLAGS = \ -Ihelpers \ \ -I/usr/src/linux-headers-4.19.0-6-common/include \ -I/usr/src/linux-headers-4.19.0-6-common/arch/$(ARCH)/include \ #    

CFLAGS incluye un directorio con encabezados auxiliares y varios directorios con encabezados de kernel. El símbolo __KERNEL__ significa que los encabezados UAPI (API de espacio de usuario) están definidos para el código del núcleo, ya que el filtro se ejecuta en el núcleo.


La protección de la pila se puede deshabilitar ( -fno-stack-protector ), porque el verificador de código eBPF aún busca una salida de la pila. La optimización debe incluirse de inmediato porque el tamaño del código de bytes eBPF es limitado.


Comencemos con un filtro que omita todos los paquetes y no haga nada:


 #include <uapi/linux/bpf.h> #include <bpf_helpers.h> SEC("prog") int xdp_main(struct xdp_md* ctx) { return XDP_PASS; } char _license[] SEC("license") = "GPL"; 

El comando make xdp_filter.o . ¿Dónde probarlo ahora?


Banco de pruebas


El soporte debe incluir dos interfaces: en la que habrá un filtro y desde el cual se enviarán los paquetes. Estos deben ser dispositivos Linux completos con su IP para verificar cómo funcionan las aplicaciones normales con nuestro filtro.


Los dispositivos como veth (Ethernet virtual) son adecuados para nosotros: son un par de interfaces de red virtuales que están "conectadas" directamente entre sí. Puede crearlos así (en esta sección, todos los comandos ip se ejecutan como root ):


 ip link add xdp-remote type veth peer name xdp-local 

Aquí xdp-remote y xdp-local son nombres de dispositivos. Se conectará un filtro a xdp-local (192.0.2.1/24), y el tráfico entrante se enviará desde xdp-remote (192.0.2.2/24). Sin embargo, hay un problema: las interfaces están en la misma máquina y Linux no enviará tráfico a una de ellas a través de la otra. Puede resolver esto con las complicadas reglas de iptables , pero tendrán que cambiar los paquetes, lo cual es inconveniente al depurar. Es mejor usar espacios de nombres de red (espacios de nombres de red, en adelante netns).


El espacio de nombres de la red contiene un conjunto de interfaces, tablas de enrutamiento y reglas de NetFilter, aisladas de objetos similares en otras redes. Cada proceso se ejecuta en un espacio de nombres, y solo los objetos de esta red son accesibles para él. De manera predeterminada, el sistema tiene un espacio de nombres de red único para todos los objetos, por lo que puede trabajar en Linux y no conocer las redes.


Cree un nuevo xdp-test nombres xdp-test y mueva xdp-remote .


 ip netns add xdp-test ip link set dev xdp-remote netns xdp-test 

Luego, el proceso que se ejecuta en xdp-test no "verá" xdp-local (permanecerá en netns de forma predeterminada) y lo enviará a través de xdp-remote cuando envíe un paquete a 192.0.2.1, porque esta es la única interfaz en 192.0.2.0/ 24 disponibles para este proceso. Esto también funciona en la dirección opuesta.


Cuando se mueve entre redes, la interfaz cae y pierde la dirección. Para configurar la interfaz en netns, debe ejecutar ip ... en este espacio de nombres de comando ip netns exec :


 ip netns exec xdp-test \ ip address add 192.0.2.2/24 dev xdp-remote ip netns exec xdp-test \ ip link set xdp-remote up 

Como puede ver, esto no es diferente de configurar xdp-local en el espacio de nombres predeterminado:


  ip address add 192.0.2.1/24 dev xdp-local ip link set xdp-local up 

Si ejecuta tcpdump -tnevi xdp-local , puede ver que los paquetes enviados desde xdp-test se entregan a esta interfaz:


 ip netns exec xdp-test ping 192.0.2.1 

Es conveniente ejecutar el shell en xdp-test . Hay un script en el repositorio que automatiza el trabajo con el soporte, por ejemplo, puede configurar el soporte con el comando sudo ./stand up y eliminarlo con el comando sudo ./stand down .


Traza


El filtro está conectado al dispositivo de la siguiente manera:


 ip -force link set dev xdp-local xdp object xdp_filter.o verbose 

El -force necesario para vincular un nuevo programa si ya hay otro vinculado. "No hay noticias son buenas noticias" no se trata de este comando, la conclusión es en cualquier caso voluminosa. verbose opcional, pero con él aparece un informe sobre el trabajo del verificador de código con la lista de ensamblaje:


 Verifier analysis: 0: (b7) r0 = 2 1: (95) exit 

Desate el programa de la interfaz:


 ip link set dev xdp-local xdp off 

En el script, estos son los sudo ./stand attach y sudo ./stand detach .


Al adjuntar un filtro, puede verificar que el ping continúa funcionando, pero ¿funciona el programa? Agrega los registros. La función bpf_trace_printk() es similar a printf() , pero solo admite hasta tres argumentos, excepto la plantilla, y una lista limitada de calificadores. La macro bpf_printk() simplifica la llamada.


  SEC("prog") int xdp_main(struct xdp_md* ctx) { + bpf_printk("got packet: %p\n", ctx); return XDP_PASS; } 

La salida va al canal de rastreo del núcleo, que debe habilitar:


 echo -n 1 | sudo tee /sys/kernel/debug/tracing/options/trace_printk 

Ver flujo de mensajes:


 cat /sys/kernel/debug/tracing/trace_pipe 

Ambos comandos hacen una llamada a sudo ./stand log .


Ping ahora debería activar los siguientes mensajes:


 <...>-110930 [004] ..s1 78803.244967: 0: got packet: 00000000ac510377 

Si observa detenidamente la salida del verificador, notará cálculos extraños:


 0: (bf) r3 = r1 1: (18) r1 = 0xa7025203a7465 3: (7b) *(u64 *)(r10 -8) = r1 4: (18) r1 = 0x6b63617020746f67 6: (7b) *(u64 *)(r10 -16) = r1 7: (bf) r1 = r10 8: (07) r1 += -16 9: (b7) r2 = 16 10: (85) call bpf_trace_printk#6 <...> 

El hecho es que los programas eBPF no tienen una sección de datos, por lo que la única forma de codificar una cadena de formato es con los argumentos inmediatos de los comandos de VM:


 $ python -c "import binascii; print(bytes(reversed(binascii.unhexlify('0a7025203a74656b63617020746f67'))))" b'got packet: %p\n' 

Por esta razón, la salida de depuración infla mucho el código resultante.


Envío de paquetes XDP


Cambiemos el filtro: deje que devuelva todos los paquetes entrantes. Esto es incorrecto desde el punto de vista de la red, ya que sería necesario cambiar las direcciones en los encabezados, pero ahora el trabajo es importante en principio.


  bpf_printk("got packet: %p\n", ctx); - return XDP_PASS; + return XDP_TX; } 

Ejecute tcpdump en xdp-remote . Debe mostrar la solicitud de eco ICMP saliente y entrante idéntica y dejar de mostrar la respuesta de eco ICMP. Pero no se muestra. Resulta que para que XDP_TX funcione en un programa en xdp-local es necesario que la interfaz del xdp-remote se xdp-remote interfaz xdp-remote , incluso si está vacía, y debería elevarse.


¿Cómo me enteré?

El mecanismo de eventos de rendimiento, por cierto, usando la misma máquina virtual permite rastrear la ruta del paquete en el núcleo , es decir, eBPF se usa para desmontar con eBPF.


Tienes que sacar el bien del mal, porque no hay nada más que hacer con él.

 $ sudo perf trace --call-graph dwarf -e 'xdp:*' 0.000 ping/123455 xdp:xdp_bulk_tx:ifindex=19 action=TX sent=0 drops=1 err=-6 veth_xdp_flush_bq ([veth]) veth_xdp_flush_bq ([veth]) veth_poll ([veth]) <...> 

¿Qué es el código 6?


 $ errno 6 ENXIO 6 No such device or address 

La función veth_xdp_flush_bq() recibe un código de error de veth_xdp_xmit() , donde buscamos por ENXIO y encontramos un comentario.


Restaure el filtro mínimo ( XDP_PASS ) en el archivo xdp_dummy.c , agréguelo al Makefile, adjúntelo a xdp-remote :


 ip netns exec remote \ ip link set dev int xdp object dummy.o 

Ahora tcpdump muestra lo que se espera:


 62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84) 192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64 62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84) 192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64 

Si solo se muestra ARP, debe eliminar los filtros (esto se hace mediante sudo ./stand detach ), comenzar a hacer ping , luego configurar los filtros e intentar nuevamente. El problema es que el filtro XDP_TX afecta tanto a ARP como a la pila
xdp-test nombres xdp-test logró "olvidar" la dirección MAC 192.0.2.1, no podrá resolver esta IP.


Declaración del problema.


Pasemos a la tarea indicada: escribir el mecanismo de cookies SYN en XDP.


Hasta ahora, la inundación SYN sigue siendo un ataque DDoS popular, cuya esencia es la siguiente. Al establecer una conexión (protocolo de enlace TCP), el servidor recibe un SYN, asigna recursos para una conexión futura, responde con un paquete SYNACK y espera un ACK. El atacante simplemente envía paquetes SYN desde direcciones falsas en la cantidad de miles por segundo desde cada host desde una botnet multimillonaria. El servidor se ve obligado a asignar recursos inmediatamente después de la llegada del paquete, y se libera por un gran tiempo de espera, como resultado, la memoria o los límites se agotan, no se aceptan nuevas conexiones, el servicio no está disponible.


Si no asigna recursos para el paquete SYN, pero solo responde con un paquete SYNACK, entonces, ¿cómo puede el servidor entender que el paquete ACK que vino después se refiere a un paquete SYN que no se guardó? Después de todo, un atacante también puede generar ACK falsos. La esencia de la cookie SYN es codificar los parámetros de seqnum en seqnum como un hash de direcciones, puertos y sal cambiante. Si el ACK logró llegar antes del cambio de sal, una vez más puede calcular el hash y compararlo con acknum . El atacante no puede fingir acknum , ya que la sal incluye un secreto y no tendrá tiempo para resolverlo debido al canal limitado.


La cookie SYN se ha implementado durante mucho tiempo en el kernel de Linux e incluso puede activarse automáticamente si SYN llega demasiado rápido y en masa.


Programa educativo sobre protocolo de enlace TCP

TCP proporciona transferencia de datos como una secuencia de bytes, por ejemplo, las solicitudes HTTP se envían a través de TCP. El flujo se transmite en trozos en paquetes. Todos los paquetes TCP tienen banderas lógicas y números de secuencia de 32 bits:


  • La combinación de banderas determina el papel de un paquete en particular. El indicador SYN significa que este es el primer paquete emisor en la conexión. El indicador ACK significa que el remitente recibió todos los datos de conexión antes del byte acknum . Un paquete puede tener varias banderas y se llama por su combinación, por ejemplo, un paquete SYNACK.


  • El número de secuencia (seqnum) define el desplazamiento en el flujo de datos para el primer byte que se transmite en este paquete. Por ejemplo, si en el primer paquete con X bytes de datos este número era N, en el siguiente paquete con nuevos datos será N + X. Al comienzo de la conexión, cada lado selecciona este número arbitrariamente.


  • Número de acuse de recibo (acknum): el mismo desplazamiento que seqnum, pero no determina el número de bytes que se transmitirán, pero el número del primer byte del destinatario que el remitente no vio.



Al comienzo de la conexión, las partes deben acordar seqnum y acknum . El cliente envía un paquete SYN con su seqnum = X El servidor responde con un paquete SYNACK, donde escribe su seqnum = Y y establece acknum = X + 1 . El cliente responde a SYNACK con un paquete ACK, donde seqnum = X + 1 , acknum = Y + 1 . Después de eso, comienza la transferencia de datos real.


Si el interlocutor no confirma la recepción del paquete, TCP lo envía nuevamente por tiempo de espera.


¿Por qué no se usan siempre las cookies SYN?

En primer lugar, si se pierde SYNACK o ACK, tendrá que esperar el reenvío; la conexión se ralentiza. En segundo lugar, en el paquete SYN, ¡y solo en él! - se transmiten una serie de opciones que afectan el funcionamiento posterior de la conexión. Sin recordar los paquetes SYN entrantes, el servidor ignora estas opciones, en los siguientes paquetes el cliente ya no los enviará. En este caso, TCP puede funcionar, pero al menos en la etapa inicial, la calidad de la conexión disminuirá.


En términos de paquetes, un programa XDP debe hacer lo siguiente:


  • SYNACK con cookie para responder a SYN;
  • responder a ACK RST (desconectar);
  • descartar otros paquetes.

Seudocódigo de algoritmo junto con el análisis del paquete:


    Ethernet,  .    IPv4,  .     , (*)    ,  .    TCP,  . (**)   SYN,  SYN-ACK  cookie.   ACK,   acknum   cookie,  .      N  . (*)  RST. (**)     . 

Uno (*) indica los puntos en los que controlar el estado del sistema: en la primera etapa, puede prescindir de ellos simplemente implementando un protocolo de enlace TCP con la generación de una cookie SYN como seqnum.


En su lugar (**) , si bien no tenemos una tabla, omitiremos el paquete.


Implementación de protocolo de enlace TCP


Analiza el paquete y verifica el código


Necesitamos estructuras de encabezado de red: Ethernet ( uapi/linux/if_ether.h ), IPv4 ( uapi/linux/ip.h ) y TCP ( uapi/linux/tcp.h ). El último que no pude conectar debido a errores relacionados con atomic64_t , tuve que copiar las definiciones necesarias en el código.


Todas las funciones que se asignan en C para facilitar la lectura deberían estar incorporadas en el lugar de la llamada, ya que el verificador eBPF en el núcleo prohíbe las transiciones, es decir, de hecho, bucles y llamadas a funciones.


 #define INTERNAL static __attribute__((always_inline)) 

La macro LOG() deshabilita la impresión en la versión de lanzamiento.


El programa es un transportador de funciones. Cada uno recibe un paquete en el que se resalta el encabezado del nivel correspondiente, por ejemplo, process_ether() espera que el ether esté lleno. Según los resultados del análisis de campo, la función puede transferir el paquete a un nivel superior. El resultado de la función es la acción XDP. Hasta ahora, los manejadores SYN y ACK pasan todos los paquetes.


 struct Packet { struct xdp_md* ctx; struct ethhdr* ether; struct iphdr* ip; struct tcphdr* tcp; }; INTERNAL int process_tcp_syn(struct Packet* packet) { return XDP_PASS; } INTERNAL int process_tcp_ack(struct Packet* packet) { return XDP_PASS; } INTERNAL int process_tcp(struct Packet* packet) { ... } INTERNAL int process_ip(struct Packet* packet) { ... } INTERNAL int process_ether(struct Packet* packet) { struct ethhdr* ether = packet->ether; LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto)); if (ether->h_proto != bpf_ntohs(ETH_P_IP)) { return XDP_PASS; } // B struct iphdr* ip = (struct iphdr*)(ether + 1); if ((void*)(ip + 1) > (void*)packet->ctx->data_end) { return XDP_DROP; /* malformed packet */ } packet->ip = ip; return process_ip(packet); } SEC("prog") int xdp_main(struct xdp_md* ctx) { struct Packet packet; packet.ctx = ctx; // A struct ethhdr* ether = (struct ethhdr*)(void*)ctx->data; if ((void*)(ether + 1) > (void*)ctx->data_end) { return XDP_PASS; } packet.ether = ether; return process_ether(&packet); } 

Llamo la atención sobre los controles marcados como A y B. Si comenta A, el programa se ensamblará, pero habrá un error de verificación durante la carga:


 Verifier analysis: <...> 11: (7b) *(u64 *)(r10 -48) = r1 12: (71) r3 = *(u8 *)(r7 +13) invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0) R7 offset is outside of the packet processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 Error fetching program/map! 

La línea clave es el invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0) : hay rutas de ejecución cuando el decimotercer byte desde el comienzo del búfer está fuera del paquete. Según el listado, es difícil entender de qué línea estamos hablando, pero hay un número de instrucción (12) y un desensamblador que muestra las líneas del código fuente:


 llvm-objdump -S xdp_filter.o | less 

En este caso, apunta a una cadena


 LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto)); 

por lo cual está claro que el problema está en el ether . Siempre sería así.


Responder a SYN


El objetivo en esta etapa es formar un paquete SYNACK correcto con un seqnum fijo, que será reemplazado por una cookie SYN en el futuro. Todos los cambios ocurren en process_tcp_syn() y el área circundante.


Paquete de verificación


Curiosamente, aquí está la línea más notable, más precisamente, un comentario al respecto:


 /* Required to verify checksum calculation */ const void* data_end = (const void*)ctx->data_end; 

Al escribir la primera versión del código, se utilizó el kernel 5.1, para cuyo verificador había una diferencia entre data_end y (const void*)ctx->data_end . Al escribir el artículo, el núcleo 5.3.1 no tenía ese problema. Quizás el compilador accedió a la variable local de manera diferente al campo. Moraleja: el código simplificado puede ayudar con una gran cantidad de anidamiento.


Verificaciones de rutina adicionales de longitudes en honor del verificador; aproximadamente MAX_CSUM_BYTES continuación.


 const u32 ip_len = ip->ihl * 4; if ((void*)ip + ip_len > data_end) { return XDP_DROP; /* malformed packet */ } if (ip_len > MAX_CSUM_BYTES) { return XDP_ABORTED; /* implementation limitation */ } const u32 tcp_len = tcp->doff * 4; if ((void*)tcp + tcp_len > (void*)ctx->data_end) { return XDP_DROP; /* malformed packet */ } if (tcp_len > MAX_CSUM_BYTES) { return XDP_ABORTED; /* implementation limitation */ } 

Paquete extendido


Rellene seqnum y acknum , configure ACK (SYN ya está configurado):


 const u32 cookie = 42; tcp->ack_seq = bpf_htonl(bpf_ntohl(tcp->seq) + 1); tcp->seq = bpf_htonl(cookie); tcp->ack = 1; 

Intercambie puertos TCP, dirección IP y dirección MAC. La biblioteca estándar no es accesible desde el programa XDP, por lo que memcpy() es una macro que oculta el intrínseco Clang.


 const u16 temp_port = tcp->source; tcp->source = tcp->dest; tcp->dest = temp_port; const u32 temp_ip = ip->saddr; ip->saddr = ip->daddr; ip->daddr = temp_ip; struct ethhdr temp_ether = *ether; memcpy(ether->h_dest, temp_ether.h_source, ETH_ALEN); memcpy(ether->h_source, temp_ether.h_dest, ETH_ALEN); 

Nuevo cálculo de suma de control


Las sumas de comprobación de IPv4 y TCP requieren la adición de todas las palabras de 16 bits en los encabezados, y el tamaño de los encabezados está escrito en ellas, es decir, en el momento de la compilación se desconoce. Esto es un problema porque el verificador no saltará un bucle regular a un límite variable. Pero el tamaño de los encabezados es limitado: hasta 64 bytes cada uno. Puede hacer un bucle con un número fijo de iteraciones, que puede finalizar antes de lo programado.


Observo que hay RFC 1624 sobre cómo volver a calcular la suma de verificación parcialmente si solo se cambian las palabras de paquetes fijos. Sin embargo, el método no es universal y su implementación sería más difícil de mantener.


Función de cálculo de suma de control:


 #define MAX_CSUM_WORDS 32 #define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2) INTERNAL u32 sum16(const void* data, u32 size, const void* data_end) { u32 s = 0; #pragma unroll for (u32 i = 0; i < MAX_CSUM_WORDS; i++) { if (2*i >= size) { return s; /* normal exit */ } if (data + 2*i + 1 + 1 > data_end) { return 0; /* should be unreachable */ } s += ((const u16*)data)[i]; } return s; } 

, size , , .


32- :


 INTERNAL u32 sum16_32(u32 v) { return (v >> 16) + (v & 0xffff); } 

:


 ip->check = 0; ip->check = carry(sum16(ip, ip_len, data_end)); u32 tcp_csum = 0; tcp_csum += sum16_32(ip->saddr); tcp_csum += sum16_32(ip->daddr); tcp_csum += 0x0600; tcp_csum += tcp_len << 8; tcp->check = 0; tcp_csum += sum16(tcp, tcp_len, data_end); tcp->check = carry(tcp_csum); return XDP_TX; 

carry() 32- 16- , RFC 791.


TCP


netcat , ACK, Linux RST-, SYN — SYNACK - , .


 $ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666 192.0.2.1 6666: Connection reset by peer 

tcpdump xdp-remote , , hping3 .



XDP . , , . Linux, , SipHash, XDP .


TODO, :


  • XDP- cookie_seed ( ) , , .


  • SYN cookie ACK- , IP , .



:


 $ sudoip netns exec xdp-test nc -nv 192.0.2.1 6666 192.0.2.1 6666: Connection reset by peer 

( flags=0x2 — SYN, flags=0x10 — ACK):


 Ether(proto=0x800) IP(src=0x20e6e11a dst=0x20e6e11e proto=6) TCP(sport=50836 dport=6666 flags=0x2) Ether(proto=0x800) IP(src=0xfe2cb11a dst=0xfe2cb11e proto=6) TCP(sport=50836 dport=6666 flags=0x10) cookie matches for client 20200c0 

IP, SYN flood , ACK flood, :


 sudo ip netns exec xdp-test hping3 --flood -A -s 1111 -p 2222 192.0.2.1 

:


 Ether(proto=0x800) IP(src=0x15bd11a dst=0x15bd11e proto=6) TCP(sport=3236 dport=2222 flags=0x10) cookie mismatch 

Conclusión


eBPF XDP , . , XDP — , , DPDK kernel bypass. , XDP , , , . , userspace-.


, , , userspace- .


Referencias


Source: https://habr.com/ru/post/473286/


All Articles