Servidores

Beeper: parseo HTTP dentro del kernel con eBPF para acelerar tu servidor web

Cuando moví mi servidor web a un contenedor y empecé a medir con calma, el cuello de botella no estaba donde yo suponía. Las consultas a la base de datos ocupaban milisegundos, el disco respondía bien, y sin embargo el servidor apenas rendía unas pocas miles de peticiones por segundo. El culpable era el camino que ninguna herramienta te muestra: cada petición estática pasaba por el espacio de usuario, se convertía en bytes, se copiaba al kernel, y volvía a subir. Todo ese viaje ocurre miles de veces por segundo.

La comunidad de eBPF lleva años moviendo cosas al kernel, pero casi siempre se detiene en la capa de transporte. Parsear HTTP dentro del kernel se creía imposible: los protocolos de capa 7 son autodescriptivos, necesitan más estado y más lógica de control de la que el verificador de eBPF acepta. En la Linux Plumbers Conference de octubre de 2026, un equipo de ETH Zurich, NYU y NVIDIA presentó una forma de hacerlo: construir en espacio de usuario un DFA parecido al de Aho-Corasick y cargarlo en el kernel. Ese proyecto se llama Beeper, y la demostración que lo acompaña, hyper-fast-path, sirve archivos estáticos directamente desde el kernel. Un detalle que confunde a quien busca el material original: en la ficha de la charla el sistema aparece con otro nombre, HTTBPF, mientras que el código publicado usa Beeper.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30 a 60 minutos en una máquina de pruebas, contando la instalación de Rust, clang y la compilación del crate.
  • Requisitos: kernel 6.8 o superior, privilegios de superusuario para cargar programas eBPF, y las cabeceras de tu kernel para compilar los programas BPF.
  • Resultado: un servidor capaz de responder archivos estáticos desde el kernel. El README del proyecto reporta hasta 4.5x de rendimiento máximo con HTTP/2 en tráfico local.
  • Troubleshooting: si el kernel es anterior a 6.8 falta el soporte base, y si el activo pesa más de 16 KB el camino rápido no puede servirlo por sí solo.

Qué es Beeper: un parser de capa 7 que vive dentro del kernel

Beeper (BEEline’s ParsER) es una biblioteca escrita en Rust que permite analizar protocolos de capa 7 directamente desde un programa eBPF. Su nombre es una forma de juego de palabras: BEEline’s ParsER, el parser extraído del proyecto Beeline, que es el trabajo de investigación original publicado como artículo académico.

El proyecto se publica bajo licencia GPL-3.0 y está en GitHub como lerboe/beeper. A la fecha de este artículo acumula 123 estrellas, 7 forks y 3 problemas abiertos. También está en crates.io como crate beeper, en su versión 0.1.7, con apenas 119 descargas acumuladas. Ese número es la primera señal de que esto es investigación y no un producto: nadie lo está usando en producción todavía.

El soporte de protocolos es el siguiente:

Protocolo Estado Kernel mínimo
HTTP/1.1 Completo 6.8
HTTP/2 Completo 6.8
RESP2 En desarrollo Sin especificar

La idea de fondo es el punto clave: el verificador de eBPF no acepta programas arbitrariamente complejos. Beeper no intenta meter un parser HTTP completo en el kernel. Hace algo más elegante y, a la vez, más difícil: construye en espacio de usuario una estructura determinista finita de tipo Aho-Corasick, y esa estructura es la que se carga en el kernel. En el kernel queda solo un autómata que reconoce patrones y guarda offsets, no un parser que interpreta la gramática completa.

Por qué esto no se había hecho antes

Las diapositivas de la charla explican el problema con dos ejemplos que cualquier persona que haya lidiado con HTTP lo reconoce. El primer problema es la estructura de datos explícita: los encabezados de HTTP/1.1 no distinguen mayúsculas de minúsculas, admiten espacios opcionales, y el orden es arbitrario. Escribir un parser que maneje todo eso dentro de eBPF significa cargar más estado y saltos que el verificador rechaza.

El segundo problema es la codificación especializada: HTTP/2 empaqueta los encabezados en un formato tipo longitud-valor, donde tanto el tipo como la longitud son enteros de tamaño variable, y donde el propio tipo lleva un bit que indica si el campo se puede cachear. Decodificar eso dentro de eBPF es caro.

La solución que propone Beeper es no decodificar. El DFA se construye asumiendo que el tipo y la longitud son variables desconocidas de antemano, y el autómata recorre todos los bytes posibles para contabilizar la longitud, sin necesidad de interpretar el valor numérico. El resultado es un programa eBPF mucho más pequeño que el que haría falta de otra forma, y que el verificador acepta.

Dónde se engancha el kernel

Beeper se integra en el espacio de usuario a través del crate xbpf, que se encarga de compilar el programa eBPF y de exportar las cabeceras de C. El programa en sí se engancha en dos tipos de programa BPF distintos: SK_MSG y SK_SKB. El primero opera a nivel de socket y solo se invoca para tráfico local; el segundo opera a nivel de paquete y funciona para tráfico remoto, pero tiene un límite importante que veremos más adelante.

El artículo que da origen al proyecto, publicado en arXiv como Enforcing Application-Layer Policies in eBPF, va un paso más allá y aplica la misma técnica a las mallas de servicio: en lugar de que cada servicio pase por un proxy lateral, un plano de datos eBPF aplica directamente las políticas de capa 7 en el kernel. Ese es el proyecto Beeline, y Beeper es la pieza de parsing que lo hace posible.

hyper-fast-path: el servidor que responde desde el kernel

Para entender qué se puede hacer con Beeper, el repositorio lerboe/hyper-fast-path es la mejor referencia. Es un servidor de archivos estáticos construido sobre hyper en Rust, con una ruta rápida en eBPF que intercepta las peticiones de archivos pequeños y las responde sin que el espacio de usuario se entere. Si la petición no corresponde a un archivo registrado en la ruta rápida, el servidor sigue su curso normal y la sirve en espacio de usuario.

El repositorio trae una serie de archivos de prueba de distintos tamaños, desde 1 KB hasta 128 KB, y cada uno tiene una variante con el sufijo -sp que no está registrada en la ruta rápida. Esa pareja de archivos es exactamente lo que necesitas para comparar ambos caminos con la misma carga.

El servidor acepta tres argumentos que te van a interesar: --no-fastpath desactiva la ruta rápida por completo, --addr define la dirección de escucha, y --dummies controla cuántas conexiones atienden en paralelo desde el kernel, con 1024 por defecto.

Instalar las dependencias y comprobar que tu kernel sirve

Antes de clonar nada, confirma que tu kernel cumple el mínimo. El proyecto documenta la versión 6.8 como el piso para HTTP/1.1 y HTTP/2, y conviene comprobar que el soporte de BPF está habilitado en el sistema.

uname -r
ls -d /sys/fs/bpf
sudo apt install -y linux-headers-$(uname -r) linux-tools-$(uname -r)
sudo bpftool feature probe kernel | grep -c sk_msg

El primer comando muestra la versión del kernel, el segundo confirma que el sistema de archivos BPF está montado y el tercero instala las cabeceras y las herramientas de BPF que corresponden a esa versión exacta. El último necesita privilegios, porque sondea cargando programas de prueba: si el contador devuelve 0, tu kernel no expone el tipo de programa sk_msg y la ruta rápida no va a funcionar.

La cadena de herramientas de compilación es la que pide el README del proyecto: clang y llvm en su versión 18, más las bibliotecas de desarrollo de libelf y zlib. Si tu distribución no tiene clang-18 en los repositorios por defecto, ahí es donde te vas a atascar.

sudo apt install -y clang-18 llvm-18 libelf-dev zlib1g-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --version
cargo --version

El crate usa la edición 2024 de Rust, así que necesitas una versión reciente del compilador; el instalador de rustup trae la estable más actual. Verificar que cargo responde evita descubrir veinte minutos después que la terminal que abriste no había cargado el perfil.

Compilar Beeper y pasar su propia suite de pruebas

El repositorio es un workspace de Cargo que incluye la biblioteca, un ejemplo y utilidades de prueba. Su suite usa h2spec para validar el parser de HTTP/2 contra una lista de casos de conformidad, así que ejecutar las pruebas es la forma más directa de comprobar que tu cadena de herramientas quedó bien.

git clone https://github.com/lerboe/beeper.git
cd beeper
RUST_LOG=trace cargo test

La variable RUST_LOG=trace no es decorativa: el propio CI del proyecto la exporta con el comentario explícito de que los programas eBPF se compilan con ese nivel, para que sus errores lleguen al registro. Si una prueba falla sin esa variable, el error se queda enterrado y no sabrás qué pasó.

Levantar el servidor y confirmar que el kernel responde

Ahora sí, el servidor de ejemplo. Necesitas oha para las pruebas de carga más adelante, y el generador de carga debe estar en otra máquina si quieres reproducir las cifras de tráfico remoto.

cargo install oha
git clone https://github.com/lerboe/hyper-fast-path.git
cd hyper-fast-path
RUST_LOG=trace cargo run --bin example

El servidor escucha en 127.0.0.1:8080 por defecto. En una terminal aparte, pide un archivo que sí está en la ruta rápida y observa la respuesta:

curl -vv http://127.0.0.1:8080/8KB.txt

En los registros del servidor deberías ver la línea Served request, que indica que el kernel atendió la petición. Si en cambio ves la traza completa de axum sirviendo el archivo, la ruta rápida no se enganchó y hay que revisar los permisos: cargar programas eBPF exige privilegios que una sesión normal no tiene.

Ahora la comparación que vale la pena, el mismo archivo en la ruta rápida y en la lenta:

curl -s -o /dev/null -w 'fast path  8KB  http=%{http_code}  tiempo=%{time_total}s' http://127.0.0.1:8080/8KB.txt
echo
curl -s -o /dev/null -w 'slow path  8KB  http=%{http_code}  tiempo=%{time_total}s' http://127.0.0.1:8080/8KB-sp.txt
echo

La diferencia en un entorno de escritorio será mínima, porque ahí el cuello de botella es la latencia de la red y no el copia y pega de buffers. La ventaja aparece cuando saturas la conexión, y para eso están los scripts de medición.

Medir el rendimiento con las herramientas del proyecto

El repositorio incluye dos scripts de medición que se encargan de la parte tediosa: fijar el gobernador de CPU en performance, anclar el servidor a un núcleo con taskset, generar la carga con oha y restaurar las configuraciones al terminar.

ulimit -n 10000
script/bench-local.sh -n beeper-local
uv run script/vis.py beeper-local

El ulimit importa: los scripts se niegan a arrancar si el límite de descriptores abiertos es menor a 10000, porque con 100 conexiones concurrentes y las respuestas del kernel el consumo de descriptores sube rápido. El script de benchmark remoto tiene la misma estructura pero además fija las afinidades de IRQ de la tarjeta de red y desactiva el reparto de paquetes, para que la CPU que mide sea la que realmente procesa las peticiones.

LOAD_GEN_HOST=OTRA_MAQUINA script/bench-remote.sh -n beeper-remoto

En ese modo el servidor queda abierto al exterior y hay que ajustar el cortafuegos, porque el script necesita que la máquina generadora de carga alcance el puerto 8080. Si tu máquina usa ufw, el propio script se encarga de abrir y cerrar la regla al terminar.

Para ver qué quedó cargado en el kernel mientras el servidor corre, la herramienta de referencia sigue siendo bpftool:

sudo bpftool prog show
sudo bpftool map show

Los números: qué mide cada cifra y de dónde sale

Aquí hay que tener cuidado, porque en este tema circulan cifras que se mezclan y cada una mide algo distinto. La tabla del README de hyper-fast-path, publicada por el propio autor del proyecto, muestra el resultado de servir un archivo de 32 KB contra un servidor axum de referencia:

Protocolo Tráfico Latencia a 2K RPS Rendimiento máximo
HTTP/1.1 Local 54 por ciento menos 4.3x
HTTP/1.1 Remoto 44 por ciento menos 3x
HTTP/2 Local 53 por ciento menos 4.5x
HTTP/2 Remoto 44 por ciento menos 3.4x

La cifra de 4.5x que circuló como titular viene de la tercera fila de la tabla: rendimiento máximo con HTTP/2 en tráfico local. Es el mejor caso posible y el README es explícito en que esos números son de la configuración del autor. El 3x de HTTP/1.1 remoto es, en cambio, el número conservador.

Las diapositivas de la conferencia usan otra unidad de medida, con archivos de 32 KB servidos en un banco de pruebas controlado: reportan 3.0x de rendimiento adicional en HTTP/1.1 y 3.4x en HTTP/2. Y el resumen del artículo de Beeline habla de otra cosa por completo: reducir la latencia mediana hasta 6x y sostener 3x más rendimiento, pero comparado con los proxies laterales de las mallas de servicio, no con un servidor web.

Si alguien te cita el 6x esperando que aceleres tu servidor web, no es el mismo experimento.

Los límites que el anuncio original no menciona

Un anuncio de prensa rara vez menciona los límites, y estos son los que van a definir si esto te sirve.

El techo de 16 KB del camino por paquete

El tipo de programa SK_SKB, el que atiende el tráfico remoto, no puede responder con cargas útiles de más de 16 KB. La función bpf_skb_change_tail que modifica el paquete está limitada a un máximo de cuatro páginas. El camino SK_MSG sí puede responder archivos más grandes, pero únicamente se invoca para tráfico local, lo que significa que un cliente remoto no puede aprovecharlo en la configuración actual.

La solución que propone el equipo en la charla es redirigir el tráfico de entrada de SK_SKB hacia SK_MSG, con el rodeo correspondiente. Es la dirección que toma el proyecto, aunque el crate todavía está en una versión 0.1.x y el comportamiento se está ajustando.

La ruta rápida tiene un costo

Este es el detalle que más me sorprendió y que no aparece en ningún resumen. Cuando el servidor tiene que recurrir a espacio de usuario, lo hace peor que sin la ruta rápida: las diapositivas miden un 18 por ciento menos de rendimiento en HTTP/1.1 y un 12 por ciento menos en HTTP/2, con una latencia 15 por ciento más alta en el primer caso. Traducido: el fast path no es gratis, se paga con una penalización en el camino lento.

Eso tiene una consecuencia práctica muy concreta. Si tu servidor sirve en su mayoría contenido dinámico, el balance puede ser negativo.

El caché de encabezados de HTTP/2

HTTP/2 cachea los encabezados de cada petición para poder enviarlos por referencia en las siguientes. Cuando la ruta rápida intercepta una petición y la reenvía al espacio de usuario, esa caché se desincroniza. La solución del proyecto es prepender los cambios de caché a la petición antes de reenviarla, de modo que el servidor de espacio de usuario reciba la información correcta. Es ingenioso y también es la clase de detalle que genera errores sutiles en producción.

Madurez y licencia

Un crate en la versión 0.1.7 con 119 descargas y 3 issues abiertos no es algo que vayas a poner mañana delante de un sitio en producción. La licencia GPL-3.0 implica que si integras el código en un servicio cerrado, las obligaciones de copyleft se activan. Y todo el trabajo de integración, desde el parseo en el kernel hasta la lógica de reenvío, lo estás escribiendo tú: el proyecto ofrece la pieza de parsing y una demostración, no un producto.

Mi opinión: ¿vale la pena seguirle el ritmo?

Para un servidor que sirve mucho contenido estático, sí, y el motivo no es el titular sino el mecanismo. La ruta rápida elimina copias de buffers entre espacios, no solo instrucciones de CPU, y ese tipo de ganancia es la que no se compra con más núcleos. Si tu carga es de archivos pequeños y repetidos, el cambio en la factura de CPU puede ser notable.

Para el 90 por ciento de los servidores que administro, no. La mayoría sirve contenido dinámico, mezcla HTML con respuestas de base de datos, y ahí el fast path no aplica o se paga con la penalización del camino lento. Además, el perfil de riesgo no me resulta tranquilo: eBPF en el camino de datos es un componente crítico, y un programa mal escrito en ese nivel no falla de forma aislada, falla de forma global.

Lo que sí me parece valioso es la dirección. Durante años el rumor de que la capa 7 no podía tocar el kernel se trató como una limitación permanente de eBPF, y este trabajo muestra que la barrera era de complejidad, no de principio. Si el patrón se consolida, el modo de construir proxies, cortafuegos de aplicación y herramientas de observabilidad va a cambiar en la misma medida en que cambió iptables cuando eBPF lo reemplazó.

Mi plan es sencillo: montar esto en una máquina de pruebas, medir con mis cargas reales en lugar de con archivos de prueba, y esperar a que el proyecto supere la versión 0.1.x antes de plantearme nada serio. Para eso, este artículo y los comandos de arriba te ahorran la primera tarde.

Preguntas frecuentes

¿Beeper reemplaza a nginx o al servidor HTTP?

No. Beeper es una biblioteca de parsing que se integra en un programa eBPF, y hyper-fast-path es la demostración que la usa para responder archivos estáticos desde el kernel sobre un servidor hyper. Tu servidor web sigue haciendo su trabajo normal: solo las peticiones que coinciden con un archivo registrado en la ruta rápida se responden antes, en el kernel.

¿Cuál es el requisito mínimo de kernel y qué hago si el mío es anterior?

El README del proyecto documenta la versión 6.8 como el mínimo para HTTP/1.1 y HTTP/2, y esa cifra viene del soporte base que necesita la familia de programas BPF que usa. Si tu kernel es anterior no hay ajuste que valga: tienes que actualizar. Para confirmarlo sin instalar nada más, bpftool feature probe kernel y busca sk_msg; si el contador sale en cero, tu kernel no expone ese tipo de programa.

¿Qué pasa con los archivos de más de 16 KB?

El tipo de programa SK_SKB, que atiende el tráfico remoto, no puede responder con cargas útiles de más de 16 KB porque la función que modifica el paquete está limitada a cuatro páginas. El camino SK_MSG sí admite archivos más grandes, pero solo se invoca para tráfico local, así que un cliente remoto no puede aprovecharlo. El equipo propone redirigir el tráfico de SK_SKB hacia SK_MSG, y esa es la redirección que muestra la charla.

¿El fast path sirve contenido dinámico o solo archivos estáticos?

Solo archivos estáticos, y solo los que registres explícitamente. En la demostración son los archivos de 1 KB a 128 KB que el ejemplo carga al arrancar, y los que llevan el sufijo -sp sirven de control: son el mismo archivo pero sin registrar, para poder medir el camino lento en las mismas condiciones. Cualquier ruta fuera de esa tabla sigue el camino normal hacia espacio de usuario.

¿Tener el fast path activo vuelve más lento el camino lento?

Sí, y conviene que lo tengas presente antes de activarlo. Las diapositivas de la conferencia miden una caída del 18 por ciento de rendimiento en HTTP/1.1 y del 12 por ciento en HTTP/2 cuando el servidor tiene que recurrir a espacio de usuario, con una latencia 15 por ciento mayor en el primer caso. Si tu carga es casi toda dinámica, el fast path puede ser un neto negativo.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *