1. Що таке eBPF?
eBPF (extended Berkeley Packet Filter) — це підсистема ядра Linux, яка дозволяє запускати програми ізольованого програмного середовища всередині ядра без зміни вихідного коду ядра чи завантаження модулів ядра. Програми перед виконанням перевіряються засобом перевірки байт-коду ядра, що забезпечує безпеку.
Для роботи в мережі програми eBPF підключаються доточки гачкау мережевому стеку ядра і може перевіряти, змінювати, перенаправляти або скидати пакети. Ключова перевага надiptablesчи модулі ядра — це продуктивність і можливість програмування: програми eBPF скомпільовані за допомогою JIT у рідний код і можуть обмінюватися станом черезкарти(сховища ключів і значень, спільні між ядром і простором користувача).
| Гачок | Розташування | Затримка | Випадок використання |
|---|---|---|---|
| XDP | Драйвер мережевої карти, перед виділенням sk_buff | Найнижчий | Відкидання DDoS, балансування навантаження |
| tc вхід/вихід | Після виділення sk_buff | Низький | Формування трафіку, розмітка, перенаправлення |
| розетковий фільтр | Шлях отримання сокета | Середній | фільтрація у стилі tcpdump |
| kprobe/tracepoint | Вхід/вихід функції ядра | Варіюється | Спостережливість, калькування |
2. Точки підключення XDP
Програми XDP (eXpress Data Path) запускаються якомога раніше в мережевому стеку — всередині драйвера NIC, перш ніж ядро виділитьsk_buff. Це означає:
- Рідний XDP: драйвер нативно підтримує XDP (Intel i40e, Mellanox mlx5 тощо). Найшвидший — працює в контексті драйвера.
- Загальний XDP: Запасний варіант для драйверів без вбудованої підтримки. Біжить слідом
sk_buffрозподіл — усе ще швидше, ніж iptables, але не так швидко, як нативний. - Розвантажений XDP: Програма працює на самій ASIC NIC. Потрібне обладнання SmartNIC (наприклад, Netronome). Нульова вартість ЦП.
Програма XDP повертає один із п’яти вердиктів:
| Код повернення | Дія |
|---|---|
XDP_DROP | Негайно відкинути пакет — відкинути найменшу затримку |
XDP_PASS | Перейти до звичайного мережевого стеку |
XDP_TX | Зворотна передача через той самий інтерфейс (відбій) |
XDP_REDIRECT | Переспрямовувати на інший інтерфейс або сокет AF_XDP |
XDP_ABORTED | Шлях помилки — скинути з подією трасування |
3. Приклад скидання пакетів XDP
Наступна програма видаляє всі UDP-пакети з вихідної IP-адреси, що зберігається на карті eBPF, дозволяючи площині керування простором користувача оновлювати список блокувань під час виконання.
// xdp_drop_udp.c — Drop UDP from IPs in a BPF map
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
// BPF map: src IP → drop flag (1 = drop)
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32); // source IPv4 address
__type(value, __u32); // 1 = block
} blocklist SEC(".maps");
SEC("xdp")
int xdp_drop_udp(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
// Parse Ethernet header
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS;
// Parse IPv4 header
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return XDP_PASS;
if (ip->protocol != IPPROTO_UDP) return XDP_PASS;
// Check blocklist map
__u32 src = ip->saddr;
__u32 *val = bpf_map_lookup_elem(&blocklist, &src);
if (val && *val == 1) return XDP_DROP;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
data_end. Кожна арифметична операція вказівника має супроводжуватися перевіркою меж, інакше програма не завантажиться.Завантажте та прикріпіть сip:
# Compile
clang -O2 -target bpf -c xdp_drop_udp.c -o xdp_drop_udp.o
# Attach to interface (native XDP)
ip link set eth0 xdp obj xdp_drop_udp.o sec xdp
# Add an IP to the blocklist via bpftool
bpftool map update name blocklist key 0x01 0x02 0x03 0x04 value 0x01 0x00 0x00 0x00
# Remove XDP program
ip link set eth0 xdp off
4. AF_XDP: обхід ядра
AF_XDPце сімейство сокетів, яке в поєднанні з XDPXDP_REDIRECTвердикт, доставляє пакети безпосередньо в область пам’яті користувача (UMEM) без участі ядра для кожного пакета. Це відповідь екосистеми eBPF на модель обходу ядра DPDK.
Ключові компоненти:
- UMEM: Область пам’яті, зареєстрована в просторі користувача, розділена на кадри. Спільний доступ між ядром і простором користувача через спільну пам’ять.
- Кільця: Чотири кільця без блокування на сокет: заповнення (простір користувача → ядро з вільними кадрами), завершення (ядро → простір користувача з кадрами TX-done), кільце RX (ядро → простір користувача з отриманими кадрами), кільце TX (простір користувача → ядро з кадрами для надсилання).
- Режим нульового копіювання: якщо драйвер підтримує це, кадри передаються без копіювання — лише передача вказівника.
AF_XDP ідеально підходить для нестандартної обробки пакетів зі швидкістю рядка без операційної складності DPDK (без великих сторінок, без закріплення процесора для базового використання).
5. tc BPF: Формування та фільтрація трафіку
tc(регулювання дорожнього руху) Програми БНФ додаються на стclsactqdisc і може працювати на вході чи виході. На відміну від XDP, вони бачать повнуsk_buffі може отримати доступ до метаданих сокетів, мереж VLAN і заголовків тунелів.
// tc_mark.c — Mark packets with DSCP EF (46) for VoIP traffic on port 5060
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
SEC("classifier")
int tc_mark_voip(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return TC_ACT_OK;
if (eth->h_proto != __constant_htons(ETH_P_IP)) return TC_ACT_OK;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return TC_ACT_OK;
if (ip->protocol != IPPROTO_UDP) return TC_ACT_OK;
struct udphdr *udp = (void *)(ip + 1);
if ((void *)(udp + 1) > data_end) return TC_ACT_OK;
// Mark SIP traffic (port 5060) with DSCP EF (46 = 0xB8 in TOS byte)
if (udp->dest == __constant_htons(5060) || udp->source == __constant_htons(5060)) {
// DSCP EF = 46, shifted left 2 bits in TOS field = 184 (0xB8)
bpf_skb_store_bytes(skb, offsetof(struct iphdr, tos) + sizeof(struct ethhdr),
&((__u8){184}), 1, BPF_F_RECOMPUTE_CSUM);
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
# Attach tc BPF program
tc qdisc add dev eth0 clsact
tc filter add dev eth0 egress bpf da obj tc_mark.o sec classifier
6. Обмеження швидкості за допомогою карт eBPF
Карти eBPF забезпечують обробку з урахуванням стану. У наведеному нижче шаблоні реалізовано обмеження швидкості для кожного джерела-IP за допомогою сегмента маркерів, що зберігається в aBPF_MAP_TYPE_LRU_HASH:
// Conceptual token bucket per source IP — checks tokens, drops if exceeded
struct ratelimit_entry {
__u64 tokens; // current token count
__u64 last_update; // nanoseconds timestamp
};
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32); // source IP
__type(value, struct ratelimit_entry);
} rate_map SEC(".maps");
// In XDP program:
// 1. bpf_ktime_get_ns() — get current time
// 2. Lookup entry for src IP
// 3. Refill tokens: tokens += (elapsed_ns / 1e9) * rate_pps
// 4. If tokens >= 1: decrement and XDP_PASS
// 5. Else: XDP_DROP
7. bpftool & bpftrace Інтроспекція
Два основних інструменти для роботи з живими програмами eBPF:
# bpftool — inspect loaded programs and maps
bpftool prog list # list all loaded eBPF programs
bpftool prog show id 42 # details for program ID 42
bpftool prog dump xlated id 42 # disassemble to eBPF bytecode
bpftool prog dump jited id 42 # dump JIT-compiled native code
bpftool map list # list all BPF maps
bpftool map dump name blocklist # dump all entries in map "blocklist"
bpftool map update name blocklist \
key 192 168 1 100 value 1 0 0 0 # add entry (network byte order)
# bpftrace — DTrace-style one-liners for kernel tracing
# Count XDP drops per second
bpftrace -e 'tracepoint:xdp:xdp_exception { @drops[args->action] = count(); } interval:s:1 { print(@drops); clear(@drops); }'
# Trace tcp_retransmit_skb — show retransmit events with comm name
bpftrace -e 'kprobe:tcp_retransmit_skb { printf("%s retransmit\n", comm); }'
# Histogram of packet sizes on eth0
bpftrace -e 'tracepoint:net:netif_receive_skb /args->name == "eth0"/ { @size = hist(args->len); }'
8. Порівняння: eBPF/XDP проти DPDK проти RDMA
| Особливість | eBPF/XDP | ДПДК | RDMA |
|---|---|---|---|
| Залучення ядра | Мінімальний (XDP у драйвері) | Немає (повний обхід) | Немає (RDMA NIC) |
| Модель пам'яті | Стандарт + AF_XDP UMEM | Потрібні величезні сторінки | Зареєстровані області пам'яті |
| Максимальна пропускна здатність | ~100 Гбіт/с рідний XDP | >100 Гбіт/с | 200+ Гбіт/с (InfiniBand) |
| використання ЦП | Низький (керований подіями) | Високий (завантажені ядра опитування) | Близько нуля (розвантажено) |
| Складність операції | Низькі — стандартні інструменти | Високий — виділені ядра, величезні сторінки | Високий — управління тканиною |
| Випадок використання | Пом'якшення DDoS, LB, спостережливість | Віртуальні маршрутизатори, NFV, генерація пакетів | Сховище (NVMe-oF), HPC MPI |
| Мова | Обмежений C / Rust | C / Іржа | API дієслів (C) |