eBPF XDP ile Linux'de Yuksek Performansli Paket Isleme
Linux'un standart ağ yığını paketleri DMA transferinden sonra sk_buff yapısına alır, sonra netfilter, TCP/IP stack ve socket katmanlarından geçirir. Her katman gecikme ve CPU yükü ekler. eBPF tabanlı XDP (eXpress Data Path) paketi NIC sürücüsünde, sk_buff ayrılmadan önce yakalar. Bu erken noktada karar milisaniyeler yerine mikrosaniyelerle alınır.
Linux 4.18 ile gelen bpfilter çekirdek alt sistemi iptables uyumlu kuralları eBPF bayt koduna çevirir. Her paket için sırayla kural zinciri taramak yerine bpfilter kuralları yüklenme sırasında optimize edilmiş bayt koduna derlenir. Paket başına işlem süresi beş ila on kat düşer. Ama bpfilter hâlâ deneysel; NAT, mangle tablosu ve karmaşık eşleştirme uzantıları desteklenmez. Genel amaçlı güvenlik duvarı için nftables ile BPF kombinasyonu daha olgun bir seçenek.
XDP nasil calisiyor
Bir NIC veri çerçevesini DMA ile aldığında sürücü önce descriptor ring'i okur. XDP bu noktada devreye girer. Paket ham tampon bellekte dururken eBPF programı başlar. Çekirdeğin ağ katmanı tarafından ayrıştırılmış meta veri henüz mevcut değildir.
eBPF doğrulayıcısı programı çekirdeğe yüklemeden önce statik analiz yapar. Sonsuz döngüleri, sınır dışı bellek erişimlerini ve geçersiz talimatları reddeder. Bir program sınır denetimi atlarsa doğrulayıcı reddeder ve program yüklenmez.
Üç mod var. Native modda program ağ arabirim kartının kendi sürücüsünde çalışır, en yüksek performansı sunar ama tüm sürücüler desteklemez (ixgbe, i40e, mlx5, virtio_net, veth destekler). Generic modda çekirdeğin ağ yığını XDP programını emüle eder, sk_buff zaten ayrılmıştır ama tüm ağ arabirimleriyle çalışır. Offloaded modda program doğrudan NIC üzerinde çalışır, CPU'ya yük binmez, Netronome NFP ve NVIDIA BlueField gibi SmartNIC'ler destekler.
Dort donus kodu
Bir XDP programı paket üzerinde dört karar verebilir.
XDP_DROP paketi en erken aşamada düşürür. DDoS koruması için temel mekanizma. Yalnızca birkaç yüz CPU çevrimi harcar.
XDP_PASS normal çekirdek ağ yığına gönderir. Program paket içeriğini değiştirdikten sonra bu aksiyonu dönebilir.
XDP_TX paketi aynı NIC üzerinden geri gönderir. ICMP echo-request'i echo-reply'e çevirip aynı arabirimden yollamak gibi senaryolarda kullanılır.
XDP_REDIRECT paketi başka bir NIC'e veya AF_XDP soketine yönlendirir. Load balancer yazılıyorsa bu en iyi seçenektir.
XDP_ABORTED hata durumu, XDP_DROP gibi işlenir ama bir izleme olayı tetikler.
Kod ornegi
XDP programları C dilinde yazılır, clang ile eBPF bayt koduna derlenir. En basit örnek tüm ICMP paketlerini düşürür:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u64));
__uint(max_entries, 1);
} icmp_drops SEC(".maps");
SEC("xdp")
int drop_icmp(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 + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol == IPPROTO_ICMP) {
__u32 key = 0;
__u64 *n = bpf_map_lookup_elem(&icmp_drops, &key);
if (n)
__sync_fetch_and_add(n, 1);
return XDP_DROP;
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
Derleme ve yükleme:
clang -O2 -g -target bpf -c xdp_drop.c -o xdp_drop.o
ip link set dev eth0 xdpgeneric obj xdp_drop.o sec xdp Geliştirme ortamı için clang, llvm, libbpf, libxdp ve bpftool gerekir. Debian tabanlı sistemlerde:
apt-get install clang llvm libbpf-dev libxdp-dev xdp-tools bpftool Durumu kontrol etmek için ip link show eth0 çıktısında xdp/id:XXX ibaresi görülür. bpftool prog show çalışan programları listeler.
Rakamlarla performans
Bir sunucu üzerinde test ettiğimde şöyle sonuçlar aldım. İptables aynı koşullarda saniyede 3 milyon paketi zor işliyordu. XDP ile on milyonlarca paketi düşürmek aynı donanımda mümkün. Tabii bu basit drop mantığı için geçerli; karmaşık paket ayrıştırma ekleyince rakamlar düşer.
Modern donanımda native XDP ile saniyede 10 ila 25 milyon paket düşürmek mümkün. Aynı ortamda iptables 0,5 ila 2 milyon paketle sınırlı kalır. XDP bu farkı tamamen erken noktada karar alarak elde ediyor.
Sinirlari
XDP her sorunun çözümü değil. Karmaşık stateful mantık, uygulama katmanı incelemesi veya bağlantı izleme gerektiren senaryolarda nftables veya iptables hâlâ daha uygun. XDP en erken aşamada çalıştığı için bağlantı takibi yapamaz; bağlantı durumu paketlerin sırasını ve durum bilgisini gerektirir ve bu bilgi henüz ayrıştırılmamıştır. Stateful fallback için XDP'yi TC veya netfilter ile kombinasyon kullanmak gerekir.
Üretimde kullanmak için tek başına bpftool yeterli değil. Cilium, Katran, Cloudflare L4Drop gibi projeler bir daemon ile BPF haritasını yönetir. Bu daemon filtre kurallarını haritaya yazar, XDP programı paket başına haritadan okur. Kuralları güncellemek daemon'u yeniden başlatmadan map güncellemesiyle olur.
Bir sunucu yeniden başlattığında XDP programını otomatik yüklemesi gerekiyorsa systemd servisi veya udev kuralı kullanılabilir. Kalıcılık için programı /sys/fs/bpf/ dizinine pinlemek gerekir. Ayrıca her netdev'e yalnızca bir XDP programı bağlanabilir; ikinci bir program yüklenince birincisi sessizce değiştirilir. Birden fazla alt program gerekiyorsa bpf_tail_call ile dağıtım yapılır.
Güvenlik tarafı güçlü. eBPF doğrulayıcısı çekirdeğin kararlılığını tehlikeye atmadan kullanıcı tanımlı kod çalıştırmayı garanti eder. Sınırsız döngü, kontrolsüz bellek erişimi veya geçersiz talimat yüklenmeyi reddeder. Bu mekanizma sayesinde çekirdek modülü yazmadan doğrudan NIC sürücüsüne programlanabilir kanca eklemek mümkün.