Depuración de una red utilizando eBPF (RHEL 8 Beta)

Todo con las pasadas vacaciones!

Decidimos dedicar nuestro primer artículo después de las vacaciones a Linux, es decir, a nuestro maravilloso curso de Administrador de Linux , que tenemos en la cohorte de los cursos más dinámicos, es decir, con los materiales y prácticas más relevantes. Bueno y, en consecuencia, ofrecemos artículos interesantes y una lección abierta .

Publicado por Matteo Croce
Título original: Depuración de red con eBPF (RHEL 8 Beta)

Introduccion

La creación de redes es una experiencia emocionante, pero no siempre se evitan los problemas. La resolución de problemas puede ser complicada, ya que intenta reproducir el comportamiento incorrecto que ocurre "en el campo".

Afortunadamente, existen herramientas que pueden ayudar con esto: espacios de nombres de red, máquinas virtuales, tc y netfilter . Se pueden reproducir configuraciones de red simples usando espacios de nombres de red y dispositivos veth, mientras que configuraciones más complejas requieren conectar máquinas virtuales con un puente de software y usar herramientas de red estándar, como iptables o tc , para simular un comportamiento incorrecto. Si hay un problema con las respuestas ICMP generadas cuando el servidor SSH iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable , iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable en el espacio de nombres correcto puede ayudar a resolver el problema.

Este artículo describe cómo solucionar problemas complejos de red con eBPF (BPF extendido) , una versión avanzada del Berkeley Packet Filter. eBPF es una tecnología relativamente nueva, el proyecto se encuentra en una etapa temprana, por lo que la documentación y el SDK aún no están listos. Pero esperemos mejoras, especialmente porque el XDP (eXpress Data Path) viene con Red Hat Enterprise Linux 8 Beta , que puede descargar y ejecutar ahora mismo.

eBPF no resolverá todos los problemas, pero sigue siendo una poderosa herramienta de depuración de red que merece atención. Estoy seguro de que jugará un papel muy importante en el futuro de las redes.



El problema

Depuré el problema de red Open vSwitch (OVS) , que implicó una instalación muy compleja: algunos paquetes TCP se dispersaron y se entregaron en el orden incorrecto, y el ancho de banda de las máquinas virtuales cayó de 6 Gb / s estables a 2-4 Gb / s fluctuantes. El análisis mostró que el primer paquete TCP de cada conexión con el indicador PSH se envió en el orden incorrecto: solo el primero y el único por conexión.

Traté de reproducir esta configuración con dos máquinas virtuales y, después de muchos artículos de ayuda y consultas de búsqueda, descubrí que ni iptables ni nftables pueden manipular indicadores TCP, mientras que tc puede, sino solo sobrescribiendo los indicadores e interrumpiendo nuevas conexiones y TCP en general

Podría ser posible resolver el problema con una combinación de iptables , conntrack y tc , pero decidí que este es un gran trabajo para eBPF.

¿Qué es eBPF?

eBPF es una versión mejorada del filtro de paquetes Berkeley. Ella trae muchas mejoras a BPF. En particular, le permite escribir en la memoria, y no solo leer, por lo que los paquetes no solo se pueden filtrar, sino también editar.

A menudo, eBPF simplemente se llama BPF, y el propio BPF se llama cBPF (BPF clásico (clásico)), por lo que la palabra "BPF" puede usarse para referirse a ambas versiones, dependiendo del contexto: en este artículo siempre hablo de la versión extendida.

"Under the hood" eBPF tiene una máquina virtual muy simple que puede ejecutar pequeños fragmentos de bytecode y editar algunos buffers de memoria. Existen limitaciones en eBPF que lo protegen del uso malicioso:

  • Los ciclos están prohibidos para que el programa siempre finalice en un momento específico;
  • Solo puede acceder a la memoria a través de la pila y el búfer de memoria virtual;
  • Solo se pueden llamar las funciones permitidas del kernel.

Un programa puede cargarse en el kernel de varias formas mediante depuración y rastreo . En nuestro caso, eBPF está interesado en trabajar con subsistemas de red. Hay dos formas de usar el programa eBPF:

  • Conectado a través de XDP al comienzo de la ruta RX de una tarjeta de red física o virtual;
  • Conectado vía tc a qdisc en entrada o salida.

Para crear un programa eBPF para conectarse, simplemente escriba el código C y conviértalo a bytecode. El siguiente es un ejemplo simple con XDP:

 SEC("prog") int xdp_main(struct xdp_md *ctx) { void *data_end = (void *)(uintptr_t)ctx->data_end; void *data = (void *)(uintptr_t)ctx->data; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct icmphdr *icmph = (struct icmphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if (icmph + 1 > data_end) return XDP_PASS; /* matched a pong packet */ if (eth->h_proto != ntohs(ETH_P_IP) || iph->protocol != IPPROTO_ICMP || icmph->type != ICMP_ECHOREPLY) return XDP_PASS; if (iph->ttl) { /* save the old TTL to recalculate the checksum */ uint16_t *ttlproto = (uint16_t *)&iph->ttl; uint16_t old_ttlproto = *ttlproto; /* set the TTL to a pseudorandom number 1 < x < TTL */ iph->ttl = bpf_get_prandom_u32() % iph->ttl + 1; /* recalculate the checksum; otherwise, the IP stack will drop it */ csum_replace2(&iph->check, old_ttlproto, *ttlproto); } return XDP_PASS; } char _license[] SEC("license") = "GPL"; 

El fragmento anterior, sin include expresiones, ayudantes y código opcional, es un programa XDP que cambia el TTL de las respuestas de eco ICMP recibidas, es decir, pongs, por un número aleatorio. La función principal obtiene la estructura xdp_md , que contiene dos punteros al principio y al final del paquete.

Para compilar nuestro código en el bytecode eBPF, se requiere un compilador con el soporte adecuado. Clang lo admite y crea el bytecode eBPF especificando bpf como destino en el momento de la compilación:

 $ clang -O2 -target bpf -c xdp_manglepong.c -o xdp_manglepong.o 

El comando anterior crea un archivo que, a primera vista, parece un archivo de objeto normal, pero después de una inspección más cercana, resulta que el tipo de computadora especificado es Linux eBPF, y no el tipo nativo de sistema operativo:

 $ readelf -h xdp_manglepong.o ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Linux BPF <--- HERE [...] 

Una vez recibido el contenedor de un archivo de objeto normal, el programa eBPF está listo para descargar y conectarse al dispositivo a través de XDP. Esto se puede hacer usando ip del paquete iproute2 con la siguiente sintaxis:

 # ip -force link set dev wlan0 xdp object xdp_manglepong.o verbose 

Este comando especifica la interfaz wlan0 de destino y, gracias a la opción -force, sobrescribe cualquier código eBPF existente que ya se haya cargado. Después de cargar el bytecode eBPF, el sistema se comporta de la siguiente manera:

 $ ping -c10 192.168.85.1 PING 192.168.85.1 (192.168.85.1) 56(84) bytes of data. 64 bytes from 192.168.85.1: icmp_seq=1 ttl=41 time=0.929 ms 64 bytes from 192.168.85.1: icmp_seq=2 ttl=7 time=0.954 ms 64 bytes from 192.168.85.1: icmp_seq=3 ttl=17 time=0.944 ms 64 bytes from 192.168.85.1: icmp_seq=4 ttl=64 time=0.948 ms 64 bytes from 192.168.85.1: icmp_seq=5 ttl=9 time=0.803 ms 64 bytes from 192.168.85.1: icmp_seq=6 ttl=22 time=0.780 ms 64 bytes from 192.168.85.1: icmp_seq=7 ttl=32 time=0.847 ms 64 bytes from 192.168.85.1: icmp_seq=8 ttl=50 time=0.750 ms 64 bytes from 192.168.85.1: icmp_seq=9 ttl=24 time=0.744 ms 64 bytes from 192.168.85.1: icmp_seq=10 ttl=42 time=0.791 ms --- 192.168.85.1 ping statistics --- 10 packets transmitted, 10 received, 0% packet loss, time 125ms rtt min/avg/max/mdev = 0.744/0.849/0.954/0.082 ms 

Cada paquete pasa a través de eBPF, que finalmente realiza algunos cambios y decide si descarta el paquete o se salta.

Cómo puede ayudar eBPF

Volviendo al problema de red original, recordamos que era necesario marcar varios indicadores TCP, uno por conexión, y ni iptables ni tc podían hacer esto. Escribir código para este escenario no es nada difícil: configure dos máquinas virtuales conectadas por un puente OVS y simplemente conecte eBPF a uno de los dispositivos virtuales de VM.

Parece una gran solución, pero tenga en cuenta que XDP solo admite el procesamiento de paquetes recibidos, y conectar el eBPF a la ruta rx de la máquina virtual receptora no tendrá ningún efecto en el conmutador.

Para resolver este problema, eBPF debe cargarse usando tc y conectarse a la ruta de salida de la VM, porque tc puede cargar y conectar programas eBPF a qdisk. Para marcar los paquetes que salen del host, eBPF debe estar conectado al qdisk de salida.

Al cargar el programa eBPF, existen algunas diferencias entre la API XDP y tc : por defecto, diferentes nombres de sección, el tipo de estructura del argumento de la función principal, diferentes valores de retorno. Pero esto no es un problema. A continuación se muestra un fragmento de un programa que marca TCP cuando se une a una acción tc:

 #define RATIO 10 SEC("action") int bpf_main(struct __sk_buff *skb) { void *data = (void *)(uintptr_t)skb->data; void *data_end = (void *)(uintptr_t)skb->data_end; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct tcphdr *tcphdr = (struct tcphdr *)(iph + 1); /* sanity check needed by the eBPF verifier */ if ((void *)(tcphdr + 1) > data_end) return TC_ACT_OK; /* skip non-TCP packets */ if (eth->h_proto != __constant_htons(ETH_P_IP) || iph->protocol != IPPROTO_TCP) return TC_ACT_OK; /* incompatible flags, or PSH already set */ if (tcphdr->syn || tcphdr->fin || tcphdr->rst || tcphdr->psh) return TC_ACT_OK; if (bpf_get_prandom_u32() % RATIO == 0) tcphdr->psh = 1; return TC_ACT_OK; } char _license[] SEC("license") = "GPL"; 

La compilación en bytecode se realiza como se muestra en el ejemplo XDP anterior utilizando lo siguiente:

 clang -O2 -target bpf -c tcp_psh.c -o tcp_psh.o 

Pero la descarga es diferente:

 # tc qdisc add dev eth0 clsact # tc filter add dev eth0 egress matchall action bpf object-file tcp_psh.o 

Ahora el eBPF se carga en el lugar correcto y los paquetes que salen de la VM están marcados. Después de verificar los paquetes recibidos en la segunda VM, veremos lo siguiente:



tcpdump confirma que el nuevo código eBPF está funcionando y que aproximadamente 1 de cada 10 paquetes TCP tiene establecido el indicador PSH. ¡Solo se necesitaron 20 líneas de código C para marcar selectivamente los paquetes TCP que salen de la máquina virtual, reproducir el error que ocurre "en la batalla", y todo sin recompilar o incluso reiniciar! Esto simplificó enormemente la verificación de la solución Open vSwitch , que era imposible de lograr con otras herramientas.

Conclusión

eBPF es una tecnología bastante nueva, y la comunidad tiene una opinión clara sobre su implementación. También vale la pena señalar que los proyectos basados ​​en eBPF , como bpfilter , se están volviendo más populares y, como resultado, muchos proveedores de equipos están comenzando a implementar el soporte de eBPF directamente en las tarjetas de red.

eBPF no resolverá todos los problemas, por lo que no debe abusar de él, pero sigue siendo una herramienta muy poderosa para la depuración de la red y merece atención. Estoy seguro de que jugará un papel importante en el futuro de las redes.

El fin

Estamos esperando sus comentarios aquí, y también lo invitamos a visitar nuestra lección abierta , donde también puede hacer preguntas.

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


All Articles