Пишемо заштиту од ДДоС напада на КСДП. Нуклеарни део

Технологија еКспресс Дата Патх (КСДП) дозвољава произвољну обраду саобраћаја на Линук интерфејсима пре него што пакети уђу у мрежни стог кернела. Примена КСДП-а - заштита од ДДоС напада (ЦлоудФларе), сложени филтери, прикупљање статистике (Нетфлик). КСДП програме извршава еБПФ виртуелна машина, тако да имају ограничења и на њихов код и на доступне функције кернела, у зависности од типа филтера.

Чланак има за циљ да надокнади недостатке бројних материјала о КСДП-у. Прво, они обезбеђују готов код који одмах заобилази карактеристике КСДП-а: припремљен за верификацију или превише једноставан да изазове проблеме. Када касније покушате да напишете сопствени код од нуле, нема разумевања шта да радите са типичним грешкама. Друго, не покрива начине локалног тестирања КСДП-а без ВМ-а и хардвера, упркос чињеници да они имају своје замке. Текст је намењен програмерима који познају мреже и Линук који су заинтересовани за КСДП и еБПФ.

У овом делу ћемо детаљно разумети како се КСДП филтер саставља и како га тестирати, затим ћемо написати једноставну верзију добро познатог механизма СИН колачића на нивоу обраде пакета. Док не формирамо "белу листу"
верификовани клијенти, водите бројаче и управљајте филтером - довољно дневника.

Писаћемо на Ц - ово није модерно, већ практично. Сав код је доступан на ГитХуб-у на линку на крају и подељен је на урезивање према корацима описаним у чланку.

Одрицање од одговорности. У току чланка биће развијено мини решење за одбијање ДДоС напада, јер је то реалан задатак за КСДП и моју област. Међутим, главни циљ је разумевање технологије, ово није водич за креирање готове заштите. Код туторијала није оптимизован и изоставља неке нијансе.

Кратак преглед КСДП-а

Навешћу само кључне тачке да не бих дуплирао документацију и постојеће чланке.

Дакле, код филтера се учитава у кернел. Филтеру се прослеђују долазни пакети. Као резултат, филтер мора да донесе одлуку: да проследи пакет језгру (XDP_PASS), испусти пакет (XDP_DROP) или га пошаљи назад (XDP_TX). Филтер може да промени пакет, што посебно важи за XDP_TX. Такође можете срушити програм (XDP_ABORTED) и одбаците пакет, али ово је аналогно assert(0) - за отклањање грешака.

Виртуелна машина еБПФ (ектендед Берклеи Пацкет Филтер) намерно је поједностављена тако да кернел може да провери да се код не врти у петљи и да не оштети меморију других људи. Кумулативна ограничења и провере:

  • Лоопс (скокови назад) су забрањени.
  • Постоји стек за податке, али нема функција (све Ц функције морају бити уметнуте).
  • Приступ меморији изван стека и бафера пакета је забрањен.
  • Величина кода је ограничена, али у пракси то није много значајно.
  • Дозвољене су само посебне функције кернела (еБПФ помоћници).

Развој и инсталирање филтера изгледа овако:

  1. изворни код (нпр. kernel.c) компајлира у објекат (kernel.o) за архитектуру виртуелне машине еБПФ. Од октобра 2019. Цланг подржава компајлирање у еБПФ и обећава га у ГЦЦ 10.1.
  2. Ако у овом објектном коду постоје позиви структурама језгра (на пример, табелама и бројачима), уместо њихових ИД-а постоје нуле, односно такав код се не може извршити. Пре учитавања у кернел, ове нуле морају бити замењене ИД-овима одређених објеката креираних путем позива кернела (повезати код). То можете учинити помоћу спољних услужних програма или можете написати програм који ће повезати и учитати одређени филтер.
  3. Кернел проверава програм који се учитава. Проверава одсуство циклуса и неизлазак граница пакета и стека. Ако верификатор не може да докаже да је код исправан, програм се одбацује - мора се моћи угодити њему.
  4. Након успешне верификације, кернел компајлира објектни код еБПФ архитектуре у машински код архитектуре система (баш на време).
  5. Програм је прикључен на интерфејс и почиње да обрађује пакете.

Пошто КСДП ради у кернелу, отклањање грешака се заснива на евиденцији праћења и, у ствари, на пакетима које програм филтрира или генерише. Међутим, еБПФ чува преузети код безбедним за систем, тако да можете експериментисати са КСДП-ом директно на вашем локалном Линук-у.

Припрема животне средине

Скупштина

Цланг не може директно да изда објектни код за еБПФ архитектуру, тако да се процес састоји од два корака:

  1. Преведите Ц код у ЛЛВМ бајт код (clang -emit-llvm).
  2. Претвори бајт код у еБПФ објектни код (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

Макефиле за Арцх Линук (кернел 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 - Архитектура система. Путања и алати могу мало да се разликују између дистрибуција.

Пример разлике за Дебиан 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__ значи да су УАПИ (усерспаце АПИ) заглавља дефинисана за код кернела, пошто се филтер извршава у кернелу.

Заштита стека може да се онемогући (-fno-stack-protector) зато што верификатор еБПФ кода ионако проверава да није ван граница стека. Требало би одмах да омогућите оптимизације, јер је величина еБПФ бајткода ограничена.

Почнимо са филтером који пропушта све пакете и не ради ништа:

#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 prikuplja xdp_filter.o. Где га сада можете тестирати?

Тест постоље

Сталак треба да садржи два интерфејса: на којима ће бити филтер и са којих ће се слати пакети. То морају бити потпуни Линук уређаји са сопственим ИП адресама да би се проверило како обичне апликације раде са нашим филтером.

Уређаји као што је ветх (виртуелни етернет) су погодни за нас: они су пар виртуелних мрежних интерфејса „повезаних“ директно један са другим. Можете их креирати овако (у овом одељку све команде 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) долазни саобраћај ће бити послат. Међутим, постоји проблем: интерфејси су на истој машини, а Линук неће слати саобраћај једном од њих преко другог. Можете га решити лукавим правилима iptables, али ће морати да мењају пакете, што је незгодно приликом отклањања грешака. Боље је користити мрежне просторе имена (мрежни простори имена, даље мреже).

Мрежни именски простор садржи скуп интерфејса, табела рутирања и НетФилтер правила која су изолована од сличних објеката у другим мрежама. Сваки процес се покреће у неком именском простору и њему су доступни само објекти ове мреже. Подразумевано, систем има један мрежни простор имена за све објекте, тако да можете да радите на Линук-у и не знате за нетнс.

Хајде да направимо нови простор имена 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 који је доступан овом процесу. Ово такође функционише обрнуто.

Када се крећете између мрежа, интерфејс се спушта и губи адресу. Да бисте подесили интерфејс у ​​нетнс, потребно је да покренете 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.

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

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

Чињеница је да еБПФ програми немају одељак података, тако да је једини начин за кодирање стринга формата непосредни аргументи ВМ команди:

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

Из тог разлога, излаз за отклањање грешака у великој мери надувава резултујући код.

Слање КСДП пакета

Хајде да променимо филтер: нека шаље све долазне пакете назад. Ово је нетачно са мрежне тачке гледишта, пошто би било потребно променити адресе у заглављима, али сада је важан принцип рада.

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

Лансирање tcpdump на xdp-remote. Требало би да приказује идентичан одлазни и долазни ИЦМП ехо захтев и да престане да приказује ИЦМП ехо одговор. Али то се не види. Испада да ради XDP_TX у програму за xdp-local моратиза упаривање интерфејса xdp-remote програм је такође додељен, чак и ако је био празан, и он је подигнут.

Како сам знао?

Праћење путање пакета у кернелу механизам перф догађаја омогућава, иначе, коришћење исте виртуелне машине, односно еБПФ се користи за растављање са еБПФ-ом.

Од зла морате направити добро, јер од њега нема шта друго.

$ 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, додајте га у Макефиле, повежите се са 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

Ако је уместо тога приказан само АРП, потребно је да уклоните филтере (ово чини sudo ./stand detach), дозволити ping, затим инсталирајте филтере и покушајте поново. Проблем је што филтер XDP_TX утиче и на АРП, а ако стек
именских простора xdp-test успео да "заборави" МАЦ адресу 192.0.2.1, неће моћи да реши ову ИП адресу.

Проблем статемент

Пређимо на наведени задатак: написати механизам СИН колачића на КСДП.

До сада, СИН флоод остаје популаран ДДоС напад, чија је суштина следећа. Када се веза успостави (ТЦП руковање), сервер прима СИН, додељује ресурсе за будућу везу, одговара СИНАЦК пакетом и чека АЦК. Нападач једноставно шаље СИН пакете са лажних адреса у количини од хиљада у секунди са сваког хоста у више хиљада ботнета. Сервер је приморан да додели ресурсе одмах по доласку пакета, али га отпушта након дужег временског ограничења, као резултат тога, меморија или ограничења су исцрпљени, нове везе се не прихватају, услуга је недоступна.

Ако не доделите ресурсе на СИН пакет, већ само одговорите са СИНАЦК пакетом, како онда сервер може да разуме да АЦК пакет који је дошао касније припада СИН пакету који није сачуван? На крају крајева, нападач такође може да генерише лажне АЦК-ове. Суштина СИН колачића је да се кодира seqnum параметри везе као хеш адреса, портова и промена соли. Ако је АЦК успео да стигне пре промене соли, можете поново израчунати хеш и упоредити са acknum. лажан acknum нападач не може, пошто сол укључује тајну, и неће имати времена да је сортира због ограниченог канала.

СИН колачићи су већ дуго имплементирани у Линук кернел и чак се могу аутоматски омогућити ако СИН-ови стигну пребрзо и масовно.

Едукативни програм о ТЦП руковању

ТЦП обезбеђује пренос података као ток бајтова, на пример, ХТТП захтеви се преносе преко ТЦП-а. Стрим се преноси део по део у пакетима. Сви ТЦП пакети имају логичке заставице и 32-битне секвенце:

  • Комбинација заставица дефинише улогу одређеног пакета. Ознака СИН значи да је ово први пакет пошиљаоца на вези. Ознака АЦК значи да је пошиљалац примио све податке о вези до једног бајта. acknum. Пакет може имати неколико заставица и назван је по њиховој комбинацији, на пример, СИНАЦК пакет.

  • Број секвенце (секнум) одређује помак у току података за први бајт који се шаље у овом пакету. На пример, ако је у првом пакету са Кс бајтова података овај број био Н, у следећем пакету са новим подацима биће Н+Кс. На почетку везе, свака страна бира овај број насумично.

  • Број потврде (ацкнум) - исти помак као секнум, али не одређује број пренетог бајта, већ број првог бајта од примаоца, који пошиљалац није видео.

На почетку везе, стране морају да се договоре seqnum и acknum. Клијент шаље СИН пакет са својим seqnum = X. Сервер одговара СИНАЦК пакетом, где пише свој seqnum = Y и излаже acknum = X + 1. Клијент одговара на СИНАЦК са АЦК пакетом, где seqnum = X + 1, acknum = Y + 1. Након тога почиње стварни пренос података.

Ако саговорник не потврди пријем пакета, ТЦП га поново шаље до истека времена.

Зашто се СИН колачићи не користе увек?

Прво, ако се изгуби СИНАЦК или АЦК, мораћете да сачекате поновно слање - успостављање везе се успорава. Друго, у СИН пакету - и само у њему! - преноси се низ опција које утичу на даљи рад везе. Не памтећи долазне СИН пакете, сервер стога игнорише ове опције, у следећим пакетима клијент их више неће слати. ТЦП може радити у овом случају, али барем у почетној фази, квалитет везе ће се смањити.

Што се тиче пакета, КСДП програм треба да уради следеће:

  • одговорите на СИН са СИНАЦК са колачићем;
  • одговорите на АЦК са РСТ (прекините везу);
  • испусти друге пакете.

Псеудокод алгоритма заједно са рашчлањивањем пакета:

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

Једна (*) означене су тачке у којима треба да управљате стањем система – у првој фази можете без њих једноставним имплементирањем ТЦП руковања са генерисањем СИН колачића као секвенце.

На сајту (**), док немамо табелу, прескочићемо пакет.

Имплементација ТЦП руковања

Парсирање пакета и верификација кода

Потребне су нам структуре заглавља мреже: Етхернет (uapi/linux/if_ether.h), ИПв4 (uapi/linux/ip.h) и ТЦП (uapi/linux/tcp.h). Последње нисам могао да се повежем због грешака у вези са atomic64_t, морао сам да копирам потребне дефиниције у код.

Све функције које се разликују у Ц-у због читљивости морају бити уметнуте на место позива, пошто еБПФ верификатор у кернелу забрањује скокове уназад, односно, у ствари, петље и позиве функција.

#define INTERNAL static __attribute__((always_inline))

Макро LOG() онемогућава штампање у верзији издања.

Програм је низ функција. Сваки прима пакет у којем је истакнуто заглавље одговарајућег нивоа, нпр. process_ether() чека да се попуни ether. На основу резултата анализе поља, функција може пренети пакет на виши ниво. Резултат функције је КСДП акција. Док СИН и АЦК руковаоци пропуштају све пакете.

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. Увек би било тако.

Одговорите на СИН

Циљ у овој фази је да се генерише исправан СИНАЦК пакет са фиксним seqnum, који ће у будућности бити замењен колачићем СИН. Све промене се дешавају у 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, поставите АЦК (СИН је већ подешен):

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

Замените ТЦП портове, ИП и МАЦ адресе. Стандардна библиотека није доступна из КСДП програма, тако да memcpy() — макро који скрива Цланг интринсик.

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

Поновно израчунавање контролне суме

ИПв4 и ТЦП контролни суми захтевају додавање свих 16-битних речи у заглавља, а величина заглавља је уписана у њима, односно у време компилације је непозната. Ово је проблем јер верификатор неће прескочити нормалну петљу све до граничне променљиве. Али величина заглавља је ограничена: до 64 бајта свако. Можете направити петљу са фиксним бројем итерација, које се могу завршити раније.

Напомињем да постоји РФЦ КСНУМКС о томе како делимично поново израчунати контролни збир ако се промене само фиксне речи пакета. Међутим, метода није универзална, а имплементацију би било теже одржавати.

Функција израчунавања контролне суме:

#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() прави контролни збир од 32-битног збира 16-битних речи, према РФЦ 791.

ТЦП провера руковања

Филтер исправно успоставља везу са netcat, прескачући коначни АЦК, на који је Линук одговорио са РСТ пакетом, пошто мрежни стек није примио СИН – конвертован је у СИНАЦК и послат назад – и са становишта ОС-а стигао је пакет који није везано за отворене везе.

$ 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 не реагује на нетачне контролне суме.

Са становишта КСДП-а, сама провера је тривијална. Алгоритам прорачуна је примитиван и вероватно рањив на софистицираног нападача. Линук кернел, на пример, користи криптографски СипХасх, али његова имплементација за КСДП је очигледно ван оквира овог чланка.

Појавио се за нове ТОДО-е везане за спољну интеракцију:

  • КСДП програм не може да сачува cookie_seed (тајни део соли) у глобалној променљивој, потребно вам је складиште кернела чија ће вредност бити периодично ажурирана из поузданог генератора.

  • Ако се СИН колачић у АЦК пакету подудара, не морате да штампате поруку, већ запамтите ИП верификованог клијента да бисте даље прескочили пакете са њега.

Валидација од стране легитимног клијента:

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

Дневници су забележили пролазак провере (flags=0x2 је СИН, flags=0x10 је АЦК):

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

Све док не постоји листа верификованих ИП адреса, неће бити заштите од саме СИН поплаве, али ево реакције на АЦК поплаву коју покреће ова команда:

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

Закључак

Понекад се еБПФ уопште и посебно КСДП представљају више као напредни администраторски алат него као развојна платформа. Заиста, КСДП је алатка за ометање обраде пакета кернела, а не алтернатива стеку кернела, као што је ДПДК и друге опције заобилажења кернела. С друге стране, КСДП вам омогућава да имплементирате прилично сложену логику, коју је, штавише, лако ажурирати без паузе у обради саобраћаја. Верификатор не ствара велике проблеме, лично не бих одбио такве за делове кода корисничког простора.

У другом делу, ако је тема интересантна, употпунићемо табелу проверених клијената и прекинућемо везе, имплементирати бројаче и написати услужни програм корисничког простора за управљање филтером.

Референце:

Извор: ввв.хабр.цом

Додај коментар