Мо муҳофизат аз ҳамлаҳои DDoS дар XDP менависем. Қисми ҳастаӣ

Технологияи eXpress Data Path (XDP) имкон медиҳад, ки коркарди трафики тасодуфӣ дар интерфейсҳои Linux пеш аз ворид шудани пакетҳо ба стеки шабакаи ядроӣ анҷом дода шавад. Истифодаи XDP - муҳофизат аз ҳамлаҳои DDoS (CloudFlare), филтрҳои мураккаб, ҷамъоварии омор (Netflix). Барномаҳои XDP аз ҷониби мошини виртуалии eBPF иҷро карда мешаванд, аз ин рӯ онҳо вобаста ба намуди филтр ҳам код ва ҳам функсияҳои мавҷудаи ядро ​​​​маҳдудият доранд.

Макола барои пур кардани камбудихои материалхои сершумор оид ба XDP нигаронида шудааст. Аввалан, онҳо рамзи тайёреро пешниҳод мекунанд, ки фавран хусусиятҳои XDP-ро аз байн мебарад: он барои санҷиш омода аст ё барои эҷоди мушкилот хеле содда аст. Вақте ки шумо кӯшиш мекунед, ки коди худро аз сифр нависед, шумо намедонед, ки бо хатогиҳои маъмулӣ чӣ кор кунед. Дуюм, роҳҳои ба таври маҳаллӣ санҷиши XDP бе VM ва сахтафзор фаро гирифта нашудаанд, сарфи назар аз он ки онҳо домҳои худро доранд. Матн барои барномасозоне, ки бо шабака ва Linux шиносанд, ки ба XDP ва eBPF таваҷҷӯҳ доранд, пешбинӣ шудааст.

Дар ин қисм, мо ба таври муфассал фаҳмем, ки филтри XDP чӣ гуна ҷамъоварӣ карда мешавад ва чӣ гуна озмоиш кардани он, пас мо як версияи оддии механизми маъруфи кукиҳои SYN-ро дар сатҳи коркарди бастаҳо менависем. Мо ҳоло "рӯйхати сафед" эҷод намекунем
мизоҷони тасдиқшуда, ҳисобкунакҳоро нигоҳ доред ва филтрро идора кунед - гузоришҳои кофӣ.

Мо дар C менависем - ин муд нест, аммо амалӣ аст. Ҳама кодҳо дар GitHub тавассути истиноди охири дастрасанд ва мувофиқи марҳилаҳои дар мақола тавсифшуда ба ӯҳдадориҳо тақсим карда мешаванд.

Эзоҳ. Дар давоми ин мақола, ман як ҳалли хурдро барои пешгирии ҳамлаҳои DDoS таҳия хоҳам кард, зеро ин як вазифаи воқеӣ барои XDP ва соҳаи тахассуси ман аст. Аммо, ҳадафи асосӣ фаҳмидани технология аст, ки ин дастур барои эҷоди муҳофизати омода нест. Рамзи дарсӣ оптимизатсия карда нашудааст ва баъзе нозукиҳоро сарфи назар мекунад.

Шарҳи мухтасари XDP

Ман танҳо нуктаҳои асосиро шарҳ медиҳам, то ҳуҷҷатҳо ва мақолаҳои мавҷударо такрор накунам.

Ҳамин тавр, рамзи филтр ба ядро ​​бор карда мешавад. Бастаҳои воридотӣ ба филтр интиқол дода мешаванд. Дар натиҷа, филтр бояд қарор қабул кунад: бастаро ба ядро ​​​​гузаронед (XDP_PASS), бастаи партофтан (XDP_DROP) ё онро баргардонед (XDP_TX). Филтр метавонад бастаро тағир диҳад, ин махсусан барои он дуруст аст XDP_TX. Шумо инчунин метавонед барномаро қатъ кунед (XDP_ABORTED) ва бастаро аз нав танзим кунед, аммо ин шабеҳ аст assert(0) - барои ислоҳ.

Мошини маҷозии eBPF (extended Berkley Packet Filter) дидаву дониста содда карда шудааст, то ядро ​​​​санҷад, ки рамз давр назанад ва ба хотираи одамони дигар осеб нарасонад. Маҳдудиятҳо ва чекҳои ҷамъшуда:

  • Дохилшавӣ (ба ақиб) манъ аст.
  • Барои додаҳо стек мавҷуд аст, аммо ягон функсия вуҷуд надорад (ҳамаи функсияҳои C бояд хаттӣ карда шаванд).
  • Дастрасии хотира берун аз стек ва буфери пакет манъ аст.
  • Андозаи код маҳдуд аст, аммо дар амал ин чандон муҳим нест.
  • Танҳо зангҳо ба функсияҳои махсуси ядро ​​(ёварҳои eBPF) иҷозат дода мешаванд.

Тарҳрезӣ ва насби филтр чунин аст:

  1. Рамзи манбаъ (масалан kernel.c) ба объект (kernel.o) барои меъмории мошини виртуалии eBPF. То моҳи октябри соли 2019, тартиб додани eBPF аз ҷониби Clang дастгирӣ карда мешавад ва дар GCC 10.1 ваъда шудааст.
  2. Агар ин рамзи объект зангҳоро ба сохторҳои ядро ​​(масалан, ҷадвалҳо ва ҳисобкунакҳо) дар бар гирад, идентификаторҳои онҳо бо сифрҳо иваз карда мешаванд, ки ин маънои онро дорад, ки чунин код иҷро карда намешавад. Пеш аз бор кардан ба ядро, шумо бояд ин сифрҳоро бо ID-ҳои объектҳои мушаххасе, ки тавассути зангҳои ядро ​​​​офарида шудаанд, иваз кунед (кодро пайванд кунед). Шумо метавонед ин корро бо утилитаҳои беруна иҷро кунед, ё шумо метавонед барномаеро нависед, ки филтри мушаххасро пайванд ва бор мекунад.
  3. Ядро барномаи боршударо тафтиш мекунад. Набудани давраҳо ва аз сарҳади бастаҳо ва стекҳо гузаштан тафтиш карда мешавад. Агар тафтишкунанда дуруст будани кодро исбот карда натавонад, барнома рад карда мешавад - шумо бояд ба ӯ писанд ояд.
  4. Пас аз санҷиши бомуваффақият, ядро ​​рамзи объекти меъмории eBPF-ро ба рамзи мошин барои меъмории система (дар вақташ) тартиб медиҳад.
  5. Барнома ба интерфейс пайваст мешавад ва коркарди бастаҳоро оғоз мекунад.

Азбаски XDP дар ядро ​​​​кор мекунад, ислоҳкунӣ бо истифода аз гузоришҳои пайгирӣ ва воқеан бастаҳое, ки барнома филтр мекунад ё тавлид мекунад, анҷом дода мешавад. Бо вуҷуди ин, eBPF кафолат медиҳад, ки рамзи зеркашидашуда барои система бехатар аст, бинобар ин шумо метавонед бо XDP мустақиман дар Linux-и маҳаллии худ озмоиш кунед.

Омода кардани муҳити зист

Ассамблея

Clang наметавонад бевосита рамзи объектро барои меъмории eBPF тавлид кунад, бинобар ин раванд аз ду марҳила иборат аст:

  1. Рамзи C-ро ба байткоди LLVM тартиб диҳед (clang -emit-llvm).
  2. Табдил додани байткод ба рамзи объекти eBPF (llc -march=bpf -filetype=obj).

Ҳангоми навиштани филтр, якчанд файлҳо бо функсияҳои ёрирасон ва макросҳо муфид хоҳанд буд аз санҷишҳои ядро ​​​​. Муҳим аст, ки онҳо ба версияи ядро ​​​​ мувофиқат кунанд (KVER). Онҳоро зеркашӣ кунед 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 барои Arch Linux (ядро 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 дорои роҳ ба сарлавҳаҳои ядро, ARCH — меъмории система. Роҳҳо ва асбобҳо метавонанд байни тақсимот каме фарқ кунанд.

Намунаи фарқиятҳо барои Debian 10 (ядро 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 директорияро бо сарлавҳаҳои ёрирасон ва якчанд каталогҳоро бо сарлавҳаҳои ядро ​​пайваст кунед. Рамз __KERNEL__ маънои онро дорад, ки сарлавҳаҳои UAPI (userspace API) барои рамзи ядро ​​муайян карда шудаанд, зеро филтр дар ядро ​​иҷро карда мешавад.

Муҳофизати стекро ғайрифаъол кардан мумкин аст (-fno-stack-protector), зеро санҷиши рамзи eBPF то ҳол вайронкуниҳои берун аз ҳудуди стекро тафтиш мекунад. Дарҳол ба кор даровардани оптимизатсия меарзад, зеро андозаи байткоди eBPF маҳдуд аст.

Биёед бо филтр оғоз кунем, ки ҳамаи бастаҳоро мегузарад ва ҳеҷ кор намекунад:

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

гурӯҳ make ҷамъ меорад xdp_filter.o. Ҳоло онро дар куҷо санҷед?

Пойгоҳи санҷишӣ

Стенд бояд ду интерфейсро дар бар гирад: дар он филтр мавҷуд аст ва аз он бастаҳо фиристода мешаванд. Инҳо бояд дастгоҳҳои мукаммали Linux бо IP-и худ бошанд, то тафтиш кунанд, ки барномаҳои муқаррарӣ бо филтри мо чӣ гуна кор мекунанд.

Дастгоҳҳои навъи veth (virtual Ethernet) барои мо мувофиқанд: инҳо як ҷуфт интерфейсҳои шабакаи виртуалӣ мебошанд, ки мустақиман ба ҳамдигар "пайваст шудаанд". Шумо метавонед онҳоро чунин созед (дар ин бахш ҳама фармонҳо ip аз он гузаронида мешаванд root):

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

Ин аст, xdp-remote и xdp-local - номҳои дастгоҳ. Дар бораи xdp-local (192.0.2.1/24) филтр замима карда мешавад, бо xdp-remote (192.0.2.2/24) трафики воридотӣ фиристода мешавад. Аммо, мушкилот вуҷуд дорад: интерфейсҳо дар як мошин ҷойгиранд ва Linux трафикро ба яке аз онҳо тавассути дигараш намефиристад. Шумо метавонед инро бо қоидаҳои душвор ҳал кунед iptables, аммо онҳо маҷбур мешаванд, ки бастаҳоро иваз кунанд, ки ин барои ислоҳ кардан номувофиқ аст. Беҳтар аст, ки фазои номҳои шабакавӣ (минбаъд netns) истифода шавад.

Фазои номи шабака маҷмӯи интерфейсҳо, ҷадвалҳои масир ва қоидаҳои NetFilterро дар бар мегирад, ки аз объектҳои шабеҳ дар шабакаҳои дигар ҷудо шудаанд. Ҳар як раванд дар фазои ном кор мекунад ва танҳо ба объектҳои ин шабакаҳо дастрасӣ дорад. Бо нобаёнӣ, система барои ҳама объектҳо фазои ягонаи шабака дорад, бинобар ин шумо метавонед дар Linux кор кунед ва дар бораи netns маълумот надоред.

Биёед як фазои нав эҷод кунем xdp-test ва онро ба он ҷо интиқол диҳед xdp-remote.

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

Пас аз он, раванд ба амал меояд xdp-test, "намебинад" xdp-local (он ба таври пешфарз дар netns боқӣ мемонад) ва ҳангоми фиристодани баста ба 192.0.2.1 он аз он мегузарад. xdp-remoteзеро он ягона интерфейси 192.0.2.0/24 аст, ки ба ин раванд дастрас аст. Ин ҳам дар самти муқобил кор мекунад.

Ҳангоми ҳаракат дар байни netns, интерфейс поён меравад ва суроғаи худро гум мекунад. Барои танзим кардани интерфейс дар netns, шумо бояд иҷро кунед ip ... дар ин фазои номи фармон 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

Тавре ки шумо мебинед, ин аз танзимот фарқ надорад xdp-local дар фазои номи пешфарз:

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

Агар шумо давед tcpdump -tnevi xdp-local, шумо мебинед, ки бастаҳо аз xdp-test, ба ин интерфейс интиқол дода мешаванд:

ip netns exec xdp-test   ping 192.0.2.1

Барои ба кор андохтани снаряд қулай аст xdp-test. Дар анбор скрипт, ки автоматикунонии кор бо стенд дорад, масалан, шумо метавонед стенд бо фармони танзим; sudo ./stand up ва нест кунед sudo ./stand down.

Пайгирӣ

Филтр бо дастгоҳ чунин алоқаманд аст:

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

Калидвожа -force барои пайваст кардани барномаи нав лозим аст, агар барномаи дигар аллакай пайваст бошад. «Ягон хабар хушхабар нест» дар бораи ин фармон нест, хулоса ба хар хол том аст. нишон медиҳад verbose ихтиёрӣ, аммо бо он гузориш дар бораи кори санҷиши код бо рӯйхати васлкунӣ пайдо мешавад:

Verifier analysis:

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

Барномаро аз интерфейс ҷудо кунед:

ip link set dev xdp-local xdp off

Дар скрипт ин фармонҳо мебошанд sudo ./stand attach и sudo ./stand detach.

Бо пайваст кардани филтр, шумо метавонед боварӣ ҳосил кунед, ки ping корашро давом медихад, аммо программа кор мекунад? Биёед қайдҳоро илова кунем. Функсия bpf_trace_printk() монанд ба printf(), аммо танҳо то се далели ғайр аз намуна ва рӯйхати маҳдуди мушаххаскуниро дастгирӣ мекунад. макро bpf_printk() зангро осон мекунад.

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

Натиҷа ба канали пайгирии ядро ​​​​меравад, ки бояд фаъол карда шавад:

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

Дидани риштаи паём:

cat /sys/kernel/debug/tracing/trace_pipe

Ҳардуи ин фармонҳо занг мезананд sudo ./stand log.

Ping акнун бояд паёмҳои зеринро ба вуҷуд орад:

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

Агар шумо ба натиҷаи тафтишкунанда бодиққат назар кунед, шумо ҳисобҳои аҷибро мебинед:

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

Далели он аст, ки барномаҳои eBPF қисмати маълумот надоранд, аз ин рӯ ягона роҳи рамзгузории сатри формат далелҳои фаврии фармонҳои VM мебошад:

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

Аз ин сабаб, баромади дебаг коди натиҷавиро хеле варам мекунад.

Фиристодани бастаҳои XDP

Биёед филтрро тағир диҳем: бигзор он ҳамаи бастаҳои воридшударо баргардонад. Ин аз нуқтаи назари шабака нодуруст аст, зеро иваз кардани суроғаҳо дар сарлавҳаҳо лозим буд, аммо ҳоло кор дар принсип муҳим аст.

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

Оғози tcpdump ба xdp-remote. Он бояд якхелаи Echo Request-и содиротӣ ва воридшавандаро нишон диҳад ва намоиши Echo Reply ICMP-ро қатъ кунад. Аммо он нишон намедиҳад. Маълум мешавад, ки барои кор XDP_TX дар барнома оид ба xdp-local зарур астба интерфейси ҷуфт xdp-remote як барнома низ, агар холӣ бошад, таъин карда шуд ва ӯ баланд шуд.

Ман инро аз куҷо медонистам?

Роҳи бастаро дар ядро ​​пайгирӣ кунед Механизми рӯйдодҳои perf имкон медиҳад, ки бо истифода аз ҳамон мошини маҷозӣ, яъне eBPF барои ҷудокунӣ бо eBPF истифода мешавад.

Шумо бояд аз бадӣ некӣ кунед, зеро чизи дигаре нест, ки онро аз он хориҷ кунед.

$ 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 чист?

$ errno 6
ENXIO 6 No such device or address

функсия veth_xdp_flush_bq() рамзи хатогиро аз veth_xdp_xmit(), ки аз рӯи ҷустуҷӯ ENXIO ва шарҳро пайдо кунед.

Биёед филтри ҳадди ақалро барқарор кунем (XDP_PASS) дар файл xdp_dummy.c, онро ба Makefile илова кунед, ба он пайваст кунед xdp-remote:

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

Акнун tcpdump нишон медиҳад, ки чӣ интизор аст:

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

Агар ба ҷои он танҳо ARPҳо нишон дода шаванд, шумо бояд филтрҳоро хориҷ кунед (ин sudo ./stand detach), сар додан ping, пас филтрҳоро насб кунед ва дубора кӯшиш кунед. Мушкилот дар он аст, ки филтр XDP_TX ҳам дар ARP ва ҳам дар стек эътибор дорад
фазои номҳо xdp-test суроғаи MAC 192.0.2.1-ро "фаромӯш" карда тавонист, он IP-ро ҳал карда наметавонад.

Тартиб додани масъала

Биёед ба вазифаи зикршуда мегузарем: механизми кукиҳои SYN-ро дар XDP нависед.

Обхезии SYN як ҳамлаи маъмули DDoS боқӣ мемонад, ки моҳияти он чунин аст. Вақте ки пайвастшавӣ барқарор карда мешавад (дастфишори TCP), сервер SYN мегирад, захираҳоро барои пайвасти оянда ҷудо мекунад, бо бастаи SYNACK посух медиҳад ва интизори ACK мешавад. Ҳамлагар танҳо дар як сония ҳазорҳо бастаҳои SYN-ро аз суроғаҳои қаллобӣ аз ҳар як ҳост дар ботнети бисёрҳазорнафарӣ мефиристад. Сервер маҷбур мешавад, ки дарҳол пас аз расидани баста захираҳоро ҷудо кунад, аммо онҳоро пас аз муддати тӯлонӣ мебарорад, дар натиҷа, хотира ё маҳдудиятҳо тамом мешаванд, пайвастҳои нав қабул карда намешаванд ва хидмат дастрас нест.

Агар шумо захираҳоро дар асоси бастаи SYN ҷудо накунед, балки танҳо бо бастаи SYNACK ҷавоб диҳед, пас чӣ гуна сервер метавонад фаҳмад, ки бастаи ACK, ки дертар омадааст, ба бастаи SYN, ки захира нашудааст, дахл дорад? Дар ниҳоят, ҳамлакунанда инчунин метавонад ACK-ҳои қалбакӣ тавлид кунад. Мақсади кукии SYN ин рамзгузорӣ кардани он дар он аст seqnum параметрҳои пайвастшавӣ ҳамчун хэш суроғаҳо, бандарҳо ва намаки тағйирёбанда. Агар ACK тавонист пеш аз тағир додани намак расида бошад, шумо метавонед хэшро дубора ҳисоб кунед ва онро бо муқоиса кунед acknum. Forge acknum ҳамлакунанда наметавонад, зеро намак сирро дар бар мегирад ва аз сабаби маҳдуд будани канал вақт надорад.

Кукии SYN кайҳо боз дар ядрои Linux татбиқ карда шудааст ва ҳатто метавонад ба таври худкор фаъол карда шавад, агар SYN-ҳо хеле зуд ва оммавӣ ворид шаванд.

Барномаи таълимӣ оид ба дастфишори TCP

TCP интиқоли маълумотро ҳамчун ҷараёни байт таъмин мекунад, масалан, дархостҳои HTTP тавассути TCP интиқол дода мешаванд. Ҷараён ба қисмҳо дар бастаҳо интиқол дода мешавад. Ҳама пакетҳои TCP дорои парчамҳои мантиқӣ ва рақамҳои пайдарпайии 32-бит мебошанд:

  • Омезиши парчамҳо нақши бастаи мушаххасро муайян мекунад. Парчами SYN нишон медиҳад, ки ин аввалин бастаи ирсолкунанда дар пайвастшавӣ аст. Парчами ACK маънои онро дорад, ки ирсолкунанда тамоми маълумоти пайвастшавиро то байт гирифтааст acknum. Баста метавонад якчанд парчам дошта бошад ва аз рӯи комбинатсияи онҳо номида мешавад, масалан, бастаи SYNACK.

  • Рақами пайдарпай (seqnum) ҷубронро дар ҷараёни маълумот барои байти аввал, ки дар ин баста интиқол дода мешавад, муайян мекунад. Масалан, агар дар бастаи якум бо X байт маълумот ин адад N бошад, дар бастаи оянда бо маълумоти нав он N+X хоҳад буд. Дар оғози пайвастшавӣ ҳар як тараф ин рақамро ба таври тасодуфӣ интихоб мекунад.

  • Рақами тасдиқ (акнум) - ҳамон ҷуброн бо seqnum, аммо он шумораи байтҳои интиқолшавандаро муайян намекунад, балки шумораи байти аввалро аз қабулкунанда, ки ирсолкунанда надидааст, муайян мекунад.

Дар ибтидои пайвастшавӣ тарафҳо бояд мувофиқат кунанд seqnum и acknum. Мизоҷ бастаи SYN-ро бо он мефиристад seqnum = X. Сервер бо бастаи SYNACK ҷавоб медиҳад, ки дар он ҷо онро сабт мекунад seqnum = Y ва фош мекунад acknum = X + 1. Мизоҷ ба SYNACK бо бастаи ACK ҷавоб медиҳад, ки дар он ҷо seqnum = X + 1, acknum = Y + 1. Пас аз ин, интиқоли воқеии маълумот оғоз меёбад.

Агар ҳамсол қабули бастаро эътироф накунад, TCP пас аз гузашти вақт онро дубора мефиристад.

Чаро кукиҳои SYN на ҳамеша истифода мешаванд?

Аввалан, агар SYNACK ё ACK гум шавад, шумо бояд интизор шавед, ки он дубора фиристода шавад - танзими пайвастшавӣ суст мешавад. Дуюм, дар бастаи SYN - ва танҳо дар он! — як қатор вариантҳо интиқол дода мешаванд, ки ба кори минбаъдаи пайвастшавӣ таъсир мерасонанд. Бе дар хотир доштани бастаҳои SYN, сервер ин имконотро нодида мегирад, муштарӣ онҳоро дар бастаҳои навбатӣ намефиристад; TCP метавонад дар ин ҳолат кор кунад, аммо ҳадди аққал дар марҳилаи аввал сифати пайвастшавӣ паст мешавад.

Аз нуқтаи назари бастаҳо, барномаи XDP бояд корҳои зеринро иҷро кунад:

  • ба SYN бо SYNACK бо куки ҷавоб диҳед;
  • ба ACK бо RST посух диҳед (қатъ кардан);
  • бастаҳои боқимондаро партоед.

Псевдокоди алгоритм дар якҷоягӣ бо таҳлили бастаҳо:

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

Як (*) нуқтаҳое, ки шумо бояд ҳолати системаро идора кунед, қайд карда шудаанд - дар марҳилаи аввал шумо метавонед бидуни онҳо танҳо тавассути татбиқи дастфишори TCP бо тавлиди кукии SYN ҳамчун секннум кор кунед.

Дар ҷои (**), дар ҳоле ки мо миз надорем, мо бастаро мегузарем.

Татбиқи дастфишори TCP

Таҳлили баста ва тасдиқи код

Мо ба сохторҳои сарлавҳаи шабака ниёз дорем: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) ва TCP (uapi/linux/tcp.h). Ман натавонистам охиринро бо сабаби хатогиҳои марбут ба он пайваст кунам atomic64_t, Ба ман лозим омад, ки таърифҳои заруриро ба код нусхабардорӣ кунам.

Ҳама функсияҳое, ки дар C барои хондан таъкид шудаанд, бояд дар нуқтаи занг ворид карда шаванд, зеро санҷиши eBPF дар ядро ​​бозгашт пайгирӣ, яъне дар асл ҳалқаҳо ва зангҳои функсияро манъ мекунад.

#define INTERNAL static __attribute__((always_inline))

макро LOG() чопро дар сохти релиз хомӯш мекунад.

Барнома як конвейери вазифаҳост. Ҳар як бастаеро мегирад, ки дар он сарлавҳаи сатҳи мувофиқ таъкид шудааст, масалан, process_ether() интизор аст, ки он пур мешавад ether. Дар асоси натиҷаҳои таҳлили саҳроӣ, функсия метавонад пакетро ба сатҳи баландтар гузаронад. Натиҷаи функсия амали XDP мебошад. Дар айни замон, коркардкунандагони SYN ва ACK ҳама пакетҳоро мегузаранд.

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

Ман таваҷҷуҳи шуморо ба чекҳои қайдшудаи A ва B ҷалб мекунам. Агар шумо A шарҳ диҳед, барнома сохта мешавад, аммо ҳангоми боркунӣ хатогии тафтиш ба вуҷуд меояд:

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!

Сатри калидӣ invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): Роҳҳои иҷро мавҷуданд, вақте ки байти сездаҳум аз оғози буфер берун аз баста аст. Аз рӯйхат фаҳмидани он душвор аст, ки мо дар бораи кадом сатр гап мезанем, аммо рақами дастур (12) ва дезасемблер мавҷуд аст, ки сатрҳои рамзи сарчашмаро нишон медиҳад:

llvm-objdump -S xdp_filter.o | less

Дар ин ҳолат, он ба хат ишора мекунад

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

ки ин масъаларо равшан нишон медихад ether. Ҳамеша чунин хоҳад буд.

Ба SYN ҷавоб диҳед

Ҳадаф дар ин марҳила тавлиди бастаи дурусти SYNACK бо бастаи собит аст seqnum, ки дар оянда бо кукии SYN иваз карда мешавад. Ҳама тағйирот дар process_tcp_syn() ва районхои гирду атроф.

Тафтиши баста

Аҷиб он аст, ки ин хати ҷолибтарин, дурусттараш шарҳи он аст:

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

Ҳангоми навиштани версияи якуми код, ядрои 5.1 истифода мешуд, ки барои санҷиши он фарқият байни data_end и (const void*)ctx->data_end. Ҳангоми навиштан, ядрои 5.3.1 ин мушкилот надошт. Мумкин аст, ки компилятор ба тағирёбандаи маҳаллӣ ба таври дигар аз майдон дастрасӣ дошта бошад. Ахлоқи ҳикоя: Соддасозии код метавонад ҳангоми зиёд будани лона кӯмак кунад.

Баъдан санҷишҳои муқаррарии дарозии шӯҳрати тафтишкунанда мебошанд; О MAX_CSUM_BYTES дар поён.

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

Кушодани баста

Мо пур мекунем seqnum и acknum, танзим ACK (SYN аллакай муқаррар шудааст):

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

Портҳои TCP, суроғаи IP ва суроғаҳои MAC-ро иваз кунед. Китобхонаи стандартӣ аз барномаи XDP дастрас нест, бинобар ин memcpy() - макрос, ки дохилии 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);

Аз нав ҳисоб кардани маблағҳои назоратӣ

Маблағҳои назоратии IPv4 ва TCP илова кардани ҳамаи калимаҳои 16-битро дар сарлавҳаҳо талаб мекунанд ва андозаи сарлавҳаҳо дар онҳо навишта мешавад, яъне дар вақти тартибдиҳӣ номаълум. Ин мушкилот аст, зеро тафтишкунанда ҳалқаи муқаррариро ба тағирёбандаи сарҳадӣ намегузаронад. Аммо андозаи сарлавҳаҳо маҳдуд аст: ҳар як то 64 байт. Шумо метавонед як ҳалқаро бо шумораи муайяни такрорӣ созед, ки он метавонад барвақт анҷом ёбад.

Ман қайд мекунам, ки вуҷуд дорад RFC 1624 дар бораи чӣ гуна қисман аз нав ҳисоб кардани маблағи чек, агар танҳо калимаҳои муқарраршудаи бастаҳо иваз карда шаванд. Бо вуҷуди ин, усул универсалӣ нест ва татбиқи онро нигоҳ доштан душвортар хоҳад буд.

Функсияи ҳисобкунии маблағи чек:

#define MAX_CSUM_WORDS 32
#define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2)

INTERNAL u32
sum16(const void* data, u32 size, const void* data_end) {
    u32 s = 0;
#pragma unroll
    for (u32 i = 0; i < MAX_CSUM_WORDS; i++) {
        if (2*i >= size) {
            return s; /* normal exit */
        }
        if (data + 2*i + 1 + 1 > data_end) {
            return 0; /* should be unreachable */
        }
        s += ((const u16*)data)[i];
    }
    return s;
}

Гарчанде size бо коди занг санҷида мешавад, шарти баромади дуюм зарур аст, то тафтишкунанда тавонад анҷоми давриро исбот кунад.

Барои калимаҳои 32-бит версияи соддатар амалӣ карда мешавад:

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

Воқеан аз нав ҳисоб кардани маблағҳои чек ва фиристодани пакет:

ip->check = 0;
ip->check = carry(sum16(ip, ip_len, data_end));

u32 tcp_csum = 0;
tcp_csum += sum16_32(ip->saddr);
tcp_csum += sum16_32(ip->daddr);
tcp_csum += 0x0600;
tcp_csum += tcp_len << 8;
tcp->check = 0;
tcp_csum += sum16(tcp, tcp_len, data_end);
tcp->check = carry(tcp_csum);

return XDP_TX;

функсия carry() Мувофиқи RFC 32, аз маблағи 16-битии калимаҳои 791-бит маблағи санҷишро месозад.

Санҷиши дастфишори TCP

Филтр пайвасти дурустро бо netcat, ACK-и ниҳоӣ, ки Linux ба он бо бастаи RST ҷавоб дод, аз даст дод, зеро стеки шабака SYN-ро қабул накард - он ба SYNACK табдил дода шуд ва баргардонида шуд - ва аз нуқтаи назари OS, бастае расид, ки ба кушодани он алоқаманд набуд. алоқаҳо.

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

Муҳим аст, ки бо барномаҳои мукаммал тафтиш кунед ва риоя кунед tcpdump ба xdp-remote зеро, масалан, hping3 ба тафтиши нодуруст чавоб намедихад.

Аз нуқтаи назари XDP, худи санҷиш ночиз аст. Алгоритми ҳисоб оддӣ аст ва эҳтимолан ба ҳамлагари мураккаб осебпазир аст. Масалан, ядрои Linux, SipHash криптографиро истифода мебарад, аммо татбиқи он барои XDP бешубҳа аз доираи ин мақола берун аст.

Барои TODO-ҳои нави марбут ба иртиботи беруна ҷорӣ карда шудааст:

  • Барномаи XDP наметавонад захира кунад cookie_seed (қисми махфии намак) дар як тағирёбандаи глобалӣ, ба шумо нигоҳдорӣ дар ядро ​​лозим аст, ки арзиши он давра ба давра аз генератори боэътимод нав карда мешавад.

  • Агар кукии SYN дар бастаи ACK мувофиқат кунад, ба шумо лозим нест, ки паёмро чоп кунед, аммо IP-и муштарии тасдиқшударо дар хотир доред, то интиқоли пакетҳоро аз он идома диҳед.

Санҷиши қонунии муштарӣ:

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

Журналҳо нишон медиҳанд, ки чек гузашт (flags=0x2 - ин SYN аст, flags=0x10 ACK аст):

Ether(proto=0x800)
  IP(src=0x20e6e11a dst=0x20e6e11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x2)
Ether(proto=0x800)
  IP(src=0xfe2cb11a dst=0xfe2cb11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x10)
      cookie matches for client 20200c0

Гарчанде ки рӯйхати IP-ҳои тасдиқшуда мавҷуд нест, аз худи сели SYN ҳеҷ гуна муҳофизат нахоҳад буд, аммо ин аст аксуламал ба обхезии ACK, ки бо фармони зерин оғоз шудааст:

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

Воридоти сабт:

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

хулоса

Баъзан eBPF дар маҷмӯъ ва махсусан XDP на ҳамчун платформаи рушд бештар ҳамчун воситаи пешрафтаи мудир муаррифӣ карда мешавад. Дарвоқеъ, XDP абзорест барои дахолат ба коркарди пакетҳо аз ҷониби ядро, на алтернатива ба стеки ядро, ба монанди DPDK ва дигар имконоти гузариш ядро. Аз тарафи дигар, XDP ба шумо имкон медиҳад, ки мантиқи хеле мураккабро амалӣ кунед, ки илова бар ин, бе қатъ дар коркарди трафик навсозӣ кардан осон аст. Тасдиқкунанда шахсан мушкилоти калон эҷод намекунад, ман инро барои қисмҳои коди истифодабарандагон рад намекунам.

Дар қисми дуюм, агар мавзӯъ ҷолиб бошад, мо ҷадвали муштариёни тасдиқшуда ва ҷудокуниро пур мекунем, ҳисобкунакҳоро амалӣ месозем ва барои идоракунии филтр утилитаи фазои корбарӣ менависем.

Истинодҳо:

Манбаъ: will.com

Илова Эзоҳ