Teknologi EXpress Data Path (XDP) memungkinkan pemrosesan lalu lintas yang sewenang-wenang pada antarmuka Linux sebelum paket tiba di tumpukan jaringan kernel. Penerapan XDP - perlindungan terhadap serangan DDoS (CloudFlare), filter canggih, pengumpulan statistik (Netflix). Program XDP dijalankan oleh mesin virtual eBPF, oleh karena itu, mereka memiliki batasan pada kode mereka dan pada fungsi kernel yang tersedia, tergantung pada jenis filter.
Artikel ini dimaksudkan untuk mengisi kekurangan berbagai materi XDP. Pertama, mereka menyediakan kode siap pakai yang segera mem-bypass fitur XDP: disiapkan untuk verifikasi atau terlalu sederhana untuk menyebabkan masalah. Ketika Anda mencoba menulis kode Anda dari awal dari awal, tidak ada pemahaman tentang apa yang harus dilakukan dengan kesalahan umum. Kedua, metode untuk menguji XDP secara lokal tanpa VM dan perangkat keras tidak tercakup, meskipun faktanya mereka memiliki perangkap sendiri. Teks ini ditujukan untuk pemrogram yang mengenal jaringan dan Linux yang tertarik pada XDP dan eBPF.
Pada bagian ini, kita akan memeriksa secara rinci bagaimana filter XDP dirakit dan bagaimana mengujinya, kemudian kita akan menulis versi sederhana dari mekanisme cookie SYN yang terkenal di tingkat pemrosesan paket. Meskipun kami tidak akan membentuk "daftar putih"
pelanggan yang diverifikasi, simpan penghitung dan kelola filter - log yang cukup.
Kami akan menulis dalam C - ini tidak modis, tetapi praktis. Semua kode tersedia di GitHub melalui tautan di bagian akhir dan dibagi menjadi komit sesuai dengan tahapan yang dijelaskan dalam artikel.
Penafian. Selama artikel, solusi-mini akan dikembangkan untuk memukul mundur dari serangan DDoS, karena ini adalah tugas yang realistis untuk XDP dan area saya. Namun, tujuan utamanya adalah untuk berurusan dengan teknologi, ini bukan panduan untuk menciptakan perlindungan yang sudah jadi. Kode pelatihan tidak dioptimalkan dan menghilangkan beberapa nuansa.
Sekilas tentang XDP
Saya hanya akan menguraikan poin-poin utama agar tidak menduplikasi dokumentasi dan artikel yang ada.
Jadi, kode filter dimuat ke dalam kernel. Paket masuk dikirim ke filter. Akibatnya, filter harus membuat keputusan: lewati paket ke kernel ( XDP_PASS
), buang paket ( XDP_DROP
) atau kirim kembali ( XDP_TX
). Filter dapat mengubah paket, ini terutama berlaku untuk XDP_TX
. Anda juga dapat menghentikan program ( XDP_ABORTED
) dan membuang paket, tetapi ini adalah analog dari pernyataan assert(0)
untuk debugging.
Mesin virtual eBPF (extended Berkley Packet Filter) secara khusus dibuat sederhana sehingga kernel dapat memverifikasi bahwa kode tidak berulang dan tidak merusak memori orang lain. Pembatasan agregat dan pemeriksaan:
- Siklus terlarang (melompat kembali).
- Ada tumpukan untuk data, tetapi tidak ada fungsi (semua fungsi C harus diuraikan).
- Akses ke memori di luar tumpukan dan buffer paket dilarang.
- Ukuran kode terbatas, tetapi dalam praktiknya ini tidak terlalu signifikan.
- Panggilan hanya diizinkan untuk fungsi kernel khusus (pembantu eBPF).
Desain dan pemasangan filter terlihat seperti ini:
- Kode sumber (misalnya,
kernel.c
) dikompilasi ke objek ( kernel.o
) di bawah arsitektur mesin virtual eBPF. Pada Oktober 2019, kompilasi dalam eBPF didukung oleh Dentang dan dijanjikan dalam GCC 10.1. - Jika dalam kode objek ini ada panggilan ke struktur kernel (misalnya, tabel dan penghitung), nol digunakan sebagai pengganti ID mereka, yaitu kode tersebut tidak dapat dieksekusi. Sebelum memuat ke dalam kernel, Anda perlu mengganti nol ini dengan ID objek tertentu yang dibuat melalui panggilan kernel (kode tautan). Anda dapat melakukan ini dengan utilitas eksternal, atau Anda dapat menulis sebuah program yang akan menautkan dan memuat filter tertentu.
- Kernel memverifikasi program yang dimuat. Tidak adanya loop dan absensi di luar batas paket dan stack diperiksa. Jika pemverifikasi tidak dapat membuktikan bahwa kode tersebut benar, program ditolak - Anda harus dapat menyenangkannya.
- Setelah verifikasi berhasil, kernel mengkompilasi kode objek arsitektur eBPF ke dalam kode mesin arsitektur sistem (just-in-time).
- Program menempel pada antarmuka dan mulai memproses paket.
Karena XDP bekerja di kernel, debugging dilakukan dengan melacak log dan, pada kenyataannya, dengan paket yang disaring atau dihasilkan oleh program. Namun, eBPF memberikan keamanan untuk kode yang dimuat untuk sistem, sehingga Anda dapat bereksperimen dengan XDP langsung di Linux lokal.
Persiapan lingkungan
Majelis
Dentang tidak bisa langsung mengeluarkan kode objek untuk arsitektur eBPF, jadi prosesnya terdiri dari dua langkah:
- Kompilasi kode C menjadi bytecode LLVM (
clang -emit-llvm
). - Konversi bytecode ke kode objek eBPF (
llc -march=bpf -filetype=obj
).
Saat menulis filter, beberapa file dengan fungsi tambahan dan makro dari pengujian kernel sangat berguna. Adalah penting bahwa mereka cocok dengan versi kernel ( KVER
). Unduh di helpers/
:
export KVER=v5.3.7 export BASE=https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/tools/testing/selftests/bpf wget -P helpers --content-disposition "${BASE}/bpf_helpers.h?h=${KVER}" "${BASE}/bpf_endian.h?h=${KVER}" unset KVER BASE
Makefile untuk Arch Linux (kernel 5.3.7):
CLANG ?= clang LLC ?= llc KDIR ?= /lib/modules/$(shell uname -r)/build ARCH ?= $(subst x86_64,x86,$(shell uname -m)) CFLAGS = \ -Ihelpers \ \ -I$(KDIR)/include \ -I$(KDIR)/include/uapi \ -I$(KDIR)/include/generated/uapi \ -I$(KDIR)/arch/$(ARCH)/include \ -I$(KDIR)/arch/$(ARCH)/include/generated \ -I$(KDIR)/arch/$(ARCH)/include/uapi \ -I$(KDIR)/arch/$(ARCH)/include/generated/uapi \ -D__KERNEL__ \ \ -fno-stack-protector -O2 -g xdp_%.o: xdp_%.c Makefile $(CLANG) -c -emit-llvm $(CFLAGS) $< -o - | \ $(LLC) -march=bpf -filetype=obj -o $@ .PHONY: all clean all: xdp_filter.o clean: rm -f ./*.o
KDIR
berisi path ke header kernel, ARCH
- arsitektur sistem. Jalur dan alat mungkin sedikit berbeda antar distribusi.
Contoh perbedaan untuk Debian 10 (kernel 4.19.67) # CLANG ?= clang LLC ?= llc-7 # KDIR ?= /usr/src/linux-headers-$(shell uname -r) ARCH ?= $(subst x86_64,x86,$(shell uname -m)) # -I CFLAGS = \ -Ihelpers \ \ -I/usr/src/linux-headers-4.19.0-6-common/include \ -I/usr/src/linux-headers-4.19.0-6-common/arch/$(ARCH)/include \ #
CFLAGS
termasuk direktori dengan header tambahan dan beberapa direktori dengan header kernel. Simbol __KERNEL__
berarti bahwa header UAPI (userspace APIs) didefinisikan untuk kode kernel, karena filter berjalan di kernel.
Perlindungan tumpukan dapat dinonaktifkan ( -fno-stack-protector
), karena pemverifikasi kode eBPF masih memeriksa jalan keluar dari tumpukan. Optimasi harus dimasukkan segera karena ukuran bytecode eBPF terbatas.
Mari kita mulai dengan filter yang melewatkan semua paket dan tidak melakukan apa pun:
#include <uapi/linux/bpf.h> #include <bpf_helpers.h> SEC("prog") int xdp_main(struct xdp_md* ctx) { return XDP_PASS; } char _license[] SEC("license") = "GPL";
Perintah make
xdp_filter.o
. Di mana mengujinya sekarang?
Test stand
Stand harus mencakup dua antarmuka: di mana akan ada filter dan dari mana paket akan dikirim. Ini harus perangkat Linux lengkap dengan IP mereka untuk memeriksa bagaimana aplikasi reguler bekerja dengan filter kami.
Perangkat seperti veth (virtual Ethernet) cocok untuk kita: mereka adalah sepasang antarmuka jaringan virtual yang "terhubung" secara langsung satu sama lain. Anda dapat membuatnya seperti ini (di bagian ini, semua perintah ip
dijalankan sebagai root
):
ip link add xdp-remote type veth peer name xdp-local
Di sini xdp-remote
dan xdp-local
adalah nama perangkat. Filter akan dilampirkan ke xdp-local
(192.0.2.1/24), dan lalu lintas masuk akan dikirim dari xdp-remote
(192.0.2.2/24). Namun, ada masalah: antarmuka berada di mesin yang sama, dan Linux tidak akan mengirimkan lalu lintas ke salah satu dari mereka melalui yang lain. Anda dapat menyelesaikan ini dengan aturan iptables
rumit, tetapi mereka harus mengubah paketnya, yang tidak nyaman saat debugging. Lebih baik menggunakan ruang nama jaringan (ruang nama jaringan, selanjutnya jaring).
Namespace jaringan berisi seperangkat antarmuka, tabel perutean, dan aturan NetFilter, terisolasi dari objek serupa di jaringan lain. Setiap proses berjalan dalam namespace, dan hanya objek-objek dari jaring ini yang dapat diakses. Secara default, sistem memiliki satu namespace jaringan untuk semua objek, sehingga Anda dapat bekerja di Linux dan tidak tahu tentang jaringan.
Buat namespace xdp-test
dan pindahkan xdp-remote
.
ip netns add xdp-test ip link set dev xdp-remote netns xdp-test
Maka proses yang berjalan di xdp-test
tidak akan βmelihatβ xdp-local
(ini akan tetap dalam jaringan secara default) dan akan mengirimkannya melalui xdp-remote
ketika mengirim paket ke 192.0.2.1, karena ini adalah satu-satunya antarmuka di 192.0.2.0/ 24 tersedia untuk proses ini. Ini juga bekerja berlawanan arah.
Saat berpindah antar jaringan, antarmuka menjatuhkan dan kehilangan alamat. Untuk mengkonfigurasi antarmuka di netns, Anda perlu menjalankan ip ...
di ip netns exec
namespace ip netns exec
command:
ip netns exec xdp-test \ ip address add 192.0.2.2/24 dev xdp-remote ip netns exec xdp-test \ ip link set xdp-remote up
Seperti yang Anda lihat, ini tidak berbeda dari pengaturan xdp-local
di namespace default:
ip address add 192.0.2.1/24 dev xdp-local ip link set xdp-local up
Jika Anda menjalankan tcpdump -tnevi xdp-local
, Anda dapat melihat bahwa paket yang dikirim dari xdp-test
dikirimkan ke antarmuka ini:
ip netns exec xdp-test ping 192.0.2.1
Lebih mudah untuk menjalankan shell di xdp-test
. Ada skrip di repositori yang mengotomatiskan pekerjaan dengan dudukan, misalnya, Anda dapat mengonfigurasi dudukan dengan perintah sudo ./stand up
dan menghapusnya dengan perintah sudo ./stand down
.
Jejak
Filter terpasang ke perangkat sebagai berikut:
ip -force link set dev xdp-local xdp object xdp_filter.o verbose
-force
diperlukan untuk mengikat program baru jika yang lain sudah terikat. "Tidak ada berita adalah kabar baik" bukan tentang perintah ini, kesimpulannya produktif. verbose
opsional, tetapi dengan itu laporan muncul pada karya verifikasi kode dengan daftar perakitan:
Verifier analysis: 0: (b7) r0 = 2 1: (95) exit
Lepaskan program dari antarmuka:
ip link set dev xdp-local xdp off
Dalam skrip, ini adalah perintah sudo ./stand attach
dan sudo ./stand detach
.
Dengan melampirkan filter, Anda dapat memverifikasi bahwa ping
terus berfungsi, tetapi apakah program ini bekerja? Tambahkan log. Fungsi bpf_trace_printk()
mirip dengan printf()
, tetapi hanya mendukung hingga tiga argumen, kecuali untuk templat, dan daftar kualifikasi yang terbatas. bpf_printk()
menyederhanakan panggilan.
SEC("prog") int xdp_main(struct xdp_md* ctx) { + bpf_printk("got packet: %p\n", ctx); return XDP_PASS; }
Outputnya pergi ke saluran jejak kernel, yang harus Anda aktifkan:
echo -n 1 | sudo tee /sys/kernel/debug/tracing/options/trace_printk
Lihat aliran pesan:
cat /sys/kernel/debug/tracing/trace_pipe
Kedua perintah ini melakukan panggilan ke sudo ./stand log
.
Ping sekarang seharusnya memicu pesan-pesan berikut di dalamnya:
<...>-110930 [004] ..s1 78803.244967: 0: got packet: 00000000ac510377
Jika Anda melihat dengan seksama pada keluaran verifier, Anda akan melihat perhitungan aneh:
0: (bf) r3 = r1 1: (18) r1 = 0xa7025203a7465 3: (7b) *(u64 *)(r10 -8) = r1 4: (18) r1 = 0x6b63617020746f67 6: (7b) *(u64 *)(r10 -16) = r1 7: (bf) r1 = r10 8: (07) r1 += -16 9: (b7) r2 = 16 10: (85) call bpf_trace_printk#6 <...>
Faktanya adalah bahwa program eBPF tidak memiliki bagian data, jadi satu-satunya cara untuk menyandikan string format adalah dengan argumen langsung dari perintah VM:
$ python -c "import binascii; print(bytes(reversed(binascii.unhexlify('0a7025203a74656b63617020746f67'))))" b'got packet: %p\n'
Karena alasan ini, output debug sangat mengembang kode yang dihasilkan.
Mengirim Paket XDP
Mari kita ubah filter: biarkan mengirim semua paket masuk kembali. Ini tidak benar dari sudut pandang jaringan, karena akan perlu untuk mengubah alamat di header, tetapi pada prinsipnya pekerjaan saat ini penting.
bpf_printk("got packet: %p\n", ctx); - return XDP_PASS; + return XDP_TX; }
Jalankan tcpdump
di xdp-remote
. Itu harus memperlihatkan ICMP Echo Request keluar dan masuk yang identik dan berhenti menampilkan ICMP Echo Reply. Tetapi tidak muncul. Ternyata agar XDP_TX
dapat berfungsi dalam program di xdp-local
perlu bahwa antarmuka xdp-remote
antarmuka xdp-remote
, bahkan jika itu kosong, dan harus dinaikkan.
Bagaimana saya mengetahuinya?Omong-omong, mekanisme perf events, menggunakan mesin virtual yang sama memungkinkan melacak jalur paket di kernel , yaitu, eBPF digunakan untuk membongkar dengan eBPF.
Anda harus berbuat baik dari kejahatan, karena tidak ada lagi yang bisa dilakukan darinya.
$ sudo perf trace --call-graph dwarf -e 'xdp:*' 0.000 ping/123455 xdp:xdp_bulk_tx:ifindex=19 action=TX sent=0 drops=1 err=-6 veth_xdp_flush_bq ([veth]) veth_xdp_flush_bq ([veth]) veth_poll ([veth]) <...>
Apa itu kode 6?
$ errno 6 ENXIO 6 No such device or address
Fungsi veth_xdp_flush_bq()
menerima kode kesalahan dari veth_xdp_xmit()
, di mana kami mencari dengan ENXIO
dan menemukan komentar.
Kembalikan filter minimum ( XDP_PASS
) dalam file xdp_dummy.c
, tambahkan ke Makefile, lampirkan ke xdp-remote
:
ip netns exec remote \ ip link set dev int xdp object dummy.o
Sekarang tcpdump
menunjukkan apa yang diharapkan:
62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84) 192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64 62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84) 192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64
Jika hanya ARP yang ditampilkan, Anda harus menghapus filter (ini dilakukan dengan sudo ./stand detach
), mulai ping
, lalu atur filter dan coba lagi. Masalahnya adalah bahwa filter XDP_TX
memengaruhi ARP dan jika stack
xdp-test
namespace berhasil "melupakan" alamat MAC 192.0.2.1, itu tidak akan dapat menyelesaikan IP ini.
Pernyataan masalah
Mari kita beralih ke tugas yang disebutkan: tulis mekanisme cookie SYN di XDP.
Sampai sekarang, SYN flood tetap menjadi serangan DDoS yang populer, intinya adalah sebagai berikut. Saat membuat koneksi (jabat tangan TCP), server menerima SYN, mengalokasikan sumber daya untuk koneksi di masa depan, merespons dengan paket SYNACK, dan menunggu ACK. Penyerang hanya mengirim paket SYN dari alamat palsu dalam jumlah ribuan per detik dari setiap host dari botnet multi-ribu. Server dipaksa untuk mengalokasikan sumber daya segera setelah kedatangan paket, dan membebaskan oleh batas waktu yang besar, sebagai akibatnya, memori atau batas habis, koneksi baru tidak diterima, layanan tidak tersedia.
Jika Anda tidak mengalokasikan sumber daya untuk paket SYN, tetapi hanya merespons dengan paket SYNACK, lalu bagaimana server dapat memahami bahwa paket ACK yang datang kemudian merujuk ke paket SYN yang tidak disimpan? Bagaimanapun, penyerang juga dapat menghasilkan ACK palsu. Inti dari cookie SYN adalah untuk menyandikan parameter seqnum
di seqnum
sebagai hash dari alamat, port dan mengganti garam. Jika ACK berhasil tiba sebelum perubahan garam, Anda dapat sekali lagi menghitung hash dan membandingkannya dengan acknum
. Penyerang tidak dapat memalsukan acknum
, karena garam termasuk rahasia, dan tidak akan punya waktu untuk memilah karena saluran terbatas.
Cookie SYN telah lama diimplementasikan dalam kernel Linux dan bahkan dapat secara otomatis hidup jika SYN tiba terlalu cepat dan secara massal.
Program pendidikan tentang jabat tangan TCPTCP menyediakan transfer data sebagai aliran byte, misalnya, permintaan HTTP dikirim melalui TCP. Aliran ditransmisikan dalam bentuk paket. Semua paket TCP memiliki flag logis dan nomor urut 32-bit:
Kombinasi bendera menentukan peran paket tertentu. Bendera SYN berarti bahwa ini adalah paket pengirim pertama dalam koneksi. Bendera ACK berarti bahwa pengirim menerima semua data koneksi sebelum byte acknum
. Paket dapat memiliki beberapa flag dan dipanggil dengan kombinasi mereka, misalnya, paket SYNACK.
Sequence number (seqnum) mendefinisikan offset dalam aliran data untuk byte pertama yang dikirimkan dalam paket ini. Misalnya, jika dalam paket pertama dengan data X byte angka ini adalah N, dalam paket berikutnya dengan data baru akan menjadi N + X. Di awal koneksi, masing-masing pihak memilih nomor ini secara sewenang-wenang.
Pengakuan nomor (acknum) - offset yang sama dengan seqnum, tetapi tidak menentukan jumlah byte yang akan dikirim, tetapi jumlah byte pertama dari penerima yang tidak dilihat pengirim.
Di awal koneksi, para pihak harus menyetujui seqnum
dan acknum
. Klien mengirim paket SYN dengan seqnum = X
Server merespons dengan paket SYNACK, di mana ia menulis seqnum = Y
dan menetapkan acknum = X + 1
. Klien merespon SYNACK dengan paket ACK, di mana seqnum = X + 1
, acknum = Y + 1
. Setelah itu, transfer data aktual dimulai.
Jika teman bicara tidak mengkonfirmasi penerimaan paket, TCP mengirimkannya lagi oleh batas waktu.
Mengapa cookie SYN tidak selalu digunakan?Pertama, jika SYNACK atau ACK hilang, Anda harus menunggu pengiriman ulang - koneksi melambat. Kedua, dalam paket SYN - dan hanya di dalamnya! - sejumlah opsi dikirimkan yang mempengaruhi operasi koneksi selanjutnya. Tanpa mengingat paket SYN yang masuk, server mengabaikan opsi-opsi ini, pada paket selanjutnya klien tidak akan mengirimnya lagi. Dalam hal ini, TCP dapat berfungsi, tetapi setidaknya pada tahap awal, kualitas koneksi akan menurun.
Dalam hal paket, program XDP harus melakukan hal berikut:
- SYNACK dengan cookie untuk menanggapi SYN;
- menanggapi ACK RST (putuskan);
- buang paket lain.
Algoritma pseudocode dan parsing paket:
Ethernet, . IPv4, . , (*) , . TCP, . (**) SYN, SYN-ACK cookie. ACK, acknum cookie, . N . (*) RST. (**) .
Satu (*)
menunjukkan titik-titik di mana untuk mengontrol keadaan sistem - pada tahap pertama, Anda dapat melakukannya tanpa mereka dengan hanya menerapkan jabat tangan TCP dengan menghasilkan cookie SYN sebagai seqnum.
Di tempat (**)
, sementara kami tidak memiliki tabel, kami akan melewatkan paket.
Implementasi handshake TCP
Parsing paket dan verifikasi kodenya
Kami membutuhkan struktur tajuk jaringan: Ethernet ( uapi/linux/if_ether.h
), IPv4 ( uapi/linux/ip.h
) dan TCP ( uapi/linux/tcp.h
). Yang terakhir saya tidak bisa terhubung karena kesalahan terkait dengan atomic64_t
, saya harus menyalin definisi yang diperlukan ke dalam kode.
Semua fungsi yang dialokasikan dalam C untuk keterbacaan harus built-in di tempat panggilan, karena verifikasi eBPF di kernel melarang transisi kembali, yaitu, pada kenyataannya, loop dan panggilan fungsi.
#define INTERNAL static __attribute__((always_inline))
Makro LOG()
menonaktifkan pencetakan di rilis rilis.
Program ini merupakan konveyor fungsi. Setiap menerima paket di mana tajuk dari tingkat yang sesuai disorot, misalnya, process_ether()
mengharapkan ether
menjadi penuh. Berdasarkan hasil analisis lapangan, fungsi tersebut dapat mentransfer paket ke tingkat yang lebih tinggi. Hasil dari fungsi adalah tindakan XDP. Sejauh ini, penangan SYN dan ACK melewati semua paket.
struct Packet { struct xdp_md* ctx; struct ethhdr* ether; struct iphdr* ip; struct tcphdr* tcp; }; INTERNAL int process_tcp_syn(struct Packet* packet) { return XDP_PASS; } INTERNAL int process_tcp_ack(struct Packet* packet) { return XDP_PASS; } INTERNAL int process_tcp(struct Packet* packet) { ... } INTERNAL int process_ip(struct Packet* packet) { ... } INTERNAL int process_ether(struct Packet* packet) { struct ethhdr* ether = packet->ether; LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto)); if (ether->h_proto != bpf_ntohs(ETH_P_IP)) { return XDP_PASS; } // B struct iphdr* ip = (struct iphdr*)(ether + 1); if ((void*)(ip + 1) > (void*)packet->ctx->data_end) { return XDP_DROP; /* malformed packet */ } packet->ip = ip; return process_ip(packet); } SEC("prog") int xdp_main(struct xdp_md* ctx) { struct Packet packet; packet.ctx = ctx; // A struct ethhdr* ether = (struct ethhdr*)(void*)ctx->data; if ((void*)(ether + 1) > (void*)ctx->data_end) { return XDP_PASS; } packet.ether = ether; return process_ether(&packet); }
Saya menarik perhatian pada cek bertanda A dan B. Jika Anda mengomentari A, program akan berkumpul, tetapi akan ada kesalahan verifikasi saat memuat:
Verifier analysis: <...> 11: (7b) *(u64 *)(r10 -48) = r1 12: (71) r3 = *(u8 *)(r7 +13) invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0) R7 offset is outside of the packet processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 Error fetching program/map!
Garis kuncinya adalah invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0)
: ada jalur eksekusi ketika byte ketiga belas dari awal buffer berada di luar paket. Menurut daftar, sulit untuk memahami baris mana yang sedang kita bicarakan, tetapi ada nomor instruksi (12) dan disassembler yang menunjukkan baris kode sumber:
llvm-objdump -S xdp_filter.o | less
Dalam hal ini, itu menunjuk ke sebuah string
LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto));
dimana jelas bahwa masalahnya ada di ether
. Akan selalu begitu.
Balas ke SYN
Tujuan pada tahap ini adalah untuk membentuk paket SYNACK yang benar dengan seqnum
tetap, yang akan digantikan oleh cookie SYN di masa mendatang. Semua perubahan terjadi di process_tcp_syn()
dan area sekitarnya.
Paket cek
Anehnya, inilah garis yang paling luar biasa, lebih tepatnya, komentar tentang itu:
/* Required to verify checksum calculation */ const void* data_end = (const void*)ctx->data_end;
Saat menulis versi pertama dari kode, kernel 5.1 digunakan, untuk verifikasi yang ada perbedaan antara data_end
dan (const void*)ctx->data_end
. Saat menulis artikel, kernel 5.3.1 tidak memiliki masalah seperti itu. Mungkin kompiler mengakses variabel lokal berbeda dari bidang. Moral - kode yang disederhanakan dapat membantu dengan banyak sarang.
Pemeriksaan rutin lebih lanjut untuk menghormati verifikasi; tentang MAX_CSUM_BYTES
bawah.
const u32 ip_len = ip->ihl * 4; if ((void*)ip + ip_len > data_end) { return XDP_DROP; /* malformed packet */ } if (ip_len > MAX_CSUM_BYTES) { return XDP_ABORTED; /* implementation limitation */ } const u32 tcp_len = tcp->doff * 4; if ((void*)tcp + tcp_len > (void*)ctx->data_end) { return XDP_DROP; /* malformed packet */ } if (tcp_len > MAX_CSUM_BYTES) { return XDP_ABORTED; /* implementation limitation */ }
Paket menyebar
Isi seqnum
dan acknum
, atur ACK (SYN sudah diatur):
const u32 cookie = 42; tcp->ack_seq = bpf_htonl(bpf_ntohl(tcp->seq) + 1); tcp->seq = bpf_htonl(cookie); tcp->ack = 1;
Tukar port TCP, alamat IP, dan alamat MAC. Pustaka standar tidak dapat diakses dari program XDP, jadi memcpy()
adalah makro yang menyembunyikan intrinsik Dentang.
const u16 temp_port = tcp->source; tcp->source = tcp->dest; tcp->dest = temp_port; const u32 temp_ip = ip->saddr; ip->saddr = ip->daddr; ip->daddr = temp_ip; struct ethhdr temp_ether = *ether; memcpy(ether->h_dest, temp_ether.h_source, ETH_ALEN); memcpy(ether->h_source, temp_ether.h_dest, ETH_ALEN);
Perhitungan ulang checksum
Checksum IPv4 dan TCP memerlukan penambahan semua kata 16-bit dalam header, dan ukuran header ditulis di dalamnya, yaitu, pada saat kompilasi tidak diketahui. Ini merupakan masalah karena verifier tidak akan melewatkan loop reguler ke batas variabel. Tetapi ukuran header terbatas: masing-masing hingga 64 byte. Anda dapat membuat lingkaran dengan sejumlah iterasi, yang dapat berakhir lebih cepat dari jadwal.
Saya perhatikan bahwa ada RFC 1624 tentang cara menghitung ulang checksum sebagian jika hanya kata-kata paket tetap yang diubah. Namun, metode ini tidak universal, dan implementasi akan lebih sulit untuk dipertahankan.
Fungsi perhitungan Checksum:
#define MAX_CSUM_WORDS 32 #define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2) INTERNAL u32 sum16(const void* data, u32 size, const void* data_end) { u32 s = 0; #pragma unroll for (u32 i = 0; i < MAX_CSUM_WORDS; i++) { if (2*i >= size) { return s; /* normal exit */ } if (data + 2*i + 1 + 1 > data_end) { return 0; /* should be unreachable */ } s += ((const u16*)data)[i]; } return s; }
, size
, , .
32- :
INTERNAL u32 sum16_32(u32 v) { return (v >> 16) + (v & 0xffff); }
:
ip->check = 0; ip->check = carry(sum16(ip, ip_len, data_end)); u32 tcp_csum = 0; tcp_csum += sum16_32(ip->saddr); tcp_csum += sum16_32(ip->daddr); tcp_csum += 0x0600; tcp_csum += tcp_len << 8; tcp->check = 0; tcp_csum += sum16(tcp, tcp_len, data_end); tcp->check = carry(tcp_csum); return XDP_TX;
carry()
32- 16- , RFC 791.
TCP
netcat
, ACK, Linux RST-, SYN β SYNACK - , .
$ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666 192.0.2.1 6666: Connection reset by peer
tcpdump
xdp-remote
, , hping3
.
SYN cookie
XDP . , , . Linux, , SipHash, XDP .
TODO, :
:
$ sudoip netns exec xdp-test nc -nv 192.0.2.1 6666 192.0.2.1 6666: Connection reset by peer
( flags=0x2
β SYN, flags=0x10
β ACK):
Ether(proto=0x800) IP(src=0x20e6e11a dst=0x20e6e11e proto=6) TCP(sport=50836 dport=6666 flags=0x2) Ether(proto=0x800) IP(src=0xfe2cb11a dst=0xfe2cb11e proto=6) TCP(sport=50836 dport=6666 flags=0x10) cookie matches for client 20200c0
IP, SYN flood , ACK flood, :
sudo ip netns exec xdp-test hping3 --flood -A -s 1111 -p 2222 192.0.2.1
:
Ether(proto=0x800) IP(src=0x15bd11a dst=0x15bd11e proto=6) TCP(sport=3236 dport=2222 flags=0x10) cookie mismatch
Kesimpulan
eBPF XDP , . , XDP β , , DPDK kernel bypass. , XDP , , , . , userspace-.
, , , userspace- .
Referensi: