Kita nulis proteksi marang serangan DDoS ing XDP. Bagian nuklir

Teknologi eXpress Data Path (XDP) ngidini pangolahan lalu lintas sewenang-wenang ing antarmuka Linux sadurunge paket mlebu tumpukan jaringan kernel. Aplikasi XDP - pangayoman marang serangan DDoS (CloudFlare), saringan kompleks, koleksi statistik (Netflix). Program XDP dieksekusi dening mesin virtual eBPF, lan mulane duwe watesan ing kode lan fungsi kernel sing kasedhiya, gumantung saka jinis panyaring.

Artikel kasebut dimaksudake kanggo ngrampungake kekurangan akeh materi ing XDP. Kaping pisanan, dheweke nyedhiyakake kode siap sing langsung ngliwati fitur XDP: disiapake kanggo verifikasi utawa gampang banget kanggo nyebabake masalah. Nalika sampeyan nyoba kanggo nulis kode dhewe saka ngeruk mengko, ora ana pangerten apa apa karo kasalahan khas. Kapindho, ora kalebu cara kanggo nyoba XDP sacara lokal tanpa VM lan hardware, sanajan kasunyatane duwe pitfalls dhewe. Teks kasebut ditujokake kanggo programer sing kenal karo jaringan lan Linux sing kasengsem ing XDP lan eBPF.

Ing bagean iki, kita bakal ngerti kanthi rinci babagan carane panyaring XDP dipasang lan cara nguji, banjur bakal nulis versi prasaja saka mekanisme cookie SYN sing kondhang ing tingkat pangolahan paket. Nganti kita nggawe "daftar putih"
klien diverifikasi, tetep counter lan ngatur Filter - cukup log.

Kita bakal nulis ing C - iki ora modis, nanging praktis. Kabeh kode kasedhiya ing GitHub ing link ing pungkasan lan dipérang dadi komit miturut langkah-langkah sing diterangake ing artikel kasebut.

Penafian. Sajrone artikel kasebut, solusi mini kanggo ngusir serangan DDoS bakal dikembangake, amarga iki minangka tugas sing nyata kanggo XDP lan wilayahku. Nanging, tujuan utama yaiku ngerteni teknologi kasebut, iki dudu pandhuan kanggo nggawe proteksi sing wis siap. Kode tutorial ora dioptimalake lan ngilangi sawetara nuansa.

A Ringkesan Brief saka XDP

Aku mung bakal nyatakake poin-poin penting supaya ora nggawe duplikat dokumentasi lan artikel sing wis ana.

Dadi, kode panyaring dimuat menyang kernel. Filter kasebut ngliwati paket sing mlebu. Akibaté, saringan kudu nggawe keputusan: ngirim paket menyang kernel (XDP_PASS), paket drop (XDP_DROP) utawa ngirim maneh (XDP_TX). Filter bisa ngganti paket, iki utamané bener kanggo XDP_TX. Sampeyan uga bisa nabrak program (XDP_ABORTED) lan nyelehake paket kasebut, nanging iki analog assert(0) - kanggo debugging.

Mesin virtual eBPF (Extended Berkley Packet Filter) sengaja digawe prasaja supaya kernel bisa mriksa manawa kode kasebut ora digulung lan ora ngrusak memori wong liya. Watesan kumulatif lan mriksa:

  • Loops (mlumpat mundur) dilarang.
  • Ana tumpukan kanggo data, nanging ora ana fungsi (kabeh fungsi C kudu inline).
  • Akses menyang memori njaba tumpukan lan paket buffer dilarang.
  • Ukuran kode diwatesi, nanging ing praktik iki ora pati penting.
  • Mung fungsi kernel khusus (pembantu eBPF) sing diidini.

Ngembangake lan nginstal filter katon kaya iki:

  1. kode sumber (mis. kernel.c) kompilasi kanggo obyek (kernel.o) kanggo arsitektur mesin virtual eBPF. Ing Oktober 2019, kompilasi menyang eBPF didhukung dening Clang lan dijanjekake ing GCC 10.1.
  2. Yen ing kode obyek iki ana telpon kanggo struktur kernel (contone, kanggo tabel lan counters), tinimbang ID sing ana nul, sing kode kuwi ora bisa kaleksanan. Sadurunge dimuat menyang kernel, nul iki kudu diganti karo ID obyek tartamtu digawe liwat telpon kernel (link kode). Sampeyan bisa nindakake iki karo keperluan external, utawa sampeyan bisa nulis program sing bakal link lan mbukak Filter tartamtu.
  3. Kernel verifikasi program sing dimuat. Iku mriksa anané siklus lan non-metu saka paket lan tumpukan wates. Yen verifikasi ora bisa mbuktekake manawa kode kasebut bener, program kasebut ditolak - siji kudu bisa nyenengake dheweke.
  4. Sawise verifikasi sukses, kernel nglumpukake kode obyek arsitektur eBPF dadi kode mesin arsitektur sistem (just-in-time).
  5. Program kasebut dipasang ing antarmuka lan miwiti ngolah paket.

Wiwit XDP mlaku ing kernel, debugging ditindakake kanthi log tilak lan, nyatane, kanthi paket sing disaring utawa digawe program. Nanging, eBPF njaga kode sing diundhuh kanthi aman kanggo sistem, supaya sampeyan bisa nyoba XDP langsung ing Linux lokal.

Nyiapake Lingkungan

Majelis

Clang ora bisa langsung ngetokake kode obyek kanggo arsitektur eBPF, mula proses kasebut dumadi saka rong langkah:

  1. Kompilasi kode C menyang kode byte LLVM (clang -emit-llvm).
  2. Ngonversi bytecode menyang kode obyek eBPF (llc -march=bpf -filetype=obj).

Nalika nulis saringan, sawetara file kanthi fungsi tambahan lan makro bakal migunani saka tes kernel. Penting yen padha cocog karo versi kernel (KVER). Ngundhuh menyang 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 kanggo 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 ngemot path menyang header kernel, ARCH - arsitektur sistem. Path lan alat bisa beda-beda rada antarane distribusi.

Conto beda kanggo 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 kalebu direktori kanthi header tambahan lan sawetara direktori kanthi header kernel. Simbol __KERNEL__ tegese UAPI (userspace API) header ditetepake kanggo kode kernel, wiwit Filter wis kaleksanan ing kernel.

Proteksi tumpukan bisa dipateni (-fno-stack-protector) amarga verifikasi kode eBPF mriksa wates tumpukan sing ora metu. Sampeyan kudu langsung ngaktifake optimasi, amarga ukuran bytecode eBPF diwatesi.

Ayo miwiti karo saringan sing ngliwati kabeh paket lan ora nindakake apa-apa:

#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";

tim make nglumpukake xdp_filter.o. Ing endi sampeyan bisa nyoba saiki?

bangku test

Ngadeg kudu kalebu rong antarmuka: sing bakal ana saringan lan saka ngendi paket bakal dikirim. Iki kudu dadi piranti Linux lengkap kanthi IP dhewe kanggo mriksa cara aplikasi biasa bisa digunakake karo filter kita.

Piranti kaya veth (Ethernet virtual) cocok kanggo kita: iku pasangan antarmuka jaringan virtual "disambungake" langsung kanggo saben liyane. Sampeyan bisa nggawe kaya iki (ing bagean iki, kabeh printah ip dileksanakake saka root):

ip link add xdp-remote type veth peer name xdp-local

iku xdp-remote и xdp-local - jeneng piranti. On xdp-local (192.0.2.1/24) Filter bakal ditempelake, karo xdp-remote (192.0.2.2/24) lalu lintas mlebu bakal dikirim. Nanging, ana masalah: antarmuka ana ing mesin sing padha, lan Linux ora bakal ngirim lalu lintas menyang salah sijine liwat liyane. Sampeyan bisa ngatasi kanthi aturan sing angel iptables, nanging kudu ngganti paket, sing ora trep nalika debugging. Luwih becik nggunakake ruang jeneng jaringan (ruang jeneng jaringan, jaringan luwih lanjut).

Ruang jeneng jaringan ngemot sakumpulan antarmuka, tabel rute, lan aturan NetFilter sing diisolasi saka obyek sing padha ing jaring liyane. Saben proses lumaku ing sawetara namespace, lan mung obyek saka netns iki kasedhiya kanggo iku. Kanthi gawan, sistem duwe ruang jeneng jaringan siji kanggo kabeh obyek, supaya sampeyan bisa nggarap Linux lan ora ngerti babagan netns.

Ayo nggawe ruang jeneng anyar xdp-test lan pindhah menyang kono xdp-remote.

ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test

Banjur proses mlaku ing xdp-test, ora bakal "ndeleng" xdp-local (bakal tetep ing netns minangka standar) lan nalika ngirim paket menyang 192.0.2.1 bakal ngliwati xdp-remote, amarga iku mung antarmuka ing 192.0.2.0/24 kasedhiya kanggo proses iki. Iki uga dianggo ing mbalikke.

Nalika obah antarane netns, antarmuka mudhun lan ilang alamat. Kanggo nyiyapake antarmuka ing netns, sampeyan kudu mbukak ip ... ing namespace printah iki ip netns exec:

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

Nalika sampeyan bisa ndeleng, iki ora beda saka setelan xdp-local ing ruang jeneng standar:

    ip address add 192.0.2.1/24 dev xdp-local
    ip link set xdp-local up

Yen mlayu tcpdump -tnevi xdp-local, sampeyan bisa ndeleng sing paket dikirim saka xdp-test, dikirim menyang antarmuka iki:

ip netns exec xdp-test   ping 192.0.2.1

Iku trep kanggo mbukak cangkang ing xdp-test. Repositori duwe skrip sing ngotomatisasi kerja karo stand, umpamane, sampeyan bisa nyetel stand kanthi prentah. sudo ./stand up lan copot sudo ./stand down.

nglacak

Filter dipasang ing piranti kaya mangkene:

ip -force link set dev xdp-local xdp object xdp_filter.o verbose

Kunci -force perlu kanggo ngubungake program anyar yen program liyane wis disambung. "Ora ana kabar sing apik" dudu babagan prentah iki, output uga akeh banget. nunjukake verbose opsional, nanging karo laporan babagan karya verifier kode karo listing assembler katon:

Verifier analysis:

0: (b7) r0 = 2
1: (95) exit

Copot program saka antarmuka:

ip link set dev xdp-local xdp off

Ing skrip, iki minangka perintah sudo ./stand attach и sudo ./stand detach.

Kanthi ngiket saringan, sampeyan bisa nggawe manawa ping terus bisa, nanging program bisa? Ayo nambah logo. Fungsi bpf_trace_printk() padha karo printf(), nanging mung ndhukung nganti telung argumen liyane saka pola, lan dhaftar winates saka specifiers. Makro bpf_printk() simplifies telpon.

   SEC("prog")
   int xdp_main(struct xdp_md* ctx) {
+      bpf_printk("got packet: %pn", ctx);
       return XDP_PASS;
   }

Output menyang saluran jejak kernel, sing kudu diaktifake:

echo -n 1 | sudo tee /sys/kernel/debug/tracing/options/trace_printk

Deleng alur pesen:

cat /sys/kernel/debug/tracing/trace_pipe

Loro-lorone tim kasebut nelpon sudo ./stand log.

Ping saiki kudu ngasilake pesen kaya iki:

<...>-110930 [004] ..s1 78803.244967: 0: got packet: 00000000ac510377

Yen sampeyan ndeleng kanthi teliti ing output verifier, sampeyan bisa sok dong mirsani petungan 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
<...>

Kasunyatane yaiku program eBPF ora duwe bagean data, mula siji-sijine cara kanggo ngode string format yaiku argumen langsung saka perintah VM:

$ python -c "import binascii; print(bytes(reversed(binascii.unhexlify('0a7025203a74656b63617020746f67'))))"
b'got packet: %pn'

Kanggo alasan iki, output debug banget bloats kode asil.

Ngirim Paket XDP

Ayo ngganti filter: ayo ngirim kabeh paket sing mlebu maneh. Iki ora bener saka sudut pandang jaringan, amarga kudu ngganti alamat ing header, nanging saiki karya ing prinsip penting.

       bpf_printk("got packet: %pn", ctx);
-      return XDP_PASS;
+      return XDP_TX;
   }

Bukak tcpdump ing xdp-remote. Sampeyan kudu nuduhake Permintaan Echo ICMP sing metu lan mlebu sing padha lan mandheg nuduhake Reply Echo ICMP. Nanging ora nuduhake. Ternyata kerja XDP_TX ing program kanggo xdp-local perlukanggo masangake antarmuka xdp-remote program uga diutus, malah yen kosong, lan wungu.

Kepiye carane aku ngerti?

Nelusuri path paket ing kernel mekanisme acara perf ngidini, dening cara, nggunakake mesin virtual padha, sing, eBPF digunakake kanggo disassembly karo eBPF.

Sampeyan kudu nggawe apik saka ala, amarga ora ana liyane kanggo nggawe.

$ 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 kode 6?

$ errno 6
ENXIO 6 No such device or address

fungsi veth_xdp_flush_bq() nemu kode kesalahan saka veth_xdp_xmit(), ngendi search dening ENXIO lan golek komentar.

Mulihake filter minimal (XDP_PASS) ing file xdp_dummy.c, tambahake menyang Makefile, ikatan menyang xdp-remote:

ip netns exec remote 
    ip link set dev int xdp object dummy.o

Saiki tcpdump nuduhake apa sing dikarepake:

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

Yen mung ARP ditampilake, sampeyan kudu mbusak saringan (iki ndadekake sudo ./stand detach), ayo ping, banjur pasang saringan lan coba maneh. Masalah iku Filter XDP_TX mengaruhi ARP uga, lan yen tumpukan
spasi jeneng xdp-test ngatur "lali" alamat MAC 192.0.2.1, dheweke ora bakal bisa ngatasi IP iki.

Formulasi masalah

Ayo pindhah menyang tugas nyatakake: kanggo nulis mekanisme cookie SYN ing XDP.

Nganti saiki, banjir SYN tetep dadi serangan DDoS sing populer, intine yaiku kaya ing ngisor iki. Nalika sambungan ditetepake (salaman TCP), server nampa SYN, allocates sumber daya kanggo sambungan mangsa, nanggapi karo paket SYNACK, lan ngenteni ACK. Penyerang mung ngirim paket SYN saka alamat palsu kanthi jumlah ewu per detik saka saben host ing botnet multi-ewu. Server dipeksa kanggo nyedhiakke sumber langsung nalika rawuh saka paket, nanging dirilis sawise wektu entek dawa, minangka asil, memori utawa watesan wis kesel, sambungan anyar ora ditampa, layanan ora kasedhiya.

Yen sampeyan ora nyedhiyakake sumber daya ing paket SYN, nanging mung nanggapi nganggo paket SYNACK, banjur kepiye server ngerti yen paket ACK sing teka mengko kalebu paket SYN sing ora disimpen? Sawise kabeh, panyerang uga bisa ngasilake ACK palsu. Inti saka cookie SYN yaiku kanggo encode seqnum paramèter sambungan minangka hash saka alamat, bandar lan ngganti uyah. Yen ACK bisa teka sadurunge owah-owahan uyah, sampeyan bisa ngetung hash maneh lan mbandhingaké karo acknum. palsu acknum panyerang ora bisa, wiwit uyah kalebu rahasia, lan ora bakal duwe wektu kanggo Ngurutake liwat amarga saka saluran winates.

Cookie SYN wis dileksanakake ing kernel Linux nganti suwe lan bisa diaktifake kanthi otomatis yen SYN teka cepet banget lan akeh.

Program pendidikan TCP handshake

TCP nyedhiyakake transfer data minangka stream byte, contone, panjalukan HTTP dikirim liwat TCP. Aliran kasebut ditularake sepotong demi sepotong ing paket. Kabeh paket TCP duwe gendera logis lan nomer urutan 32-bit:

  • Kombinasi gendera nemtokake peran paket tartamtu. Gendéra SYN tegese iki minangka paket pisanan pangirim ing sambungan kasebut. Gendéra ACK tegese pangirim wis nampa kabeh data sambungan nganti bait. acknum. Paket bisa duwe sawetara gendera lan dijenengi sawise kombinasi, contone, paket SYNACK.

  • Nomer urutan (seqnum) nemtokake offset ing stream data kanggo byte pisanan sing dikirim ing paket iki. Contone, yen ing paket pisanan karo X bita data nomer iki N, ing paket sabanjuré karo data anyar bakal dadi N + X. Ing wiwitan sambungan, saben partai milih nomer iki kanthi acak.

  • Nomer Acknowledgment (acknum) - padha offset minangka seqnum, nanging ora nemtokake nomer bait sing ditularaké, nanging nomer bait pisanan saka panampa, kang pangirim ora weruh.

Ing wiwitan sambungan, para pihak kudu setuju seqnum и acknum. Klien ngirim paket SYN karo sawijining seqnum = X. Server nanggapi karo paket SYNACK, ngendi nulis dhewe seqnum = Y lan mbabarake acknum = X + 1. Klien nanggapi SYNACK karo paket ACK, ngendi seqnum = X + 1, acknum = Y + 1. Sawise iku, transfer data nyata diwiwiti.

Yen interlocutor ora ngakoni panrimo saka paket, TCP dikirim maneh dening wektu entek.

Napa cookie SYN ora tansah digunakake?

Kaping pisanan, yen SYNACK utawa ACK ilang, sampeyan kudu ngenteni kirim maneh - panyiapan sambungan dadi alon. Kapindho, ing paket SYN - lan mung ing! - sawetara opsi ditularaké sing mengaruhi operasi luwih saka sambungan. Ora ngelingi paket SYN sing mlebu, server ora nggatekake pilihan kasebut, ing paket ing ngisor iki klien ora bakal dikirim maneh. TCP bisa digunakake ing kasus iki, nanging paling ora ing tahap wiwitan, kualitas sambungan bakal mudhun.

Ing babagan paket, program XDP kudu nindakake ing ngisor iki:

  • nanggapi SYN karo SYNACK nganggo cookie;
  • njawab ACK karo RST (break sambungan);
  • nyelehake paket liyane.

Pseudocode saka algoritma bebarengan karo parsing paket:

Если это не Ethernet,
    пропустить пакет.
Если это не IPv4,
    пропустить пакет.
Если адрес в таблице проверенных,               (*)
        уменьшить счетчик оставшихся проверок,
        пропустить пакет.
Если это не TCP,
    сбросить пакет.     (**)
Если это SYN,
    ответить SYN-ACK с cookie.
Если это ACK,
    если в acknum лежит не cookie,
        сбросить пакет.
    Занести в таблицу адрес с N оставшихся проверок.    (*)
    Ответить RST.   (**)
В остальных случаях сбросить пакет.

siji (*) TCTerms ngendi sampeyan kudu ngatur negara sistem ditandhani - ing tataran pisanan, sampeyan bisa nindakake tanpa wong dening mung ngleksanakake TCP salaman karo ngasilaken cookie SYN minangka seqnum.

Ing situs (**), nalika kita ora duwe meja, kita bakal skip paket.

implementasi TCP handshake

Parsing paket lan verifikasi kode

Kita butuh struktur header jaringan: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.hlan TCP (uapi/linux/tcp.h). Sing terakhir aku ora bisa nyambung amarga kesalahan sing ana gandhengane atomic64_t, Aku kudu nyalin definisi perlu menyang kode.

Kabeh fungsi sing dibedakake ing C kanggo readability kudu inlined ing situs telpon, wiwit verifier eBPF ing kernel nglarang maneh mlumpat, sing, ing kasunyatan, puteran lan fungsi telpon.

#define INTERNAL static __attribute__((always_inline))

Makro LOG() disables printing ing mbangun release.

Program kasebut minangka pipa fungsi. Saben nampa paket sing header tingkat sing cocog disorot, contone, process_ether() ngenteni diisi ether. Adhedhasar asil analisis lapangan, fungsi kasebut bisa nransfer paket menyang tingkat sing luwih dhuwur. Asil saka fungsi minangka tumindak XDP. Nalika pawang SYN lan ACK ngidini kabeh paket liwat.

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);
}

Aku mbayar manungsa waé kanggo mriksa ditandhani A lan B. Yen komentar metu A, program bakal mbangun, nanging bakal ana kesalahan verifikasi nalika loading:

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!

String kunci invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): ana path eksekusi nalika bait telulas saka wiwitan buffer njaba paket. Iku angel kanggo ngomong saka listing kang baris kita ngomong bab, nanging ana instruksi nomer (12) lan disassembler sing nuduhake baris saka kode sumber:

llvm-objdump -S xdp_filter.o | less

Ing kasus iki, iku nuduhake garis

LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto));

kang ndadekake cetha yen masalah iku ether. Iku mesthi kaya ngono.

Wangsulan kanggo SYN

Sasaran ing tataran iki kanggo generate paket SYNACK bener karo tetep seqnum, sing bakal diganti dening cookie SYN ing mangsa ngarep. Kabeh owah-owahan njupuk Panggonan ing process_tcp_syn() lan sakcedhake.

Priksa paket

Anehe, iki baris sing paling apik, utawa luwih, komentar kasebut:

/* Required to verify checksum calculation */
const void* data_end = (const void*)ctx->data_end;

Nalika nulis versi pisanan kode kasebut, kernel 5.1 digunakake, kanggo verifikasi sing ana bedane antarane data_end и (const void*)ctx->data_end. Nalika nulis, kernel 5.3.1 ora duwe masalah iki. Mungkin kompiler ngakses variabel lokal kanthi beda karo lapangan. Moral - ing nesting gedhe, simplifying kode bisa bantuan.

Luwih rutin mriksa dawa kanggo kamulyan verifier; O MAX_CSUM_BYTES ing ngisor iki.

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 nyebar

We isi seqnum и acknum, atur ACK (SYN wis disetel):

const u32 cookie = 42;
tcp->ack_seq = bpf_htonl(bpf_ntohl(tcp->seq) + 1);
tcp->seq = bpf_htonl(cookie);
tcp->ack = 1;

Ganti port TCP, alamat IP lan MAC. Pustaka standar ora kasedhiya saka program XDP, supaya memcpy() - makro sing ndhelikake intrinsik Clang.

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);

Petungan maneh checksum

Checksums IPv4 lan TCP mbutuhake tambahan kabeh tembung 16-bit ing header, lan ukuran header ditulis, yaiku, nalika kompilasi ora dingerteni. Iki minangka masalah amarga verifier ora bakal ngliwati loop normal nganti variabel wates. Nanging ukuran header diwatesi: nganti 64 bait saben. Sampeyan bisa nggawe daur ulang kanthi jumlah iterasi sing tetep, sing bisa mungkasi awal.

Aku Wigati sing ana RFC 1624 babagan carane ngetung maneh checksum sebagian yen mung tembung tetep saka paket sing diganti. Nanging, cara kasebut ora universal, lan implementasine bakal luwih angel dijaga.

Fungsi kalkulasi 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;
}

sanadyan size dicenthang dening kode nelpon, kondisi metu kapindho perlu supaya verifier bisa mbuktekaken mburi daur ulang.

Kanggo tembung 32-bit, versi sing luwih prasaja ditindakake:

INTERNAL u32
sum16_32(u32 v) {
    return (v >> 16) + (v & 0xffff);
}

Bener ngitung maneh checksum lan ngirim paket maneh:

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;

fungsi carry() nggawe checksum saka jumlah 32-bit saka tembung 16-bit, miturut RFC 791.

Priksa jabat tangan TCP

Filter kanthi bener nggawe sambungan karo netcat, mlumpat ACK final, sing Linux nanggapi karo paket RST, amarga tumpukan jaringan ora nampa SYN - diowahi dadi SYNACK lan dikirim maneh - lan saka sudut pandang OS, paket teka sing ora related kanggo sambungan mbukak.

$ sudo ip netns exec xdp-test   nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer

Penting kanggo mriksa karo aplikasi lengkap lan mirsani tcpdump ing xdp-remote amarga, contone, hping3 ora nanggapi checksums salah.

Saka sudut pandang XDP, mriksa dhewe ora pati penting. Algoritma pitungan iku primitif lan bisa uga rentan marang penyerang sing canggih. Kernel Linux, contone, nggunakake SipHash cryptographic, nanging implementasine kanggo XDP jelas ngluwihi ruang lingkup artikel iki.

Muncul kanggo TODO anyar sing ana gandhengane karo interaksi eksternal:

  • Program XDP ora bisa nyimpen cookie_seed (bagean rahasia saka uyah) ing variabel global, sampeyan kudu nyimpen kernel kang Nilai bakal periodik dianyari saka generator dipercaya.

  • Yen cookie SYN ing paket ACK cocog, sampeyan ora perlu kanggo print pesen, nanging elinga IP klien diverifikasi supaya luwih skip paket saka iku.

Validasi dening klien sing sah:

$ sudoip netns exec xdp-test   nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer

Log nyathet perangane cek (flags=0x2 yaiku SYN, flags=0x10 yaiku 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

Anggere ora ana dhaptar IP sing dicenthang, ora bakal ana proteksi marang banjir SYN dhewe, nanging iki reaksi marang banjir ACK sing diluncurake dening printah iki:

sudo ip netns exec xdp-test   hping3 --flood -A -s 1111 -p 2222 192.0.2.1

Entri log:

Ether(proto=0x800)
  IP(src=0x15bd11a dst=0x15bd11e proto=6)
    TCP(sport=3236 dport=2222 flags=0x10)
      cookie mismatch

kesimpulan

Kadhangkala eBPF umume lan XDP utamane ditampilake minangka alat administrator sing luwih maju tinimbang platform pangembangan. Pancen, XDP minangka alat kanggo ngganggu pangolahan paket kernel, lan dudu alternatif kanggo tumpukan kernel, kaya DPDK lan opsi bypass kernel liyane. Ing sisih liya, XDP ngidini sampeyan ngetrapake logika sing rada rumit, sing uga gampang dianyari tanpa ngaso ing proses lalu lintas. Verifier ora nggawe masalah gedhe, kanthi pribadi aku ora bakal nolak kanggo bagean kode pangguna.

Ing bagean kapindho, yen topik menarik, kita bakal ngrampungake tabel klien sing wis diverifikasi lan ngilangi sambungan, ngleksanakake counter lan nulis sarana pangguna kanggo ngatur filter.

Cathetan:

Source: www.habr.com

Add a comment