Cara menjatuhkan 10 juta paket per detik

Di perusahaan, tim kami untuk melawan serangan DDoS disebut "packet droppers". Sementara semua tim lain melakukan hal-hal keren dengan lalu lintas melewati jaringan kami, kami senang menemukan cara baru untuk menghilangkannya.


Foto: Brian Evans , CC BY-SA 2.0

Kemampuan untuk dengan cepat menjatuhkan paket sangat penting dalam menentang serangan DDoS.

Paket drop yang mencapai server kami dapat dilakukan di beberapa level. Setiap metode memiliki pro dan kontra. Di bawah potongan, kami melihat semua yang kami uji.

Catatan Penerjemah: dalam output beberapa perintah yang disajikan, spasi tambahan dihapus untuk menjaga keterbacaan.

Situs uji


Untuk kenyamanan dalam membandingkan metode, kami akan memberi Anda beberapa angka, namun, jangan menerimanya secara harfiah, karena kepalsuan tes. Kami akan menggunakan salah satu kartu jaringan Intel 10Gb / s kami. Karakteristik server yang tersisa tidak begitu penting, karena kami ingin fokus pada batasan sistem operasi, bukan perangkat keras.

Tes kami akan terlihat sebagai berikut:

  • Kami menciptakan banyak paket kecil UDP, mencapai nilai 14 juta paket per detik;
  • Semua lalu lintas ini diarahkan ke satu inti prosesor dari server yang dipilih;
  • Kami mengukur jumlah paket yang diproses oleh kernel pada satu inti prosesor.

Lalu lintas buatan dihasilkan sedemikian rupa untuk menciptakan beban maksimum: alamat IP acak dan port pengirim digunakan. Ini seperti apa di tcpdump:

$ tcpdump -ni vlan100 -c 10 -t udp and dst port 1234 IP 198.18.40.55.32059 > 198.18.0.12.1234: UDP, length 16 IP 198.18.51.16.30852 > 198.18.0.12.1234: UDP, length 16 IP 198.18.35.51.61823 > 198.18.0.12.1234: UDP, length 16 IP 198.18.44.42.30344 > 198.18.0.12.1234: UDP, length 16 IP 198.18.106.227.38592 > 198.18.0.12.1234: UDP, length 16 IP 198.18.48.67.19533 > 198.18.0.12.1234: UDP, length 16 IP 198.18.49.38.40566 > 198.18.0.12.1234: UDP, length 16 IP 198.18.50.73.22989 > 198.18.0.12.1234: UDP, length 16 IP 198.18.43.204.37895 > 198.18.0.12.1234: UDP, length 16 IP 198.18.104.128.1543 > 198.18.0.12.1234: UDP, length 16 

Pada server yang dipilih, semua paket akan menjadi dalam satu antrian RX dan, karenanya, akan diproses oleh satu inti. Kami mencapainya dengan kontrol aliran perangkat keras:

 ethtool -N ext0 flow-type udp4 dst-ip 198.18.0.12 dst-port 1234 action 2 

Pengujian kinerja adalah proses yang kompleks. Ketika kami menyiapkan tes, kami melihat bahwa keberadaan soket mentah aktif secara negatif mempengaruhi kinerja, jadi sebelum menjalankan tes, Anda perlu memastikan bahwa tidak ada tcpdump yang berjalan. Ada cara mudah untuk memeriksa proses yang buruk:

 $ ss -A raw,packet_raw -l -p|cat Netid State Recv-Q Send-Q Local Address:Port p_raw UNCONN 525157 0 *:vlan100 users:(("tcpdump",pid=23683,fd=3)) 

Dan akhirnya, kami mematikan Intel Turbo Boost di server kami:

 echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo 

Terlepas dari kenyataan bahwa Turbo Boost adalah hal yang hebat dan meningkatkan throughput setidaknya 20%, itu secara signifikan merusak standar deviasi dalam pengujian kami. Dengan turbo aktif, deviasi mencapai ยฑ 1,5%, sementara tanpa turbo hanya 0,25%.



Langkah 1. Jatuhkan paket dalam aplikasi


Mari kita mulai dengan ide untuk mengirimkan semua paket ke aplikasi dan mengabaikannya di sana. Untuk kejujuran percobaan, pastikan iptables tidak memengaruhi kinerja dengan cara apa pun:

 iptables -I PREROUTING -t mangle -d 198.18.0.12 -p udp --dport 1234 -j ACCEPT iptables -I PREROUTING -t raw -d 198.18.0.12 -p udp --dport 1234 -j ACCEPT iptables -I INPUT -t filter -d 198.18.0.12 -p udp --dport 1234 -j ACCEPT 

Aplikasi adalah siklus sederhana di mana data yang diterima segera dibuang:

 s = socket.socket(AF_INET, SOCK_DGRAM) s.bind(("0.0.0.0", 1234)) while True: s.recvmmsg([...]) 

Kami sudah menyiapkan kode , jalankan:

 $ ./dropping-packets/recvmmsg-loop packets=171261 bytes=1940176 

Solusi ini memungkinkan kernel untuk mengambil hanya 175 ribu paket dari antrian perangkat keras, seperti yang diukur oleh ethtool dan mmwatch kami :

 $ mmwatch 'ethtool -S ext0|grep rx_2' rx2_packets: 174.0k/s 

Secara teknis, 14 juta paket per detik tiba di server, namun satu inti prosesor tidak dapat mengatasi volume seperti itu. mpstat mengkonfirmasi ini:

 $ watch 'mpstat -u -I SUM -P ALL 1 1|egrep -v Aver' 01:32:05 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 01:32:06 PM 0 0.00 0.00 0.00 2.94 0.00 3.92 0.00 0.00 0.00 93.14 01:32:06 PM 1 2.17 0.00 27.17 0.00 0.00 0.00 0.00 0.00 0.00 70.65 01:32:06 PM 2 0.00 0.00 0.00 0.00 0.00 100.00 0.00 0.00 0.00 0.00 01:32:06 PM 3 0.95 0.00 1.90 0.95 0.00 3.81 0.00 0.00 0.00 92.38 


Seperti yang dapat kita lihat, aplikasi ini bukan hambatan: CPU # 1 digunakan pada 27,17% + 2,17%, sementara penanganan interupsi membutuhkan 100% pada CPU # 2.

Menggunakan recvmessagge(2) memainkan peran penting. Setelah kerentanan Spectre ditemukan, panggilan sistem menjadi lebih mahal karena KPTI dan retpoline yang digunakan dalam kernel

 $ tail -n +1 /sys/devices/system/cpu/vulnerabilities/* ==> /sys/devices/system/cpu/vulnerabilities/meltdown <== Mitigation: PTI ==> /sys/devices/system/cpu/vulnerabilities/spectre_v1 <== Mitigation: __user pointer sanitization ==> /sys/devices/system/cpu/vulnerabilities/spectre_v2 <== Mitigation: Full generic retpoline, IBPB, IBRS_FW 


Langkah 2. Membunuh conntrack


Kami secara khusus membuat beban seperti itu dengan IP dan port pengirim yang berbeda untuk memuat conntrack sebanyak mungkin. Jumlah entri dalam conntrack selama tes cenderung semaksimal mungkin dan kami dapat memverifikasi ini:

 $ conntrack -C 2095202 $ sysctl net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_max = 2097152 

Selain itu, di dmesg Anda juga dapat melihat jeritan conntrack:

 [4029612.456673] nf_conntrack: nf_conntrack: table full, dropping packet [4029612.465787] nf_conntrack: nf_conntrack: table full, dropping packet [4029617.175957] net_ratelimit: 5731 callbacks suppressed 

Jadi mari kita matikan:

 iptables -t raw -I PREROUTING -d 198.18.0.12 -p udp -m udp --dport 1234 -j NOTRACK 

Dan mulai ulang tes:

 $ ./dropping-packets/recvmmsg-loop packets=331008 bytes=5296128 


Ini memungkinkan kami untuk mencapai tanda 333 ribu paket per detik. Hore!
PS Menggunakan SO_BUSY_POLL kita dapat mencapai sebanyak 470 ribu per detik, namun, ini adalah topik untuk pos yang terpisah.

Langkah 3. Filter Berkeley Batch


Mari kita lanjutkan. Mengapa kita perlu mengirimkan paket ke aplikasi? Meskipun ini bukan solusi umum, kita dapat mengikat filter paket Berkeley klasik ke soket dengan memanggil setsockopt(SO_ATTACH_FILTER) dan mengkonfigurasi filter untuk menjatuhkan paket kembali ke kernel.
Siapkan kodenya , jalankan:

 $ ./bpf-drop packets=0 bytes=0 

Menggunakan filter paket (filter Berkeley klasik dan lanjutan memberikan kinerja yang hampir sama), kami mendapatkan sekitar 512 ribu paket per detik. Selain itu, menjatuhkan paket selama interupsi membebaskan prosesor dari keharusan membangunkan aplikasi.

Langkah 4. iptables DROP setelah routing


Sekarang kita bisa menjatuhkan paket dengan menambahkan aturan berikut ke iptables di rantai INPUT:

 iptables -I INPUT -d 198.18.0.12 -p udp --dport 1234 -j DROP 

Biarkan saya mengingatkan Anda bahwa kami sudah menonaktifkan conntrack dengan aturan -j NOTRACK . Dua aturan ini memberi kita 608 ribu paket per detik.

Mari kita lihat angka-angka di iptables:

 $ mmwatch 'iptables -L -v -n -x | head' Chain INPUT (policy DROP 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 605.9k/s 26.7m/s DROP udp -- * * 0.0.0.0/0 198.18.0.12 udp dpt:1234 

Yah, tidak buruk, tapi kita bisa berbuat lebih baik.

Langkah 5. iptab DROP dalam PREROUTING


Teknik yang lebih cepat adalah dengan menjatuhkan paket sebelum routing menggunakan aturan ini:

 iptables -I PREROUTING -t raw -d 198.18.0.12 -p udp --dport 1234 -j DROP 

Ini memungkinkan kita untuk menjatuhkan paket besar 1,688 juta per detik.

Sebenarnya, ini adalah lompatan yang sedikit mengejutkan dalam kinerja. Saya masih tidak mengerti alasannya, mungkin rute kami rumit, atau mungkin hanya bug dalam konfigurasi server.

Bagaimanapun, iptables mentah jauh lebih cepat.

Langkah 6. DROP nftables


Utilitas iptables sekarang agak lama. Dia digantikan oleh nftables. Lihat penjelasan video ini tentang mengapa nftables di atas. Nftables menjanjikan lebih cepat dari iptables yang mulai memutih karena berbagai alasan, termasuk rumor bahwa retpoline banyak memperlambat iptables.

Tetapi artikel kami masih bukan tentang membandingkan iptables dan nftables, jadi mari kita coba yang tercepat yang bisa saya lakukan:

 nft add table netdev filter nft -- add chain netdev filter input { type filter hook ingress device vlan100 priority -500 \; policy accept \; } nft add rule netdev filter input ip daddr 198.18.0.0/24 udp dport 1234 counter drop nft add rule netdev filter input ip6 daddr fd00::/64 udp dport 1234 counter drop 

Penghitung dapat dilihat seperti ini:

 $ mmwatch 'nft --handle list chain netdev filter input' table netdev filter { chain input { type filter hook ingress device vlan100 priority -500; policy accept; ip daddr 198.18.0.0/24 udp dport 1234 counter packets 1.6m/s bytes 69.6m/s drop # handle 2 ip6 daddr fd00::/64 udp dport 1234 counter packets 0 bytes 0 drop # handle 3 } } 

Pengait input nftables menunjukkan nilai sekitar 1,53 juta paket. Ini sedikit kurang dari rantai PREROUTING di iptables. Tetapi ada sebuah misteri dalam hal ini: secara teoritis, kait nftables berjalan lebih awal dari iptables PREROUTING dan, karenanya, harus diproses lebih cepat.

Dalam pengujian kami, nftables sedikit lebih lambat dari iptables, tetapi nftables lebih dingin. : P

Langkah 7. tc DROP


Agak tak terduga, kait tc (traffic control) terjadi lebih awal dari iptables PREROUTING. tc memungkinkan kita untuk memilih paket sesuai dengan kriteria sederhana dan, tentu saja, menjatuhkannya. Sintaksnya agak tidak biasa, jadi sebaiknya gunakan skrip ini untuk konfigurasi. Dan kita membutuhkan aturan yang agak rumit yang terlihat seperti ini:

 tc qdisc add dev vlan100 ingress tc filter add dev vlan100 parent ffff: prio 4 protocol ip u32 match ip protocol 17 0xff match ip dport 1234 0xffff match ip dst 198.18.0.0/24 flowid 1:1 action drop tc filter add dev vlan100 parent ffff: protocol ipv6 u32 match ip6 dport 1234 0xffff match ip6 dst fd00::/64 flowid 1:1 action drop 

Dan kita dapat memeriksanya dalam aksi:

 $ mmwatch 'tc -s filter show dev vlan100 ingress' filter parent ffff: protocol ip pref 4 u32 filter parent ffff: protocol ip pref 4 u32 fh 800: ht divisor 1 filter parent ffff: protocol ip pref 4 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 (rule hit 1.8m/s success 1.8m/s) match 00110000/00ff0000 at 8 (success 1.8m/s ) match 000004d2/0000ffff at 20 (success 1.8m/s ) match c612000c/ffffffff at 16 (success 1.8m/s ) action order 1: gact action drop random type none pass val 0 index 1 ref 1 bind 1 installed 1.0/s sec Action statistics: Sent 79.7m/s bytes 1.8m/s pkt (dropped 1.8m/s, overlimits 0 requeues 0) 

Hook tc memungkinkan kami untuk menjatuhkan hingga 1,8 juta paket per detik pada satu inti. Ini bagus!
Tapi kita bisa melakukannya lebih cepat ...

Langkah 8. XDP_DROP


Dan akhirnya, senjata terkuat kami: XDP - eXpress Data Path . Menggunakan XDP, kita dapat menjalankan kode Berkley Packet Filter (eBPF) yang diperluas secara langsung dalam konteks driver jaringan dan, yang paling penting, bahkan sebelum mengalokasikan memori untuk skbuff , yang menjanjikan kita peningkatan kecepatan.

Biasanya, proyek XDP terdiri dari dua bagian:

  • kode eBPF yang dapat diunduh
  • bootloader yang menempatkan kode di antarmuka jaringan yang benar

Menulis bootloader Anda adalah tugas yang sulit, jadi gunakan saja chip iproute2 baru dan muat kode dengan perintah sederhana:

 ip link set dev ext0 xdp obj xdp-drop-ebpf.o 

Dam Ta!

Kode sumber untuk program eBPF yang dapat diunduh tersedia di sini . Program melihat karakteristik paket IP seperti protokol UDP, subnet pengirim, dan port tujuan:

 if (h_proto == htons(ETH_P_IP)) { if (iph->protocol == IPPROTO_UDP && (htonl(iph->daddr) & 0xFFFFFF00) == 0xC6120000 // 198.18.0.0/24 && udph->dest == htons(1234)) { return XDP_DROP; } } 

Program XDP harus dibangun menggunakan dentang modern, yang dapat menghasilkan bytecode BPF. Setelah itu, kami dapat mengunduh dan menguji fungsionalitas program BFP:

 $ ip link show dev ext0 4: ext0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdp qdisc fq state UP mode DEFAULT group default qlen 1000 link/ether 24:8a:07:8a:59:8e brd ff:ff:ff:ff:ff:ff prog/xdp id 5 tag aedc195cc0471f51 jited 

Dan kemudian lihat statistik di ethtool :

 $ mmwatch 'ethtool -S ext0|egrep "rx"|egrep -v ": 0"|egrep -v "cache|csum"' rx_out_of_buffer: 4.4m/s rx_xdp_drop: 10.1m/s rx2_xdp_drop: 10.1m/s 

Yoo hoo! Dengan XDP, kita dapat menjatuhkan hingga 10 juta paket per detik!


Foto: Andrew Filer , CC BY-SA 2.0

Kesimpulan


Kami mengulangi percobaan untuk IPv4 dan IPv6 dan menyiapkan diagram ini:


Secara umum, dapat dikatakan bahwa pengaturan kami untuk IPv6 sedikit lebih lambat. Tetapi karena paket IPv6 agak lebih besar, perbedaan dalam kecepatan diharapkan.

Linux memiliki banyak cara untuk memfilter paket, masing-masing dengan kecepatan dan kompleksitasnya sendiri.

Untuk melindungi dari DDoS, cukup masuk akal untuk memberikan paket ke aplikasi dan memprosesnya di sana. Aplikasi yang disesuaikan dengan baik dapat menunjukkan hasil yang baik.

Untuk serangan DDoS dengan IP acak atau spoof mungkin berguna untuk menonaktifkan conntrack untuk mendapatkan peningkatan kecil dalam kecepatan, tetapi berhati-hatilah: ada serangan terhadap yang conntrack sangat berguna.

Dalam kasus lain, masuk akal untuk menambahkan firewall Linux sebagai salah satu cara untuk mengurangi serangan DDoS. Dalam beberapa kasus, lebih baik menggunakan tabel "-t raw PREROUTING", karena jauh lebih cepat daripada tabel filter.

Untuk kasus yang paling canggih, kami selalu menggunakan XDP. Dan ya, ini adalah hal yang sangat kuat. Berikut ini adalah grafik seperti di atas, hanya dengan XDP:


Jika Anda ingin mengulang percobaan, ini adalah README, tempat kami mendokumentasikan semuanya .

Kami di CloudFlare menggunakan ... hampir semua teknik ini. Beberapa trik dalam ruang pengguna terintegrasi ke dalam aplikasi kami. Teknik iptables ditemukan di Gatebot kami. Akhirnya, kami mengganti solusi inti kami sendiri dengan XDP.

Terima kasih banyak kepada Jesper Dangaard Brouer atas bantuan mereka.

Source: https://habr.com/ru/post/id419921/


All Articles