eBPF permite ejecutar pequeños programas dentro del kernel de Linux sin modificar su código ni cargar módulos. El kernel verifica cada programa antes de cargarlo (que termine, que no acceda a memoria no válida) y lo compila a código nativo, así que es seguro y muy rápido. Herramientas como Cilium, Falco, Pixie o las de observabilidad de Brendan Gregg están construidas sobre eBPF. En este tutorial usarás eBPF en Ubuntu 24.04 de tres formas: trazando el sistema con bpftrace, con las herramientas ya hechas de BCC y escribiendo y cargando tu propio programa XDP que filtra paquetes.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. Su kernel 6.8 incluye todo lo necesario, incluida la información de tipos BTF.
  • Un usuario no root con privilegios sudo. Cargar programas eBPF requiere privilegios de root.
  • Conocimientos básicos de C para la parte de XDP.

Tipos de programas eBPF

Un programa eBPF se engancha a un evento del kernel y se ejecuta cada vez que ocurre. Los tipos más usados son:

Punto de engancheQué interceptaUso típico
tracepointPuntos estables definidos por el kernel (syscalls, planificador, red)Trazado y métricas
kprobe / kretprobeEntrada o salida de casi cualquier función del kernelDepuración profunda
uprobeFunciones de programas de usuarioPerfilado de aplicaciones
XDPPaquetes recibidos, antes de que el kernel los proceseFirewall, balanceo, mitigación DDoS
tcPaquetes en la capa de control de tráfico, entrada y salidaPolíticas de red de contenedores

Los programas guardan y comparten datos mediante mapas (tablas hash, arrays, buffers circulares) que también se pueden leer desde el espacio de usuario.

Paso 1: Instalar las herramientas

Instala bpftrace, las herramientas de BCC, las cabeceras del kernel que usa BCC y bpftool, que en Ubuntu se distribuye dentro de los paquetes linux-tools:

sudo apt update
sudo apt install bpftrace bpfcc-tools linux-headers-$(uname -r) linux-tools-common linux-tools-$(uname -r)

Instala también el compilador y las cabeceras de libbpf, que usarás en la parte de XDP:

sudo apt install clang llvm libbpf-dev linux-libc-dev

Comprueba que las herramientas responden:

bpftrace --version
sudo bpftool version
bpftrace v0.20.2
bpftool v7.4.0
using libbpf v1.4
features: llvm, skeletons

Las versiones exactas pueden variar según las actualizaciones de Ubuntu.

Paso 2: Trazar el sistema con bpftrace

bpftrace es un lenguaje de alto nivel para escribir programas eBPF de una línea, similar a awk. Primero lista los tracepoints disponibles, por ejemplo los de la llamada al sistema execve:

sudo bpftrace -l 'tracepoint:syscalls:sys_enter_exec*'
tracepoint:syscalls:sys_enter_execve
tracepoint:syscalls:sys_enter_execveat

Muestra en tiempo real cada programa que se ejecuta en el servidor y qué proceso lo lanza:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%-6d %-16s %s\n", pid, comm, str(args->filename)); }'

Abre otra sesión SSH y ejecuta cualquier comando, por ejemplo ls. En la primera aparecerá:

Attaching 1 probe...
48213  bash             /usr/bin/ls

Pulsa Ctrl+C para salir. Ese programa se ha cargado en el kernel, ha funcionado y se ha descargado sin dejar rastro.

Los mapas de bpftrace (variables que empiezan por @) agregan datos dentro del kernel y los muestran al salir. Cuenta las llamadas al sistema por proceso durante unos segundos:

sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Attaching 1 probe...
^C

@[systemd-journal]: 112
@[sshd]: 845
@[bpftrace]: 1320

Y dibuja un histograma del tamaño de las lecturas que realizan los procesos:

sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ { @bytes = hist(args->ret); }'
@bytes:
[1]                   24 |@@@@                                                |
[2, 4)                 3 |                                                    |
[4, 8)                41 |@@@@@@@                                             |
[8, 16)              288 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
...

El filtro /args->ret > 0/ solo cuenta las lecturas que devolvieron datos. Agregar en el kernel es lo que hace a eBPF tan eficiente: solo el resultado final cruza al espacio de usuario, no cada evento.

Paso 3: Usar las herramientas de BCC

BCC incluye decenas de herramientas listas para diagnosticar problemas de rendimiento. En Ubuntu se instalan con el sufijo -bpfcc. Estas son las más útiles en un servidor:

HerramientaQué muestra
execsnoop-bpfccProcesos nuevos, incluidos los de vida muy corta
opensnoop-bpfccArchivos que abre cada proceso
tcpconnect-bpfccConexiones TCP salientes
tcplife-bpfccConexiones TCP cerradas con su duración y bytes
biolatency-bpfccHistograma de latencia de disco
runqlat-bpfccTiempo que los procesos esperan a tener CPU

Por ejemplo, para ver qué conexiones salientes abre el servidor:

sudo tcpconnect-bpfcc

Desde otra sesión, ejecuta curl -s https://www.kernel.org > /dev/null y verás la conexión:

Tracing connect ... Hit Ctrl-C to end
PID     COMM         IP SADDR            DADDR            DPORT
51234   curl         4  203.0.113.10     139.178.84.217   443

Para medir la latencia del disco durante 10 segundos y mostrar un histograma:

sudo biolatency-bpfcc 10 1
Tracing block device I/O... Hit Ctrl-C to end.

     usecs               : count     distribution
        64 -> 127        : 12       |*****                                   |
       128 -> 255        : 87       |****************************************|
       256 -> 511        : 35       |****************                        |

Si la mayoría de operaciones caen en milisegundos en lugar de microsegundos, el disco es un cuello de botella. Cada herramienta tiene su página de manual (man tcpconnect-bpfcc) con ejemplos.

Paso 4: Inspeccionar programas y mapas con bpftool

bpftool muestra todos los programas eBPF cargados en el sistema, incluidos los de systemd, Docker o Cilium. Deja un one-liner de bpftrace corriendo en otra sesión y lista los programas:

sudo bpftool prog list
...
312: tracepoint  name sys_enter_execve  tag 7c2d8b9a1e4f5d60  gpl
        loaded_at 2026-09-25T10:15:03+0000  uid 0
        xlated 312B  jited 190B  memlock 4096B  map_ids 45

Cada programa tiene un identificador, un tipo, el tamaño del bytecode traducido (xlated) y del código nativo (jited), y los mapas que usa. Para ver los mapas:

sudo bpftool map list

Y para ver las instrucciones de un programa concreto, usa su identificador:

sudo bpftool prog dump xlated id 312

Paso 5: Escribir un programa XDP que descarta ICMP

XDP ejecuta el programa en cuanto la tarjeta de red recibe un paquete, antes de que el kernel reserve memoria para él. Por eso se usa para mitigar ataques DDoS y para balanceadores de carga. Aquí escribirás un programa que descarta todos los paquetes ICMP (ping) y deja pasar el resto.

Para no arriesgarte a perder el acceso SSH, lo probarás en una interfaz virtual conectada a un network namespace. Crea el entorno de pruebas:

sudo ip netns add xdptest
sudo ip link add veth-host type veth peer name veth-ns netns xdptest
sudo ip addr add 10.77.0.1/24 dev veth-host
sudo ip link set veth-host up
sudo ip -n xdptest addr add 10.77.0.2/24 dev veth-ns
sudo ip -n xdptest link set veth-ns up

Comprueba que el ping funciona antes de cargar nada:

sudo ip netns exec xdptest ping -c 2 10.77.0.1
2 packets transmitted, 2 received, 0% packet loss, time 1002ms

Crea un directorio de trabajo y el código fuente:

mkdir -p ~/xdp && cd ~/xdp
nano xdp_drop_icmp.c
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

SEC("xdp")
int xdp_drop_icmp(struct xdp_md *ctx)
{
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    if (ip->protocol == IPPROTO_ICMP)
        return XDP_DROP;

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

Cada acceso a una cabecera va precedido de una comprobación contra data_end. No es opcional: el verificador del kernel rechaza cualquier programa que pueda leer fuera del paquete. XDP_DROP descarta el paquete y XDP_PASS lo entrega al kernel con normalidad.

Compila el programa a bytecode eBPF. La opción -I añade el directorio de cabeceras específicas de la arquitectura, que clang no busca por su cuenta cuando el destino es bpf:

clang -O2 -g -target bpf -I/usr/include/$(uname -m)-linux-gnu -c xdp_drop_icmp.c -o xdp_drop_icmp.o

Comprueba que el objeto contiene la sección xdp:

llvm-objdump -h xdp_drop_icmp.o | grep xdp
  2 xdp           00000060 0000000000000000 TEXT

Paso 6: Cargar y probar el programa XDP

Carga el programa en veth-host con ip. El modo xdpgeneric funciona con cualquier interfaz, aunque su controlador no soporte XDP nativo:

sudo ip link set dev veth-host xdpgeneric obj xdp_drop_icmp.o sec xdp

Comprueba que está enganchado:

ip link show dev veth-host
5: veth-host@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 7a:3e:91:0c:5d:21 brd ff:ff:ff:ff:ff:ff link-netns xdptest
    prog/xdp id 327 name xdp_drop_icmp tag 5b1e7c9d3a2f8e41 jited

Repite el ping desde el namespace. Los paquetes ICMP que llegan a veth-host se descartan:

sudo ip netns exec xdptest ping -c 3 -W 1 10.77.0.1
3 packets transmitted, 0 received, 100% packet loss, time 2046ms

El programa también aparece en sudo bpftool prog list con el tipo xdp, y sudo bpftool net list muestra en qué interfaz está enganchado.

Descarga el programa y comprueba que el ping vuelve a funcionar:

sudo ip link set dev veth-host xdpgeneric off
sudo ip netns exec xdptest ping -c 2 10.77.0.1
2 packets transmitted, 2 received, 0% packet loss, time 1001ms

Cuando termines, borra el entorno de pruebas:

sudo ip netns delete xdptest

Solución de problemas

fatal error: 'asm/types.h' file not found al compilar: falta la ruta de cabeceras de la arquitectura. Añade -I/usr/include/$(uname -m)-linux-gnu al comando de clang y comprueba que linux-libc-dev está instalado.

El verificador rechaza el programa (invalid access to packet): algún acceso a una cabecera no está protegido por una comprobación contra data_end. Añade verbose al final del comando ip link set ... sec xdp para ver el registro completo del verificador, que indica la instrucción que falla.

ERROR: Could not resolve symbol o failed to compile en herramientas BCC: BCC compila sus programas al vuelo con las cabeceras del kernel en ejecución. Instala linux-headers-$(uname -r); si acabas de actualizar el kernel, reinicia antes para que coincidan.

bpftool: command not found o WARNING: bpftool not found for kernel: falta el paquete linux-tools de tu versión exacta del kernel. Instala linux-tools-$(uname -r).

Operation not permitted al ejecutar cualquier herramienta: Ubuntu no permite cargar programas eBPF a usuarios sin privilegios (kernel.unprivileged_bpf_disabled). Ejecuta las herramientas con sudo.

Conclusión

Has trazado procesos y llamadas al sistema con bpftrace, diagnosticado red y disco con las herramientas de BCC, inspeccionado programas cargados con bpftool y escrito, compilado y cargado un filtro XDP propio. Con esto tienes la base para entender herramientas construidas sobre eBPF. Como siguientes pasos puedes:

  • Ampliar el programa XDP con un mapa hash de direcciones IP bloqueadas que puedas actualizar con bpftool map update.
  • Explorar el resto de herramientas de BCC (ls /usr/sbin/*-bpfcc) y los scripts de ejemplo del repositorio de bpftrace.
  • Probar Cilium como plugin de red en un clúster de Kubernetes para ver eBPF aplicado a redes de contenedores.