Tunaandika ulinzi dhidi ya mashambulizi ya DDoS kwenye XDP. Sehemu ya nyuklia

Teknolojia ya eXpress Data Path (XDP) inaruhusu uchakataji holela wa trafiki kwenye violesura vya Linux kabla ya pakiti kuingia kwenye rundo la mtandao wa kernel. Utumizi wa XDP - ulinzi dhidi ya mashambulizi ya DDoS (CloudFlare), filters tata, ukusanyaji wa takwimu (Netflix). Programu za XDP hutekelezwa na mashine pepe ya eBPF, na kwa hivyo ina vizuizi kwa nambari zao zote mbili na kazi zinazopatikana za kernel, kulingana na aina ya kichungi.

Nakala hiyo imekusudiwa kufidia mapungufu ya vifaa vingi kwenye XDP. Kwanza, hutoa msimbo uliotengenezwa tayari ambao hupita mara moja vipengele vya XDP: iliyotayarishwa kwa uthibitishaji au rahisi sana kusababisha matatizo. Unapojaribu kuandika msimbo wako mwenyewe kutoka mwanzo baadaye, hakuna ufahamu wa nini cha kufanya na makosa ya kawaida. Pili, haitoi njia za kujaribu XDP ndani bila VM na vifaa, licha ya ukweli kwamba wana mitego yao wenyewe. Maandishi yanalenga watayarishaji programu wanaofahamu mitandao na Linux ambao wanapenda XDP na eBPF.

Katika sehemu hii, tutaelewa kwa undani jinsi chujio cha XDP kimekusanyika na jinsi ya kukijaribu, basi tutaandika toleo rahisi la utaratibu unaojulikana wa vidakuzi vya SYN kwenye kiwango cha usindikaji wa pakiti. Mpaka tutengeneze "orodha nyeupe"
wateja waliothibitishwa, weka vihesabio na udhibiti kichujio - kumbukumbu za kutosha.

Tutaandika katika C - hii sio mtindo, lakini ni ya vitendo. Nambari zote zinapatikana kwenye GitHub kwenye kiunga mwishoni na imegawanywa katika ahadi kulingana na hatua zilizoelezewa kwenye kifungu.

Hukumu. Katika kipindi cha kifungu, suluhisho la mini la kurudisha nyuma mashambulizi ya DDoS litatengenezwa, kwa sababu hii ni kazi ya kweli kwa XDP na eneo langu. Hata hivyo, lengo kuu ni kuelewa teknolojia, hii sio mwongozo wa kuunda ulinzi tayari. Msimbo wa mafunzo haujaboreshwa na huacha baadhi ya nuances.

Muhtasari mfupi wa XDP

Nitasema mambo muhimu tu ili si kurudia nyaraka na makala zilizopo.

Kwa hivyo, msimbo wa kichungi hupakiwa kwenye kernel. Kichujio hupitishwa pakiti zinazoingia. Kama matokeo, kichungi lazima kifanye uamuzi: kupitisha pakiti kwenye kernel (XDP_PASS), pakiti ya kuacha (XDP_DROP) au uirudishe (XDP_TX) Kichujio kinaweza kubadilisha kifurushi, hii ni kweli hasa kwa XDP_TX. Unaweza pia kuvunja programu (XDP_ABORTED) na kuacha kifurushi, lakini hii ni sawa assert(0) - kwa utatuzi.

Mashine pepe ya eBPF (Kichujio cha Pakiti kilichopanuliwa cha Berkley) inafanywa rahisi kimakusudi ili kernel iweze kuangalia kama msimbo haujizunguki na hauharibu kumbukumbu za watu wengine. Vizuizi na ukaguzi wa jumla:

  • Loops (kuruka nyuma) ni marufuku.
  • Kuna mrundikano wa data, lakini hakuna chaguo za kukokotoa (vitendaji vyote vya C lazima viwe na mstari).
  • Ufikiaji wa kumbukumbu nje ya rafu na bafa ya pakiti ni marufuku.
  • Ukubwa wa kanuni ni mdogo, lakini katika mazoezi hii sio muhimu sana.
  • Vitendaji maalum vya kernel (wasaidizi wa eBPF) pekee ndio wanaoruhusiwa.

Kutengeneza na kusanikisha kichungi inaonekana kama hii:

  1. msimbo wa chanzo (km. kernel.c) hukusanya kupinga (kernel.o) kwa usanifu wa mashine pepe ya eBPF. Kuanzia Oktoba 2019, utayarishaji wa eBPF utatumika na Clang na kuahidiwa katika GCC 10.1.
  2. Ikiwa katika msimbo huu wa kitu kuna wito kwa miundo ya kernel (kwa mfano, kwa meza na vihesabu), badala ya vitambulisho vyao kuna sufuri, yaani, kanuni hiyo haiwezi kutekelezwa. Kabla ya kupakia kwenye kernel, sufuri hizi lazima zibadilishwe na vitambulisho vya vitu maalum vilivyoundwa kupitia simu za kernel (unganisha msimbo). Unaweza kufanya hivyo na huduma za nje, au unaweza kuandika programu ambayo itaunganisha na kupakia chujio maalum.
  3. Kernel inathibitisha programu inayopakiwa. Inachunguza kutokuwepo kwa mizunguko na kutotoka kwa kifurushi na mipaka ya stack. Ikiwa mthibitishaji hawezi kuthibitisha kwamba msimbo ni sahihi, programu inakataliwa - mtu lazima awe na uwezo wa kumpendeza.
  4. Baada ya uthibitishaji uliofaulu, kernel inakusanya msimbo wa kitu cha usanifu wa eBPF kuwa msimbo wa mashine ya usanifu wa mfumo (kwa wakati tu).
  5. Programu imeunganishwa kwenye kiolesura na huanza usindikaji wa pakiti.

Kwa kuwa XDP inaendesha kwenye kernel, utatuzi unategemea kumbukumbu za ufuatiliaji na, kwa kweli, kwenye pakiti ambazo programu huchuja au kuzalisha. Hata hivyo, eBPF huweka msimbo uliopakuliwa salama kwa mfumo, kwa hivyo unaweza kujaribu XDP moja kwa moja kwenye Linux ya karibu nawe.

Kuandaa Mazingira

Mkutano

Clang haiwezi kutoa nambari ya kitu moja kwa moja kwa usanifu wa eBPF, kwa hivyo mchakato una hatua mbili:

  1. Unganisha nambari ya C kwa bytecode ya LLVM (clang -emit-llvm).
  2. Badilisha bytecode kuwa msimbo wa kitu cha eBPF (llc -march=bpf -filetype=obj).

Wakati wa kuandika kichungi, faili kadhaa zilizo na vitendaji vya msaidizi na macros zitakuja kusaidia kutoka kwa vipimo vya kernel. Ni muhimu kwamba zilingane na toleo la kernel (KVER) Pakua kwa 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

Fanya faili kwa 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 ina njia ya vichwa vya kernel, ARCH - usanifu wa mfumo. Njia na zana zinaweza kutofautiana kidogo kati ya usambazaji.

Mfano wa tofauti kwa 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 ni pamoja na saraka iliyo na vichwa vya usaidizi na saraka kadhaa zilizo na vichwa vya kernel. Alama __KERNEL__ inamaanisha kuwa vichwa vya UAPI (userspace API) vimefafanuliwa kwa msimbo wa kernel, kwani kichujio kinatekelezwa kwenye kernel.

Ulinzi wa rafu unaweza kuzimwa (-fno-stack-protector) kwa sababu kithibitishaji cha msimbo wa eBPF hukagua kutotoka kwa mipaka ya rafu. Unapaswa kuwezesha uboreshaji mara moja, kwa sababu ukubwa wa eBPF bytecode ni mdogo.

Wacha tuanze na kichungi ambacho hupitisha pakiti zote na haifanyi chochote:

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

Timu make hukusanya xdp_filter.o. Unaweza kuipima wapi sasa?

benchi ya mtihani

Msimamo unapaswa kujumuisha miingiliano miwili: ambayo kutakuwa na chujio na ambayo pakiti zitatumwa. Hizi lazima ziwe vifaa kamili vya Linux vilivyo na IP zao wenyewe ili kuangalia jinsi programu za kawaida zinavyofanya kazi na kichujio chetu.

Vifaa kama vile veth (Ethernet virtual) vinafaa kwa ajili yetu: ni jozi ya violesura vya mtandao pepe "vilivyounganishwa" moja kwa moja. Unaweza kuziunda kama hii (katika sehemu hii, amri zote ip kutekelezwa kutoka root):

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

Hapa xdp-remote и xdp-local - majina ya kifaa. Washa xdp-local (192.0.2.1/24) kichujio kitaambatishwa, na xdp-remote (192.0.2.2/24) trafiki inayoingia itatumwa. Walakini, kuna shida: miingiliano iko kwenye mashine moja, na Linux haitatuma trafiki kwa mmoja wao kupitia nyingine. Unaweza kuisuluhisha kwa sheria ngumu iptables, lakini watalazimika kubadilisha vifurushi, ambayo ni ngumu wakati wa kurekebisha. Ni bora kutumia nafasi za majina za mtandao (nafasi za majina za mtandao, neti zaidi).

Nafasi ya majina ya mtandao ina seti ya violesura, jedwali za kuelekeza, na sheria za NetFilter ambazo zimetengwa kutoka kwa vitu sawa katika neti zingine. Kila mchakato unaendeshwa katika nafasi fulani ya majina, na ni vitu vya neti hizi pekee vinavyopatikana kwake. Kwa chaguo-msingi, mfumo una nafasi moja ya majina ya mtandao kwa vitu vyote, kwa hivyo unaweza kufanya kazi kwenye Linux na usijue kuhusu neti.

Hebu tuunde nafasi mpya ya majina xdp-test na kuhamia huko xdp-remote.

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

Kisha mchakato unaendelea xdp-test, si "kuona" xdp-local (itabaki kwenye neti kwa chaguo-msingi) na wakati wa kutuma pakiti kwa 192.0.2.1 itaipitisha. xdp-remote, kwa sababu hiyo ndiyo kiolesura pekee katika 192.0.2.0/24 kinachopatikana kwa mchakato huu. Hii pia inafanya kazi kinyume chake.

Wakati wa kusonga kati ya neti, kiolesura huenda chini na kupoteza anwani. Ili kusanidi kiolesura katika netns, unahitaji kukimbia ip ... katika nafasi hii ya majina ya amri 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

Kama unaweza kuona, hii sio tofauti na mpangilio xdp-local katika nafasi ya majina chaguomsingi:

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

Ikiwa kukimbia tcpdump -tnevi xdp-local, unaweza kuona kwamba pakiti zimetumwa kutoka xdp-test, huwasilishwa kwa kiolesura hiki:

ip netns exec xdp-test   ping 192.0.2.1

Ni rahisi kuendesha ganda ndani xdp-test. Hifadhi ina hati inayofanya kazi otomatiki na msimamo, kwa mfano, unaweza kusanidi kisima na amri. sudo ./stand up na kuiondoa sudo ./stand down.

kufuatilia

Kichujio kimeunganishwa kwenye kifaa kama hii:

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

Muhimu -force inahitajika kuunganisha programu mpya ikiwa nyingine tayari imeunganishwa. "Hakuna habari ni habari njema" sio juu ya amri hii, matokeo yake ni mengi. onyesha verbose hiari, lakini nayo ripoti juu ya kazi ya kithibitishaji nambari na orodha ya mkusanyaji inaonekana:

Verifier analysis:

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

Ondoa programu kutoka kwa kiolesura:

ip link set dev xdp-local xdp off

Katika hati, hizi ni amri sudo ./stand attach и sudo ./stand detach.

Kwa kufunga kichujio, unaweza kuhakikisha kuwa ping inaendelea kufanya kazi, lakini je, programu inafanya kazi? Hebu tuongeze nembo. Kazi bpf_trace_printk() sawa na printf(), lakini inaauni hadi hoja tatu pekee isipokuwa mchoro, na orodha ndogo ya vibainishi. Jumla bpf_printk() kurahisisha simu.

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

Matokeo huenda kwa kituo cha ufuatiliaji cha kernel, ambacho kinahitaji kuwezeshwa:

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

Tazama mtiririko wa ujumbe:

cat /sys/kernel/debug/tracing/trace_pipe

Timu zote mbili zinapiga simu sudo ./stand log.

Ping inapaswa sasa kutoa ujumbe kama huu ndani yake:

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

Ukiangalia kwa karibu matokeo ya kithibitishaji, unaweza kugundua mahesabu ya kushangaza:

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

Ukweli ni kwamba programu za eBPF hazina sehemu ya data, kwa hivyo njia pekee ya kusimba kamba ya umbizo ni hoja za mara moja za amri za VM:

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

Kwa sababu hii, pato la utatuzi huzuia sana nambari inayotokana.

Inatuma pakiti za XDP

Wacha tubadilishe kichungi: iruhusu itume pakiti zote zinazoingia. Hii sio sahihi kutoka kwa mtazamo wa mtandao, kwani itakuwa muhimu kubadili anwani kwenye vichwa, lakini sasa kazi kwa kanuni ni muhimu.

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

Uzinduzi tcpdump juu ya xdp-remote. Inapaswa kuonyesha Ombi sawa la ICMP Echo linalotoka na linaloingia na kuacha kuonyesha Jibu la ICMP Echo. Lakini haionyeshi. Inageuka kufanya kazi XDP_TX katika programu ya xdp-local inahitajikaili kuoanisha kiolesura xdp-remote programu pia ilipewa, hata ikiwa ilikuwa tupu, na iliinuliwa.

Nilijuaje?

Kufuatilia njia ya kifurushi kwenye kernel utaratibu wa matukio ya perf huruhusu, kwa njia, kutumia mashine hiyo hiyo ya kawaida, yaani, eBPF inatumika kwa disassembly na eBPF.

Ni lazima ufanye wema kutokana na ubaya, kwa sababu hakuna kitu kingine cha kufanya.

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

Kanuni ya 6 ni nini?

$ errno 6
ENXIO 6 No such device or address

Kazi veth_xdp_flush_bq() hupata msimbo wa makosa kutoka veth_xdp_xmit(), ambapo tafuta kwa ENXIO na kupata maoni.

Rejesha kichujio cha chini zaidi (XDP_PASS) katika faili xdp_dummy.c, ongeza kwa Makefile, funga kwa xdp-remote:

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

Sasa tcpdump inaonyesha kile kinachotarajiwa:

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

Ikiwa ARP pekee ndiyo itaonyeshwa badala yake, unahitaji kuondoa vichujio (hii hufanya sudo ./stand detach), acha ping, kisha usakinishe vichujio na ujaribu tena. Tatizo ni kwamba chujio XDP_TX huathiri ARP pia, na ikiwa stack
nafasi za majina xdp-test imeweza "kusahau" anwani ya MAC 192.0.2.1, hataweza kutatua IP hii.

Taarifa ya tatizo

Wacha tuendelee kwenye kazi iliyotajwa: kuandika utaratibu wa kuki wa SYN kwenye XDP.

Hadi sasa, mafuriko ya SYN bado ni shambulio maarufu la DDoS, kiini chake ni kama ifuatavyo. Muunganisho unapoanzishwa (TCP handshake), seva hupokea SYN, hutenga rasilimali kwa muunganisho wa siku zijazo, hujibu kwa pakiti ya SYNACK, na kusubiri ACK. Mshambulizi hutuma tu pakiti za SYN kutoka kwa anwani ghushi kwa kiasi cha maelfu kwa sekunde kutoka kwa kila seva pangishi katika boti ya maelfu mengi. Seva inalazimika kutenga rasilimali mara moja baada ya kuwasili kwa pakiti, lakini huifungua baada ya muda mrefu, kwa sababu hiyo, kumbukumbu au mipaka imechoka, viunganisho vipya havikubaliki, huduma haipatikani.

Ikiwa hutagawa rasilimali kwenye pakiti ya SYN, lakini jibu tu kwa pakiti ya SYNACK, basi seva inawezaje kuelewa kwamba pakiti ya ACK iliyokuja baadaye ni ya pakiti ya SYN ambayo haikuhifadhiwa? Baada ya yote, mshambuliaji anaweza pia kutoa ACKs bandia. Kiini cha kidakuzi cha SYN ni kusimba ndani seqnum vigezo vya uunganisho kama heshi ya anwani, bandari na kubadilisha chumvi. Ikiwa ACK imeweza kufika kabla ya chumvi kubadilika, unaweza kukokotoa heshi tena na kulinganisha na acknum. bandia acknum mshambulizi hawezi, kwa kuwa chumvi inajumuisha siri, na haitakuwa na muda wa kutatua kwa sababu ya njia ndogo.

Vidakuzi vya SYN vimetekelezwa kwenye kinu cha Linux kwa muda mrefu na vinaweza kuwashwa kiotomatiki ikiwa SYN zitafika haraka sana na kwa wingi.

Mpango wa elimu juu ya TCP handshake

TCP hutoa uhamisho wa data kama mtiririko wa baiti, kwa mfano, maombi ya HTTP hupitishwa kupitia TCP. Mkondo hupitishwa kipande kwa kipande katika pakiti. Pakiti zote za TCP zina bendera za kimantiki na nambari za mlolongo wa 32-bit:

  • Mchanganyiko wa bendera hufafanua jukumu la kifurushi fulani. Alama ya SYN inamaanisha kuwa hii ndiyo pakiti ya kwanza ya mtumaji kwenye muunganisho. Alama ya ACK inamaanisha kuwa mtumaji amepokea data yote ya muunganisho hadi baiti. acknum. Pakiti inaweza kuwa na bendera kadhaa na inaitwa baada ya mchanganyiko wao, kwa mfano, pakiti ya SYNACK.

  • Nambari ya mfuatano (seqnum) inabainisha suluhu katika mtiririko wa data kwa baiti ya kwanza inayotumwa katika pakiti hii. Kwa mfano, ikiwa katika pakiti ya kwanza yenye ka X za data nambari hii ilikuwa N, katika pakiti inayofuata na data mpya itakuwa N+X. Mwanzoni mwa simu, kila upande huchagua nambari hii kwa nasibu.

  • Nambari ya kukiri (acknum) - kukabiliana sawa na seqnum, lakini haijui idadi ya byte iliyopitishwa, lakini idadi ya byte ya kwanza kutoka kwa mpokeaji, ambayo mtumaji hakuiona.

Mwanzoni mwa uunganisho, wahusika lazima wakubaliane seqnum и acknum. Mteja hutuma pakiti ya SYN na yake seqnum = X. Seva hujibu kwa pakiti ya SYNACK, ambapo huandika yake seqnum = Y na kufichua acknum = X + 1. Mteja anajibu SYNACK na pakiti ya ACK, ambapo seqnum = X + 1, acknum = Y + 1. Baada ya hapo, uhamisho halisi wa data huanza.

Ikiwa interlocutor haitambui kupokea pakiti, TCP huituma tena kwa muda.

Kwa nini vidakuzi vya SYN hazitumiwi kila wakati?

Kwanza, ikiwa SYNACK au ACK imepotea, itabidi ungojee kutuma tena - uanzishaji wa unganisho unapungua. Pili, kwenye pakiti ya SYN - na ndani yake tu! - idadi ya chaguzi hupitishwa zinazoathiri uendeshaji zaidi wa uunganisho. Bila kukumbuka pakiti zinazoingia za SYN, seva hupuuza chaguo hizi, katika pakiti zifuatazo mteja hatazituma tena. TCP inaweza kufanya kazi katika kesi hii, lakini angalau katika hatua ya awali, ubora wa uunganisho utapungua.

Kwa upande wa vifurushi, programu ya XDP inapaswa kufanya yafuatayo:

  • jibu SYN kwa SYNACK na kuki;
  • jibu ACK na RST (vunja unganisho);
  • kuacha pakiti nyingine.

Pseudocode ya algorithm pamoja na uchanganuzi wa pakiti:

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

Moja (*) pointi ambapo unahitaji kudhibiti hali ya mfumo ni alama - katika hatua ya kwanza, unaweza kufanya bila wao kwa tu kutekeleza TCP handshake na kuzalisha SYN kuki kama seqnum.

Kwenye tovuti (**), wakati hatuna meza, tutaruka pakiti.

Utekelezaji wa kupeana mkono wa TCP

Uchanganuzi wa kifurushi na uthibitishaji wa msimbo

Tunahitaji miundo ya kichwa cha mtandao: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) na TCP (uapi/linux/tcp.h) Ya mwisho sikuweza kuunganisha kwa sababu ya makosa yanayohusiana nayo atomic64_t, ilinibidi kunakili ufafanuzi unaohitajika kwenye nambari.

Vipengele vyote vya kukokotoa ambavyo vimebainishwa katika C kwa kusomeka lazima ziwekwe kwenye tovuti ya simu, kwa kuwa kithibitishaji cha eBPF kwenye kernel kinakataza kuruka nyuma, ambayo ni, kwa kweli, vitanzi na simu za utendaji.

#define INTERNAL static __attribute__((always_inline))

Macro LOG() inalemaza uchapishaji katika muundo wa toleo.

Mpango huo ni bomba la kazi. Kila mmoja hupokea pakiti ambayo kichwa cha kiwango kinacholingana kinasisitizwa, kwa mfano, process_ether() kusubiri kujazwa ether. Kulingana na matokeo ya uchambuzi wa shamba, kazi inaweza kuhamisha pakiti hadi ngazi ya juu. Matokeo ya chaguo za kukokotoa ni kitendo cha XDP. Wakati vidhibiti vya SYN na ACK vinaruhusu pakiti zote kupitia.

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

Ninatilia maanani ukaguzi uliowekwa alama A na B. Ikiwa utatoa maoni A, programu itaundwa, lakini kutakuwa na hitilafu ya uthibitishaji wakati wa kupakia:

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!

Kamba muhimu invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): kuna njia za utekelezaji wakati baiti ya kumi na tatu kutoka mwanzo wa bafa iko nje ya pakiti. Ni ngumu kutofautisha kutoka kwa tangazo ni mstari gani tunazungumza, lakini kuna nambari ya maagizo (12) na kitenganisha kinachoonyesha mistari ya msimbo wa chanzo:

llvm-objdump -S xdp_filter.o | less

Katika kesi hii, inaelekeza kwenye mstari

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

ambayo inaweka wazi kuwa tatizo ni ether. Ingekuwa hivyo kila wakati.

Jibu kwa SYN

Lengo katika hatua hii ni kutoa pakiti sahihi ya SYNACK na fasta seqnum, ambayo nafasi yake itachukuliwa na kidakuzi cha SYN katika siku zijazo. Mabadiliko yote hufanyika ndani process_tcp_syn() na mazingira.

Kuangalia kifurushi

Cha ajabu, hapa kuna mstari wa kushangaza zaidi, au tuseme, maoni yake:

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

Wakati wa kuandika toleo la kwanza la msimbo, kernel 5.1 ilitumiwa, kwa uthibitishaji ambao kulikuwa na tofauti kati ya data_end и (const void*)ctx->data_end. Wakati wa kuandika, 5.3.1 kernel haikuwa na shida hii. Labda mkusanyaji alikuwa akipata utofauti wa ndani tofauti na uwanja. Maadili - kwenye kiota kikubwa, kurahisisha kanuni inaweza kusaidia.

Ukaguzi zaidi wa kawaida wa urefu kwa utukufu wa kithibitishaji; O MAX_CSUM_BYTES chini.

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

Kuenea kwa kifurushi

Tunajaza seqnum и acknum, weka ACK (SYN tayari imewekwa):

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

Badili bandari za TCP, IP na anwani za MAC. Maktaba ya kawaida haipatikani kutoka kwa programu ya XDP, kwa hivyo memcpy() - macro ambayo huficha ndani ya 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);

Uhesabuji upya wa Checksum

IPv4 na TCP checksums zinahitaji kuongezwa kwa maneno yote 16-bit kwenye vichwa, na ukubwa wa vichwa umeandikwa ndani yao, yaani, wakati wa kukusanya haijulikani. Hili ni tatizo kwa sababu kithibitishaji hakitaruka kitanzi cha kawaida hadi utofauti wa mpaka. Lakini ukubwa wa vichwa ni mdogo: hadi 64 bytes kila mmoja. Unaweza kutengeneza kitanzi na nambari maalum ya kurudia, ambayo inaweza kumaliza mapema.

Nakumbuka kuwa kuna RFC 1624 kuhusu jinsi ya kuhesabu tena cheki kwa sehemu ikiwa tu maneno ya kudumu ya pakiti yanabadilishwa. Walakini, njia hiyo sio ya ulimwengu wote, na utekelezaji itakuwa ngumu zaidi kudumisha.

Kazi ya kuhesabu 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;
}

Ingawa size kuangaliwa na msimbo wa kupiga simu, hali ya pili ya kuondoka ni muhimu ili mthibitishaji aweze kuthibitisha mwisho wa kitanzi.

Kwa maneno 32-bit, toleo rahisi zaidi linatekelezwa:

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

Kwa kweli kuhesabu upya hundi na kutuma pakiti nyuma:

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;

Kazi carry() hufanya hundi kutoka kwa jumla ya 32-bit ya maneno 16-bit, kulingana na RFC 791.

TCP kuangalia handshake

Kichujio huanzisha muunganisho kwa usahihi netcat, kuruka ACK ya mwisho, ambayo Linux ilijibu kwa pakiti ya RST, kwa kuwa stack ya mtandao haikupokea SYN - ilibadilishwa kuwa SYNACK na kurudishwa - na kutoka kwa mtazamo wa OS, pakiti ilifika ambayo haikuwa. kuhusiana na miunganisho wazi.

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

Ni muhimu kuangalia na maombi kamili na uangalie tcpdump juu ya xdp-remote kwa sababu, kwa mfano, hping3 haijibu hesabu zisizo sahihi.

Kwa mtazamo wa XDP, cheki yenyewe ni ndogo. Kanuni ya kukokotoa ni ya awali na pengine inaweza kuathiriwa na mshambulizi mahiri. Kiini cha Linux, kwa mfano, kinatumia SipHash ya kriptografia, lakini utekelezaji wake kwa XDP ni wazi zaidi ya upeo wa makala hii.

Imeonekana kwa TODO mpya zinazohusiana na mwingiliano wa nje:

  • Mpango wa XDP hauwezi kuhifadhi cookie_seed (sehemu ya siri ya chumvi) katika utofauti wa kimataifa, unahitaji duka la kernel ambalo thamani yake itasasishwa mara kwa mara kutoka kwa jenereta inayoaminika.

  • Ikiwa kidakuzi cha SYN katika pakiti ya ACK kinalingana, huhitaji kuchapisha ujumbe, lakini kumbuka IP ya mteja aliyeidhinishwa ili kuruka zaidi pakiti kutoka kwayo.

Uthibitishaji na mteja halali:

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

Kumbukumbu zilirekodi kifungu cha hundi (flags=0x2 ni SYN flags=0x10 ni 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

Maadamu hakuna orodha ya IP zilizoangaliwa, hakutakuwa na ulinzi dhidi ya mafuriko ya SYN yenyewe, lakini hapa kuna majibu ya mafuriko ya ACK yaliyozinduliwa na amri hii:

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

Maingizo ya kumbukumbu:

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

Hitimisho

Wakati mwingine eBPF kwa ujumla na XDP haswa huwasilishwa kama zana ya msimamizi wa hali ya juu kuliko jukwaa la ukuzaji. Hakika, XDP ni zana ya kuingilia uchakataji wa pakiti za kernel, na sio mbadala wa mkusanyiko wa kernel, kama DPDK na chaguzi zingine za bypass kernel. Kwa upande mwingine, XDP hukuruhusu kutekeleza mantiki ngumu, ambayo, zaidi ya hayo, ni rahisi kusasisha bila pause katika usindikaji wa trafiki. Kithibitishaji hakileti shida kubwa, kibinafsi singekataa vile kwa sehemu za nambari ya nafasi ya mtumiaji.

Katika sehemu ya pili, ikiwa mada inavutia, tutakamilisha jedwali la wateja walioidhinishwa na kuvunja miunganisho, kutekeleza vihesabio na kuandika matumizi ya nafasi ya mtumiaji ili kudhibiti kichungi.

Marejeo:

Chanzo: mapenzi.com

Kuongeza maoni