00 eBPF nedir
eBPF (extended Berkeley Packet Filter), Linux kernel'ını yeniden derlemek veya modül yazmak zorunda kalmadan kernel içinde güvenli program çalıştırmayı sağlayan devrimsel bir altyapıdır.
Temel fikir
Geleneksel yaklaşımda kernel davranışını değiştirmek için ya kernel modülü yazılır (güvenlik riski, karmaşıklık) ya da kernel kaynak kodu değiştirilerek yeniden derlenir (uzun süre, dağıtım bağımlılığı). eBPF bunların hiçbirini gerektirmez: kullanıcı alanında yazılan küçük programlar kernel'a yüklenir, doğrulanır (verifier) ve JIT derleme ile native makine koduna çevrilir. Kernel belirli noktalarda (hook) bu programları çalıştırır.
eBPF C kodu → LLVM/Clang → BPF bytecode → kernel verifier → JIT compiler → native kod → hook noktasında çalışır
BPF sanal makinesi
BPF sanal makinesi 64-bit RISC mimarisiyle modellenmiştir. 11 adet 64-bit genel amaçlı kayıt (r0–r10), 512 byte'lık stack ve sınırlı sayıda instruction set içerir. Bu kısıtlamalar kasıtlıdır: verifier programın sonlanacağını (döngü yokluğu), bellek sınırlarını aşmadığını ve güvenli helper'lar dışında kernel fonksiyon çağırmadığını ispat etmek için bu kısıtları kullanır.
| Bileşen | Görev |
|---|---|
| BPF Verifier | Programın güvenli olduğunu statik analiz ile kanıtlar — sonsuz döngü yok, OOB erişim yok |
| JIT Compiler | BPF bytecode → x86-64 / ARM64 / RISC-V native kod; %0 interpreter overhead |
| BPF Maps | Kernel-userspace arası paylaşılan veri yapıları — hash, array, ring buffer |
| BPF Helpers | Kernel'ın güvenli API'si — bpf_ktime_get_ns(), bpf_map_lookup_elem() vb. |
| BTF (BPF Type Format) | Kernel veri yapısı tip bilgisi — CO-RE (Compile Once Run Everywhere) için temel |
Güvenlik garantileri
Kullanım senaryoları
| Alan | Örnek | Araç |
|---|---|---|
| Tracing / Profiling | Hangi fonksiyon ne kadar sürüyor, CPU hotspot | bpftrace, BCC, perf |
| Ağ | DDoS mitigation, load balancing, paket filtreleme | XDP, TC, Cilium |
| Güvenlik | Syscall filtreleme, LSM politikası, sandbox | Seccomp-BPF, BPF-LSM |
| Gözlemlenebilirlik | Service mesh telemetry, container ağ izleme | Cilium, Pixie, Falco |
| Performans | Disk I/O latency, scheduler analizi | BCC biolatency, runqlat |
eBPF, kernel 3.18'de temel özelliklerle tanıtıldı. Modern eBPF özellikleri (BPF ringbuf, CO-RE, BPF LSM, BPF iterator) kernel 5.8+ gerektirir. Üretim sistemleri için kernel 5.15 LTS veya üzeri önerilir. Kernel versiyonunu doğrulamak için: uname -r.
Bu bölümde
- eBPF = kernel modülü gücü + verifier güvenliği — yeniden derleme gerekmez
- BPF VM → verifier (güvenlik) → JIT (performans) → hook noktasında çalışır
- Tracing, ağ, güvenlik, gözlemlenebilirlik — tek altyapı, çok kullanım
- Kernel 5.15+ LTS önerilir; modern CO-RE için BTF desteği gerekli
01 eBPF program türleri
Her eBPF program türü farklı bir kernel hook noktasına bağlanır — doğru türü seçmek hem çalışma garantisi hem de erişilebilecek veri için belirleyicidir.
Program türleri tablosu
| Tür | Hook noktası | Tetiklenme | Kullanım |
|---|---|---|---|
kprobe | Kernel fonksiyon girişi | Herhangi kernel fonksiyon çağrıldığında | Kernel davranışı izleme, argüman okuma |
kretprobe | Kernel fonksiyon dönüşü | Fonksiyon return ettiğinde | Return değeri okuma, latency ölçüm |
tracepoint | Statik kernel tracepoint | Kernel önceden tanımlanmış nokta | Stabil API, kernel güncellemelerinde kırılmaz |
uprobe | Userspace fonksiyon girişi | Belirtilen process fonksiyon çağırır | Uygulama tracing, Python/Go runtime izleme |
uretprobe | Userspace fonksiyon dönüşü | Userspace fonksiyon return | Uygulama latency ölçümü |
XDP | NIC sürücüsü (skb öncesi) | Her gelen paket | Yüksek hızlı paket filtreleme, DDoS |
TC (Traffic Control) | Network stack ingress/egress | Her paket | Paket manipülasyon, QoS, NAT |
socket filter | Socket seviyesi | Socket üzerinden geçen paket | Seccomp-BPF, özel paket filtreleme |
LSM (BPF-LSM) | Linux Security Module hook | Güvenlik kararı gerektiğinde | Politika zorlama, sandboxing (kernel 5.7+) |
perf_event | Donanım/yazılım sayacı | PMU event veya timer | CPU profiling, hardware counter okuma |
cgroup | Cgroup hook noktaları | Process cgroup geçişleri | Container ağ politikası |
fentry/fexit | Kernel fonksiyon (BTF ile) | Fonksiyon giriş/çıkış | kprobe gibi ama daha hızlı, CO-RE uyumlu |
kprobe vs tracepoint — hangisini seç
Mevcut tracepoint'leri listeleme
# Tüm tracepoint kategorileri
ls /sys/kernel/debug/tracing/events/
# block ext4 irq kmem net sched signal skb sock syscalls task ...
# Syscall tracepoint'leri
ls /sys/kernel/debug/tracing/events/syscalls/ | grep open
# sys_enter_open sys_exit_open sys_enter_openat sys_exit_openat
# Tracepoint format bilgisi
cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/format
# name: sys_enter_openat
# ID: 612
# format:
# field:int __syscall_nr; offset:8; size:4;
# field:int dfd; offset:16; size:8;
# field:const char * filename; offset:24; size:8;
# field:int flags; offset:32; size:8;
# bpftrace ile tracepoint listesi
bpftrace -l 'tracepoint:syscalls:*open*'
Bu bölümde
- kprobe: herhangi kernel fonksiyon — güçlü ama kararsız ABI
- tracepoint: stabil API, kernel versiyonları arası taşınabilir — tercih edilmeli
- XDP: NIC sürücüsünde paket işleme — en yüksek performanslı ağ hook
- fentry/fexit: BTF tabanlı modern kprobe alternatifi, CO-RE uyumlu
02 bpftrace — tek satır tracing
bpftrace, AWK benzeri bir dil ile kernel ve uygulama davranışını anlık olarak incelemenizi sağlayan yüksek seviyeli eBPF ön yüzüdür.
Kurulum
# Ubuntu 22.04+
sudo apt-get install -y bpftrace
# Versiyon kontrolü
bpftrace --version
# bpftrace v0.20.4
# Kernel BTF desteğini doğrula
ls /sys/kernel/btf/vmlinux
# /sys/kernel/btf/vmlinux
# Kernel debug FS monte edilmeli
sudo mount -t debugfs none /sys/kernel/debug
Temel sözdizimi
# Genel form: probe /filtre/ { aksiyon }
# probe: kprobe:fonksiyon, tracepoint:kategori:olay, interval:s:N
# Açılan dosyaları yazdır
sudo bpftrace -e 'kprobe:do_sys_openat2 { printf("%s\n", str(arg1)); }'
# Tracepoint ile (daha stabil)
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat {
printf("pid=%d comm=%s file=%s\n",
pid, comm, str(args->filename));
}
'
# Belirli prosesi izle
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat
/comm == "nginx"/ {
printf("%s\n", str(args->filename));
}
'
Yararlı yerleşik değişkenler
| Değişken | Tip | Açıklama |
|---|---|---|
pid | int | Process ID |
tid | int | Thread ID |
comm | string | Process adı (ilk 16 karakter) |
uid | int | User ID |
cpu | int | CPU numarası |
nsecs | uint64 | Boot'tan bu yana nanosaniye |
elapsed | uint64 | Program başlangıcından bu yana ns |
args | struct | Tracepoint argümanları (BTF ile tipli) |
retval | int64 | Fonksiyon return değeri (kretprobe) |
curtask | task_struct* | Mevcut task_struct pointer |
Probe sözdizimi örnekleri
# kprobe: kernel fonksiyon girişi
sudo bpftrace -e 'kprobe:tcp_connect { printf("TCP connect: pid=%d\n", pid); }'
# kretprobe: return değeri oku
sudo bpftrace -e 'kretprobe:sys_read { printf("read() = %d\n", retval); }'
# tracepoint: stabil API
sudo bpftrace -e 'tracepoint:sched:sched_process_exec { printf("%s exec\n", str(args->filename)); }'
# uprobe: kullanıcı alanı fonksiyon
sudo bpftrace -e 'uprobe:/usr/lib/x86_64-linux-gnu/libssl.so.3:SSL_write { printf("SSL write: pid=%d\n", pid); }'
# interval: periyodik yazdırma
sudo bpftrace -e 'interval:s:1 { printf("--- 1 saniye ---\n"); }'
# BEGIN/END: başlangıç ve bitiş hook'ları
sudo bpftrace -e '
BEGIN { printf("Tracing başladı\n"); }
END { printf("Tracing bitti\n"); }
kprobe:do_nanosleep { printf("sleep: pid=%d\n", pid); }
'
# Probe listesi — eşleşen probe'ları listele
sudo bpftrace -l 'kprobe:tcp_*'
sudo bpftrace -l 'tracepoint:net:*'
Map (toplama) kullanımı
# Syscall sayısı — process başına
sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter {
@syscalls[comm] = count();
}
interval:s:5 { print(@syscalls); clear(@syscalls); }
'
# read() latency histogramı
sudo bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read
/@start[tid]/ {
@lat_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
interval:s:5 { print(@lat_us); }
'
# Stack trace ile CPU hotspot
sudo bpftrace -e '
profile:hz:99 {
@[kstack] = count();
}
interval:s:10 { print(@); exit(); }
'
Bu bölümde
bpftrace -e 'kprobe:func { printf(...); }': tek satır kernel tracing- Yerleşik değişkenler: pid, comm, nsecs, args, retval
@map[key] = count(),hist(): bpftrace içi aggregation mapbpftrace -l 'kprobe:tcp_*': eşleşen probe noktalarını listele
03 bpftrace araçları
BCC ve bpftrace ile gelen hazır araçlar — sistem davranışını anında görüntüleme için kullanıma hazır one-liner'lar ve araçlar.
opensnoop — dosya açma izleme
# opensnoop.bt — tüm dosya açma işlemlerini izle
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat {
printf("%-6d %-16s %s\n", pid, comm, str(args->filename));
}
'
# Örnek çıktı:
# PID COMM FILE
# 1234 nginx /etc/nginx/nginx.conf
# 1235 python3 /usr/lib/python3.11/re.py
# 1236 bash /proc/1236/stat
# BCC sürümü (daha zengin çıktı)
sudo /usr/share/bcc/tools/opensnoop
execsnoop — process çalıştırma izleme
# Her exec() çağrısını yakala
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_execve {
printf("%-6d %-16s %s\n", pid, comm, str(args->filename));
}
'
# Örnek çıktı:
# PID COMM EXE
# 4521 bash /usr/bin/ls
# 4522 ls /usr/bin/ls
# 4523 make /usr/bin/gcc
tcpconnect — TCP bağlantı izleme
# Tüm yeni TCP bağlantıları
sudo bpftrace -e '
kprobe:tcp_connect {
$sk = (struct sock *)arg0;
printf("%-6d %-16s → %s:%d\n",
pid, comm,
ntop(AF_INET, $sk->__sk_common.skc_daddr),
$sk->__sk_common.skc_dport);
}
'
# Örnek çıktı:
# PID COMM DST PORT
# 8901 curl 93.184.216.34 80
# 8902 apt 91.189.91.38 80
biolatency — disk I/O latency histogramı
# Disk I/O gecikme dağılımı (10 saniye)
sudo bpftrace -e '
tracepoint:block:block_rq_issue { @start[args->sector] = nsecs; }
tracepoint:block:block_rq_complete
/@start[args->sector]/ {
@latency_us = hist((nsecs - @start[args->sector]) / 1000);
delete(@start[args->sector]);
}
interval:s:10 { print(@latency_us); exit(); }
'
# Örnek çıktı:
# @latency_us:
# [8, 16) 245 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# [16, 32) 512 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# [32, 64) 97 |@@@@@@@@@@
# [64, 128) 12 |@
runqlat — scheduler gecikme ölçümü
# Process'in CPU'ya ne kadar bekleyeceğini ölç
sudo bpftrace -e '
tracepoint:sched:sched_wakeup,
tracepoint:sched:sched_wakeup_new {
@qtime[args->pid] = nsecs;
}
tracepoint:sched:sched_switch {
if (args->prev_state == TASK_RUNNING) {
@qtime[args->prev_pid] = nsecs;
}
$ns = @qtime[args->next_pid];
if ($ns) {
@runqlat_us = hist((nsecs - $ns) / 1000);
delete(@qtime[args->next_pid]);
}
}
interval:s:5 { print(@runqlat_us); exit(); }
'
Bu bölümde
- opensnoop: tüm dosya açma işlemlerini gerçek zamanlı göster
- execsnoop: her yeni process başlatmayı yakala
- tcpconnect: yeni TCP bağlantılarını izle — hedef IP + port
- biolatency: disk I/O gecikme histogramı; runqlat: scheduler gecikme dağılımı
04 BCC — BPF Compiler Collection
BCC, Python ön yüzüyle C BPF programı yazmayı kolaylaştıran bir çerçevedir — kprobe bağlama, histogram toplama ve kullanıcı alanına veri gönderme.
Kurulum
# Ubuntu 22.04
sudo apt-get install -y bpfcc-tools python3-bpfcc linux-headers-$(uname -r)
# BCC Python modülü
python3 -c "from bcc import BPF; print('BCC OK')"
# Hazır BCC araçları
ls /usr/share/bcc/tools/
# biolatency execsnoop opensnoop tcpconnect tcptop profile ...
İlk BCC programı — syscall sayacı
#!/usr/bin/env python3
from bcc import BPF
import time
# BPF C programı (string olarak)
bpf_program = """
#include <uapi/linux/ptrace.h>
BPF_HASH(syscall_count, u32, u64);
TRACEPOINT_PROBE(raw_syscalls, sys_enter) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 *count = syscall_count.lookup(&pid);
if (count) {
(*count)++;
} else {
u64 init = 1;
syscall_count.update(&pid, &init);
}
return 0;
}
"""
# BPF programını derle ve yükle
b = BPF(text=bpf_program)
print("Syscall sayılıyor... (Ctrl+C ile dur)")
time.sleep(5)
# Sonuçları oku ve yazdır
print(f"{'PID':<8} {'Syscall Sayısı'}")
for pid, count in sorted(b["syscall_count"].items(),
key=lambda x: x[1].value, reverse=True)[:10]:
print(f"{pid.value:<8} {count.value}")
kprobe bağlama ve perf buffer
#!/usr/bin/env python3
from bcc import BPF
import ctypes
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct event_t {
u32 pid;
char comm[TASK_COMM_LEN];
char fname[256];
};
BPF_PERF_OUTPUT(events);
int trace_openat(struct pt_regs *ctx, int dfd,
const char __user *filename, int flags) {
struct event_t ev = {};
ev.pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&ev.comm, sizeof(ev.comm));
bpf_probe_read_user_str(&ev.fname, sizeof(ev.fname), filename);
events.perf_submit(ctx, &ev, sizeof(ev));
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event="do_sys_openat2", fn_name="trace_openat")
class Event(ctypes.Structure):
_fields_ = [
("pid", ctypes.c_uint),
("comm", ctypes.c_char * 16),
("fname", ctypes.c_char * 256),
]
def print_event(cpu, data, size):
ev = ctypes.cast(data, ctypes.POINTER(Event)).contents
print(f"{ev.pid:<8} {ev.comm.decode():<16} {ev.fname.decode()}")
b["events"].open_perf_buffer(print_event)
print("Dosya açma izleniyor...")
while True:
b.perf_buffer_poll()
BPF_HISTOGRAM ile latency ölçümü
#!/usr/bin/env python3
from bcc import BPF
import time
bpf_text = """
#include <uapi/linux/ptrace.h>
BPF_HASH(start, u32, u64);
BPF_HISTOGRAM(dist, u64);
int kprobe__vfs_read(struct pt_regs *ctx) {
u32 tid = bpf_get_current_pid_tgid();
u64 ts = bpf_ktime_get_ns();
start.update(&tid, &ts);
return 0;
}
int kretprobe__vfs_read(struct pt_regs *ctx) {
u32 tid = bpf_get_current_pid_tgid();
u64 *tsp = start.lookup(&tid);
if (tsp) {
u64 delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
dist.increment(bpf_log2l(delta_us));
start.delete(&tid);
}
return 0;
}
"""
b = BPF(text=bpf_text)
print("vfs_read() latency ölçülüyor (10 sn)...")
time.sleep(10)
b["dist"].print_log2_hist("usecs")
Bu bölümde
- BCC = Python ön yüzü + C BPF kodu — hızlı geliştirme, dinamik derleme
BPF_HASH,BPF_PERF_OUTPUT,BPF_HISTOGRAM: built-in BCC map makrolarıb.attach_kprobe(): Python'dan kernel fonksiyona kprobe bağlamaperf_buffer_poll(): kernel → userspace olay akışı (düşük overhead)
05 libbpf ve CO-RE
libbpf ve CO-RE (Compile Once — Run Everywhere) ile kernel versiyonundan bağımsız taşınabilir eBPF programları yazma.
CO-RE neden gerekli
BCC her çalıştırmada kernel'a özgü header dosyaları ile BPF kodunu dinamik olarak derler — bu üretim sistemlerinde hem yavaş hem de risklidir (clang/llvm hedef sistemde kurulu olmalı). CO-RE ise geliştirme makinesinde bir kez derlenen BPF programını farklı kernel versiyonlarında çalıştırır. Bu, BTF (BPF Type Format) ile mümkün olur: kernel, veri yapısı tip bilgisini BTF formatında expose eder; libbpf bu bilgiyle binary'deki field offset'lerini çalışma anında ayarlar.
BPF C + BTF annotations → Clang (geliştirme makinesinde) → .o (BTF dahil) → hedef kernel libbpf CO-RE relocate → yükle
Kurulum
# libbpf geliştirme kütüphanesi
sudo apt-get install -y \
libbpf-dev \
clang \
llvm \
linux-headers-$(uname -r)
# bpftool — BPF program / map yönetimi + skeleton üretimi
sudo apt-get install -y linux-tools-$(uname -r)
# Versiyon
bpftool version
# bpftool v7.3.0
# Sistem BTF desteğini doğrula
bpftool btf dump file /sys/kernel/btf/vmlinux format raw | head -2
# [1] INT int size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
CO-RE BPF programı
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h" // bpftool ile üretilir: bpftool btf dump file vmlinux
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
struct event {
__u32 pid;
__u32 uid;
char comm[16];
char fname[256];
};
// Ring buffer map — düşük overhead, büyük event akışı için
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024); // 256 KB ring buffer
} events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_openat")
int trace_openat(struct trace_event_raw_sys_enter *ctx)
{
struct event *e;
const char *fname_ptr;
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
e->uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
// ctx->args[1] = filename pointer
fname_ptr = (const char *)BPF_CORE_READ(ctx, args[1]);
bpf_probe_read_user_str(e->fname, sizeof(e->fname), fname_ptr);
bpf_ringbuf_submit(e, 0);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Skeleton üretimi ve userspace yükleyici
# vmlinux.h üret (bir kez, kernel başına)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# BPF programını derle
clang -g -O2 -target bpf \
-D__TARGET_ARCH_x86 \
-I/usr/include/bpf \
-c openat_tracer.bpf.c \
-o openat_tracer.bpf.o
# Skeleton oluştur (C header)
bpftool gen skeleton openat_tracer.bpf.o > openat_tracer.skel.h
# Skeleton içeriği (otomatik üretilen fonksiyonlar):
# openat_tracer_bpf__open() → BPF obje aç
# openat_tracer_bpf__load() → kernel'a yükle
# openat_tracer_bpf__attach() → hook noktasına bağla
# openat_tracer_bpf__destroy() → temizle
#include <stdio.h>
#include <signal.h>
#include <bpf/libbpf.h>
#include "openat_tracer.skel.h"
static volatile bool running = true;
static int handle_event(void *ctx, void *data, size_t sz)
{
struct event *e = data;
printf("pid=%-6u uid=%-6u comm=%-16s file=%s\n",
e->pid, e->uid, e->comm, e->fname);
return 0;
}
int main(void)
{
struct openat_tracer_bpf *skel;
struct ring_buffer *rb;
int err;
/* Skeleton aç, yükle, bağla */
skel = openat_tracer_bpf__open_and_load();
if (!skel) { fprintf(stderr, "load failed\n"); return 1; }
err = openat_tracer_bpf__attach(skel);
if (err) { fprintf(stderr, "attach failed\n"); goto cleanup; }
/* Ring buffer consumer */
rb = ring_buffer__new(bpf_map__fd(skel->maps.events), handle_event, NULL, NULL);
if (!rb) { err = -1; goto cleanup; }
printf("Tracing openat()... Ctrl+C ile dur\n");
while (running)
ring_buffer__poll(rb, 100);
ring_buffer__free(rb);
cleanup:
openat_tracer_bpf__destroy(skel);
return err;
}
Bu bölümde
- CO-RE: geliştirme makinesinde derle, farklı kernel'larda çalıştır — BTF ile field offset yeniden konumlandırma
bpftool btf dump → vmlinux.h: tüm kernel tipleri tek headerbpftool gen skeleton: otomatik open/load/attach/destroy C API üretirBPF_MAP_TYPE_RINGBUF: yüksek throughput event akışı için modern alternatif
06 BPF Maps
BPF map'leri, kernel BPF programları ile kullanıcı alanı arasındaki paylaşılan veri yapılarıdır — sayaç, histogram, event akışı ve durum takibi için.
Map türleri
| Tür | Açıklama | Kullanım |
|---|---|---|
BPF_MAP_TYPE_HASH | Hash tablosu — key/value çifti | PID bazlı timestamp, sayaç |
BPF_MAP_TYPE_ARRAY | Sabit boyutlu dizi, indeks = key | CPU başına sayaç, konfigürasyon |
BPF_MAP_TYPE_PERCPU_HASH | CPU başına hash — kilit gerektirmez | Yüksek performanslı sayaç |
BPF_MAP_TYPE_PERCPU_ARRAY | CPU başına dizi | Per-CPU istatistik |
BPF_MAP_TYPE_LRU_HASH | LRU tahliyeli hash | Bağlantı takibi, sınırlı bellek |
BPF_MAP_TYPE_PERF_EVENT_ARRAY | Perf ring buffer | Event streaming (eski yöntem) |
BPF_MAP_TYPE_RINGBUF | Paylaşımlı ring buffer (kernel 5.8+) | Yüksek throughput event akışı |
BPF_MAP_TYPE_STACK_TRACE | Stack iz depolama | CPU profiling, flame graph |
BPF_MAP_TYPE_PROG_ARRAY | BPF program referans dizisi | tail call — program zincirleme |
Hash map kullanımı (kernel tarafı)
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
// pid → u64 sayaç hash map
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 4096);
__type(key, __u32); // PID
__type(value, __u64); // syscall sayısı
} pid_syscall_count SEC(".maps");
SEC("tracepoint/raw_syscalls/sys_enter")
int count_syscalls(struct trace_event_raw_sys_enter *ctx)
{
__u32 pid = bpf_get_current_pid_tgid() >> 32;
__u64 *count = bpf_map_lookup_elem(&pid_syscall_count, &pid);
if (count) {
__sync_fetch_and_add(count, 1);
} else {
__u64 init = 1;
bpf_map_update_elem(&pid_syscall_count, &pid, &init, BPF_ANY);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Map okuma (kullanıcı alanı)
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "syscall_count.skel.h"
/* ... skel yükleme ve attach sonrası ... */
int map_fd = bpf_map__fd(skel->maps.pid_syscall_count);
__u32 pid = 0, next_pid;
__u64 count;
/* Hash map'i iterate et */
while (bpf_map_get_next_key(map_fd, &pid, &next_pid) == 0) {
if (bpf_map_lookup_elem(map_fd, &next_pid, &count) == 0) {
printf("pid=%u count=%llu\n", next_pid, count);
}
pid = next_pid;
}
/* Belirli key için değer oku */
__u32 target_pid = 1234;
if (bpf_map_lookup_elem(map_fd, &target_pid, &count) == 0)
printf("pid %u: %llu syscall\n", target_pid, count);
/* Key sil */
bpf_map_delete_elem(map_fd, &target_pid);
Ring buffer — yüksek throughput event akışı
/* === Kernel tarafı === */
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24); /* 16 MB */
} rb SEC(".maps");
/* Event gönder */
struct my_event *e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (e) {
e->pid = bpf_get_current_pid_tgid() >> 32;
bpf_ringbuf_submit(e, 0); /* veya bpf_ringbuf_discard(e, 0) */
}
/* === Userspace tarafı === */
static int handle_event(void *ctx, void *data, size_t sz) {
struct my_event *e = data;
printf("event: pid=%u\n", e->pid);
return 0;
}
struct ring_buffer *rb_consumer = ring_buffer__new(
bpf_map__fd(skel->maps.rb),
handle_event, NULL, NULL
);
while (running)
ring_buffer__poll(rb_consumer, 100 /* timeout ms */);
Bu bölümde
- HASH: key/value genel amaçlı depolama; ARRAY: indeks bazlı sabit boyut
- PERCPU_HASH/ARRAY: atomik olmadan yüksek hızlı sayaç — per-CPU güncelleme
- RINGBUF (kernel 5.8+): yüksek throughput event akışı — PERF_EVENT_ARRAY'in modern halefi
bpf_map_get_next_key(): userspace'den map iterate etme — tüm kayıtları okuma
07 XDP — eXpress Data Path
XDP, ağ paketlerini kernel ağ yığınına girmeden önce NIC sürücüsü seviyesinde işleme alır — saniyede 10+ milyon paket işleyebilen en yüksek performanslı Linux ağ hook'u.
XDP aksiyon kararları
| Aksiyon | Değer | Sonuç |
|---|---|---|
XDP_ABORTED | 0 | Program hatası — paketi düşür, tracepoint tetikle |
XDP_DROP | 1 | Paketi düşür — DDoS mitigation için en hızlı yol |
XDP_PASS | 2 | Paketi kernel ağ yığınına ilet — normal işlem |
XDP_TX | 3 | Paketi aynı ağ kartından geri gönder |
XDP_REDIRECT | 4 | Paketi başka bir ağ arabirimine yönlendir |
XDP paket sayacı — temel örnek
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u64);
} pkt_count SEC(".maps");
SEC("xdp")
int xdp_counter(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
/* Ethernet başlığı sınır kontrolü */
struct ethhdr *eth = data;
if (((void *)eth + sizeof(*eth)) > data_end)
return XDP_ABORTED;
/* Paketi say */
__u32 key = 0;
__u64 *count = bpf_map_lookup_elem(&pkt_count, &key);
if (count)
(*count)++;
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";
XDP ile basit DDoS mitigation (kaynak IP engelle)
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
/* Engellenen IP'ler seti */
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1024);
__type(key, __u32); /* src IP (network byte order) */
__type(value, __u8); /* 1 = engelle */
} blocklist SEC(".maps");
SEC("xdp")
int xdp_block_ip(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if (((void *)eth + sizeof(*eth)) > data_end)
return XDP_ABORTED;
/* Yalnızca IPv4 paketlerine bak */
if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
return XDP_PASS;
struct iphdr *iph = (struct iphdr *)(eth + 1);
if (((void *)iph + sizeof(*iph)) > data_end)
return XDP_ABORTED;
__u32 src_ip = iph->saddr;
__u8 *blocked = bpf_map_lookup_elem(&blocklist, &src_ip);
if (blocked && *blocked == 1)
return XDP_DROP;
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";
XDP programını yükleme ve yönetme
# ip link ile XDP programı yükle
sudo ip link set eth0 xdp obj xdp_block.bpf.o sec xdp
# Yüklü XDP programını görüntüle
ip link show eth0
# 2: eth0: <BROADCAST,MULTICAST,UP> ...
# link/ether ... brd ...
# prog/xdp id 42 tag ab12cd34ef56 jited
# XDP programını kaldır
sudo ip link set eth0 xdp off
# bpftool ile XDP yükle (daha detaylı kontrol)
sudo bpftool prog load xdp_block.bpf.o /sys/fs/bpf/xdp_block
sudo bpftool net attach xdp id $(bpftool prog show name xdp_block_ip | awk '/^[0-9]/{print $1}') dev eth0
# IP engelle (map'e ekle)
sudo bpftool map update name blocklist \
key hex c0 a8 01 64 \ # 192.168.1.100 (network byte order)
value hex 01
# Map içeriğini görüntüle
sudo bpftool map dump name blocklist
XDP programı yanlış yazıldığında tüm ağ bağlantısı kesilebilir. Geliştirme aşamasında XDP_PASS varsayılan aksiyonu kullanın ve gerçek donanım yerine QEMU veya veth (virtual ethernet) çiftleri üzerinde test edin. Üretim sistemine yüklemeden önce ip link set eth0 xdpgeneric (generic/slower) modda test edin.
Bu bölümde
- XDP_DROP: NIC seviyesinde paket düşürme — DDoS için en hızlı yol
- Sınır kontrolü zorunlu: verifier her pointer erişiminde data_end kontrolü ister
ip link set eth0 xdp obj prog.o sec xdp: XDP yükleme komutu- LRU_HASH map: sınırlı bellekte büyük bağlantı/IP takibi
08 eBPF güvenlik
Seccomp-BPF, BPF-LSM ve capability gereksinimleri — eBPF'i güvenlik için kullanma ve eBPF'i güvenli çalıştırma.
Capability gereksinimleri
| Capability | İzin verilen işlem | Ne zaman gerekli |
|---|---|---|
CAP_BPF | Privileged BPF programları yükle, map oluştur | Kernel 5.8+ — bpftool, libbpf için |
CAP_PERFMON | Perf event monitörü, kprobe/tracepoint bağlama | Kernel 5.8+ — tracing programlar |
CAP_NET_ADMIN | XDP, TC BPF program yükleme | Ağ BPF programları |
CAP_SYS_ADMIN | Her şey dahil — eski yöntem | Kernel < 5.8 veya root benzeri erişim |
Seccomp-BPF — syscall sandboxing
Seccomp-BPF, process'in hangi syscall'ları çağırabileceğini BPF programıyla kısıtlar. Her syscall çağrısında BPF programı çalışır ve izin verir, reddeder veya sinyal gönderir.
#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
/* libseccomp ile BPF filtresi oluştur */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
/* İzin verilen syscall'lar — whitelist */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
/* Filtreyi uygula */
seccomp_load(ctx);
seccomp_release(ctx);
/* Artık yalnızca izin verilen syscall'lar çağrılabilir */
write(STDOUT_FILENO, "Sandbox aktif\n", 14);
/* Bu satır SIGKILL ile öldürür (execve izinde değil) */
/* execve("/bin/sh", NULL, NULL); */
return 0;
}
/* Derleme: gcc -o sandbox sandbox.c -lseccomp */
BPF-LSM — Linux Security Module hook
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
/* Kernel config: CONFIG_BPF_LSM=y ve security= ayarında "bpf" olmalı */
SEC("lsm/file_open")
int BPF_PROG(restrict_file_open, struct file *file)
{
struct inode *inode = BPF_CORE_READ(file, f_inode);
umode_t mode = BPF_CORE_READ(inode, i_mode);
/* Set-UID binary açılmasını logla */
if (mode & S_ISUID) {
bpf_printk("LSM: SUID file open pid=%d\n",
bpf_get_current_pid_tgid() >> 32);
}
return 0; /* 0 = izin ver; -EPERM = reddet */
}
char LICENSE[] SEC("license") = "GPL";
Unprivileged BPF kısıtlama
# Unprivileged BPF'i tamamen kapat
sudo sysctl -w kernel.unprivileged_bpf_disabled=1
# kernel.unprivileged_bpf_disabled = 1
# Kalıcı yapmak için: /etc/sysctl.d/99-bpf.conf
# JIT hardening (spectre mitigations)
sudo sysctl -w net.core.bpf_jit_harden=2
# 0 = hardening yok
# 1 = ayrıcalıksız kullanıcılar için hardening
# 2 = tüm kullanıcılar için hardening
# Yüklü BPF programlarını listele
sudo bpftool prog show
# 42: xdp name xdp_block_ip tag ab12cd34ef56 jited
# loaded_at 2026-01-15T14:30:00+0300 uid 0
# xlated 1024B jited 576B memlock 4096B map_ids 5
# Program bytecode + JIT disassembly
sudo bpftool prog dump jited name xdp_block_ip
Bu bölümde
- Kernel 5.8+: CAP_BPF + CAP_PERFMON — root olmadan güvenli BPF
- Seccomp-BPF: process'e syscall whitelist — container ve uygulama sandboxing
- BPF-LSM: Linux güvenlik kararlarına hook — kernel module gerektirmeden politika
kernel.unprivileged_bpf_disabled=1: üretim sistemlerinde BPF'i kısıtla
09 Pratik: HTTP latency tracker
tcp_sendmsg ve tcp_recvmsg arasındaki süreyi kprobe ile ölçme — bpftrace one-liner'dan libbpf CO-RE C programına evrim.
Strateji
tcp_sendmsg() çağrıldı → zaman damgası kaydet → tcp_recvmsg() döndü → delta hesapla → histograma ekle
Adım 1: bpftrace one-liner ile kavram kanıtı
# TCP send → recv arası süreyi ölç
sudo bpftrace -e '
kprobe:tcp_sendmsg {
@send_ts[tid] = nsecs;
}
kretprobe:tcp_recvmsg
/@send_ts[tid]/ {
$delta_us = (nsecs - @send_ts[tid]) / 1000;
@latency_us = hist($delta_us);
delete(@send_ts[tid]);
}
interval:s:5 {
print(@latency_us);
clear(@latency_us);
}
'
# Örnek çıktı (5 saniyede bir):
# @latency_us:
# [32, 64) 3 |@
# [64, 128) 47 |@@@@@@@@@@@@@@@@@@
# [128, 256) 112 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# [256, 512) 23 |@@@@@@@@@
# [512, 1K) 4 |@
Adım 2: BCC Python ile process filtreli versiyon
#!/usr/bin/env python3
from bcc import BPF
import argparse, time
parser = argparse.ArgumentParser()
parser.add_argument("--pid", type=int, default=0, help="Filter by PID")
args = parser.parse_args()
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
BPF_HASH(start_ts, u32, u64); // tid → send timestamp
BPF_HISTOGRAM(dist, u64); // latency histogram (log2 us)
int kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk,
struct msghdr *msg, size_t size) {
u32 tid = bpf_get_current_pid_tgid();
FILTER_PID
u64 ts = bpf_ktime_get_ns();
start_ts.update(&tid, &ts);
return 0;
}
int kretprobe__tcp_recvmsg(struct pt_regs *ctx) {
u32 tid = bpf_get_current_pid_tgid();
u64 *tsp = start_ts.lookup(&tid);
if (tsp == 0) return 0;
u64 delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
dist.increment(bpf_log2l(delta_us));
start_ts.delete(&tid);
return 0;
}
"""
pid_filter = ""
if args.pid:
pid_filter = f"""
u32 pid = bpf_get_current_pid_tgid() >> 32;
if (pid != {args.pid}) return 0;
"""
bpf_text = bpf_text.replace("FILTER_PID", pid_filter)
b = BPF(text=bpf_text)
print(f"HTTP latency izleniyor"
+ (f" (pid={args.pid})" if args.pid else "")
+ "... Ctrl+C")
try:
while True:
time.sleep(5)
print("\n--- Son 5 saniye ---")
b["dist"].print_log2_hist("usecs")
b["dist"].clear()
except KeyboardInterrupt:
pass
Adım 3: libbpf CO-RE C programı
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#define MAX_ENTRIES 8192
#define HIST_SLOTS 26 /* log2(usec) = 0..25 (1 ns .. ~33 s) */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
__type(key, __u32); /* tid */
__type(value, __u64); /* send timestamp (ns) */
} start SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, HIST_SLOTS);
__type(key, __u32);
__type(value, __u64);
} latency_hist SEC(".maps");
static inline __u32 log2(__u64 v)
{
__u32 r = 0;
if (v > 0xFFFFFFFF) { v >>= 32; r += 32; }
if (v > 0x0000FFFF) { v >>= 16; r += 16; }
if (v > 0x000000FF) { v >>= 8; r += 8; }
if (v > 0x0000000F) { v >>= 4; r += 4; }
if (v > 0x00000003) { v >>= 2; r += 2; }
if (v > 0x00000001) { r += 1; }
return r;
}
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(tcp_sendmsg_entry)
{
__u32 tid = bpf_get_current_pid_tgid();
__u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&start, &tid, &ts, BPF_ANY);
return 0;
}
SEC("kretprobe/tcp_recvmsg")
int BPF_KRETPROBE(tcp_recvmsg_exit)
{
__u32 tid = bpf_get_current_pid_tgid();
__u64 *tsp = bpf_map_lookup_elem(&start, &tid);
if (!tsp)
return 0;
__u64 delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
bpf_map_delete_elem(&start, &tid);
__u32 slot = log2(delta_us);
if (slot >= HIST_SLOTS)
slot = HIST_SLOTS - 1;
__u64 *cnt = bpf_map_lookup_elem(&latency_hist, &slot);
if (cnt)
__sync_fetch_and_add(cnt, 1);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Userspace yükleyici ve histogram yazdırma
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <math.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "http_latency.skel.h"
#define HIST_SLOTS 26
static volatile bool running = true;
static void print_hist(int map_fd)
{
__u32 key;
__u64 val, max_val = 0;
__u64 vals[HIST_SLOTS] = {};
for (key = 0; key < HIST_SLOTS; key++) {
if (!bpf_map_lookup_elem(map_fd, &key, &val))
vals[key] = val;
if (vals[key] > max_val) max_val = vals[key];
}
printf("\n%-20s %10s %-40s\n", "usecs", "count", "distribution");
for (key = 0; key < HIST_SLOTS; key++) {
if (!vals[key]) continue;
__u64 lo = (key == 0) ? 0 : (1ULL << (key-1));
__u64 hi = (1ULL << key) - 1;
int stars = max_val ? (vals[key] * 40 / max_val) : 0;
printf("[%8llu, %8llu) %10llu |", lo, hi, vals[key]);
for (int i = 0; i < stars; i++) putchar('@');
putchar('\n');
}
/* Temizle */
__u64 zero = 0;
for (key = 0; key < HIST_SLOTS; key++)
bpf_map_update_elem(map_fd, &key, &zero, BPF_ANY);
}
int main(void)
{
struct http_latency_bpf *skel;
skel = http_latency_bpf__open_and_load();
if (!skel) { fprintf(stderr, "load failed\n"); return 1; }
if (http_latency_bpf__attach(skel)) {
fprintf(stderr, "attach failed\n"); goto cleanup;
}
printf("HTTP latency izleniyor (5 sn)...\n");
while (running) {
sleep(5);
print_hist(bpf_map__fd(skel->maps.latency_hist));
}
cleanup:
http_latency_bpf__destroy(skel);
return 0;
}
Derleme ve çalıştırma
CLANG := clang
CC := gcc
ARCH := $(shell uname -m | sed 's/x86_64/x86/')
CFLAGS := -g -Wall -I/usr/include/bpf
LDFLAGS := -lbpf -lelf -lz
all: vmlinux.h http_latency.skel.h http_latency
vmlinux.h:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
http_latency.bpf.o: http_latency.bpf.c vmlinux.h
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
-I/usr/include/bpf -c $< -o $@
http_latency.skel.h: http_latency.bpf.o
bpftool gen skeleton $< > $@
http_latency: http_latency.c http_latency.skel.h
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -lm
clean:
rm -f vmlinux.h *.o *.skel.h http_latency
# Derleme
# make
# sudo ./http_latency
Bu örnekte tcp_sendmsg → tcp_recvmsg latency, aynı thread'deki bir HTTP istek-yanıt döngüsünü ölçer. Gerçek HTTP latency için uprobe ile kullanıcı alanı HTTP kütüphanesinin gönder/al fonksiyonlarını izlemek daha doğru sonuç verir. bpftrace -l 'uprobe:/usr/lib/*/libcurl.so*:curl_*' ile libcurl fonksiyonlarını keşfedebilirsiniz.
tcp_sendmsg ve tcp_recvmsg kernel fonksiyon isimleri kernel versiyonları arasında değişebilir (örneğin tcp_sendmsg_locked). Kprobe bağlamadan önce bpftrace -l 'kprobe:tcp_send*' ile mevcut sembol adlarını doğrulayın. Stabil alternatif: tracepoint:tcp:tcp_probe veya tracepoint:sock:inet_sock_set_state.
Bu bölümde
- Adım 1 bpftrace one-liner: kavram kanıtı, 5 dakikada histogram çıktısı
- Adım 2 BCC Python: process filtresi, dinamik derleme, iteratif geliştirme
- Adım 3 libbpf CO-RE: üretim kalitesi, kernel bağımsız, clang runtime gerektirmez
- Evrim: one-liner → BCC → libbpf CO-RE — karmaşıklık gerektikçe geçiş yap