En el mundo del desarrollo y administración de sistemas Linux, los programas eBPF (Extended Berkeley Packet Filter) han emergido como piezas fundamentales para la observabilidad, seguridad y mejora del rendimiento del sistema. Estos programas funcionan dentro del núcleo del sistema operativo, permitiendo la ejecución de código personalizado con alta eficiencia y bajo impacto en el rendimiento. Sin embargo, uno de los desafíos tradicionales ha sido la portabilidad de estos programas, ya que las diferencias entre versiones del kernel y las variaciones en estructuras internas dificultan su uso universal. Aquí es donde entra en juego CO-RE, una tecnología que facilita la creación de programas eBPF compilados una sola vez y ejecutables en cualquier versión del kernel compatible sin necesidad de recompilar o realizar ajustes mayores. Comprender cómo construir programas eBPF portátiles utilizando CO-RE es indispensable para desarrolladores y administradores interesados en optimizar sus herramientas y garantizar la compatibilidad a largo plazo.
Los programas eBPF tradicionales enfrentan inconvenientes para ser trasladados de un sistema a otro, en particular cuando las versiones del núcleo Linux no coinciden exactamente. Esto se debe a que las estructuras de datos internas y los encabezados del kernel pueden cambiar entre versiones, incluyendo modificaciones en nombres de campos, orden y tamaño de las estructuras. Por ejemplo, un programa eBPF que referencia directamente un campo específico de una estructura del kernel realizada con una versión podría fallar o comportarse erráticamente si se ejecuta en un núcleo posterior donde esa estructura ha cambiado. Esta situación representa un desafío crítico para la adopción masiva y sostenida de programas eBPF, principalmente en entornos con diversidad de distribuciones y versiones del sistema operativo. Antes de CO-RE, la solución común implicaba una serie de pasos laboriosos como instalar los encabezados del kernel en cada máquina objetivo, utilizar herramientas pesadas como BCC (BPF Compiler Collection) o mantener múltiples versiones del mismo programa adaptados para cada versión del kernel o distribución.
Esta metodología resultaba ineficiente, propensa a errores y compleja de mantener, especialmente en infraestructuras grandes donde la heterogeneidad es habitual. Por otro lado, los programas que solo empleaban interfaces estables del kernel o llamadas al sistema podían funcionar en diferentes versiones, pero su funcionalidad estaba limitada por la simplicidad de dichas interfaces. CO-RE (Compile Once - Run Everywhere) surge como una solución innovadora para estos problemas. Este enfoque permite que un mismo bytecode eBPF, compilado en un único entorno, pueda ejecutarse en múltiples versiones del kernel sin necesidad de recompilar ni modificar el código. CO-RE logra esto al aprovechar el uso de información de tipos y estructuras del kernel en tiempo de compilación y ejecución, permitiendo así la reinterpretación correcta de los datos estructurados incluso si han cambiado entre versiones.
Un componente fundamental de CO-RE es BTF (BPF Type Format), un formato compacto y eficiente que almacena metadatos sobre los tipos de datos C presentes en el kernel y requeridos por los programas eBPF. BTF funciona como una forma de documentación embebida que describe cómo están organizadas las estructuras del kernel, sus campos, tamaños y desplazamientos, algo similar al formato DWARF para depuración, pero optimizado para eBPF. Durante la carga del programa eBPF en el kernel, la existencia de esta información BTF permite al cargador ajustar automáticamente referencias a funciones, estructuras y campos según sea necesario para la versión del kernel específico, asegurando que el programa acceda correctamente a los datos. Para que CO-RE funcione correctamente, es imprescindible que el kernel soporte y exponga la información BTF. Esto requiere que el kernel haya sido compilado con la opción CONFIG_DEBUG_INFO_BTF habilitada.
Por suerte, la mayoría de las distribuciones modernas de Linux ya incluyen kernels con soporte para BTF, pero en sistemas donde esto no sea posible o deseado, existen alternativas como el proyecto BTFhub que provee archivos BTF para distintos kernels y distribuciones, permitiendo cargar esta información de manera externa junto con el programa eBPF. Un aspecto relevante es la generación del archivo vmlinux.h, un encabezado C derivado del kernel que contiene las definiciones y estructuras necesarias para el desarrollo eBPF. Tradicionalmente, los desarrolladores debían manejar múltiples encabezados y dependencias para acceder a información del kernel al escribir código eBPF. Gracias a BTF y herramientas como bpftool, es posible generar un único archivo vmlinux.
h que simplifica y unifica este proceso, facilitando un desarrollo más limpio y sencillo. Al construir programas portátiles con CO-RE, se recomienda dividir el proyecto en una estructura ordenada que separe el código en espacio kernel y espacio usuario, con directorios específicos para el código eBPF, archivos BTF e integración con el sistema de compilación. El flujo de trabajo incluye la compilación del código eBPF con Clang/LLVM usando las opciones apropiadas para incluir la información BTF, la generación y almacenamiento de archivos BTF específicos para las versiones soportadas, y la implementación de mecanismos en el espacio usuario para detectar la versión del kernel objetivo y cargar dinámicamente el archivo BTF correcto cuando sea necesario. Un beneficio adicional de CO-RE es la capacidad de reducir significativamente el tamaño de los archivos BTF a través de herramientas como bpftool, que pueden minificar el archivo manteniendo solo la información relevante para el programa eBPF específico. Esto permite incrustar múltiples archivos BTF en un único binario, utilizando características del lenguaje Go como la directiva embed para empaquetar múltiples archivos BTF y distribuir software compacto y eficiente que funcione en una amplia gama de sistemas.
Ejemplos prácticos de programas portátiles basados en CO-RE incluyen proyectos como flat, tcprtt_sockops y tcprtt. Estos programas demuestran cómo combinar el código eBPF con técnicas de portabilidad avanzada para ejecutar análisis detallados de tráfico de red, medir latencias TCP y realizar operaciones eficientes en diversos kernels sin modificaciones. La adopción de atributos especiales en estructuras de datos y el uso de macros concretas como BPF_PROG facilita el acceso seguro y portable a la memoria del kernel, mejorando el rendimiento y seguridad de los programas. El ecosistema alrededor de eBPF continúa evolucionando rápidamente, con iniciativas que migran de herramientas antiguas como BCC hacia soluciones más modernas basadas en libbpf y CO-RE. Esta transformación implica mejoras significativas en mantenimiento, rendimiento y compatibilidad.
A medida que el soporte para CO-RE se vuelva estándar en nuevas versiones del kernel, es esperable que la adopción se generalice en proyectos de monitoreo, seguridad y análisis de sistemas. En conclusión, CO-RE representa un avance clave para alcanzar la verdadera portabilidad y robustez en el desarrollo de programas eBPF. Al permitir que el mismo código funcione en distintos entornos y versiones del kernel, simplifica el ciclo de desarrollo, reduce la necesidad de mantenimiento múltiple y facilita la distribución masiva de herramientas avanzadas para Linux. Para desarrolladores y profesionales interesados en aprovechar todo el potencial de eBPF, dominar el uso de CO-RE y comprender el uso adecuado de BTF es esencial para construir soluciones sostenibles, eficientes y de alto impacto. Las herramientas mencionadas, como Clang, bpftool, libbpf, junto con proyectos externos como BTFhub, ofrecen un conjunto completo para desarrollar, optimizar y distribuir programas eBPF portátiles.
Seguir de cerca la evolución del kernel Linux y las mejores prácticas en el desarrollo eBPF garantizará la capacidad de mantener soluciones operativas y actualizadas en un ecosistema Linux cada vez más dinámico y heterogéneo.