Что такое eBPF
extended BPF - виртуальная машина внутри kernel, на которой запускаются изолированные программы (eBPF programs), которые цепляются к "hook" точкам (вызов syscall, входящий пакет, контекст-свич) и могут читать/изменять данные, собирать метрики, фильтровать.
Историческое: cBPF (classic BPF) появился в 1992 как фильтр пакетов
для tcpdump (pcap_compile). В 2014 Алексей Старовойтов расширил до
eBPF: больше регистров (10×64-бит вместо 2×32-бит), новые типы
программ, maps для state, JIT-компиляция в native.
Сегодня eBPF - новая базовая kernel-абстракция:
- Networking: [[cni-plugins|cilium]], Cloudflare DDoS-фильтр, Facebook Katran (load balancer)
- Observability: bpftrace, BCC tools, Pixie, Parca (profiler), Grafana Beyla
- Security: tetragon, falco (runtime detection), [[seccomp|seccomp-bpf]]
- Tracing: kprobe/uprobe replacement для DTrace, SystemTap
Архитектура
┌─────────────────────────────┐
│ userspace │
│ ┌──────┐ ┌──────┐ ┌─────┐ │
│ │ BCC │ │libbpf│ │bpftl│ │ компиляция, загрузка
│ └──┬───┘ └──┬───┘ └──┬──┘ │
└────┼─────────┼─────────┼────┘
│ │ │ syscalls (BPF_PROG_LOAD, BPF_MAP_*)
┌────▼─────────▼─────────▼────┐
│ kernel │
│ ┌──────────────────────┐ │
│ │ verifier │ ← bytecode проверяется
│ └──────┬───────────────┘ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ JIT compiler │ → native x86-64/arm64
│ └──────┬───────────────┘ │
│ ▼ │
│ hooks: kprobe / tracepoint │
│ tc / XDP / cgroup │
│ ┌─────┴────────────┐ │
│ │ BPF maps (state) │ │
│ └──────────────────┘ │
└─────────────────────────────┘
Hook-типы (program types)
Каждый тип программы цепляется к своему месту в kernel:
| Program type | Где attach | Применение |
|---|---|---|
BPF_PROG_TYPE_KPROBE | вход/выход kernel-функции | tracing kernel calls |
BPF_PROG_TYPE_TRACEPOINT | static tracepoint | стабильнее kprobe |
BPF_PROG_TYPE_RAW_TRACEPOINT | tracepoint без преобразования аргументов | быстрее, но менее портативно |
BPF_PROG_TYPE_PERF_EVENT | perf-event (sample N циклов) | profiling, on-CPU stack |
BPF_PROG_TYPE_SOCKET_FILTER | socket recv (как cBPF) | tcpdump |
BPF_PROG_TYPE_SCHED_CLS | tc-classifier | network policy в tc |
BPF_PROG_TYPE_XDP | очень рано в RX-pipeline | DDoS, load balance |
BPF_PROG_TYPE_CGROUP_SOCK | cgroup-attached socket | network namespace policy |
BPF_PROG_TYPE_LSM | LSM hook | security policy |
Полный список: bpftool feature или https://docs.kernel.org/bpf/
BPF maps - shared state
Программы stateless внутри одного вызова. Между вызовами и с userspace общаются через maps - типизированные KV-структуры в kernel:
| Map type | Описание |
|---|---|
BPF_MAP_TYPE_HASH | хеш-таблица с произвольным ключом |
BPF_MAP_TYPE_ARRAY | массив с index-key |
BPF_MAP_TYPE_PERCPU_HASH | per-CPU без блокировок |
BPF_MAP_TYPE_LRU_HASH | LRU eviction при заполнении |
BPF_MAP_TYPE_RINGBUF | ring buffer для отправки event'ов в userspace |
BPF_MAP_TYPE_PERF_EVENT_ARRAY | устаревший вариант RINGBUF |
BPF_MAP_TYPE_LPM_TRIE | longest-prefix-match (для CIDR routing) |
BPF_MAP_TYPE_PROG_ARRAY | tail call - один прог вызывает другой |
BPF_MAP_TYPE_SOCKMAP | mapping socket → BPF program |
Userspace читает/пишет через bpf(BPF_MAP_LOOKUP_ELEM) syscall.
Maps persist через pinning в /sys/fs/bpf/:
bpftool map pin name my_map /sys/fs/bpf/my_map
Несколько программ могут шарить один map.
Verifier - почему eBPF safe
При загрузке через bpf(BPF_PROG_LOAD) ядро запускает verifier.
Он симулирует выполнение программы и проверяет:
- Программа всегда завершится (нет беспонтонных циклов)
- Все memory access валидны (только in-bounds reads/writes)
- Регистры инициализированы перед использованием
- Вызовы helper-функций разрешены для этого program type
- Указатели на map data не утекают наружу
Если что-то не так - программа отвергнута. Это и есть гарантия safety: eBPF не уронит kernel.
Ограничения classic verifier'а:
- Стек 512 байт на программу
- До 1M инструкций (Linux 5.2+; раньше 4096)
- Циклы запрещены до 5.3 (с 5.3 - bounded loops с
#pragma unrollилиforс константной верхней границей) - Tail call depth ≤ 33
Если verifier ошибся (false-positive) - переписываешь код, иногда с
странными хаками типа __builtin_constant_p или #pragma unroll.
Verifier не идеален - были CVE с escape: CVE-2021-3490 (alu32 sign extension), CVE-2022-23222 (pointer arithmetic). Patch быстрые, но reminder что eBPF не magically secure.
JIT - native speed
После verify программа JIT-компилируется в native код целевой архитектуры. На x86-64/arm64 - честный machine code, исполняется без интерпретации. Включается через:
sysctl -w net.core.bpf_jit_enable=1 # default since 4.15
Производительность близка к kernel-модулю. Cilium replaces iptables-based kube-proxy на eBPF и получает 5-10x improvement на больших rule sets.
Userspace стек
bpftool - swiss-army-knife
Из linux-tools-pkg или собрать из kernel-source:
bpftool prog list # все loaded программы
bpftool prog show id 42 # детали по ID
bpftool map list # все maps
bpftool map dump name my_map # содержимое
bpftool feature # какие program types/helpers есть
bpftool gen skeleton prog.bpf.o # сгенерить .h skeleton для libbpf
libbpf (C, рекомендованный путь)
Современный libc-style API. Программа = два файла:
// hello.bpf.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("tracepoint/syscalls/sys_enter_execve")int trace_execve(void *ctx) { bpf_printk("execve called\n");return 0;
}
char LICENSE[] SEC("license") = "GPL";Сборка: clang -O2 -target bpf -c hello.bpf.c. Загрузка через libbpf
- skeleton-генерация через
bpftool gen skeleton.
BCC (Python, legacy но удобно)
Old-school: пишешь Python, внутри строкой - C-код, BCC компилирует при запуске:
from bcc import BPF
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("clone\\n"); return 0; }').trace_print()Минус: тащит за собой LLVM в runtime, медленный старт. Современная альтернатива - libbpf + CO-RE (bpf-co-re).
bpftrace (DTrace-like one-liners)
bpftrace -e 'kprobe:do_sys_open { printf("%s opened %s\n", comm, str(arg1)); }'Идеально для интерактивного debug на проде.
Helper-функции
Внутри eBPF доступен ограниченный набор kernel-функций - "helpers":
bpf_get_current_pid_tgid()- какой процессbpf_get_current_comm(&buf, sz)- имя процессаbpf_ktime_get_ns()- времяbpf_map_lookup_elem(&map, &key)- чтение из mapbpf_perf_event_output()/bpf_ringbuf_output()- отдать event userspacebpf_get_stackid()- stack tracebpf_redirect(),bpf_clone_redirect()- сетевая обработка
Полный список: bpftool feature или man bpf-helpers.
Когда что-то пошло не так
Permission deniedпри загрузке - eBPF загрузка требуетCAP_BPF(Linux 5.8+) илиCAP_SYS_ADMIN. Запускай root или с capability.R0 invalid mem access- verifier обнаружил unbounded access. Проверьif (ptr + len > end) return 0;перед чтением.back-edge from insn X to Y- цикл, до 5.3 не разрешён. Развёрни через#pragma unroll.program is too large- inline функции делают bytecode большим. Раздели через tail call (BPF_MAP_TYPE_PROG_ARRAY).- bpftool без output, программа есть - проверь permissions
/sys/fs/bpf/, debugfs смонтирован (/sys/kernel/debug/). bpf_printkничего не выводит - читать из/sys/kernel/debug/tracing/trace_pipe(нужно root).- CO-RE не связывается - см. bpf-co-re: vmlinux.h собран не под целевое ядро.
Где почитать
- https://docs.kernel.org/bpf/ - официальная документация
- https://ebpf.io/ - tutorial и каталог проектов
- "BPF Performance Tools" Brendan Gregg - bcc/bpftrace examples
- https://github.com/iovisor/bcc - сотни готовых tools
- https://github.com/libbpf/libbpf-bootstrap - стартовый шаблон libbpf+CO-RE