Biz XDP da DDoS hujumlaridan himoya yozmoqdamiz. Yadro qismi

eXpress Data Path (XDP) texnologiyasi paketlar yadro tarmog'i stekiga kirishidan oldin Linux interfeyslarida tasodifiy trafikni qayta ishlash imkonini beradi. XDP qo'llanilishi - DDoS hujumlaridan himoya qilish (CloudFlare), murakkab filtrlar, statistika to'plami (Netflix). XDP dasturlari eBPF virtual mashinasi tomonidan bajariladi, shuning uchun filtr turiga qarab ularning kodlari va mavjud yadro funktsiyalari bo'yicha cheklovlar mavjud.

Maqola XDP bo'yicha ko'plab materiallarning kamchiliklarini to'ldirishga mo'ljallangan. Birinchidan, ular XDP xususiyatlarini darhol chetlab o'tadigan tayyor kodni taqdim etadilar: u tekshirish uchun tayyorlangan yoki muammo tug'dirish uchun juda oddiy. Keyin kodingizni noldan yozishga harakat qilganingizda, odatiy xatolar bilan nima qilishni bilmaysiz. Ikkinchidan, XDP-ni VM va apparatsiz mahalliy sinovdan o'tkazish usullari, ularning o'z tuzoqlariga ega bo'lishiga qaramay, qamrab olinmaydi. Matn XDP va eBPF bilan qiziqqan, tarmoq va Linux bilan tanish bo'lgan dasturchilar uchun mo'ljallangan.

Ushbu qismda biz XDP filtri qanday yig'ilganligini va uni qanday sinab ko'rishni batafsil tushunamiz, keyin paketlarni qayta ishlash darajasida taniqli SYN cookie fayllari mexanizmining oddiy versiyasini yozamiz. Biz hali "oq ro'yxat" yaratmaymiz
tasdiqlangan mijozlar, hisoblagichlarni saqlang va filtrni boshqaring - etarli jurnallar.

Biz C tilida yozamiz - bu moda emas, lekin amaliy. Barcha kodlar GitHub-da oxiridagi havola orqali mavjud va maqolada tasvirlangan bosqichlarga ko'ra majburiyatlarga bo'lingan.

Ogohlantirish. Ushbu maqola davomida men DDoS hujumlarini oldini olish uchun mini-yechim ishlab chiqaman, chunki bu XDP va mening tajriba soham uchun haqiqiy vazifadir. Biroq, asosiy maqsad texnologiyani tushunishdir, bu tayyor himoyani yaratish uchun qo'llanma emas. Qo'llanma kodi optimallashtirilmagan va ba'zi nuanslarni o'tkazib yuboradi.

XDP qisqacha sharhi

Hujjatlarni va mavjud maqolalarni takrorlamaslik uchun faqat asosiy fikrlarni aytib o'taman.

Shunday qilib, filtr kodi yadroga yuklanadi. Kiruvchi paketlar filtrga uzatiladi. Natijada, filtr qaror qabul qilishi kerak: paketni yadroga o'tkazing (XDP_PASS), paketni tashlab yuborish (XDP_DROP) yoki qaytarib yuboring (XDP_TX). Filtr paketni o'zgartirishi mumkin, bu ayniqsa, to'g'ri keladi XDP_TX. Shuningdek, dasturni bekor qilishingiz mumkin (XDP_ABORTED) va paketni qayta o'rnating, lekin bu o'xshash assert(0) - disk raskadrovka uchun.

eBPF (kengaytirilgan Berkley Packet Filter) virtual mashinasi ataylab soddalashtirilgan bo'lib, yadro kod aylanmasligi va boshqa odamlar xotirasiga zarar yetkazmasligini tekshirishi mumkin. Kümülatif cheklovlar va tekshiruvlar:

  • Looplar (orqaga) taqiqlanadi.
  • Ma'lumotlar uchun stek mavjud, lekin hech qanday funksiya yo'q (barcha C funktsiyalari qatorga kiritilishi kerak).
  • Stack va paket buferidan tashqari xotiraga kirish taqiqlanadi.
  • Kod hajmi cheklangan, ammo amalda bu juda muhim emas.
  • Faqat maxsus yadro funktsiyalariga (eBPF yordamchilari) qo'ng'iroqlarga ruxsat beriladi.

Filtrni loyihalash va o'rnatish quyidagicha ko'rinadi:

  1. Manba kodi (masalan kernel.c) ob'ektga kompilyatsiya qilinadi (kernel.o) eBPF virtual mashinasi arxitekturasi uchun. 2019 yil oktabr holatiga ko'ra, eBPF kompilyatsiyasi Clang tomonidan qo'llab-quvvatlanadi va GCC 10.1 da va'da qilingan.
  2. Agar ushbu ob'ekt kodida yadro tuzilmalariga (masalan, jadvallar va hisoblagichlar) qo'ng'iroqlar mavjud bo'lsa, ularning identifikatorlari nolga almashtiriladi, ya'ni bunday kodni bajarib bo'lmaydi. Yadroga yuklashdan oldin siz ushbu nollarni yadro chaqiruvlari orqali yaratilgan aniq ob'ektlarning identifikatorlari bilan almashtirishingiz kerak (kodni bog'lang). Buni tashqi yordam dasturlari yordamida amalga oshirishingiz mumkin yoki ma'lum bir filtrni bog'laydigan va yuklaydigan dastur yozishingiz mumkin.
  3. Yadro yuklangan dasturni tekshiradi. Tsikllarning yo'qligi va paket va stek chegaralaridan oshib ketmaslik tekshiriladi. Agar tekshiruvchi kodning to'g'ri ekanligini isbotlay olmasa, dastur rad etiladi - siz uni mamnun qila olishingiz kerak.
  4. Muvaffaqiyatli tekshirilgandan so'ng, yadro eBPF arxitekturasi ob'ekt kodini tizim arxitekturasi uchun mashina kodiga (o'z vaqtida) kompilyatsiya qiladi.
  5. Dastur interfeysga ulanadi va paketlarni qayta ishlashni boshlaydi.

XDP yadroda ishlaganligi sababli, disk raskadrovka iz jurnallari va aslida dastur filtrlaydigan yoki yaratadigan paketlar yordamida amalga oshiriladi. Biroq, eBPF yuklab olingan kod tizim uchun xavfsiz bo'lishini ta'minlaydi, shuning uchun siz to'g'ridan-to'g'ri mahalliy Linuxda XDP bilan tajriba o'tkazishingiz mumkin.

Atrof muhitni tayyorlash

O'rnatish

Clang to'g'ridan-to'g'ri eBPF arxitekturasi uchun ob'ekt kodini ishlab chiqara olmaydi, shuning uchun jarayon ikki bosqichdan iborat:

  1. C kodini LLVM bayt kodiga kompilyatsiya qiling (clang -emit-llvm).
  2. Bayt-kodni eBPF obyekt kodiga aylantirish (llc -march=bpf -filetype=obj).

Filtrni yozishda yordamchi funktsiyalar va makroslarga ega bo'lgan bir nechta fayllar foydali bo'ladi yadro sinovlaridan. Ular yadro versiyasiga mos kelishi muhim (KVER). Ularni yuklab oling 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

Arch Linux uchun Makefile (yadro 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 yadro sarlavhalariga yo'lni o'z ichiga oladi, ARCH - tizim arxitekturasi. Yo'llar va asboblar taqsimotlar orasida biroz farq qilishi mumkin.

Debian 10 uchun farqlarga misol (yadro 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 yordamchi sarlavhali katalogni va yadro sarlavhalari bilan bir nechta kataloglarni ulash. Belgi __KERNEL__ UAPI (userspace API) sarlavhalari yadro kodi uchun aniqlanganligini anglatadi, chunki filtr yadroda bajariladi.

Stack himoyasini o'chirib qo'yish mumkin (-fno-stack-protector), chunki eBPF kod tekshiruvi hali ham stekdan tashqarida buzilishlarni tekshiradi. Darhol optimallashtirishni yoqishga arziydi, chunki eBPF baytekodining hajmi cheklangan.

Keling, barcha paketlarni o'tkazadigan va hech narsa qilmaydigan filtrdan boshlaylik:

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

komanda make yig'adi xdp_filter.o. Endi uni qayerda sinab ko'rish kerak?

Sinov stend

Stend ikkita interfeysni o'z ichiga olishi kerak: qaysi filtr bo'ladi va qaysi paketlar yuboriladi. Muntazam ilovalar filtrimiz bilan qanday ishlashini tekshirish uchun ular o'zlarining IP manzillariga ega to'liq Linux qurilmalari bo'lishi kerak.

Biz uchun veth (virtual Ethernet) tipidagi qurilmalar mos keladi: bular bir-biriga bevosita "ulangan" virtual tarmoq interfeyslari juftligi. Siz ularni shunday yaratishingiz mumkin (ushbu bo'limda barcha buyruqlar ip dan amalga oshiriladi root):

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

u xdp-remote и xdp-local - qurilma nomlari. Yoniq xdp-local (192.0.2.1/24) filtri biriktiriladi, bilan xdp-remote (192.0.2.2/24) kiruvchi trafik yuboriladi. Biroq, muammo bor: interfeyslar bir xil mashinada va Linux ulardan biriga boshqasi orqali trafik yubormaydi. Buni murakkab qoidalar bilan hal qilishingiz mumkin iptables, lekin ular paketlarni o'zgartirishi kerak bo'ladi, bu disk raskadrovka uchun noqulay. Tarmoq nom maydonlaridan (keyingi o'rinlarda netns) foydalanish yaxshiroqdir.

Tarmoq nomlari maydoni boshqa tarmoqlardagi o'xshash ob'ektlardan ajratilgan interfeyslar, marshrutlash jadvallari va NetFilter qoidalarini o'z ichiga oladi. Har bir jarayon nomlar maydonida ishlaydi va faqat shu tarmoq ob'ektlariga kirish huquqiga ega. Odatiy bo'lib, tizim barcha ob'ektlar uchun yagona tarmoq nom maydoniga ega, shuning uchun siz Linuxda ishlashingiz va netns haqida bilmasligingiz mumkin.

Keling, yangi nom maydoni yarataylik xdp-test va uni u erga ko'chiring xdp-remote.

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

Keyin jarayon boshlanadi xdp-test, "ko'rmaydi" xdp-local (u sukut bo'yicha netnsda qoladi) va 192.0.2.1 ga paket yuborilganda u orqali o'tadi. xdp-remotechunki u 192.0.2.0/24 da ushbu jarayonga kirish mumkin bo'lgan yagona interfeysdir. Bu ham teskari yo'nalishda ishlaydi.

Netlar o'rtasida harakatlanayotganda interfeys pastga tushadi va manzilini yo'qotadi. Netns-da interfeysni sozlash uchun siz ishga tushirishingiz kerak ip ... ushbu buyruq nom maydonida 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

Ko'rib turganingizdek, bu sozlamalardan farq qilmaydi xdp-local standart nom maydonida:

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

Yugursang tcpdump -tnevi xdp-local, siz paketlar yuborilganligini ko'rishingiz mumkin xdp-test, ushbu interfeysga yetkaziladi:

ip netns exec xdp-test   ping 192.0.2.1

Ichkarida qobiqni ishga tushirish qulay xdp-test. Omborda stend bilan ishlashni avtomatlashtiradigan skript mavjud; masalan, buyruq yordamida stendni sozlashingiz mumkin. sudo ./stand up va uni o'chiring sudo ./stand down.

Kuzatish

Filtr qurilma bilan quyidagicha bog'langan:

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

Kalit -force Agar boshqasi allaqachon ulangan bo'lsa, yangi dasturni ulash uchun kerak. "Hech qanday yangilik yaxshi yangilik emas" bu buyruq haqida emas, har qanday holatda ham xulosa katta. ko'rsatish verbose ixtiyoriy, lekin u bilan birga yig'ish ro'yxati bilan kod tekshirgichining ishi haqida hisobot paydo bo'ladi:

Verifier analysis:

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

Dasturni interfeysdan ajratib oling:

ip link set dev xdp-local xdp off

Skriptda bu buyruqlar sudo ./stand attach и sudo ./stand detach.

Filtrni ulab, bunga ishonch hosil qilishingiz mumkin ping ishlashda davom etmoqda, lekin dastur ishlaydimi? Keling, jurnallarni qo'shamiz. Funktsiya bpf_trace_printk() o'xshash printf(), lekin naqshdan tashqari faqat uchtagacha argumentni va aniqlovchilarning cheklangan ro'yxatini qo'llab-quvvatlaydi. Ibratli bpf_printk() qo'ng'iroqni soddalashtiradi.

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

Chiqish yadro kuzatuv kanaliga o'tadi, uni yoqish kerak:

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

Xabar mavzusini ko'rish:

cat /sys/kernel/debug/tracing/trace_pipe

Ushbu ikkala buyruq ham qo'ng'iroq qiladi sudo ./stand log.

Ping endi quyidagi kabi xabarlarni ishga tushirishi kerak:

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

Agar siz tekshirgichning chiqishiga diqqat bilan qarasangiz, g'alati hisoblarni ko'rasiz:

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
<...>

Haqiqat shundaki, eBPF dasturlarida ma'lumotlar bo'limi yo'q, shuning uchun format satrini kodlashning yagona usuli bu VM buyruqlarining bevosita argumentlari:

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

Shu sababli, disk raskadrovka chiqishi natijasida hosil bo'lgan kodni juda shishiradi.

XDP paketlarini yuborish

Filtrni o'zgartiraylik: u barcha kiruvchi paketlarni qaytarib yuborsin. Tarmoq nuqtai nazaridan bu noto'g'ri, chunki sarlavhalardagi manzillarni o'zgartirish kerak edi, ammo endi ish printsipial jihatdan muhim.

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

Ishga tushirish tcpdump haqida xdp-remote. U bir xil chiquvchi va kiruvchi ICMP echo so'rovini ko'rsatishi va ICMP echo javobini ko'rsatishni to'xtatishi kerak. Lekin ko'rsatmaydi. Ma'lum bo'lishicha, bu ish uchun XDP_TX dasturida xdp-local kerakjuftlik interfeysiga xdp-remote dastur ham bo'sh bo'lsa ham tayinlandi va u ko'tarildi.

Men buni qayerdan bildim?

Yadrodagi paket yo'lini kuzating Perf hodisalar mexanizmi, aytmoqchi, bir xil virtual mashinadan foydalanishga imkon beradi, ya'ni eBPF eBPF bilan qismlarga ajratish uchun ishlatiladi.

Siz yomonlikdan yaxshilik qilishingiz kerak, chunki undan boshqa hech narsa yo'q.

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

6-kod nima?

$ errno 6
ENXIO 6 No such device or address

vazifa veth_xdp_flush_bq() dan xato kodini oladi veth_xdp_xmit(), qaerdan qidirish ENXIO va sharhni toping.

Minimal filtrni tiklaymiz (XDP_PASS) faylda xdp_dummy.c, uni Makefile-ga qo'shing, bog'lang xdp-remote:

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

Endi tcpdump nima kutilayotganini ko'rsatadi:

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

Agar o'rniga faqat ARP ko'rsatilsa, siz filtrlarni olib tashlashingiz kerak (bu shunday qiladi sudo ./stand detach), ketdik ping, keyin filtrlarni oʻrnating va qaytadan urinib koʻring. Muammo shundaki, filtrda XDP_TX ARPda ham, stekda ham amal qiladi
nom maydonlari xdp-test 192.0.2.1 MAC manzilini "unutishga" muvaffaq bo'ldi, bu IP-ni hal qila olmaydi.

Muammoni shakllantirish

Belgilangan vazifaga o'tamiz: XDP-da SYN cookie-fayllar mexanizmini yozing.

SYN toshqini mashhur DDoS hujumi bo'lib qolmoqda, uning mohiyati quyidagicha. Ulanish o'rnatilganda (TCP qo'l siqish), server SYN oladi, kelajakda ulanish uchun resurslarni ajratadi, SYNACK paketi bilan javob beradi va ACKni kutadi. Tajovuzkor minglab kuchli botnetdagi har bir xostdan soxta manzillardan sekundiga minglab SYN paketlarini yuboradi. Paket kelgandan so'ng server darhol resurslarni ajratishga majbur bo'ladi, lekin katta vaqtdan keyin ularni chiqaradi, natijada xotira yoki limitlar tugaydi, yangi ulanishlar qabul qilinmaydi va xizmat mavjud emas.

Agar siz SYN paketi asosida resurslarni ajratmasangiz, faqat SYNACK paketi bilan javob bersangiz, server keyin kelgan ACK paketi saqlanmagan SYN paketiga tegishli ekanligini qanday tushunishi mumkin? Axir, tajovuzkor soxta ACKlarni ham yaratishi mumkin. SYN cookie faylining maqsadi uni kodlashdir seqnum manzillar, portlar va o'zgaruvchan tuzlar xeshi sifatida ulanish parametrlari. Agar ACK tuz almashtirilgunga qadar yetib kelgan bo'lsa, siz hashni yana hisoblab, uni solishtirishingiz mumkin acknum. Forge acknum tajovuzkor qila olmaydi, chunki tuz sirni o'z ichiga oladi va cheklangan kanal tufayli uni saralashga ulgurmaydi.

SYN cookie-fayllari uzoq vaqtdan beri Linux yadrosida qo'llanilgan va hatto SYN-lar juda tez va ommaviy ravishda kelgan taqdirda ham avtomatik ravishda yoqilishi mumkin.

TCP handshake bo'yicha o'quv dasturi

TCP baytlar oqimi sifatida ma'lumotlarni uzatishni ta'minlaydi, masalan, HTTP so'rovlari TCP orqali uzatiladi. Oqim paketlarda bo'laklarga uzatiladi. Barcha TCP paketlarida mantiqiy bayroqlar va 32 bitli tartib raqamlari mavjud:

  • Bayroqlarning kombinatsiyasi ma'lum bir paketning rolini belgilaydi. SYN bayrog'i bu jo'natuvchining ulanishdagi birinchi paketi ekanligini ko'rsatadi. ACK bayrog'i jo'natuvchining baytgacha bo'lgan barcha ulanish ma'lumotlarini olganligini bildiradi acknum. Paket bir nechta bayroqlarga ega bo'lishi mumkin va ularning kombinatsiyasi bilan chaqiriladi, masalan, SYNACK paketi.

  • Tartib raqami (seqnum) ushbu paketda uzatiladigan birinchi bayt uchun ma'lumotlar oqimidagi ofsetni belgilaydi. Misol uchun, agar X bayt ma'lumotlarga ega bo'lgan birinchi paketda bu raqam N bo'lsa, yangi ma'lumotlar bilan keyingi paketda N+X bo'ladi. Ulanishning boshida har bir tomon bu raqamni tasodifiy tanlaydi.

  • Tasdiqlash raqami (aknum) - seqnum bilan bir xil ofset, lekin u uzatilayotgan baytning sonini emas, balki jo'natuvchi ko'rmagan qabul qiluvchining birinchi bayt raqamini aniqlaydi.

Ulanishning boshida tomonlar kelishib olishlari kerak seqnum и acknum. Mijoz o'zi bilan SYN paketini yuboradi seqnum = X. Server SYNACK paketi bilan javob beradi, u erda u o'zini yozib oladi seqnum = Y va fosh qiladi acknum = X + 1. Mijoz SYNACK ga ACK paketi bilan javob beradi, bu erda seqnum = X + 1, acknum = Y + 1. Shundan so'ng, haqiqiy ma'lumotlarni uzatish boshlanadi.

Agar tengdosh paketni olganligini tasdiqlamasa, TCP vaqt tugashidan keyin uni qayta yuboradi.

Nima uchun SYN cookie fayllari har doim ham ishlatilmaydi?

Birinchidan, agar SYNACK yoki ACK yo'qolsa, uni qayta yuborishni kutishingiz kerak bo'ladi - ulanishni sozlash sekinlashadi. Ikkinchidan, SYN to'plamida - va faqat unda! — ulanishning keyingi ishlashiga ta'sir qiluvchi bir qator variantlar uzatiladi. Kiruvchi SYN paketlarini eslamasdan, server bu variantlarni e'tiborsiz qoldiradi, mijoz ularni keyingi paketlarga yubormaydi. TCP bu holda ishlashi mumkin, lekin hech bo'lmaganda dastlabki bosqichda ulanish sifati pasayadi.

Paketlar nuqtai nazaridan, XDP dasturi quyidagilarni bajarishi kerak:

  • cookie bilan SYNACK bilan SYNga javob berish;
  • ACK ga RST bilan javob berish (ajratish);
  • qolgan paketlarni tashlang.

Paketni tahlil qilish bilan birga algoritmning psevdokodi:

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

Bir (*) tizim holatini boshqarishingiz kerak bo'lgan nuqtalar belgilangan - birinchi bosqichda siz ularsiz TCP qo'l siqishini seknum sifatida SYN cookie-faylini yaratish orqali amalga oshirishingiz mumkin.

Joyida (**), bizda stol bo'lmasa-da, biz paketni o'tkazib yuboramiz.

TCP qo'l siqishini amalga oshirish

Paketni tahlil qilish va kodni tekshirish

Bizga tarmoq sarlavhalari tuzilmalari kerak bo'ladi: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) va TCP (uapi/linux/tcp.h). bilan bog'liq xatolar tufayli ikkinchisiga ulana olmadim atomic64_t, kerakli ta'riflarni kodga nusxalashim kerak edi.

O'qilishi uchun C da ta'kidlangan barcha funktsiyalar qo'ng'iroq nuqtasida kiritilishi kerak, chunki yadrodagi eBPF tekshiruvi orqaga qaytishni, ya'ni aslida tsikllar va funktsiya chaqiruvlarini taqiqlaydi.

#define INTERNAL static __attribute__((always_inline))

Ibratli LOG() versiyada chop etishni o'chiradi.

Dastur funktsiyalarning konveyeridir. Ularning har biri tegishli darajadagi sarlavha ta'kidlangan paketni oladi, masalan, process_ether() to'ldirilishini kutadi ether. Dala tahlili natijalariga ko'ra, funktsiya paketni yuqori darajaga o'tkazishi mumkin. Funktsiyaning natijasi XDP harakatidir. Hozircha SYN va ACK ishlov beruvchilari barcha paketlarni uzatadi.

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

Men sizning e'tiboringizni A va B bilan belgilangan cheklarga qarataman. Agar siz A ga izoh bersangiz, dastur tuziladi, lekin yuklashda tekshirish xatosi bo'ladi:

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!

Kalit qatori invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Bufer boshidan boshlab o'n uchinchi bayt paketdan tashqarida bo'lganda ijro yo'llari mavjud. Ro'yxatda biz qaysi qator haqida gapirayotganimizni tushunish qiyin, ammo ko'rsatma raqami (12) va manba kodining satrlarini ko'rsatadigan qismlarga ajratuvchi mavjud:

llvm-objdump -S xdp_filter.o | less

Bunday holda, u chiziqqa ishora qiladi

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

bu muammoning mavjudligini aniq ko'rsatadi ether. Har doim shunday bo'lardi.

SYN ga javob bering

Ushbu bosqichdagi maqsad fiksatsiyalangan to'g'ri SYNACK paketini yaratishdir seqnum, kelajakda SYN cookie fayli bilan almashtiriladi. Barcha o'zgarishlar ichida sodir bo'ladi process_tcp_syn() va atrofdagi hududlar.

Paketni tekshirish

G'alati, bu erda eng diqqatga sazovor satr, aniqrog'i, unga sharh:

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

Kodning birinchi versiyasini yozishda 5.1 yadrosi ishlatilgan, uni tekshirish uchun o'rtasida farq bor edi. data_end и (const void*)ctx->data_end. Yozish vaqtida yadro 5.3.1da bunday muammo yo'q edi. Ehtimol, kompilyator mahalliy o'zgaruvchiga maydondan farqli ravishda kirgan bo'lishi mumkin. Hikoya axloqi: Kodni soddalashtirish ko'p uyalar mavjud bo'lganda yordam berishi mumkin.

Keyinchalik tekshirgichning ulug'vorligini muntazam ravishda tekshirish; O MAX_CSUM_BYTES quyida ko'rsatilgan.

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

Paketni ochish

Biz to'ldiramiz seqnum и acknum, ACK ni o'rnating (SYN allaqachon o'rnatilgan):

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

TCP portlarini, IP manzilini va MAC manzillarini almashtiring. XDP dasturidan standart kutubxonaga kirish mumkin emas, shuning uchun memcpy() - Clang ichki xususiyatlarini yashiradigan makros.

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

Tekshirish summalarini qayta hisoblash

IPv4 va TCP nazorat summalari sarlavhalarga barcha 16 bitli so'zlarni qo'shishni talab qiladi va sarlavhalarning o'lchami ularga yoziladi, ya'ni kompilyatsiya vaqtida noma'lum. Bu muammo, chunki tekshirgich chegara o'zgaruvchisiga oddiy tsiklni o'tkazib yubormaydi. Ammo sarlavhalarning hajmi cheklangan: har biri 64 baytgacha. Siz erta tugashi mumkin bo'lgan ma'lum miqdordagi takroriy tsiklni yaratishingiz mumkin.

borligini ta'kidlayman QRM 1624 faqat paketlarning belgilangan so'zlari o'zgartirilsa, nazorat summasini qisman qayta hisoblash haqida. Biroq, usul universal emas va amalga oshirishni saqlab qolish qiyinroq bo'ladi.

Tekshirish summasini hisoblash funktsiyasi:

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

Shunga qaramasdan size qo'ng'iroq kodi bilan tasdiqlangan bo'lsa, ikkinchi chiqish sharti tekshirgich tsiklning tugallanganligini isbotlashi uchun zarur.

32-bitli so'zlar uchun oddiyroq versiya amalga oshiriladi:

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

Aslida nazorat summalarini qayta hisoblash va paketni qaytarib yuborish:

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;

vazifa carry() RFC 32 ga binoan 16 bitli so'zlarning 791 bitli yig'indisidan nazorat summasini yaratadi.

TCP qoʻl siqish tekshiruvi

Filtr bilan aloqani to'g'ri o'rnatadi netcat, Linux RST paketi bilan javob bergan yakuniy ACKni yo'qotdi, chunki tarmoq stek SYN ni olmagan - u SYNACK ga aylantirilgan va qaytarib yuborilgan - va OS nuqtai nazaridan, ochish bilan bog'liq bo'lmagan paket keldi. ulanishlar.

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

To'liq huquqli ilovalar bilan tekshirish va kuzatish muhimdir tcpdump haqida xdp-remote chunki, masalan, hping3 noto'g'ri nazorat summalariga javob bermaydi.

XDP nuqtai nazaridan, tekshirishning o'zi ahamiyatsiz. Hisoblash algoritmi oddiy va murakkab tajovuzkorga nisbatan zaif. Masalan, Linux yadrosi kriptografik SipHash-dan foydalanadi, ammo uni XDP uchun amalga oshirish ushbu maqola doirasidan tashqarida.

Tashqi aloqa bilan bog'liq yangi TODOlar uchun kiritilgan:

  • XDP dasturi saqlay olmaydi cookie_seed (tuzning maxfiy qismi) global o'zgaruvchida siz yadroda saqlashingiz kerak, uning qiymati ishonchli generatordan vaqti-vaqti bilan yangilanadi.

  • Agar SYN cookie fayli ACK paketiga mos kelsa, siz xabarni chop etishingiz shart emas, lekin undan paketlarni uzatishni davom ettirish uchun tasdiqlangan mijozning IP manzilini eslab qoling.

Qonuniy mijoz tekshiruvi:

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

Jurnallar tekshiruvdan o'tganligini ko'rsatadi (flags=0x2 - bu SYN, flags=0x10 bu 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

Tasdiqlangan IP-lar ro'yxati mavjud bo'lmasa-da, SYN toshqinining o'zidan himoya bo'lmaydi, ammo bu erda quyidagi buyruq bilan boshlangan ACK toshqiniga reaktsiya:

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

Jurnal yozuvlari:

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

xulosa

Ba'zida umuman eBPF va xususan XDP rivojlanish platformasi sifatida emas, balki ilg'or ma'mur vositasi sifatida taqdim etiladi. Haqiqatan ham, XDP yadro tomonidan paketlarni qayta ishlashga xalaqit beradigan vosita bo'lib, DPDK va yadroni aylanib o'tishning boshqa variantlari kabi yadro stekiga muqobil emas. Boshqa tomondan, XDP sizga juda murakkab mantiqni amalga oshirishga imkon beradi, bundan tashqari, uni trafikni qayta ishlashda uzilishlarsiz yangilash oson. Tekshiruvchi katta muammolarni keltirib chiqarmaydi; shaxsan men foydalanuvchilar maydoni kodining qismlari uchun buni rad etmayman.

Ikkinchi qismda, agar mavzu qiziqarli bo'lsa, biz tasdiqlangan mijozlar va uzilishlar jadvalini to'ldiramiz, hisoblagichlarni amalga oshiramiz va filtrni boshqarish uchun foydalanuvchi maydoni yordam dasturini yozamiz.

Manbalar:

Manba: www.habr.com

a Izoh qo'shish