Desarrollador(es) | Steven McCanne, Van Jacobson |
---|---|
Lanzamiento inicial | 19 de diciembre de 1992 ( 19 de diciembre de 1992 ) |
Sistema operativo | Múltiple |
El filtro de paquetes Berkeley ( BPF ; también filtro de paquetes BSD , BPF clásico o cBPF ) es un filtro de paquetes y toma de red que permite capturar y filtrar paquetes de red informática a nivel de sistema operativo . Proporciona una interfaz sin procesar para las capas de enlace de datos , lo que permite enviar y recibir paquetes sin procesar de la capa de enlace, [1] y permite que un proceso de espacio de usuario proporcione un programa de filtro que especifique qué paquetes desea recibir. Por ejemplo, un proceso tcpdump puede querer recibir solo paquetes que inicien una conexión TCP. BPF devuelve solo paquetes que pasan el filtro que proporciona el proceso. Esto evita copiar paquetes no deseados del núcleo del sistema operativo al proceso, lo que mejora enormemente el rendimiento. El programa de filtro tiene la forma de instrucciones para una máquina virtual , que se interpretan o compilan en código de máquina mediante un mecanismo justo a tiempo (JIT) y se ejecutan en el núcleo.
BPF es utilizado por programas que necesitan, entre otras cosas, analizar el tráfico de red. Si el controlador de la interfaz de red admite el modo promiscuo , permite poner la interfaz en ese modo para que se puedan recibir todos los paquetes de la red , incluso aquellos destinados a otros hosts.
El mecanismo de filtrado BPF está disponible en la mayoría de los sistemas operativos tipo Unix . A veces, BPF se utiliza para referirse únicamente al mecanismo de filtrado, en lugar de a la interfaz completa. Algunos sistemas, como Linux y Tru64 UNIX , proporcionan una interfaz sin procesar a la capa de enlace de datos distinta de la interfaz sin procesar BPF, pero utilizan los mecanismos de filtrado BPF para esa interfaz sin procesar.
El kernel de Linux proporciona una versión extendida del mecanismo de filtrado BPF, llamado eBPF , que utiliza un mecanismo JIT y que se utiliza para el filtrado de paquetes, así como para otros fines en el kernel. eBPF también está disponible para Microsoft Windows . [2]
El artículo original fue escrito por Steven McCanne y Van Jacobson en 1992 mientras estaban en el Laboratorio Lawrence Berkeley . [1] [3]
BPF proporciona pseudodispositivos que pueden vincularse a una interfaz de red; las lecturas desde el dispositivo leerán buffers llenos de paquetes recibidos en la interfaz de red, y las escrituras en el dispositivo inyectarán paquetes en la interfaz de red.
En 2007, Robert Watson y Christian Peron añadieron extensiones de búfer de copia cero a la implementación de BPF en el sistema operativo FreeBSD [4] , lo que permite que la captura de paquetes del núcleo en el controlador de interrupciones del controlador del dispositivo escriba directamente en la memoria del proceso del usuario para evitar el requisito de dos copias para todos los datos de paquetes recibidos a través del dispositivo BPF. Si bien una copia permanece en la ruta de recepción para los procesos del usuario, esto preserva la independencia de los diferentes consumidores del dispositivo BPF, además de permitir el empaquetado de encabezados en el búfer BPF en lugar de copiar los datos de paquetes completos. [5]
Las capacidades de filtrado de BPF se implementan como un intérprete para un lenguaje de máquina para la máquina virtual BPF , una máquina de 32 bits con instrucciones de longitud fija, un acumulador y un registro de índice . Los programas en ese lenguaje pueden obtener datos del paquete, realizar operaciones aritméticas con los datos del paquete y comparar los resultados con constantes o con datos del paquete o bits de prueba en los resultados, aceptando o rechazando el paquete en función de los resultados de esas pruebas.
BPF a menudo se amplía "sobrecargando" las instrucciones de carga (ld) y almacenamiento (str).
Las implementaciones tradicionales de BPF similares a Unix se pueden utilizar en el espacio de usuario, a pesar de estar escritas para el espacio del núcleo. Esto se logra mediante condiciones de preprocesador .
Algunos proyectos utilizan conjuntos de instrucciones BPF o técnicas de ejecución diferentes a los originales.
Algunas plataformas, incluidas FreeBSD , NetBSD y WinPcap , utilizan un compilador JIT (just-in-time ) para convertir las instrucciones BPF en código nativo con el fin de mejorar el rendimiento. Linux incluye un compilador JIT BPF que está deshabilitado de forma predeterminada.
Los intérpretes en modo kernel para ese mismo lenguaje de máquina virtual se utilizan en mecanismos de capa de enlace de datos sin procesar en otros sistemas operativos, como Tru64 Unix , y para filtros de sockets en el kernel de Linux y en el mecanismo de captura de paquetes WinPcap y Npcap .
Se proporciona un intérprete de modo de usuario para BPF con la implementación libpcap/WinPcap/Npcap de la API pcap , de modo que, al capturar paquetes en sistemas sin soporte de modo kernel para ese mecanismo de filtrado, los paquetes se pueden filtrar en modo de usuario; el código que utiliza la API pcap funcionará en ambos tipos de sistemas, aunque, en sistemas donde el filtrado se realiza en modo de usuario, todos los paquetes, incluidos aquellos que se filtrarán, se copian desde el kernel al espacio de usuario. Ese intérprete también se puede utilizar al leer un archivo que contiene paquetes capturados utilizando pcap.
Otro intérprete en modo usuario es uBPF , que admite JIT y eBPF (sin cBPF). Su código se ha reutilizado para proporcionar compatibilidad con eBPF en sistemas que no son Linux. [6] El eBPF de Microsoft en Windows se basa en uBPF y el verificador formal PREVAIL. [7] rBPF , una reescritura de uBPF en Rust, es utilizado por la plataforma de cadena de bloques Solana como motor de ejecución. [8]
El BPF clásico generalmente lo emite un programa a partir de alguna regla textual de muy alto nivel que describe el patrón que debe coincidir. Una de esas representaciones se encuentra en libpcap . [9] El BPF clásico y el eBPF también se pueden escribir directamente como código de máquina o utilizando un lenguaje ensamblador para una representación textual. Los ensambladores notables incluyen la herramienta del kernel de Linux bpf_asm
(cBPF), bpfc
(cBPF) y el ubpf
ensamblador (eBPF). El bpftool
comando también puede actuar como un desensamblador para ambas versiones de BPF. Los lenguajes ensambladores no son necesariamente compatibles entre sí.
El código de bytes eBPF se ha convertido recientemente en un objetivo de los lenguajes de alto nivel. LLVM agregó compatibilidad con eBPF en 2014, y GCC lo hizo en 2019. Ambos kits de herramientas permiten compilar C y otros lenguajes compatibles en eBPF. También se puede compilar un subconjunto de P4 en eBPF utilizando BCC, un kit de compilación basado en LLVM. [10]
El ataque Spectre podría aprovechar el intérprete eBPF del kernel de Linux o el compilador JIT para extraer datos de otros procesos del kernel. [11] Una función de refuerzo JIT en el kernel mitiga esta vulnerabilidad. [12]
El grupo de seguridad informática chino Pangu Lab afirmó que la NSA utilizó BPF para ocultar las comunicaciones de red como parte de una compleja puerta trasera de Linux . [13]
Desde la versión 3.18, el núcleo Linux incluye una máquina virtual BPF extendida con diez registros de 64 bits, denominada eBPF . Se puede utilizar para fines no relacionados con la red, como para adjuntar programas eBPF a varios puntos de seguimiento . [14] [15] [16] Desde la versión 3.19 del núcleo, los filtros eBPF se pueden adjuntar a sockets , [17] [18] y, desde la versión 4.1 del núcleo, a clasificadores de control de tráfico para la ruta de datos de red de entrada y salida. [19] [20] La versión original y obsoleta ha sido renombrada retroactivamente a BPF clásico ( cBPF ). Hoy en día, el núcleo Linux ejecuta solo eBPF y el bytecode cBPF cargado se traduce de forma transparente a una representación eBPF en el núcleo antes de la ejecución del programa. [21] Todo el bytecode se verifica antes de la ejecución para evitar ataques de denegación de servicio. Hasta Linux 5.3, el verificador prohibía el uso de bucles para evitar tiempos de ejecución potencialmente ilimitados; los bucles con tiempos de ejecución limitados ahora están permitidos en núcleos más recientes. [22]