Urang nulis panyalindungan ngalawan serangan DDoS on XDP. Bagian nuklir

Téknologi eXpress Data Path (XDP) ngamungkinkeun pamrosésan lalu lintas acak dilakukeun dina antarmuka Linux sateuacan pakét asup kana tumpukan jaringan kernel. Aplikasi XDP - panyalindungan ngalawan serangan DDoS (CloudFlare), saringan kompleks, koleksi statistik (Netflix). Program XDP dieksekusi ku mesin virtual eBPF, janten aranjeunna gaduh larangan dina kode sareng fungsi kernel anu sayogi gumantung kana jinis saringan.

Tulisan ieu dimaksudkeun pikeun ngeusian kakurangan tina sababaraha bahan dina XDP. Anu mimiti, aranjeunna nyayogikeun kode anu siap-siap anu langsung ngalangkungan fitur XDP: disiapkeun pikeun verifikasi atanapi saderhana teuing pikeun nyababkeun masalah. Lamun anjeun lajeng nyobian nulis kode anjeun ti scratch, anjeun boga pamanggih naon anu kudu dipigawé kalayan kasalahan has. Kadua, cara pikeun nguji XDP sacara lokal tanpa VM sareng hardware henteu katutupan, sanaos kanyataan yén aranjeunna gaduh pitfalls sorangan. Téks dimaksudkeun pikeun programer akrab sareng jaringan sareng Linux anu museurkeun XDP sareng eBPF.

Dina bagian ieu, urang bakal ngartos sacara rinci kumaha saringan XDP dirakit sareng kumaha nguji éta, teras urang bakal nyerat versi saderhana mékanisme cookies SYN anu terkenal dina tingkat pamrosesan pakét. Kami moal nyieun "daftar bodas" acan
klien diverifikasi, tetep counters jeung ngatur filter - cukup log.

Kami bakal nyerat dina C - éta henteu modis, tapi praktis. Sadaya kode sayogi dina GitHub ngalangkungan tautan di tungtung sareng dibagi kana commits dumasar kana tahapan anu dijelaskeun dina tulisan.

Bantahan. Salami tulisan ieu, kuring bakal ngembangkeun solusi mini pikeun nyegah serangan DDoS, sabab ieu mangrupikeun tugas realistis pikeun XDP sareng daérah kaahlian kuring. Nanging, tujuan utama nyaéta ngartos téknologi; ieu sanés pituduh pikeun nyiptakeun panyalindungan anu siap-siap. Kodeu tutorial henteu dioptimalkeun sareng ngaleungitkeun sababaraha nuansa.

XDP Ihtisar Ringkes

Kuring baris outline ukur titik konci ku kituna teu jadi duplikat dokuméntasi jeung artikel aya.

Janten, kode saringan dimuat kana kernel. pakét asup dialirkeun kana filter nu. Hasilna, saringan kedah nyandak kaputusan: lebetkeun pakét kana kernel (XDP_PASS), leupaskeun pakét (XDP_DROP) atanapi kirimkeun deui (XDP_TX). filter nu bisa ngarobah iket, ieu hususna bener keur XDP_TX. Anjeun ogé tiasa ngabatalkeun program (XDP_ABORTED) jeung ngareset pakét, tapi ieu analog assert(0) - pikeun debugging.

Mesin virtual eBPF (Extended Berkley Packet Filter) ngahaja didamel saderhana supados kernel tiasa mariksa yén kodena henteu ngagulung sareng henteu ngarusak mémori jalma sanés. Watesan sareng cek kumulatif:

  • Loops (mundur) dilarang.
  • Aya tumpukan pikeun data, tapi euweuh fungsi (sadayana fungsi C kudu inlined).
  • Aksés mémori di luar tumpukan sareng panyangga pakét dilarang.
  • Ukuran kode diwatesan, tapi dina prakna ieu teu pisan signifikan.
  • Ngan nelepon ka fungsi kernel husus (eBPF helpers) nu diwenangkeun.

Ngarancang sareng masang saringan sapertos kieu:

  1. Kode sumber (mis kernel.c) disusun jadi objék (kernel.o) pikeun arsitéktur mesin virtual eBPF. Dina Oktober 2019, kompilasi ka eBPF dirojong ku Clang sareng dijanjikeun dina GCC 10.1.
  2. Lamun kode objék ieu ngandung panggero kana struktur kernel (Contona, tabel sarta counters), ID maranéhna diganti ku nol, nu hartina kode sapertos teu bisa dieksekusi. Sateuacan ngamuat kana kernel, anjeun kedah ngagentos nol ieu sareng ID objék khusus anu diciptakeun ku telepon kernel (numbu kodeu). Anjeun tiasa ngalakukeun ieu sareng utilitas éksternal, atanapi anjeun tiasa nyerat program anu bakal ngaitkeun sareng ngamuat saringan khusus.
  3. Kernel marios program anu dimuat. Henteuna siklus sareng gagalna ngaleuwihan wates pakét sareng tumpukan dipariksa. Upami verifier teu tiasa ngabuktikeun yén kodeu leres, programna ditolak - anjeun kedah tiasa nyenangkeun anjeunna.
  4. Saatos verifikasi suksés, kernel ngumpulkeun kode objek arsitéktur eBPF kana kode mesin pikeun arsitéktur sistem (just-in-time).
  5. Program éta nempel kana antarmuka sareng ngamimitian ngolah pakét.

Kusabab XDP dijalankeun dina kernel, debugging dilaksanakeun nganggo log trace sareng, kanyataanna, pakét anu disaring atanapi dibangkitkeun ku program. Nanging, eBPF mastikeun yén kode anu diunduh aman pikeun sistem, ku kituna anjeun tiasa ékspérimén sareng XDP langsung dina Linux lokal anjeun.

Nyiapkeun Lingkungan

Majelis

Clang teu tiasa langsung ngahasilkeun kode obyék pikeun arsitéktur eBPF, ku kituna prosésna diwangun ku dua léngkah:

  1. Kompilasi kode C kana LLVM bytecode (clang -emit-llvm).
  2. Ngarobih bytecode kana kode objek eBPF (llc -march=bpf -filetype=obj).

Nalika nyerat saringan, sababaraha file anu ngagaduhan fungsi bantu sareng makro bakal mangpaat tina tés kernel. Kadé aranjeunna cocog sareng versi kernel (KVER). Unduh aranjeunna ka 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 pikeun 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 ngandung jalur ka lulugu kernel, ARCH - arsitéktur sistem. Jalur sareng alat tiasa rada beda-beda antara distribusi.

Conto bédana pikeun 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 sambungkeun hiji diréktori kalawan lulugu bantu sarta sababaraha directories kalawan lulugu kernel. Lambang __KERNEL__ hartina UAPI (Userspace API) lulugu diartikeun pikeun kode kernel, saprak filter nu dieksekusi dina kernel.

Protéksi tumpukan tiasa ditumpurkeun (-fno-stack-protector), sabab verifier kode eBPF masih mariksa palanggaran kaluar-of-wates tumpukan. Éta patut langsung ngaktipkeun optimasi, sabab ukuran bytecode eBPF terbatas.

Hayu urang mimitian ku saringan anu ngalangkungan sadaya pakét sareng henteu ngalakukeun nanaon:

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

regu make ngumpulkeun xdp_filter.o. Dimana coba ayeuna?

bangku tés

Stand kudu ngawengku dua interfaces: nu bakal aya filter sarta ti mana pakét bakal dikirim. Ieu kedah janten alat Linux anu lengkep sareng IP sorangan supados mariksa kumaha aplikasi biasa dianggo sareng saringan kami.

Alat tina tipe veth (Ethernet maya) cocog pikeun urang: ieu sapasang interfaces jaringan virtual "dihubungkeun" langsung ka silih. Anjeun tiasa nyiptakeunana sapertos kieu (dina bagian ieu sadaya paréntah ip dilaksanakeun ti root):

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

Ieu téh xdp-remote и xdp-local - ngaran alat. Dina xdp-local (192.0.2.1/24) filter bakal napel, jeung xdp-remote (192.0.2.2/24) lalulintas asup bakal dikirim. Sanajan kitu, aya masalah: interfaces aya dina mesin sarua, sarta Linux Ubuntu moal ngirim lalulintas keur salah sahijina ngaliwatan séjén. Anjeun tiasa ngajawab ieu ku aturan tricky iptables, Tapi maranéhna kudu ngarobah bungkusan, nu teu merenah pikeun debugging. Hadé pisan mun éta ngagunakeun spasi ngaran jaringan (salajengna netns).

A namespace jaringan ngandung sakumpulan interfaces, routing tabel, jeung aturan NetFilter nu diisolasi tina objék sarupa dina netns séjén. Unggal prosés dijalankeun dina namespace sarta ngan boga aksés ka objék tina netns éta. Sacara standar, sistem ngabogaan ngaranspasi jaringan tunggal pikeun sakabéh objék, sangkan anjeun tiasa dianggo dina Linux Ubuntu jeung teu nyaho ngeunaan netns.

Hayu urang nyieun ngaranspasi anyar xdp-test jeung pindah ka dinya xdp-remote.

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

Lajeng prosés ngajalankeun di xdp-test, moal "tingali" xdp-local (éta bakal tetep dina netns sacara standar) sareng nalika ngirim pakét ka 192.0.2.1 éta bakal ngaliwat. xdp-remotesabab éta hijina panganteur on 192.0.2.0/24 diaksés kana prosés ieu. Ieu ogé jalan dina arah nu lalawanan.

Nalika pindah antara netns, panganteur na turun sarta leungit alamat na. Pikeun ngonpigurasikeun panganteur dina netns, Anjeun kudu ngajalankeun ip ... dina spasi ngaran paréntah ieu 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

Sakumaha anjeun tiasa tingali, ieu teu béda ti setelan xdp-local dina spasi ngaran standar:

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

Lamun lumpat tcpdump -tnevi xdp-local, Anjeun bisa nempo yén pakét dikirim ti xdp-test, dikirimkeun ka antarmuka ieu:

ip netns exec xdp-test   ping 192.0.2.1

Éta merenah pikeun ngaluncurkeun cangkang xdp-test. Repository ngagaduhan skrip anu ngajadikeun otomatis damel sareng stand; contona, anjeun tiasa ngonpigurasikeun stand kalayan paréntah. sudo ./stand up sareng mupusna sudo ./stand down.

Nyukcruk

Filter ieu pakait sareng alat sapertos kieu:

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

konci -force diperlukeun pikeun numbu hiji program anyar lamun nu sejen geus numbu. "Henteu aya warta anu saé" sanés ngeunaan paréntah ieu, kacindekan dina hal naon waé. nunjukkeun verbose opsional, tapi kalayan laporan némbongan dina karya verifier kode kalawan daptar assembly:

Verifier analysis:

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

Pupus tautan program tina antarmuka:

ip link set dev xdp-local xdp off

Dina naskah ieu paréntah sudo ./stand attach и sudo ./stand detach.

Ku ngagantelkeun saringan, anjeun tiasa mastikeun éta ping terus ngajalankeun, tapi gawéna program? Hayu urang tambahkeun log. Fungsi bpf_trace_printk() sarupa jeung printf(), tapi ngan ngarojong nepi ka tilu argumen lian ti pola, sarta daptar kawates specifiers. Makro bpf_printk() simplifies panggero.

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

Kaluaran angkat ka saluran jejak kernel, anu kedah diaktipkeun:

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

Tingali seratan seratan:

cat /sys/kernel/debug/tracing/trace_pipe

Duanana paréntah ieu nelepon sudo ./stand log.

Ping ayeuna kedah memicu pesen sapertos kieu:

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

Upami anjeun ningali sacara saksama kaluaran verifikasi, anjeun bakal perhatikeun itungan anu 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
<...>

Kanyataanna nyaéta program eBPF henteu gaduh bagian data, janten hiji-hijina cara pikeun ngodekeun format string nyaéta argumen langsung tina paréntah VM:

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

Ku sabab kitu, kaluaran debug pisan ngagedekeun kode anu dihasilkeun.

Ngirim Paket XDP

Hayu urang ngaganti filter: hayu eta ngirimkeun deui sakabeh pakét asup. Ieu lepat ti sudut pandang jaringan, sabab bakal diperlukeun pikeun ngarobah alamat dina headers, tapi ayeuna karya prinsipna penting.

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

Ngaluncurkeun tcpdump dina xdp-remote. Sakuduna nembongkeun idéntik kaluar jeung asup ICMP Echo Request jeung ngeureunkeun némbongkeun ICMP Echo Reply. Tapi teu némbongan. Tétéla éta pikeun digawé XDP_TX dina program on xdp-local anu diperyogikeunka panganteur pasangan xdp-remote program a ieu ogé ditugaskeun, malah lamun éta kosong, sarta anjeunna diangkat.

Kumaha kuring terang ieu?

Lacak jalur pakét dina kernel Mékanisme acara perf ngamungkinkeun, saliwatan, ngagunakeun mesin virtual sarua, nyaeta, eBPF dipaké pikeun disassemblies kalawan eBPF.

Anjeun kedah ngadamel anu hadé tina anu jahat, sabab teu aya anu sanés pikeun ngaleungitkeunana.

$ 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])
                                     <...>

Naon kode 6?

$ errno 6
ENXIO 6 No such device or address

fungsi veth_xdp_flush_bq() nampi kodeu kasalahan tina veth_xdp_xmit(), dimana pilarian ku ENXIO sarta manggihan komentar.

Hayu urang balikkeun saringan minimum (XDP_PASS) dina file xdp_dummy.c, tambahkeun kana Makefile, beungkeutkeun kana xdp-remote:

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

ayeuna tcpdump nunjukkeun naon anu dipiharep:

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

Upami ngan ukur ARP anu dipidangkeun, anjeun kedah ngahapus saringan (ieu henteu sudo ./stand detach), leupaskeun ping, teras setel saringan sareng cobian deui. Masalahna nyaéta saringan XDP_TX valid duanana on ARP sarta lamun tumpukan
spasi ngaran xdp-test junun "poho" alamat MAC 192.0.2.1, éta moal bisa ngabéréskeun IP ieu.

Ngarumuskeun masalah

Hayu urang ngaléngkah ka tugas nyatakeun: nulis mékanisme SYN cookies on XDP.

SYN banjir tetep serangan DDoS populér, intina nyaéta kieu. Nalika sambungan dijieun (sasalaman TCP), server narima SYN a, allocates sumberdaya pikeun sambungan hareup, ngabales ku pakét SYNACK sarta ngantosan hiji ACK. Panyerang ngan saukur ngirimkeun rébuan pakét SYN per detik tina alamat palsu ti unggal host dina botnet anu sarébu-kuat. Server kapaksa alokasi sumber langsung saatos datangna pakét, tapi ngaleupaskeun aranjeunna saatos waktos waktos anu ageung; akibatna, mémori atanapi wates béak, sambungan énggal henteu ditampi, sareng jasa henteu sayogi.

Mun anjeun teu allocate sumberdaya dumasar kana pakét SYN, tapi ngan ngabales ku pakét SYNACK, kumaha lajeng server bisa ngarti yén pakét ACK nu anjog engké nujul kana pakét SYN nu teu disimpen? Barina ogé, panyerang ogé tiasa ngahasilkeun ACK palsu. Inti tina cookie SYN nyaéta pikeun nangkodkeunana seqnum parameter sambungan salaku Hash sahiji alamat, palabuhan sarta ngarobah uyah. Lamun ACK junun anjog saméméh uyah dirobah, Anjeun bisa ngitung Hash deui tur dibandingkeun jeung acknum. Ngajalin acknum lawan teu tiasa, saprak uyah kaasup rusiah, sarta moal boga waktu pikeun nyortir ngaliwatan éta alatan saluran kawates.

Cookie SYN parantos lami dilaksanakeun dina kernel Linux bahkan tiasa otomatis diaktipkeun upami SYN sumping gancang teuing sareng sacara masal.

Program atikan ngeunaan sasalaman TCP

TCP nyadiakeun pangiriman data salaku aliran bait, contona, requests HTTP dikirimkeun ngaliwatan TCP. Aliran dikirimkeun dina potongan dina pakét. Sadaya pakét TCP gaduh umbul logis sareng nomer urutan 32-bit:

  • Kombinasi umbul nangtukeun peran pakét husus. Bandéra SYN nunjukkeun yén ieu pakét munggaran pangirim dina sambungan. Bandéra ACK hartosna yén pangirim nampi sadaya data sambungan dugi ka bait acknum. Hiji pakét tiasa gaduh sababaraha bandéra sareng disebut ku kombinasina, contona, pakét SYNACK.

  • Jumlah runtuyan (seqnum) nangtukeun offset dina aliran data pikeun bait munggaran nu dikirimkeun dina pakét ieu. Contona, upami dina pakét munggaran kalayan X bait data jumlah ieu N, dina pakét salajengna kalawan data anyar bakal N + X. Dina awal sambungan, unggal sisi milih nomer ieu acak.

  • Jumlah pangakuan (acknum) - offset sarua salaku seqnum, tapi teu nangtukeun jumlah bait keur dikirimkeun, tapi jumlah bait munggaran ti panarima, nu ngirim teu ningali.

Dina awal sambungan, pihak kudu satuju seqnum и acknum. Klién ngirimkeun pakét SYN sareng na seqnum = X. Server ngabales ku pakét SYNACK, dimana éta ngarékam na seqnum = Y jeung ngalaan acknum = X + 1. klien nu responds mun SYNACK kalawan pakét ACK, dimana seqnum = X + 1, acknum = Y + 1. Sanggeus ieu, transfer data sabenerna dimimitian.

Upami peer henteu ngaku nampi pakét, TCP ngirimkeun deui saatos waktosna.

Naha cookies SYN henteu salawasna dianggo?

Anu mimiti, upami SYNACK atanapi ACK leungit, anjeun kedah ngantosan dikirim deui - setelan sambungan bakal ngalambatkeun. Bréh, dina pakét SYN - sarta ngan di dinya! — sajumlah pilihan dikirimkeun anu mangaruhan operasi salajengna sambungan. Tanpa nginget pakét SYN anu datang, server henteu malire pilihan ieu; klien moal ngirim aranjeunna dina pakét salajengna. TCP tiasa dianggo dina hal ieu, tapi sahenteuna dina tahap awal kualitas sambungan bakal turun.

Dina watesan bungkusan, program XDP kedah ngalakukeun ieu:

  • ngabales SYN kalawan SYNACK kalawan cookie a;
  • ngabales ACK kalawan RST (pegatkeun sambungan);
  • piceun pakét sésana.

Pseudocode tina algoritma sareng parsing pakét:

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

Hiji (*) titik dimana anjeun kedah ngatur kaayaan sistem anu ditandaan - dina tahap kahiji anjeun tiasa ngalakukeun tanpa aranjeunna ku saukur ngalaksanakeun TCP sasalaman jeung generasi cookie SYN sakumaha seqnum a.

Di tempat (**), Bari urang teu boga méja, urang bakal skip pakét.

Ngalaksanakeun TCP sasalaman

Parsing pakét sareng pariksa kodeu

Urang peryogi struktur lulugu jaringan: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) jeung TCP (uapi/linux/tcp.h). Abdi henteu tiasa nyambungkeun anu terakhir kusabab kasalahan anu aya hubunganana atomic64_t, Kuring kungsi nyalin definisi diperlukeun kana kode.

Kabéh fungsi nu disorot dina C pikeun readability kudu inlined dina titik panggero, saprak verifier eBPF dina kernel prohibits backtracking, nyaeta, kanyataanna, puteran sarta panggero fungsi.

#define INTERNAL static __attribute__((always_inline))

Makro LOG() disables percetakan dina ngawangun release.

Program nyaéta conveyor fungsi. Masing-masing nampi pakét dimana header tingkat anu cocog disorot, contona, process_ether() ngarepkeunana dieusi ether. Dumasar kana hasil analisis lapangan, fungsina tiasa ngalangkungan pakét ka tingkat anu langkung luhur. Hasil tina fungsi nyaéta aksi XDP. Pikeun ayeuna, pawang SYN sareng ACK ngalangkungan sadaya pakét.

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

Kuring narik perhatian anjeun ka cék ditandaan A jeung B. Lamun anjeun mairan kaluar A, program bakal ngawangun, tapi bakal aya kasalahan 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!

Senar konci invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Aya jalur palaksanaan nalika bait katilu belas ti mimiti panyangga aya di luar pakét. Hésé kahartos tina daptar garis anu urang bahas, tapi aya nomer instruksi (12) sareng disassembler anu nunjukkeun garis kode sumber:

llvm-objdump -S xdp_filter.o | less

Dina hal ieu nunjuk ka garis

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

nu ngajadikeun eta jelas yén masalah téh ether. Ieu bakal salawasna jadi kieu.

Bales SYN

Tujuan dina tahap ieu nyaéta pikeun ngahasilkeun pakét SYNACK anu leres sareng tetep seqnum, nu bakal diganti dina mangsa nu bakal datang ku cookie SYN. Sadaya parobahan lumangsung dina process_tcp_syn() jeung wewengkon sabudeureun.

pakét verifikasi

Cukup aneh, ieu mangrupikeun garis anu paling luar biasa, atanapi langkungna, koméntar éta:

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

Nalika nyerat versi kode anu munggaran, kernel 5.1 dianggo, pikeun verifikasi anu aya bédana antara data_end и (const void*)ctx->data_end. Dina waktos nyerat, kernel 5.3.1 teu ngagaduhan masalah ieu. Éta kamungkinan yén kompiler ngaksés variabel lokal béda ti lapangan. Moral carita: Nyederhanakeun kode tiasa ngabantosan nalika seueur nyarang.

Salajengna nyaéta cék panjang rutin pikeun kamulyaan verifier; O MAX_CSUM_BYTES di handap.

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 */
}

Ngabongkar bungkusan

Urang eusian seqnum и acknum, set ACK (SYN tos 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 sareng alamat MAC. Perpustakaan standar henteu tiasa diaksés tina program XDP, janten memcpy() - makro anu nyumputkeun 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);

Recalculation of checksums

IPv4 na TCP checksums merlukeun tambahan sakabéh kecap 16-bit dina headers, sarta ukuran headers ditulis kana eta, nyaeta, kanyahoan dina waktos compile. Ieu masalah sabab verifier moal skip loop normal kana variabel wates. Tapi ukuran headers diwatesan: nepi ka 64 bait unggal. Anjeun tiasa ngadamel loop kalayan jumlah iterasi tetep, anu tiasa ditungtungan awal.

Kuring dicatet yén aya RFC 1624 ngeunaan kumaha sabagean recalculate checksum lamun ngan kecap tetep tina bungkusan nu dirobah. Sanajan kitu, metoda ieu teu universal, sarta palaksanaan bakal leuwih hese pikeun mulasara.

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

Sanajan size diverifikasi ku kodeu nelepon, kaayaan kaluar kadua diperlukeun ku kituna verifier bisa ngabuktikeun parantosan loop anu.

Pikeun kecap 32-bit, vérsi anu langkung saderhana dilaksanakeun:

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

Sabenerna ngitung deui checksum sareng ngirim pakét deui:

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() nyieun checksum tina jumlah 32-bit tina kecap 16-bit, nurutkeun RFC 791.

verifikasi sasalaman TCP

Saringan leres ngadamel sambungan sareng netcat, leungit ACK final, nu Linux Ubuntu direspon ku pakét RST, saprak tumpukan jaringan teu nampi SYN - eta dirobah jadi SYNACK sarta dikirim deui - sarta ti sudut pandang OS, hiji pakét anjog nu teu patali jeung muka. sambungan.

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

Kadé pariksa ku aplikasi full-fledged jeung niténan tcpdump dina xdp-remote sabab, contona, hping3 teu ngabales checksums salah.

Tina sudut pandang XDP, verifikasi sorangan teu pati penting. Algoritma itungan primitif sareng kamungkinan rentan ka panyerang anu canggih. Kernel Linux, contona, nganggo SipHash kriptografis, tapi palaksanaanna pikeun XDP jelas saluareun ruang lingkup tulisan ieu.

Diwanohkeun pikeun TODOs anyar nu patali jeung komunikasi éksternal:

  • program XDP teu bisa nyimpen cookie_seed (bagian rusiah uyah) dina variabel global, Anjeun kudu neundeun dina kernel, nilai nu bakal périodik diropéa tina generator dipercaya.

  • Upami cookie SYN cocog sareng pakét ACK, anjeun henteu kedah nyitak pesen, tapi émut kana IP klien anu diverifikasi pikeun neraskeun pakét anu dikirimkeun.

Verifikasi klien sah:

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

Log nunjukkeun yén cék lulus (flags=0x2 - ieu SYN, flags=0x10 nyaeta 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

Sanaos teu aya daptar IP anu diverifikasi, moal aya panyalindungan tina banjir SYN sorangan, tapi ieu réaksi kana banjir ACK anu diluncurkeun ku paréntah di handap ieu:

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

Éntri log:

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

kacindekan

Kadang-kadang eBPF sacara umum sareng XDP khususna dibere langkung salaku alat administrator canggih tibatan salaku platform pangembangan. Mémang, XDP mangrupikeun alat pikeun ngaganggu ngolah pakét ku kernel, sareng sanés alternatif pikeun tumpukan kernel, sapertos DPDK sareng pilihan bypass kernel sanés. Di sisi anu sanésna, XDP ngamungkinkeun anjeun pikeun nerapkeun logika anu rada rumit, anu, salian ti éta, gampang diénggalan tanpa gangguan dina pamrosésan lalu lintas. Verifier henteu nyiptakeun masalah anu ageung; sacara pribadi, kuring moal nampik ieu pikeun bagian-bagian kode rohangan pangguna.

Dina bagian kadua, lamun topik metot, urang bakal ngalengkepan tabel diverifikasi klien tur disconnections, nerapkeun counters jeung nulis utiliti spasi pamaké pikeun ngatur filter.

Rujukan:

sumber: www.habr.com

Tambahkeun komentar