Se costruisci sistemi o gestisci cluster e continui a scontrarti con i limiti degli agent, di iptables o dei moduli kernel, eBPF è la base più sicura, veloce e dinamica che stai cercando. Ti consente di eseguire piccoli programmi verificati all'interno del kernel Linux per osservare e influenzare il comportamento del sistema a runtime. In pratica, questo sblocca una nuova generazione di strumenti per observability, sicurezza e networking senza il rischio operativo dei moduli kernel personalizzati.
Cos'è eBPF — e perché è rilevante ora
eBPF nasce come evoluzione del classico Berkeley Packet Filter, un piccolo motore bytecode in-kernel che strumenti come tcpdump usavano per il filtraggio rapido dei pacchetti. Il moderno eBPF generalizza quella idea: scrivi una piccola funzione in C ristretto o Rust, la compili in bytecode e chiedi al kernel di caricarla. Prima che il programma possa essere eseguito, il verifier lo esegue simbolicamente per dimostrarne le proprietà di sicurezza. Il codice non deve mai dereferenziare puntatori non validi, deve delimitare i propri cicli, deve restituire il tipo di valore corretto per l'hook a cui si rivolge, e deve sempre terminare. Se supera la verifica, il kernel può compilare JIT il bytecode in istruzioni native, il che è in gran parte il motivo per cui i programmi eBPF sono così veloci.
eBPF non sostituisce i moduli kernel — offre un percorso controllato per estendere il comportamento del kernel senza scrivere o distribuire un modulo. Carichi un programma a runtime, lo colleghi a un evento — forse un hook di ricezione di rete, una chiamata a funzione del kernel, o un punto di decisione di sicurezza — e in seguito lo distacchi quando hai finito. Puoi anche pinnare programmi e strutture dati in un filesystem virtuale dedicato (bpffs in /sys/fs/bpf) in modo che sopravvivano oltre la vita del processo di caricamento. Rispetto ai moduli, c'è molto meno attrito e molto meno rischio operativo. Combinalo con i vantaggi prestazionali dell'esecuzione in-kernel ed evitando i context switch, e ottieni una tecnologia che si adatta alle esigenze cloud-native di oggi: telemetria ad alto volume, decisioni di policy a bassa latenza, e la necessità di muoversi rapidamente senza compromettere la sicurezza.
Il Tuo Primo Assaggio di eBPF
Il ciclo di base per lavorare con eBPF è semplice: carica, collega, osserva, distacca. Il modo più rapido per sentire questo ciclo è con una piccola demo collegata a qualcosa che puoi vedere immediatamente, come l'esecuzione di processi.
Ecco un esempio bpftrace senza configurazione che conta le chiamate execve() per nome di processo:
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { @[comm] = count(); }'Se vuoi uno sguardo leggermente più approfondito usando BCC (BPF Compiler Collection), questo snippet Python collega un piccolo programma in-kernel a un kprobe e mantiene un contatore per PID in una map:
from bcc import BPF
prog = r"""
BPF_HASH(exec_count, u32, u64);
int on_execve(void *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 *val = exec_count.lookup(&pid);
u64 one = 1;
if (val) { (*val)++; } else { exec_count.update(&pid, &one); }
return 0;
}
"""
b = BPF(text=prog)
b.attach_kprobe(event="do_execveat_common", fn_name="on_execve")
print("Counting execve() per PID... Ctrl-C to stop.")
try:
b.trace_print()
except KeyboardInterrupt:
pass
for k, v in b.get_table("exec_count").items():
print(f"PID {k.value}: {v.value}")Questi esempi introducono le BPF map, gli store chiave/valore in-kernel che mantengono lo stato condiviso tra gli eventi. Le map sono ciò che rende eBPF pratico: archiviano contatori e cache, trasportano configurazione e policy, e forniscono ring buffer che trasmettono eventi allo spazio utente.
Dal Codice Sorgente all'Esecuzione nel Kernel
Un programma eBPF assomiglia a una piccola funzione specializzata. Scegli un hook e scrivi una funzione con una firma che corrisponde al suo contesto — xdp_md per XDP, il record per un tracepoint o kprobe/fentry, o il socket buffer per gli hook di networking. Compili con clang -target bpf, producendo un oggetto che contiene istruzioni, definizioni di map, informazioni di debug opzionali e spesso metadati BTF (BPF Type Format). È comune ispezionare quell'oggetto con bpftool o llvm-objdump per verificare cosa hai costruito e capire approssimativamente su cosa il verifier ragionerà.
Il caricamento del programma attiva la verifica. Se i controlli passano, il kernel può conservare il bytecode originale, una rappresentazione tradotta usata per l'analisi del verifier, e un'immagine nativa compilata JIT ottimizzata per il tuo CPU. Poi colleghi il programma a un evento: ad XDP su un'interfaccia di rete per agire prima dello stack; a TC dentro lo stack per classificazione e shaping; a fentry/kprobe per il tracing dell'ingresso delle funzioni; ai tracepoint per siti di eventi stabili; o agli hook LSM per le decisioni di sicurezza. Per la durata del collegamento, la pratica moderna preferisce i BPF link, che mantengono i programmi collegati anche se il loader esce; in seguito distacchi scollegando l'handle. Se hai bisogno che programmi e map sopravvivano al tuo processo, pinnali in bpffs sotto percorsi prevedibili. E quando scrivi codice XDP, ricordati sempre dei controlli sui limiti essenziali: valida gli header usando i puntatori xdp_md->data e xdp_md->data_end prima di toccare qualsiasi cosa nel pacchetto.
Map, Streaming di Eventi e Discoverable
Le map vengono in molti tipi: hash, array, varianti per-CPU, cache LRU, LPM trie, code e stack, e la dedicata ring buffer map. Gli esempi più vecchi spesso si affidano ai perf buffer tramite perf_event_open() per trasmettere eventi allo spazio utente. Il codice più recente tende a usare il ring buffer, che semplifica il coordinamento usando un singolo file descriptor e un semplice modello producer/consumer. bpftool è indispensabile per elencare i programmi caricati, ispezionare tag immutabili, fare il dump delle istruzioni tradotte e JITate, creare ed esaminare map, e leggere i dati BTF che descrivono tipi e prototipi di funzioni. Pinnare programmi e map in bpffs a /sys/fs/bpf li rende facili da trovare tra processi e riavvii.
Tipi di Programmi e Dove Si Agganciano
Ogni programma eBPF ha un tipo, e quel tipo determina il contesto che ricevi, gli helper che puoi chiamare e i codici di ritorno che sono validi. Nel tracing, hai kprobe e kretprobe che seguono le funzioni del kernel, i tracepoint che il kernel espone come eventi stabili, e gli hook fentry/fexit abilitati da BTF che si collegano all'ingresso e all'uscita della funzione con overhead inferiore. Puoi strumentare lo spazio utente con uprobes e uretprobes, e puoi prendere decisioni granulari nel percorso di sicurezza collegando programmi BPF LSM agli hook di sicurezza del kernel. Sul lato del networking, XDP si trova nel percorso di ricezione del driver dove puoi fare il parsing degli header e decidere di passare, scartare, redirigere o trasmettere pacchetti, mentre TC ti consente di classificare e plasmare il traffico all'interno dello stack. Gli hook di socket e cgroup portano la policy vicino ai processi, e il flow dissector e altri sottosistemi forniscono entry point più specializzati. Il principale vantaggio per il networking è la latenza prevedibile: sostituire le lunghe catene iptables con datapath compilati che implementano il load-balancing dei servizi e l'applicazione delle policy di rete.
CO-RE, BTF e libbpf: Portabilità Senza Build per Host
Compilare sull'host di destinazione è ottimo per l'esplorazione, ma è problematico per la produzione. CO-RE — Compile Once, Run Everywhere — risolve questo usando le informazioni sui tipi BTF per adattare un oggetto precompilato a kernel diversi al momento del caricamento. La maggior parte delle distribuzioni moderne pubblica un file BTF canonico a /sys/kernel/btf/vmlinux. Il tuo oggetto eBPF contiene record di relocation dove fa riferimento a tipi o campi del kernel. Quando lo carichi, libbpf risolve quelle relocation rispetto al BTF dell'host in modo che le differenze nel layout delle strutture non rompano il tuo programma. Un flusso di lavoro comune è generare un header vmlinux.h da BTF e poi compilare con -O2 -g -target bpf in modo che il loader abbia tutte le informazioni di cui ha bisogno. Ad esempio:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
clang -O2 -g -target bpf -c prog.c -o prog.bpf.oLibbpf genera anche "skeleton", piccoli header C che racchiudono le tue map, programmi e attachment, rendendo il loader dello spazio utente quasi dichiarativo: open, load, attach, read. Il risultato pratico è potente: puoi distribuire un singolo artefatto che fornisce observability affidabile su kernel Linux eterogenei con un ingombro operativo minimale.
Il Verifier, Spiegato Semplicemente
Pensa al verifier come a un revisore della sicurezza che rende fattibile il codice in-kernel personalizzato in produzione. Esegue simbolicamente il tuo programma, traccia la provenienza dei puntatori e dimostra che tutti gli accessi alla memoria sono entro i limiti. Si assicura che controlli i puntatori prima di dereferenziarli, che i tuoi cicli siano delimitati o srotolati, che chiami le funzioni helper con argomenti dei tipi corretti, e che tu ritorni il valore corretto per l'hook a cui sei collegato — XDP_PASS, XDP_DROP, XDP_REDIRECT, o XDP_ABORTED nel caso di XDP. Controlla anche la stringa license nel tuo oggetto perché alcuni helper sono riservati ai programmi con licenza GPL. Quando la verifica fallisce, richiedi un log verboso; si legge come una conversazione con un revisore molto esigente e insegna rapidamente gli idiomi che il kernel accetta.
Risultati Concreti per Observability, Sicurezza e Networking
Per observability, eBPF può osservare system call, aperture di file, modifiche ai credenziali e invocazioni di funzioni del kernel; arricchire gli eventi con la lineage dei processi, l'identità di cgroup e container, e i namespace; e trasmettere questi dati allo spazio utente in tempo quasi reale. Ottieni insight ricchi senza patchare le applicazioni o alterare la loro configurazione. Per sicurezza, BPF LSM mette le decisioni allow/deny direttamente negli hook di sicurezza del kernel con consapevolezza dell'identità del container e del workload, non solo di uid/gid. Strumenti come Tetragon fondono il deep tracing con la policy in modo che i comportamenti sospetti vengano rilevati e fermati in anticipo. Per networking, XDP gestisce le decisioni early fast-path — scarto di pacchetti, redirect, forwarding di base — mentre TC applica classificazione e shaping nello stack. Insieme, abilitano CNI basati su eBPF che implementano load-balancing dei servizi, policy di rete, e coordinano persino la crittografia node-to-node con latenza inferiore e più prevedibile rispetto alle lunghe catene iptables.
Quando Non Usare eBPF
eBPF è potente, ma non è la scelta giusta per ogni problema. Se un hook in spazio utente o una chiamata a libreria soddisfa la necessità, mantieni le cose semplici. Se il tasso di eventi è basso e la latenza non è un problema, la complessità di un programma in-kernel potrebbe non valere la pena. Se hai bisogno di un lavoro prolungato o bloccante che non si adatta ai vincoli di eBPF (al di fuori di specifici tipi di programmi sleepable), sposta quella logica nello spazio utente. E se sei su kernel legacy senza header o BTF, potresti dover fare un upgrade, installare il pacchetto BTF, o distribuire dati BTF minimali prima di poter beneficiare di CO-RE.
Insidie Comuni e Come Debuggare
La maggior parte dei problemi rientra nelle stesse categorie. I rifiuti del verifier di solito significano che hai bisogno di controlli sui limiti più espliciti, meno percorsi di controllo ambigui, o cicli più piccoli/limitati; evita l'aritmetica puntatore su dati non fidati senza controlli. Se al tuo sistema manca BTF, installa il pacchetto BTF del kernel o distribuisci un BTF minimale e rigenera vmlinux.h. Raggiungere i limiti delle risorse delle map spesso indica problemi con RLIMIT_MEMLOCK o map troppo grandi; dimensionali correttamente e considera varianti LRU per le cache. Per la durata del collegamento, preferisci i BPF link in modo che i programmi rimangano collegati anche se il tuo loader esce. E se le prestazioni sembrano sospettosamente basse, conferma che il JIT sia abilitato con bpftool feature e attivalo prima del benchmarking.
La Syscall bpf() e il Plumbing Circostante
Sebbene le librerie nascondano i dettagli, tutto fluisce attraverso la syscall bpf(). La usi — tipicamente tramite libbpf o un altro wrapper — per creare map, caricare programmi e stabilire attachment. Il vecchio codice di tracing può anche chiamare perf_event_open() per collegare eventi perf al programma; gli approcci moderni si affidano ai BPF link per la durata del collegamento e ai ring buffer per la trasmissione dei dati. Leggere i risultati è semplice: consuma gli eventi dal buffer o esegui una map lookup. E poiché programmi e map possono essere pinnati in bpffs a /sys/fs/bpf, sono facili da trovare e riutilizzare tra processi e riavvii. Man mano che i sistemi crescono, le chiamate BPF-to-BPF aiutano a effettuare il refactoring della logica tra funzioni, e i globali in .rodata o .bss ti consentono di modificare il comportamento al momento del caricamento senza ricompilare.
Riepilogo
eBPF è un meccanismo sicuro e ad alte prestazioni per eseguire logica personalizzata all'interno del kernel Linux. Compili un piccolo programma; il verifier ne dimostra la sicurezza; il kernel lo compila JIT; e lo colleghi agli eventi per modificare il comportamento del sistema a runtime. Le map trasportano lo stato condiviso, i ring buffer trasmettono gli eventi, BTF e CO-RE forniscono portabilità, e libbpf più gli skeleton rendono i loader piccoli e robusti. Con hook su tracing, networking e sicurezza — XDP, TC, kprobe/fentry e LSM — puoi strumentare le applicazioni senza toccare il loro codice o configurazione e applicare policy precise con contesto ricco. Dai data plane Kubernetes al tracciamento delle syscall e alla sicurezza preventiva, eBPF è passato da trucco di nicchia a piattaforma mainstream. Il prossimo passo è pratico: scegli una piccola demo che ti incuriosisce, eseguila e itera — carica, collega, osserva, distacca — finché i pattern non diventano istintivi.
lucavallin

