embedded-deck
XDP & AF_XDP — Kernel Bypass Paket İşleme · embedded-deck embedded-deck
LİNUX NETWORK PERFORMANS XDP eBPF 2026

XDP & AF_XDP
kernel bypass paket işleme.

eXpress Data Path ile NIC RX hattında milisaniye altı kararlar alın. AF_XDP zero-copy soketlerle kullanıcı alanına Mpps hızında paket aktarın. DDoS mitigasyondan yük dengelemeye üretim senaryoları.

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.

ÖzellikXDPDPDK
Kernel içi mi?Evet — kernel'da kalırHayır — kernel bypass
Araç uyumluluğutcpdump, ss, ethtool çalışırNIC görünmez
Kurulum karmaşıklığıDüşükYüksek (hugepages, IOMMU)
Programlama modelieBPF (C alt kümesi)C/C++ (tam)
Paylaşımlı NICEvetHayır (exclusive)
Tipik latency~200 ns~100 ns
Mpps (tek çekirdek)15–25 Mpps20–40 Mpps
KULLANIM ALANLARI

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ıPerformansGereksinim
XDP_NATIVENIC sürücüsünde, skb öncesiEn yüksek (~25 Mpps)Sürücü desteği zorunlu
XDP_GENERICskb oluşturulduktan sonraOrta (~3–5 Mpps)Her NIC destekler
XDP_OFFLOADNIC 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:

mlx5 (Mellanox)
Tam native XDP desteği. AF_XDP zero-copy dahil. Üretim tercih sebebi.
i40e (Intel XL710)
Native XDP destekli. AF_XDP zero-copy mevcut.
ixgbe (Intel 82599)
Native XDP destekli. Yaygın kullanılan 10G kartları.
virtio_net
KVM sanal makinelerde native XDP desteği. Zero-copy sınırlı.
veth
Container/namespace arası iletişimde native XDP.
tun/tap
Generic mode ile çalışır, native desteklenmez.

Hook bağlama — ip komutu ve flags

xdp attach 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
DİKKAT

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ı

xdp_md — paket bağlamı
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:

xdp_bounds.c — zorunlu sınır denetimi
#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ı

KodDeğerAnlam
XDP_ABORTED0Hata — paket düşür, trace event üretir
XDP_DROP1Paketi düşür (en hızlı, DMA buffer'da bırak)
XDP_PASS2Kernel ağ yığınına ilet
XDP_TX3Aynı NIC'ten geri gönder (echo/bounce)
XDP_REDIRECT4Başka arayüze veya AF_XDP soketine yönlendir

Derleme

derleme komutu
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.

xdp_loader.c — libbpf ile yükleme
#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

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

doğrulama komutları
## 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

TipKullanımNotlar
BPF_MAP_TYPE_HASHIP kara listesi, akış tablosuO(1) lookup, çakışma var
BPF_MAP_TYPE_ARRAYSayaçlar, sabit indeksli veriEn hızlı, boyut sabittir
BPF_MAP_TYPE_PERCPU_ARRAYCPU başına sayaçlarLock gerektirmez, en performanslı
BPF_MAP_TYPE_LRU_HASHBağlantı takibi, DDoSOtomatik eski entry silme
BPF_MAP_TYPE_XSKMAPAF_XDP redirectXDP_REDIRECT için zorunlu
BPF_MAP_TYPE_DEVMAPNIC'e redirectbpf_redirect_map için

Percpu sayaç — BPF program tarafı

xdp_counter.c — percpu sayaç
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

userspace 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

xdp_blocklist.c
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 kara listeye IP ekleme
## 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ı

af_xdp_setup.c — soket kurulumu
#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ı

ModFlagBellek kopyasıSürücü gereksinimi
Zero-copyXDP_ZEROCOPYYok — DMA = UMEMmlx5, i40e, ixgbe gerekli
Copy modXDP_COPYBir kopyaHer NIC çalışır

RX döngüsü

rx_loop — AF_XDP alım 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

xdp_ddos.c — rate limiting
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

xdp_lb.c — round-robin yük dengeleme
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.

xdp_mirror.c — bpf_clone_redirect
/* 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-bench kullanımı
## 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

pktgen_sample.sh — kernel pktgen
#!/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

perf profiling
## 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 senaryosuNICBeklenen sonuç
XDP_DROP, 64B, native, 1 CPU10G mlx5~14–25 Mpps
XDP_DROP, 64B, generic, 1 CPUherhangi~3–5 Mpps
XDP_PASS + sayaç, native10G i40e~12–20 Mpps
AF_XDP zero-copy RX10G mlx5~10–14 Mpps
AF_XDP copy mod RXherhangi~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ı

proje dosyaları
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

xdp_kern.c — tam program
#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ı

xdp_user.c — ana döngü (özet)
/* ... 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

test adımları
## 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
ÖZET

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.