00 XDP nedir?
eXpress Data Path (XDP), Linux çekirdeğinin ağ yığınına entegre edilmiş yüksek performanslı bir paket işleme çerçevesidir. NIC sürücüsünün RX hook noktasında eBPF programları çalıştırır.
Geleneksel paket işleme modelinde bir paket, NIC'ten kernel'ın ağ yığınına, oradan socket buffer'a ve son olarak kullanıcı alanına ulaşır. Bu yolculuk boyunca defalarca bellek kopyası ve bağlam geçişi yaşanır. 10 GbE bağlantıda wire-speed yaklaşık 14.8 Mpps anlamına gelir; standart kernel yığını bu hızı tek çekirdekle karşılayamaz.
XDP bu sorunu kökten çözer: eBPF programı, paket skb (socket buffer) oluşturulmadan önce, doğrudan DMA buffer üzerinde çalışır. Karar verme (geçir, düşür, yönlendir) nanosaniye mertebesinde tamamlanır. Paket düşürme senaryosunda kernel yığınının geri kalanına hiç dokunulmaz.
┌──────────────────────────────────────────────────────────────────┐
│ NIC (RX Ring) │
│ DMA → RX descriptor ring → paket buffer │
└──────────────────────────┬───────────────────────────────────────┘
│ XDP HOOK (en erken nokta)
┌─────────▼──────────┐
│ eBPF/XDP Program │ ← sizin kodunuz buraya
│ xdp_action döner │
└──┬──────┬──────┬───┘
│ │ │
DROP TX PASS
│ │ │
▼ ▼ ▼
/dev/null NIC kernel ağ yığını (skb oluştur)
DPDK ile karşılaştırma
DPDK ve XDP benzer performans hedeflerini çok farklı felsefeyle yaklaşır. DPDK sürücüyü tamamen kullanıcı alanına taşır; standart kernel araçları (ifconfig, ethtool, tcpdump) artık o NIC'i göremez. XDP ise kernel içinde kalır; mevcut araçlarla uyumluluk korunur, başka uygulamalar aynı NIC'i paylaşabilir.
| Özellik | XDP | DPDK |
|---|---|---|
| Kernel içi mi? | Evet — kernel'da kalır | Hayır — kernel bypass |
| Araç uyumluluğu | tcpdump, ss, ethtool çalışır | NIC görünmez |
| Kurulum karmaşıklığı | Düşük | Yüksek (hugepages, IOMMU) |
| Programlama modeli | eBPF (C alt kümesi) | C/C++ (tam) |
| Paylaşımlı NIC | Evet | Hayır (exclusive) |
| Tipik latency | ~200 ns | ~100 ns |
| Mpps (tek çekirdek) | 15–25 Mpps | 20–40 Mpps |
XDP'nin üretimde en yaygın kullanım alanları: Cloudflare DDoS koruması (saniyede milyarlarca paket filtreleme), Facebook Katran yük dengeleyici, Cilium/eBPF tabanlı Kubernetes ağ politikası, yüksek frekanslı ticaret sistemlerinde paket damgalama ve kernel bypass monitoring.
01 XDP hook türleri
XDP programları üç farklı hook noktasında çalışabilir. Seçim, sürücü desteğine ve performans gereksinimlerine bağlıdır.
| Hook Türü | Çalışma noktası | Performans | Gereksinim |
|---|---|---|---|
| XDP_NATIVE | NIC sürücüsünde, skb öncesi | En yüksek (~25 Mpps) | Sürücü desteği zorunlu |
| XDP_GENERIC | skb oluşturulduktan sonra | Orta (~3–5 Mpps) | Her NIC destekler |
| XDP_OFFLOAD | NIC donanımında (ASIC/FPGA) | En yüksek (wire-speed) | Netronome SmartNIC gerekli |
XDP_NATIVE — native driver desteği
En performanslı moddur. NIC sürücüsü XDP hook'u desteklemek zorundadır. Popüler sürücülerin desteği:
Hook bağlama — ip komutu ve flags
/* flags değerleri */
XDP_FLAGS_SKB_MODE // XDP_GENERIC — her NIC çalışır
XDP_FLAGS_DRV_MODE // XDP_NATIVE — sürücü desteği gerekli
XDP_FLAGS_HW_MODE // XDP_OFFLOAD — SmartNIC gerekli
XDP_FLAGS_UPDATE_IF_NOEXIST // Üzerine yazmayı engelle
XDP_FLAGS_REPLACE // Atomik program değişimi (kernel 5.7+)
// ip komutu ile bağlama:
ip link set eth0 xdpdrv obj prog.o sec xdp # native
ip link set eth0 xdpgeneric obj prog.o sec xdp # generic
ip link set eth0 xdp off # kaldır
Geliştirme ortamında önce XDP_GENERIC ile başlayın. Her NIC üzerinde çalışır ve hata ayıklamayı kolaylaştırır. Üretimde XDP_NATIVE'e geçin. Sürücü desteğini ethtool -i eth0 ile kontrol edin.
02 BPF program yazımı
XDP programları C'nin kısıtlı bir alt kümesinde yazılır ve clang/LLVM ile eBPF bytecode'una derlenir. Kernel verifier her programı yüklemeden önce güvenlik açısından denetler.
xdp_md context yapısı
struct xdp_md {
__u32 data; /* paket başlangıcı (DMA buffer adresi) */
__u32 data_end; /* paket sonu */
__u32 data_meta; /* meta veri alanı başlangıcı */
__u32 ingress_ifindex; /* gelen arayüz ifindex */
__u32 rx_queue_index; /* RX kuyruk numarası */
__u32 egress_ifindex; /* redirect için çıkış arayüzü */
};
Bounds check zorunluluğu
Kernel verifier, her bellek erişiminin data_end sınırını aşmadığını statik olarak doğrular. Bounds check yapılmazsa program yükleme reddedilir:
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
SEC("xdp")
int xdp_prog(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
/* ZORUNLU: Ethernet header sığıyor mu? */
if (eth + 1 > data_end)
return XDP_DROP;
/* Sadece IPv4 ile ilgileniyoruz */
if (eth->h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *iph = data + sizeof(*eth);
/* IP header bounds check */
if (iph + 1 > data_end)
return XDP_DROP;
/* UDP mu? */
if (iph->protocol != IPPROTO_UDP)
return XDP_PASS;
struct udphdr *udph = data + sizeof(*eth) + (iph->ihl * 4);
if (udph + 1 > data_end)
return XDP_DROP;
/* UDP port 9999 → logla */
if (udph->dest == __constant_htons(9999))
bpf_printk("UDP 9999 hit, src=%pI4\n", &iph->saddr);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
xdp_action dönüş kodları
| Kod | Değer | Anlam |
|---|---|---|
| XDP_ABORTED | 0 | Hata — paket düşür, trace event üretir |
| XDP_DROP | 1 | Paketi düşür (en hızlı, DMA buffer'da bırak) |
| XDP_PASS | 2 | Kernel ağ yığınına ilet |
| XDP_TX | 3 | Aynı NIC'ten geri gönder (echo/bounce) |
| XDP_REDIRECT | 4 | Başka arayüze veya AF_XDP soketine yönlendir |
Derleme
clang -O2 -g -Wall -target bpf \
-D__TARGET_ARCH_x86 \
-I/usr/include/x86_64-linux-gnu \
-c xdp_prog.c -o xdp_prog.o
# BTF (BPF Type Format) bilgisi içeriyor mu?
llvm-objdump -h xdp_prog.o | grep BTF
03 libbpf ile yükleme
libbpf, BPF programlarını kernel'a yüklemek, map'leri yönetmek ve XDP hook'larını bağlamak için standart C kütüphanesidir. Modern eBPF geliştirmenin temel taşıdır.
#include <bpf/libbpf.h>
#include <net/if.h>
#include <linux/if_link.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static volatile bool running = true;
static void sig_handler(int sig) { running = false; }
int main(int argc, char *argv[])
{
const char *ifname = argc > 1 ? argv[1] : "eth0";
int ifindex = if_nametoindex(ifname);
/* 1. BPF objesini aç */
struct bpf_object *obj = bpf_object__open_file("xdp_prog.o", NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "bpf_object__open_file hata\n");
return 1;
}
/* 2. Yükle (maps oluşturulur, verifier çalışır) */
if (bpf_object__load(obj)) {
fprintf(stderr, "bpf_object__load hata\n");
return 1;
}
/* 3. "xdp_prog" fonksiyonunu bul */
struct bpf_program *prog =
bpf_object__find_program_by_name(obj, "xdp_prog");
/* 4. XDP hook'una bağla — link nesnesi döner */
struct bpf_link *link = bpf_program__attach_xdp(prog, ifindex);
if (libbpf_get_error(link)) {
fprintf(stderr, "attach hata (native için sürücü desteği gerekli)\n");
return 1;
}
printf("XDP yüklendi: %s\n", ifname);
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
while (running)
sleep(1);
/* 5. Temiz kaldır */
bpf_link__destroy(link);
bpf_object__close(obj);
printf("XDP kaldırıldı.\n");
return 0;
}
Makefile
CC = gcc
CFLAGS = -O2 -Wall
LIBS = -lbpf -lelf -lz
all: xdp_prog.o xdp_loader
xdp_prog.o: xdp_prog.c
clang -O2 -target bpf -c $< -o $@
xdp_loader: xdp_loader.c
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
load:
sudo ./xdp_loader eth0
unload:
sudo ip link set eth0 xdp off
Yüklenen programı doğrulama
## Bağlı XDP programlarını listele
ip link show eth0 # prog/id:NNN satırı görünmeli
## Tüm BPF programlarını listele
bpftool prog list
## Program detayı
bpftool prog show id 42
## Bytecode dump (doğrulama amaçlı)
bpftool prog dump xlated id 42
## JIT edilmiş makine kodu
bpftool prog dump jited id 42
04 eBPF maps
eBPF maps, XDP programı ile kullanıcı alanı arasında veri paylaşımını, durum tutmayı ve konfigürasyon aktarmayı sağlayan anahtar-değer veri yapılarıdır.
Yaygın map tipleri
| Tip | Kullanım | Notlar |
|---|---|---|
| BPF_MAP_TYPE_HASH | IP kara listesi, akış tablosu | O(1) lookup, çakışma var |
| BPF_MAP_TYPE_ARRAY | Sayaçlar, sabit indeksli veri | En hızlı, boyut sabittir |
| BPF_MAP_TYPE_PERCPU_ARRAY | CPU başına sayaçlar | Lock gerektirmez, en performanslı |
| BPF_MAP_TYPE_LRU_HASH | Bağlantı takibi, DDoS | Otomatik eski entry silme |
| BPF_MAP_TYPE_XSKMAP | AF_XDP redirect | XDP_REDIRECT için zorunlu |
| BPF_MAP_TYPE_DEVMAP | NIC'e redirect | bpf_redirect_map için |
Percpu sayaç — BPF program tarafı
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_count(struct xdp_md *ctx)
{
__u32 key = 0;
__u64 *val = bpf_map_lookup_elem(&pkt_count, &key);
if (val)
__sync_fetch_and_add(val, 1); /* percpu: lock gereksiz */
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
Kullanıcı alanından percpu map okuma
#include <bpf/libbpf.h>
/* map fd'yi bpf_object üzerinden bul */
struct bpf_map *map = bpf_object__find_map_by_name(obj, "pkt_count");
int map_fd = bpf_map__fd(map);
int ncpus = libbpf_num_possible_cpus();
__u64 values[ncpus];
__u32 key = 0;
/* Tüm CPU'lardan oku */
bpf_map_lookup_elem(map_fd, &key, values);
__u64 total = 0;
for (int i = 0; i < ncpus; i++)
total += values[i];
printf("Toplam paket sayısı: %llu\n", total);
IP kara listesi — LRU_HASH
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32); /* IPv4 adresi */
__type(value, __u8); /* engelleme flag */
} blocklist SEC(".maps");
SEC("xdp")
int xdp_block(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if (eth + 1 > data_end) return XDP_DROP;
if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *iph = data + sizeof(*eth);
if (iph + 1 > data_end) return XDP_DROP;
__u32 src = iph->saddr;
if (bpf_map_lookup_elem(&blocklist, &src))
return XDP_DROP; /* kara listede → düşür */
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
## bpftool ile map güncelleme
bpftool map update id 5 key c0 a8 01 01 value 01
## c0 a8 01 01 = 192.168.1.1 (little-endian)
## Map içeriğini görüntüle
bpftool map dump id 5
## Userspace C kodu ile güncelleme
__u32 bad_ip = inet_addr("192.168.1.1");
__u8 val = 1;
bpf_map_update_elem(map_fd, &bad_ip, &val, BPF_ANY);
05 AF_XDP soketleri
AF_XDP, XDP programından paketleri doğrudan kullanıcı alanına bellek kopyası yapmadan aktaran özel bir soket ailesidir. Zero-copy modunda DMA buffer'lar doğrudan kullanıcı alanına map edilir.
┌───────────────────────────────────────────────────────────────┐
│ UMEM (paylaşımlı bellek alanı) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Frame 0 │ │Frame 1 │ │Frame 2 │ │Frame N │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└──────────────────────────────────────────────────────────┬───┘
│
┌────────────────── Kuyruklar (ring buffer'lar) ────────────┤
│ Fill Ring → kullanıcı boş frame addr gönderir │
│ Completion ← kernel gönderim tamamlanan addr verir │
│ RX Ring ← kernel alınan paket + frame addr verir │
│ TX Ring → kullanıcı gönderilecek frame addr verir │
└───────────────────────────────────────────────────────────┘
xsk_socket oluşturma adımları
#include <xdp/xsk.h> /* libxdp / libbpf */
#include <sys/mman.h>
#define FRAME_SIZE XSK_UMEM__DEFAULT_FRAME_SIZE /* 4096 */
#define NUM_FRAMES 4096
#define UMEM_SIZE (FRAME_SIZE * NUM_FRAMES)
struct xsk_socket_info {
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
struct xsk_ring_prod fq; /* fill queue */
struct xsk_ring_cons cq; /* comp. queue */
struct xsk_umem *umem;
struct xsk_socket *xsk;
void *bufs; /* mmap adresi */
};
int setup_xsk(struct xsk_socket_info *xsk_info,
const char *ifname, int queue_id)
{
/* 1. UMEM için hugepage destekli bellek ayır */
xsk_info->bufs = mmap(NULL, UMEM_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
/* 2. UMEM oluştur */
struct xsk_umem_config ucfg = {
.fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.frame_size = FRAME_SIZE,
.frame_headroom = 0,
.flags = 0,
};
xsk_umem__create(&xsk_info->umem, xsk_info->bufs, UMEM_SIZE,
&xsk_info->fq, &xsk_info->cq, &ucfg);
/* 3. Fill kuyruğunu doldur (kernel'a boş frame'ler ver) */
__u32 idx;
xsk_ring_prod__reserve(&xsk_info->fq, NUM_FRAMES / 2, &idx);
for (int i = 0; i < NUM_FRAMES / 2; i++)
*xsk_ring_prod__fill_addr(&xsk_info->fq, idx++) =
(__u64)i * FRAME_SIZE;
xsk_ring_prod__submit(&xsk_info->fq, NUM_FRAMES / 2);
/* 4. XSK soketi oluştur — zero-copy mod */
struct xsk_socket_config xcfg = {
.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.xdp_flags = XDP_FLAGS_DRV_MODE,
.bind_flags = XDP_ZEROCOPY,
};
return xsk_socket__create(&xsk_info->xsk, ifname, queue_id,
xsk_info->umem,
&xsk_info->rx, &xsk_info->tx, &xcfg);
}
Zero-copy vs Copy mod karşılaştırması
| Mod | Flag | Bellek kopyası | Sürücü gereksinimi |
|---|---|---|---|
| Zero-copy | XDP_ZEROCOPY | Yok — DMA = UMEM | mlx5, i40e, ixgbe gerekli |
| Copy mod | XDP_COPY | Bir kopya | Her NIC çalışır |
RX döngüsü
void rx_loop(struct xsk_socket_info *xsk)
{
struct pollfd fds = {
.fd = xsk_socket__fd(xsk->xsk),
.events = POLLIN,
};
while (running) {
poll(&fds, 1, 1000); /* 1 sn timeout */
__u32 idx_rx = 0, idx_fq = 0;
unsigned int rcvd =
xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx);
if (!rcvd) continue;
xsk_ring_prod__reserve(&xsk->fq, rcvd, &idx_fq);
for (unsigned int i = 0; i < rcvd; i++) {
const struct xdp_desc *desc =
xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++);
/* Zero-copy: UMEM içindeki veriye doğrudan eriş */
uint8_t *pkt = xsk_umem__get_data(xsk->bufs, desc->addr);
process_packet(pkt, desc->len);
/* Frame'i fill kuyruğuna geri ver */
*xsk_ring_prod__fill_addr(&xsk->fq, idx_fq++) = desc->addr;
}
xsk_ring_cons__release(&xsk->rx, rcvd);
xsk_ring_prod__submit(&xsk->fq, rcvd);
}
}
06 XDP uygulama senaryoları
XDP'nin gerçek üretim ortamlarındaki üç temel kullanım senaryosu: DDoS mitigasyonu, yük dengeleme ve paket yansıtma.
Senaryo 1: DDoS mitigasyonu — rate limiting + otomatik kara liste
struct flow_stats {
__u64 packets;
__u64 last_seen_ns;
};
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32);
__type(value, struct flow_stats);
} flow_table SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32);
__type(value, __u8);
} blocked SEC(".maps");
#define RATE_LIMIT 10000ULL /* pps eşiği */
#define WINDOW_NS 1000000000ULL /* 1 saniye */
SEC("xdp")
int ddos_protect(struct xdp_md *ctx)
{
/* ... header parse, bounds check ... */
__u32 src_ip = iph->saddr;
/* Kara listede mi? Hızlı yol */
if (bpf_map_lookup_elem(&blocked, &src_ip))
return XDP_DROP;
struct flow_stats *fs = bpf_map_lookup_elem(&flow_table, &src_ip);
__u64 now = bpf_ktime_get_ns();
if (!fs) {
struct flow_stats new_fs = { .packets = 1, .last_seen_ns = now };
bpf_map_update_elem(&flow_table, &src_ip, &new_fs, BPF_ANY);
return XDP_PASS;
}
if (now - fs->last_seen_ns > WINDOW_NS) {
fs->packets = 1;
fs->last_seen_ns = now;
} else {
fs->packets++;
if (fs->packets > RATE_LIMIT) {
__u8 val = 1;
bpf_map_update_elem(&blocked, &src_ip, &val, BPF_ANY);
bpf_printk("DDoS: blocked %pI4\n", &src_ip);
return XDP_DROP;
}
}
return XDP_PASS;
}
Senaryo 2: L4 yük dengeleme — bpf_redirect_map
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(max_entries, 8);
__type(key, __u32);
__type(value, __u32); /* backend NIC ifindex */
} tx_ports SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u32); /* round-robin sayacı */
} rr_counter SEC(".maps");
#define NUM_BACKENDS 4
SEC("xdp")
int xdp_lb(struct xdp_md *ctx)
{
__u32 key = 0;
__u32 *cnt = bpf_map_lookup_elem(&rr_counter, &key);
if (!cnt) return XDP_PASS;
__u32 backend_idx = __sync_fetch_and_add(cnt, 1) % NUM_BACKENDS;
return bpf_redirect_map(&tx_ports, backend_idx, XDP_PASS);
}
Senaryo 3: Paket yansıtma (mirror)
Üretim trafiğini analiz sistemine kopyalamak için XDP_TX kullanılamaz; bunun yerine bpf_clone_redirect helper'ı veya TC (traffic control) BPF ile birlikte kullanılır. XDP programı paketi analiz arayüzüne yönlendirirken orijinali de kernel yığınına iletir.
/* mirror_ifindex değeri userspace'den map aracılığıyla alınır */
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u32); /* mirror arayüz ifindex */
} mirror_cfg SEC(".maps");
SEC("xdp")
int xdp_mirror(struct xdp_md *ctx)
{
__u32 key = 0;
__u32 *ifindex = bpf_map_lookup_elem(&mirror_cfg, &key);
if (ifindex && *ifindex)
bpf_clone_redirect(ctx, *ifindex, 0);
return XDP_PASS; /* orijinali kernel yığınına ilet */
}
07 Performans ölçümü
XDP performansını ölçmek için doğru araçlar kullanılmazsa yanıltıcı sonuçlar alınabilir. Wire-speed test için trafik üreteç tarafını da dikkate almak gerekir.
xdp-bench — yerleşik benchmark
## xdp-tools paketini kur
apt install xdp-tools
## XDP_DROP modunda Mpps ölç
xdp-bench drop eth0
## XDP_TX modunda echo test
xdp-bench tx eth0
## Örnek çıktı (10G NIC):
# Received 14,880,952 pps ( 9.97 Gbps) period:0.250041
# Received 14,880,952 pps ( 9.97 Gbps) period:0.250068
Kernel pktgen ile trafik üretimi
#!/bin/bash
PGD=/proc/net/pktgen
pgset() { echo $1 > $PGD/$2; }
echo "add_device eth0" > $PGD/kpktgend_0
echo "rem_device_all" > $PGD/kpktgend_0
echo "add_device eth0" > $PGD/kpktgend_0
pgset "count 0" $PGD/eth0 # süreksi
pgset "pkt_size 64" $PGD/eth0 # minimum frame
pgset "dst_mac aa:bb:cc:dd:ee:ff" $PGD/eth0
pgset "dst 192.168.1.100" $PGD/eth0
pgset "udp_dst_min 9000" $PGD/eth0
pgset "udp_dst_max 9100" $PGD/eth0
pgset "flag UDPSRC_RND" $PGD/eth0
echo "start" > $PGD/pgctrl
perf stat ile CPU döngüsü profili
## BPF programı profil et (5 sn)
perf stat -e cycles,instructions,cache-misses \
-p $(pgrep xdp_loader) -- sleep 5
## ethtool XDP istatistikleri
ethtool -S eth0 | grep -i xdp
## Kernel trace (XDP_ABORTED hatalarını izle)
trace-cmd record -e xdp:xdp_exception &
sleep 5
trace-cmd report
| Test senaryosu | NIC | Beklenen sonuç |
|---|---|---|
| XDP_DROP, 64B, native, 1 CPU | 10G mlx5 | ~14–25 Mpps |
| XDP_DROP, 64B, generic, 1 CPU | herhangi | ~3–5 Mpps |
| XDP_PASS + sayaç, native | 10G i40e | ~12–20 Mpps |
| AF_XDP zero-copy RX | 10G mlx5 | ~10–14 Mpps |
| AF_XDP copy mod RX | herhangi | ~2–4 Mpps |
08 Pratik: 10 Mpps UDP sayacı & AF_XDP zero-copy capture
Tam çalışan bir örnek: XDP programı UDP paketlerini sayar ve port 9999'u AF_XDP soketine yönlendirir; kullanıcı alanı zero-copy ile alır ve işler.
Proje yapısı
xdp-capture/
├── xdp_kern.c # BPF kernel programı
├── xdp_user.c # Kullanıcı alanı (AF_XDP + istatistik)
├── common.h # Paylaşımlı sabitler ve map isimleri
└── Makefile
BPF kernel programı — xdp_kern.c
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
#include "common.h"
/* XSKMAP: AF_XDP soket haritası (kuyruk başına bir soket) */
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, MAX_QUEUES);
__type(key, __u32);
__type(value, __u32);
} xsks_map SEC(".maps");
/* Percpu istatistik: [0]=toplam, [1]=redirect */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 2);
__type(key, __u32);
__type(value, __u64);
} stats_map SEC(".maps");
SEC("xdp")
int xdp_capture(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if (eth + 1 > data_end) return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *iph = data + sizeof(*eth);
if (iph + 1 > data_end) return XDP_PASS;
if (iph->protocol != IPPROTO_UDP) return XDP_PASS;
struct udphdr *udph = data + sizeof(*eth) + (iph->ihl * 4);
if (udph + 1 > data_end) return XDP_PASS;
/* Toplam sayaç */
__u32 key = 0;
__u64 *cnt = bpf_map_lookup_elem(&stats_map, &key);
if (cnt) __sync_fetch_and_add(cnt, 1);
/* Port 9999 → AF_XDP soketine yönlendir */
if (udph->dest == __constant_htons(9999)) {
__u32 rkey = 1;
__u64 *rcnt = bpf_map_lookup_elem(&stats_map, &rkey);
if (rcnt) __sync_fetch_and_add(rcnt, 1);
return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, 0);
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
Kullanıcı alanı — AF_XDP RX + istatistik ekranı
/* ... setup_xsk() çağrısından sonra ... */
pthread_t stats_tid;
pthread_create(&stats_tid, NULL, stats_thread, obj);
rx_loop(xsk_info); /* sonsuz RX döngüsü */
/* İstatistik thread'i — her saniye ekrana yaz */
void *stats_thread(void *arg)
{
struct bpf_object *obj = (struct bpf_object *)arg;
struct bpf_map *m = bpf_object__find_map_by_name(obj, "stats_map");
int fd = bpf_map__fd(m);
int ncpus = libbpf_num_possible_cpus();
__u64 vals[ncpus];
__u64 prev_total = 0, prev_redir = 0;
while (running) {
sleep(1);
__u32 k0 = 0, k1 = 1;
__u64 total = 0, redir = 0;
bpf_map_lookup_elem(fd, &k0, vals);
for (int i = 0; i < ncpus; i++) total += vals[i];
bpf_map_lookup_elem(fd, &k1, vals);
for (int i = 0; i < ncpus; i++) redir += vals[i];
printf("UDP pps: %6llu | AF_XDP pps: %6llu\n",
total - prev_total, redir - prev_redir);
prev_total = total;
prev_redir = redir;
}
return NULL;
}
Derleme ve test
## 1. Derle
make
## 2. Yükle ve çalıştır (kuyruk 0, eth0)
sudo ./xdp_user eth0 0
## 3. Başka terminalden UDP flood gönder
sudo pktgen_sample03_burst_single_flow.sh \
-i eth1 -d 192.168.1.1 -m aa:bb:cc:dd:ee:ff
## 4. Beklenen çıktı (~10G NIC, native XDP):
# UDP pps: 14800000 | AF_XDP pps: 14800000
# UDP pps: 14799852 | AF_XDP pps: 14799852
## 5. Temizle
sudo ip link set eth0 xdp off
XDP, kernel'ın ağ yığınını atlamadan wire-speed paket işleme sağlar. BPF verifier güvenliği garanti eder, libbpf modern API sunar, AF_XDP zero-copy ile kullanıcı alanına Mpps hızında paket aktarır. DDoS korumasından yük dengelemeye, monitoring'den packet capture'a geniş üretim kullanım alanı sunar.