İçeriğe geç
NetDevOps
8 dk okuma Ağ Yönetimi

XDP ile DDoS Engelleme: Cloudflare L4Drop Mimarisi ve Uygulaması

Saniyede 8 milyon paketi düşürürken CPU kullanımının sadece yüzde 10 artması kulağa abartı geliyor ama Cloudflare'in üretim ortamında ölçtüğü rakam tam olarak bu. Saldırı trafiği 40 katına çıkarken softirq CPU yükünün sadece iki katına çıkması, XDP'nin neden DDoS mitigasyonunda oyunu değiştirdiğini özetliyor. Bu yazıda Cloudflare'in L4Drop aracının arkasındaki mimariyi, Floodgate'den neden vazgeçildiğini ve kendi sunucularınızda aynı yaklaşımı nasıl uygulayabileceğinizi inceliyoruz.

Floodgate Neden Emekli Oldu?

Cloudflare'in eski DDoS hattı dört bileşenden oluşuyordu: Gatebot trafiği analiz edip şüpheli desenlere kural üretiyordu, bpftools bu desenleri klasik BPF bayt koduna çeviriyordu, iptables xt_bpf modülüyle paketleri düşürüyordu, büyük saldırılarda ise Floodgate devreye girip paketleri çekirdeği tamamen atlayarak kullanıcı alanındaki bir BPF yorumlayıcısına yönlendiriyordu. Sistem çalışıyordu ama kritik bir bağımlılığı vardı: Floodgate, trafiği kullanıcı alanına yönlendirmek için Solarflare'in kapalı kaynak teknolojisini kullanıyordu.

Gen9 ve ARM tabanlı yeni sunucular farklı ağ kartlarıyla gelince bu mimari sürdürülemez hale geldi. Ayrıca kernel bypass yaklaşımı busy polling gerektiriyordu; yani bir CPU çekirdeği sürekli paket kolluyordu. Bu yüzden Floodgate ancak saldırı trafiği belirli bir eşiği aşınca açılıyordu, "her zaman açık" çalışmak maliyetliydi.

XDP ile Tanışma

eXpress Data Path, ağ kartı sürücüsünün paketi aldığı anda — çekirdek ağ yığınına hiç girmeden — eBPF kodu çalıştırılmasına izin veriyor. Klasik BPF'nin genişletilmiş hali olan eBPF şunları getiriyor: eBPF programları ile kullanıcı alanı arasında paylaşılan key-value yapıları olan map'ler, C dilinin önemli bir alt kümesini eBPF'ye derleyen Clang backend'i ve her programı statik olarak analiz edip hem güvenliği hem çalışma süresi garantisini sağlayan çekirdek doğrulayıcısı.

Busy polling olmadığı için XDP tabanlı bir çözüm 7/24 açık kalabiliyor. Programlar birden fazla CPU'da paralel çalışabildiği için tek çekirdeğe sabitlenmiş Floodgate'in işleyebildiğinden daha yüksek paket hızlarına ulaşmak mümkün. Red Hat'in xdp-filter ile yaptığı testlerde tek çekirdekte saniyede 26 milyon paket düşürüldüğü raporlanmış; aynı donanımda nftables bu rakamın oldukça altında kalıyor.

Kurallar Map'te Değil, Kodun İçinde

İlk bakışta mantıklı görünen yaklaşım, kuralları bir eBPF map'inde saklayıp gelen her paketi map'teki kurallarla karşılaştıran tek bir program yazmak. Facebook'un güvenlik duvarı tam olarak böyle çalışıyor ve kurallar kullanıcı alanından kolayca eklenip çıkarılabiliyor.

Ama Cloudflare'in bpftools ile ürettiği filtreler bu modele uymuyor. Tek bir p0f imzası hem IP hem TCP seçeneklerini kontrol edebiliyor; yani keyfi paket verisi eşleştirmesi ve başlıklar arası keyfi karşılaştırmalar gerekiyor. Üstüne çekirdek doğrulayıcısının döngüleri yasaklaması eklenince — bu kısıtlama programın her zaman sonlu sayıda komutta biteceğini garanti ediyor — kuralları map'te tutmak imkânsızlaşıyor.

Çözüm olarak Gatebot ve bpftools'un ürettiği kuralları doğrudan eBPF'ye derleyen bir araç yazılmış. Gatebot kurallarından bir C programı üretiliyor, Clang ile eBPF'ye derleniyor. Bedeli şu: kural eklemek veya çıkarmak için programın yeniden derlenmesi gerekiyor ve çok sayıda kuralda çekirdeğin kod karmaşıklığı limitine takılma riski var. Karşılığında paketin herhangi bir baytına karşı herhangi bir kontrol yazılabiliyor.

Klasik BPF'den eBPF'ye Çeviri Problemi

Adında "extended" geçmesine rağmen klasik BPF ile eBPF komut setleri uyumlu değil; hatta birebir eşleşme bile yok. Çekirdeğin kendi BPF'den eBPF'ye çeviricisinde tek bir IP başlık uzunluğu komutu 6 eBPF komutuna karşılık geliyor.

L4Drop ekibi bu sorunu BPF'den C'ye derleyici yazarak çözmüş. Örneğin bpftools ile *.example.com alan adına yapılan DNS sorgularını eşleştiren bir BPF programı üretildiğinde çıktı şöyle bir bayt kodu oluyor:

$ ./bpfgen dns -- "*.example.com"
18,177 0 0 0,0 0 0 20,12 0 0 0,...

Bu bayt kodu C'ye çevrildiğinde her BPF komutu tek bir C ifadesine, BPF yazmaçları (a, x ve m) ise değişkenlere dönüşüyor:

bool cbpf_0_0(uint8_t *data, uint8_t *data_end) {
    __attribute__((unused))
    uint32_t a, x, m[16];

    if (data + 1 > data_end) return false;
    x = 4*(*(data + 0) & 0xf);
    ...
}

Bu yaklaşımın ek faydası Clang'in programın tamamını optimize edebilmesi. Üretilen C kodu, çekirdeğin şart koştuğu sınır dışı paket erişimlerini engelleyen minimum sayıda guard içeriyor.

Düşürmeden Önce Örnekle

Gatebot'un analiz yapabilmesi için sunucuya gelen tüm trafiğin belirli bir oranda örneklenmesi gerekiyor — düşürülen paketler dahil. Yani örnekleme, düşürmeden önce yapılmak zorunda. Burada eBPF'nin kısıtlı helper fonksiyon setinden ikisi devreye giriyor: bpf_get_prandom_u32() rastgele sayı üretiyor, bpf_xdp_event_output ise paketi bir perf event ring buffer'a kopyalıyor. Kullanıcı alanındaki daemon bu buffer'dan okuyup örnekleri Gatebot'a iletiyor:

uint64_t rnd = (uint64_t)get_prandom_u32();

if (rnd < threshold) {
    uint64_t flags = len << 32;
    flags |= BPF_F_CURRENT_CPU;

    if (xdp_event_output(ctx, &sampled_packets, flags, &len, sizeof(len))) {
        return XDP_ABORTED;
    }
}

Lokasyona Özel Kurallar: ELF Yama Tekniği

Cloudflare'in devasa anycast ağında gerçek bir DDoS saldırısı genelde birçok veri merkezini aynı anda vurur ama her saldırı böyle değil. Bazen sadece belirli lokasyonlar için kural gerekiyor. Her lokasyon için ayrı eBPF programı derlemek yerine, program derlendikten sonra yüklemeden önce kuralları açıp kapatabilmek istenmiş.

Map'te "bu kural aktif mi" bayrağı tutmak ilk akla gelen ama her map araması kod boyutunu büyütüyor ve çekirdeğin XDP kod karmaşıklığı limiti düşünülünce tek programa sığan kural sayısı azalıyor. Bunun yerine üretilen eBPF ELF dosyası çekirdeğe yüklenmeden önce yamanıyor. C kodunda her kural şöyle bir koşulla korunuyor:

asm("%0 = RULE_0_ENABLED ll" : "=r"(enabled));

if (enabled) {
    return XDP_DROP;
} else {
    return XDP_PASS;
}

Sembol adıyla yapılan bu asm yüklemesi, ELF relocation bilgisinde RULE_0_ENABLED adıyla görünüyor. Yükleyici kütüphane relocation tablosunu okuyup ilgili yükleme komutunu buluyor, 0 ile 1 arasında değiştirerek kuralı kapatıp açıyor. Çekirdek sabitlere karşı yapılan koşul kontrollerini zaten optimize edip kaldırdığı için performans kaybı olmuyor.

Üretim Rakamları

L4Drop'un üretimde yakaladığı bir saldırı anının ölçümleri şöyle: saldırı başladığında gelen paket sayısı dik şekilde yükseliyor, ilk anda çekirdek ağ yığını bunaldığı için ağ kartında paket kayıpları görülüyor ve CPU kullanımı sıçrıyor. Gatebot saldırıyı tespit edip kural ürettikten sonra L4Drop saniyede 8 milyonun üzerinde paketi düşürmeye başlıyor. Düşürülen trafik eğrisi gelen trafik eğrisini birebir takip ediyor; geçirilen trafik miktarı ise saldırı öncesi, sırası ve sonrasında değişmiyor. Saldırının en yoğun anında toplam CPU kullanımındaki artış sadece yüzde 10 civarında. Gelen paket sayısı 40 kattan fazla artarken softirq CPU kullanımı sadece iki kat biraz üzerine çıkıyor.

Kendi Sunucunuzda Ne Yapabilirsiniz?

Cloudflare'in tam pipeline'ını kopyalamak çoğu ortam için gereksiz ama aynı prensipler küçük ölçekte de geçerli. Red Hat tabanlı sistemlerde xdp-filter aracı IP, MAC veya port bazlı kurallarla paketleri doğrudan ağ kartında düşürebiliyor ve nftables'a göre ciddi performans avantajı sağlıyor. Kendi eBPF programınızı yazmak isterseniz Cilium projesinin eBPF kütüphanesi veya libbpf ile başlayabilirsiniz; XDP programınızı ip link set dev eth0 xdpgeneric obj program.o sec xdp komutuyla yükleyip test edebilirsiniz. Generic mod tüm ağ kartlarında çalışır ama native (driver) mod gerçek performansı verir; bunun için ağ kartı sürücünüzün XDP desteklemesi gerekir.

Bu mimariden çıkarılacak asıl ders şu: paketi ne kadar erken düşürürseniz saldırının maliyeti o kadar düşük. iptables zincirinin derinliklerinde ya da kullanıcı alanında düşürülen her paket, o ana kadar harcanan tüm işlemci zamanı demek. XDP ile düşürme kararı, paket henüz ağ kartının belleğindeyken veriliyor — gerisi sadece istatistik.

ctCertWatch

SSL sertifika yayınlarını gerçek zamanlı takip eden bir izleme servisi. Regex filtreleme, DNS çözümleme ve webhook uyarıları ile phishing domainlerini, marka taklidini ve yetkisiz sertifikaları anında…

Rust

ipReconary

IP aralıkları, alt ağlar ve ASN bilgilerine dayalı olarak host, domain ve servis keşfi yapan bir ağ keşif (recon) aracı. Güvenlik uzmanları ve OSINT araştırmacıları için ağ altyapısını hızlı ve güveni…

Rust

Wireguard-Server-Panel

Web arayüzüne sahip, kendi sunucunuzda barındırılan bir WireGuard VPN servisi. İstemci yönetimi, mobil bağlantı için QR kod desteği, gerçek zamanlı trafik istatistikleri ve bağlantı kayıtları sunar. T…

LoadAlertTracker

Sistem yükünü neredeyse hiç kaynak tüketmeden izleyen, hafif bir servis. Belirlenen eşikler aşıldığında Telegram, Discord ve Mattermost gibi platformlara gerçek zamanlı uyarı gönderir.

Rust

Trustsslroot

Tek komutla eksiksiz bir özel PKI yapısı (Kök CA → Ara CA → sunucu ve istemci sertifikaları) oluşturan araç. Farklı formatlarda (p12/bks/jks) paketler üretir ve kullanıma hazır çıktı klasörleri sunar.

Go

DDNS-FW

Değişken IP adresini DDNS üzerinden gerçek zamanlı takip eden, hafif ve güvenilir bir araç. İzin verilen IP listelerini otomatik güncelleyerek kesintisiz erişim ve güvenlik sağlar. Tek dosyadan çalışı…

Rust

Cloudfiltred

Sunucu üzerinde 80/443 portlarına erişimi yalnızca Cloudflare IP aralıklarıyla sınırlandıran, tek dosyadan çalışan bir Linux servisi. Kendini kurar, sistem servisi olarak çalışır ve IP listelerini düz…

Rust

node_exporter

Linux için geliştirilmiş, hafif ve tek dosyadan çalışan, güvenli erişim kontrollü bir sistem metrikleri servisi.

Rust