Биз XDP боюнча DDoS чабуулдарынан коргоо жазып жатабыз. Ядролук бөлүгү

eXpress Data Path (XDP) технологиясы пакеттер ядро ​​тармагынын стекине киргенге чейин Linux интерфейстеринде кокус трафикти иштетүүгө мүмкүндүк берет. XDP колдонуу - DDoS чабуулдарынан коргоо (CloudFlare), татаал фильтрлер, статистикалык чогултуу (Netflix). XDP программалары eBPF виртуалдык машинасы тарабынан аткарылат, ошондуктан чыпка түрүнө жараша алардын кодуна да, жеткиликтүү ядро ​​функцияларына да чектөөлөр бар.

Макала XDP боюнча көптөгөн материалдардын кемчиликтерин толтуруу үчүн арналган. Биринчиден, алар дароо XDP өзгөчөлүктөрүн кыйгап өткөн даяр кодду беришет: ал текшерүү үчүн даярдалган же көйгөй жаратууга өтө жөнөкөй. Андан кийин кодуңузду нөлдөн баштап жазууга аракет кылганыңызда, типтүү каталар менен эмне кылууну билбей каласыз. Экинчиден, XDPти VM жана жабдыксыз локалдык тестирлөө жолдору, алардын өз тузактары бар экендигине карабастан, камтылган эмес. Текст XDP жана eBPFге кызыккан тармактар ​​жана Linux менен тааныш программисттер үчүн арналган.

Бул бөлүктө биз XDP чыпкасы кантип чогултулганын жана аны кантип сынап көрүүнү майда-чүйдөсүнө чейин түшүнөбүз, андан кийин пакетти иштетүү деңгээлинде белгилүү SYN кукилер механизминин жөнөкөй версиясын жазабыз. Азырынча биз “ак тизме” түзбөйбүз
текшерилген кардарлар, эсептегичтерди сактоо жана чыпкаларды башкаруу - жетиштүү журналдар.

Биз C тилинде жазабыз - бул мода эмес, бирок практикалык. Бардык код GitHubда аягындагы шилтеме аркылуу жеткиликтүү жана макалада сүрөттөлгөн этаптарга ылайык милдеттенмелерге бөлүнгөн.

Баш тартуу. Бул макаланын жүрүшүндө DDoS чабуулдарынан сактануу үчүн мини-чечим иштелип чыгат, анткени бул XDP жана менин талаам үчүн реалдуу милдет. Бирок, негизги максат технологияны түшүнүү болуп саналат; бул даяр коргоону түзүү үчүн колдонмо эмес. Окутуучу коду оптималдаштырылган эмес жана кээ бир нюанстарды калтырган.

XDP Кыскача сереп

Документтерди жана учурдагы макалаларды кайталабоо үчүн мен негизги пункттарды гана белгилейм.

Ошентип, чыпка коду ядрого жүктөлөт. Кирүүчү пакеттер чыпкага өткөрүлөт. Натыйжада, чыпка чечим кабыл алышы керек: пакетти ядрого өткөрүп берүү (XDP_PASS), пакетти таштоо (XDP_DROP) же кайра жөнөтүңүз (XDP_TX). Фильтр пакетти өзгөртө алат, бул өзгөчө үчүн туура XDP_TX. Сиз ошондой эле программаны токтото аласыз (XDP_ABORTED) жана пакетти баштапкы абалга келтириңиз, бирок бул окшош assert(0) - мүчүлүштүктөрдү оңдоо үчүн.

eBPF (кеңейтилген 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

Arch Linux үчүн Makefile (ядро 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. Аны азыр кайда сынап көрүш керек?

Сыноочу стенд

Стенд эки интерфейсти камтышы керек: аларда чыпка болот жана андан пакеттер жөнөтүлөт. Кадимки тиркемелер биздин чыпка менен кантип иштээрин текшерүү үчүн булар өздөрүнүн IP менен толук кандуу Linux түзмөктөрү болушу керек.

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 (ал демейки боюнча нетнде кала берет) жана пакетти 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. Ал бирдей чыгуучу жана кирүүчү ICMP Echo Request'ти көрсөтүп, ICMP Echo Reply көрсөтүүнү токтотушу керек. Бирок көрсөтпөйт. Иш үчүн экен 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 чече албайт.

Тапшырманын коюлушу

Келгиле, айтылган тапшырмага өтөбүз: XDPде SYN кукилеринин механизмин жазуу.

SYN ташкыны популярдуу DDoS чабуулу бойдон калууда, анын маңызы төмөндөгүдөй. Байланыш орногондо (TCP кол алышуу), сервер SYN алат, келечектеги байланыш үчүн ресурстарды бөлүштүрөт, SYNACK пакети менен жооп берет жана ACKти күтөт. Чабуулчу жөн гана миңдеген күчтүү ботнеттеги ар бир хосттон секундасына миңдеген жасалма даректерден SYN пакеттерин жөнөтөт. Сервер пакет келгенде дароо ресурстарды бөлүштүрүүгө аргасыз болот, бирок аларды чоң тайм-ауттан кийин бошотот; натыйжада эстутум же чектөөлөр түгөнүп, жаңы туташуулар кабыл алынбайт жана кызмат жеткиликтүү эмес.

Эгерде сиз ресурстарды SYN пакетинин негизинде бөлүштүрбөсөңүз, бирок SYNACK пакети менен гана жооп берсеңиз, сервер кийинчерээк келген ACK пакети сакталбаган SYN пакетине тиешелүү экенин кантип түшүнө алат? Анткени, чабуулчу жасалма ACKтарды да жаратышы мүмкүн. SYN cookie файлынын максаты - аны коддоо seqnum туташуу параметрлери даректердин, порттордун жана өзгөрүлүүчү туздардын хэштери катары. Эгерде ACK туз өзгөртүлгөнгө чейин жетип калса, сиз хэшти кайра эсептеп, аны менен салыштырсаңыз болот acknum. Forge acknum чабуулчу кыла албайт, анткени туз сырды камтыйт жана чектелген каналдан улам аны иргей албайт.

SYN кукиси көптөн бери Linux ядросунда ишке ашырылып келет жана SYN өтө тез жана массалык түрдө келип калса, автоматтык түрдө иштетилиши мүмкүн.

TCP кол алышуу боюнча билим берүү программасы

TCP байт агымы катары маалыматтарды берүүнү камсыз кылат, мисалы, HTTP сурамдары TCP аркылуу өткөрүлөт. Агым пакеттерде бөлүктөргө бөлүнүп берилет. Бардык TCP пакеттеринде логикалык желекчелер жана 32 биттик катар номерлери бар:

  • Желектердин айкалышы белгилүү бир пакеттин ролун аныктайт. SYN желеги бул байланыштагы жөнөтүүчүнүн биринчи пакети экенин билдирет. ACK желеги жөнөтүүчү байтка чейинки бардык байланыш маалыматтарын алганын билдирет acknum. Пакеттин бир нече желектери болушу мүмкүн жана алардын айкалышы боюнча аталат, мисалы, SYNACK пакети.

  • Тартип номери (seqnum) бул пакетте берилүүчү биринчи байт үчүн маалымат агымындагы офсетти аныктайт. Мисалы, X байт маалыматтары бар биринчи пакетте бул сан N болсо, жаңы маалыматтары бар кийинки пакетте N+X болот. Байланыштын башында ар бир тарап бул санды туш келди тандайт.

  • Ырастоо номери (акнум) секнум менен бирдей офсет, бирок ал өткөрүлүп жаткан байттын санын эмес, алуучудан биринчи байттын санын, жөнөтүүчү көрбөгөнүн аныктайт.

Байланыштын башында тараптар макулдашып алышы керек 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 программасы төмөнкүлөрдү аткарышы керек:

  • куки менен SYNACK менен SYNге жооп бериңиз;
  • RST менен ACK жооп берүү (ажыратуу);
  • калган пакеттерди таштаңыз.

Пакетти талдоо менен бирге алгоритмдин псевдокоду:

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

Бир (*) Системанын абалын башкаруу үчүн зарыл болгон пункттар белгиленген - биринчи этапта сиз аларсыз TCP кол алышуусун секнум катары SYN cookie файлын түзүү менен жөн гана ишке ашыруу менен кыла аласыз.

Ордунда (**), бизде үстөл жок болсо да, биз пакетти өткөрүп жиберебиз.

TCP кол алышуу ишке ашырылууда

Пакетти талдоо жана кодду текшерүү

Бизге тармак баш структуралары керек болот: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) жана TCP (uapi/linux/tcp.h). Каталардан улам акыркысына эч качан кошула алган жокмун atomic64_t, Мен кодго керектүү аныктамаларды көчүрүү керек болчу.

Си тилинде окулушу үчүн бөлүнгөн бардык функциялар чалуу чекитинде киргизилиши керек, анткени ядродогу 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);
}

Мен сиздин көңүлүңүздү А жана В деп белгиленген чектерге бурамын. Эгер сиз А деп комментарий жазсаңыз, программа түзүлөт, бирок жүктөөдө текшерүү катасы пайда болот:

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 үчүн аны ишке ашыруу бул макаланын алкагынан тышкары экени анык.

Тышкы өз ара аракеттенүүгө байланыштуу жаңы TODOs үчүн пайда болду:

  • XDP программасы сактай албайт cookie_seed (туздун жашыруун бөлүгү) глобалдык өзгөрмөдө, сиз ядродо сактагыч керек, анын мааниси ишенимдүү генератордон мезгил-мезгили менен жаңыланып турат.

  • Эгерде SYN cookie файлы 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 кыйла татаал логиканы ишке ашырууга мүмкүндүк берет, андан тышкары, трафикти иштетүүдө үзгүлтүксүз жаңыртуу оңой. Текшерүүчү чоң көйгөйлөрдү жаратпайт; жеке мен колдонуучулардын мейкиндигинин кодунун бөлүктөрүндө мындан баш тартпайт элем.

Экинчи бөлүктө, эгерде тема кызыктуу болсо, биз текшерилген кардарлардын жана өчүрүүлөрдүн таблицасын толтурабыз, эсептегичтерди ишке киргизебиз жана чыпканы башкаруу үчүн колдонуучулар мейкиндигин жазабыз.

шилтеме:

Source: www.habr.com

Комментарий кошуу