XDP rašome apsaugą nuo DDoS atakų. Branduolinė dalis
„eXpress Data Path“ (XDP) technologija leidžia savavališkai apdoroti srautą „Linux“ sąsajose prieš paketams patenkant į branduolio tinklo krūvą. XDP taikymas - apsauga nuo DDoS atakų (CloudFlare), sudėtingi filtrai, statistikos rinkimas (Netflix). XDP programas vykdo virtualioji mašina eBPF, todėl joms taikomi apribojimai ir jų kodui, ir galimoms branduolio funkcijoms, atsižvelgiant į filtro tipą.
Straipsnis skirtas kompensuoti daugelio XDP medžiagų trūkumus. Pirma, jie pateikia paruoštą kodą, kuris iš karto apeina XDP funkcijas: paruoštas patikrinimui arba per paprastas, kad kiltų problemų. Kai vėliau bandote parašyti savo kodą nuo nulio, nesuprantate, ką daryti su tipinėmis klaidomis. Antra, jis neapima būdų, kaip vietoje išbandyti XDP be VM ir aparatinės įrangos, nepaisant to, kad jie turi savų spąstų. Tekstas skirtas programuotojams, susipažinusiems su tinklais ir Linux, kurie domisi XDP ir eBPF.
Šioje dalyje mes išsamiai suprasime, kaip yra surenkamas XDP filtras ir kaip jį išbandyti, tada parašysime paprastą visiems žinomo SYN slapukų mechanizmo versiją paketų apdorojimo lygyje. Kol sudarysime „baltąjį sąrašą“
patikrintus klientus, vesti skaitiklius ir tvarkyti filtrą – pakankamai žurnalų.
Rašysime C – tai ne madinga, bet praktiška. Visas kodas yra prieinamas GitHub pabaigoje esančioje nuorodoje ir yra padalintas į įsipareigojimus pagal straipsnyje aprašytus veiksmus.
Atsakomybės apribojimas. Straipsnio eigoje bus sukurtas mini sprendimas, kaip atremti DDoS atakas, nes tai yra realus XDP ir mano srities uždavinys. Tačiau pagrindinis tikslas yra suprasti technologiją, tai nėra vadovas, kaip sukurti paruoštą apsaugą. Mokymo kodas nėra optimizuotas ir praleidžia kai kuriuos niuansus.
Trumpa XDP apžvalga
Pateiksiu tik pagrindinius dalykus, kad nedubliuotų dokumentacija ir esami straipsniai.
Taigi, filtro kodas įkeliamas į branduolį. Filtras perduoda gaunamus paketus. Dėl to filtras turi priimti sprendimą: perduoti paketą branduoliui (XDP_PASS), mesti paketą (XDP_DROP) arba atsiųskite atgal (XDP_TX). Filtras gali pakeisti pakuotę, tai ypač pasakytina apie XDP_TX. Taip pat galite sulaužyti programą (XDP_ABORTED) ir numeskite pakuotę, bet tai yra analogiška assert(0) - derinimui.
eBPF (Extended Berkley Packet Filter) virtualioji mašina yra sąmoningai supaprastinta, kad branduolys galėtų patikrinti, ar kodas nepersiciklina ir nepažeidžia kitų žmonių atminties. Kaupiami apribojimai ir patikrinimai:
Kilpos (šokimai atgal) draudžiamos.
Yra duomenų krūva, bet nėra funkcijų (visos C funkcijos turi būti įtrauktos).
Prieiga prie atminties už dėklo ir paketų buferio ribų yra draudžiama.
Kodo dydis yra ribotas, tačiau praktiškai tai nėra labai reikšminga.
Leidžiamos tik specialios branduolio funkcijos (eBPF pagalbininkai).
Filtro kūrimas ir įdiegimas atrodo taip:
šaltinio kodas (pvz. kernel.c) kompiliuoja į objektą (kernel.o) eBPF virtualios mašinos architektūrai. Nuo 2019 m. spalio mėn. kompiliavimą į eBPF palaiko Clang ir žada GCC 10.1.
Jei šiame objekto kode yra iškvietimai į branduolio struktūras (pavyzdžiui, į lenteles ir skaitiklius), vietoj jų ID yra nuliai, tai yra, toks kodas negali būti vykdomas. Prieš įkeliant į branduolį, šie nuliai turi būti pakeisti konkrečių objektų, sukurtų branduolio iškvietimu, ID (susieti kodą). Tai galite padaryti naudodami išorines programas arba galite parašyti programą, kuri susies ir įkels konkretų filtrą.
Branduolys patikrina įkeliamą programą. Ji patikrina, ar nėra ciklų ir ar neišeina iš pakuotės ir krūvos ribų. Jei tikrintojas negali įrodyti, kad kodas teisingas, programa atmetama – reikia mokėti jam įtikti.
Po sėkmingo patikrinimo branduolys sukompiliuoja eBPF architektūros objekto kodą į sistemos architektūros mašinos kodą (laiku).
Programa yra prijungta prie sąsajos ir pradeda apdoroti paketus.
Kadangi XDP veikia branduolyje, derinimas pagrįstas sekimo žurnalais ir, tiesą sakant, paketais, kuriuos programa filtruoja arba generuoja. Tačiau eBPF saugo atsisiųstą kodą sistemoje, todėl galite eksperimentuoti su XDP tiesiog savo vietinėje Linux sistemoje.
Aplinkos paruošimas
Asamblėja
Clang negali tiesiogiai išduoti objekto kodo eBPF architektūrai, todėl procesas susideda iš dviejų žingsnių:
Sukompiliuokite C kodą į LLVM baito kodą (clang -emit-llvm).
Konvertuoti baitinį kodą į eBPF objekto kodą (llc -march=bpf -filetype=obj).
Rašant filtrą, pravers pora failų su pagalbinėmis funkcijomis ir makrokomandomis iš branduolio testų. Svarbu, kad jie atitiktų branduolio versiją (KVER). Atsisiųskite juos į helpers/:
KDIR yra kelias į branduolio antraštes, ARCH - architektūros sistema. Skirtinguose platinimuose keliai ir įrankiai gali šiek tiek skirtis.
„Debian 10“ skirtumų pavyzdys (branduolis 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 apima katalogą su pagalbinėmis antraštėmis ir kelis katalogus su branduolio antraštėmis. Simbolis __KERNEL__ reiškia, kad UAPI (vartotojo erdvės API) antraštės yra apibrėžtos branduolio kodui, nes filtras vykdomas branduolyje.
Krūvos apsauga gali būti išjungta (-fno-stack-protector), nes eBPF kodo tikrintuvas vis tiek tikrina, ar nėra krūvos ribų. Turėtumėte nedelsiant įjungti optimizavimą, nes eBPF baito kodo dydis yra ribotas.
Pradėkime nuo filtro, kuris perduoda visus paketus ir nieko nedaro:
Komanda make renka xdp_filter.o. Kur dabar galima išbandyti?
Bandymo stendas
Stovas turi turėti dvi sąsajas: ant kurios bus filtras ir iš kurios bus siunčiami paketai. Tai turi būti pilni Linux įrenginiai su savo IP, kad būtų galima patikrinti, kaip įprastos programos veikia su mūsų filtru.
Mums tinka tokie įrenginiai kaip veth (virtualusis Ethernet): tai pora virtualių tinklo sąsajų, „sujungtų“ tiesiogiai viena su kita. Galite juos sukurti taip (šiame skyriuje visos komandos ip atliekama nuo root):
ip link add xdp-remote type veth peer name xdp-local
Čia xdp-remote и xdp-local — įrenginių pavadinimai. Įjungta xdp-local (192.0.2.1/24) bus pritvirtintas filtras, su xdp-remote (192.0.2.2/24) bus siunčiamas įeinantis srautas. Tačiau yra problema: sąsajos yra tame pačiame kompiuteryje, o „Linux“ nesiųs srauto į vieną iš jų per kitą. Galite tai išspręsti naudodami sudėtingas taisykles iptables, tačiau jiems teks keisti paketus, o tai nepatogu derinant. Geriau naudoti tinklo vardų sritis (tinklo vardų sritis, toliau netns).
Tinklo vardų erdvėje yra sąsajų, maršruto parinkimo lentelių ir „NetFilter“ taisyklių rinkinys, kurie yra atskirti nuo panašių objektų kituose netns. Kiekvienas procesas vyksta tam tikroje vardų erdvėje ir jam pasiekiami tik šio tinklo objektai. Pagal numatytuosius nustatymus sistema turi vieną tinklo vardų erdvę visiems objektams, todėl galite dirbti su Linux ir nežinoti apie netns.
Sukurkime naują vardų erdvę xdp-test ir persikelti ten xdp-remote.
ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test
Tada vyksta procesas xdp-test, "nepamatys" xdp-local (pagal nutylėjimą jis liks netns) ir siunčiant paketą į 192.0.2.1 jis bus perduotas xdp-remote, nes tai yra vienintelė 192.0.2.0/24 sąsaja, prieinama šiam procesui. Tai taip pat veikia atvirkščiai.
Judant tarp netn sąsaja nusileidžia ir praranda adresą. Norėdami nustatyti sąsają netns, turite paleisti ip ... šioje komandų vardų erdvėje 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
Kaip matote, tai niekuo nesiskiria nuo nustatymo xdp-local numatytojoje vardų srityje:
ip address add 192.0.2.1/24 dev xdp-local
ip link set xdp-local up
Jei bėgsi tcpdump -tnevi xdp-local, matote, kad paketai siunčiami iš xdp-test, pristatomi į šią sąsają:
ip netns exec xdp-test ping 192.0.2.1
Patogu įleisti apvalkalą xdp-test. Saugykloje yra scenarijus, kuris automatizuoja darbą su stovu, pvz., stendą galite nustatyti su komanda sudo ./stand up ir pašalinkite jį sudo ./stand down.
sekimas
Filtras prie įrenginio pritvirtinamas taip:
ip -force link set dev xdp-local xdp object xdp_filter.o verbose
Raktas -force reikia susieti naują programą, jei jau susieta kita. „Jokios naujienos yra geros naujienos“ nėra apie šią komandą, bet kokiu atveju išvestis yra didelė. nurodyti verbose neprivaloma, tačiau kartu pasirodo ataskaita apie kodo tikrintojo darbą su surinkėjo sąrašu:
Verifier analysis:
0: (b7) r0 = 2
1: (95) exit
Atjunkite programą nuo sąsajos:
ip link set dev xdp-local xdp off
Scenarijuje tai yra komandos sudo ./stand attach и sudo ./stand detach.
Pririšę filtrą galite tuo įsitikinti ping ir toliau veikia, bet ar programa veikia? Pridėkime logotipus. Funkcija bpf_trace_printk() panašus į printf(), bet palaiko tik iki trijų argumentų, išskyrus šabloną, ir ribotą specifikacijų sąrašą. Makro bpf_printk() supaprastina skambutį.
Dėl šios priežasties derinimo išvestis labai išpučia gautą kodą.
XDP paketų siuntimas
Pakeiskime filtrą: tegul jis siunčia visus įeinančius paketus atgal. Tinklo požiūriu tai neteisinga, nes reikėtų keisti adresus antraštėse, bet dabar darbas iš esmės svarbus.
Paleisti tcpdump apie xdp-remote. Jis turėtų rodyti identišką siunčiamą ir gaunamą ICMP aido užklausą ir nustoti rodyti ICMP aido atsakymą. Bet nerodo. Pasirodo, veikia XDP_TX programoje, skirtoje xdp-localturisusieti sąsają xdp-remote taip pat buvo priskirta programa, net jei ji buvo tuščia, ir ji buvo iškelta.
Kaip aš sužinojau?
Paketo kelio sekimas branduolyje Perf įvykių mechanizmas, beje, leidžia naudoti tą pačią virtualią mašiną, tai yra, eBPF naudojamas išmontuoti su eBPF.
Iš blogio reikia daryti gėrį, nes nieko daugiau iš to padaryti nereikia.
Jei vietoj to rodomas tik ARP, turite pašalinti filtrus (tai daro sudo ./stand detach), leisti ping, tada įdiekite filtrus ir bandykite dar kartą. Problema ta, kad filtras XDP_TX paveikia ir ARP, o jei kaminas
vardų erdvės xdp-test sugebėjo "pamiršti" MAC adresą 192.0.2.1, jis negalės išspręsti šio IP.
Problemos teiginys
Pereikime prie nurodytos užduoties: parašyti SYN slapukų mechanizmą XDP.
Iki šiol SYN potvynis išlieka populiari DDoS ataka, kurios esmė tokia. Kai užmezgamas ryšys (TCP rankos paspaudimas), serveris gauna SYN, paskirsto resursus būsimam ryšiui, atsako SYNACK paketu ir laukia ACK. Užpuolikas tiesiog siunčia SYN paketus iš netikrų adresų tūkstančiais per sekundę iš kiekvieno pagrindinio kompiuterio kelių tūkstančių robotų tinkle. Serveris yra priverstas paskirstyti resursus iš karto po paketo atėjimo, tačiau po ilgo laiko jį atleidžia, dėl to išsenka atmintis ar limitai, nepriimami nauji ryšiai, paslauga nepasiekiama.
Jei neskiriate išteklių SYN paketui, o atsakote tik SYNACK paketu, kaip serveris gali suprasti, kad vėliau atėjęs ACK paketas priklauso SYN paketui, kuris nebuvo išsaugotas? Juk užpuolikas taip pat gali generuoti netikrus ACK. SYN slapuko esmė yra užkoduoti seqnum ryšio parametrus kaip adresų, prievadų ir besikeičiančios druskos maišą. Jei ACK pavyko gauti iki druskos keitimo, galite dar kartą apskaičiuoti maišą ir palyginti su acknum. netikras acknum užpuolikas negali, nes druska apima paslaptį, ir neturės laiko jos rūšiuoti dėl riboto kanalo.
SYN slapukai „Linux“ branduolyje buvo įdiegti ilgą laiką ir netgi gali būti automatiškai įjungti, jei SYN gaunami per greitai ir masiškai.
Mokomoji programa apie TCP rankos paspaudimą
TCP suteikia duomenų perdavimą kaip baitų srautą, pavyzdžiui, HTTP užklausos perduodamos per TCP. Srautas perduodamas gabalas po gabalo paketais. Visi TCP paketai turi logines vėliavėles ir 32 bitų eilės numerius:
Vėliavos derinys apibrėžia konkretaus paketo vaidmenį. SYN vėliavėlė reiškia, kad tai yra pirmasis siuntėjo ryšio paketas. ACK vėliavėlė reiškia, kad siuntėjas gavo visus ryšio duomenis iki baito. acknum. Paketas gali turėti keletą vėliavėlių ir pavadintas pagal jų derinį, pavyzdžiui, SYNACK paketas.
Sekos numeris (seqnum) nurodo pirmojo baito, kuris siunčiamas šiame pakete, poslinkį duomenų sraute. Pavyzdžiui, jei pirmame pakete su X baitų duomenų šis skaičius buvo N, kitame pakete su naujais duomenimis jis bus N+X. Ryšio pradžioje kiekviena šalis šį skaičių pasirenka atsitiktinai.
Patvirtinimo numeris (acknum) – toks pat poslinkis kaip ir seqnum, tačiau jis nustato ne perduodamo baito numerį, o pirmojo baito iš gavėjo numerį, kurio siuntėjas nematė.
Ryšio pradžioje šalys turi susitarti seqnum и acknum. Klientas kartu su juo siunčia SYN paketą seqnum = X. Serveris atsako SYNACK paketu, kur įrašo savo seqnum = Y ir atskleidžia acknum = X + 1. Klientas į SYNACK atsako ACK paketu, kur seqnum = X + 1, acknum = Y + 1. Po to prasideda tikrasis duomenų perdavimas.
Jei pašnekovas nepatvirtina paketo gavimo, TCP jį iš naujo siunčia pasibaigus laikui.
Kodėl SYN slapukai ne visada naudojami?
Pirma, praradus SYNACK arba ACK, turėsite palaukti pakartotinio siuntimo – ryšio užmezgimas sulėtėja. Antra, SYN pakete – ir tik jame! - perduodama daugybė parinkčių, kurios turi įtakos tolesniam ryšio veikimui. Neatsimindamas gaunamų SYN paketų, serveris nepaiso šių parinkčių, kituose paketuose klientas jų nebesiųs. Šiuo atveju TCP gali veikti, tačiau bent jau pradiniame etape ryšio kokybė sumažės.
Kalbant apie paketus, XDP programa turėtų atlikti šiuos veiksmus:
atsakyti į SYN su SYNACK su slapuku;
atsakyti ACK su RST (nutraukti ryšį);
mesti kitus paketus.
Algoritmo pseudokodas kartu su paketų analize:
Если это не Ethernet,
пропустить пакет.
Если это не IPv4,
пропустить пакет.
Если адрес в таблице проверенных, (*)
уменьшить счетчик оставшихся проверок,
пропустить пакет.
Если это не TCP,
сбросить пакет. (**)
Если это SYN,
ответить SYN-ACK с cookie.
Если это ACK,
если в acknum лежит не cookie,
сбросить пакет.
Занести в таблицу адрес с N оставшихся проверок. (*)
Ответить RST. (**)
В остальных случаях сбросить пакет.
Vienas (*) pažymėti taškai, kuriuose reikia valdyti sistemos būseną – pirmame etape galite išsiversti be jų tiesiog įgyvendindami TCP rankos paspaudimą sugeneruodami SYN slapuką kaip seką.
Vietoje (**), kol neturime stalo, paketą praleisime.
TCP rankų paspaudimo įgyvendinimas
Paketo analizė ir kodo patikrinimas
Mums reikia tinklo antraštės struktūrų: Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) ir TCP (uapi/linux/tcp.h). Paskutinis negalėjau prisijungti dėl klaidų, susijusių su atomic64_t, turėjau nukopijuoti reikiamus apibrėžimus į kodą.
Visos funkcijos, kurios yra išskirtos C, kad būtų galima skaityti, turi būti įtrauktos į iškvietimo vietą, nes eBPF tikrintuvas branduolyje draudžia šuolius atgal, tai yra, kilpas ir funkcijų iškvietimus.
Makro LOG() išjungia spausdinimą leidimo versijoje.
Programa yra funkcijų rinkinys. Kiekvienas gauna paketą, kuriame paryškinta atitinkamo lygio antraštė, pvz. process_ether() laukia, kol bus užpildytas ether. Remiantis lauko analizės rezultatais, funkcija gali perkelti paketą į aukštesnį lygį. Funkcijos rezultatas yra XDP veiksmas. Nors SYN ir ACK tvarkyklės leidžia visus paketus.
Raktų eilutė invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): yra vykdymo keliai, kai tryliktas baitas nuo buferio pradžios yra už paketo ribų. Iš sąrašo sunku pasakyti, apie kurią eilutę kalbame, tačiau yra instrukcijos numeris (12) ir išmontavimo priemonė, rodanti šaltinio kodo eilutes:
kuris aiškiai parodo, kad problema yra ether. Visada taip būtų.
Atsakykite į SYN
Šio etapo tikslas yra sugeneruoti teisingą SYNACK paketą su fiksuotu seqnum, kurį ateityje pakeis SYN slapukas. Visi pakeitimai vyksta process_tcp_syn() ir aplinka.
Paketo tikrinimas
Kaip bebūtų keista, čia yra pati nuostabiausia eilutė, tiksliau, jos komentaras:
Rašant pirmąją kodo versiją buvo naudojamas 5.1 branduolys, kurio tikrinimui buvo skirtumas tarp data_end и (const void*)ctx->data_end. Rašymo metu 5.3.1 branduolys šios problemos neturėjo. Galbūt kompiliatorius vietinį kintamąjį pasiekė kitaip nei lauką. Moralas – dideliame lizde gali padėti kodo supaprastinimas.
Tolesni įprastiniai ilgio patikrinimai – tikrintojo šlovė; O MAX_CSUM_BYTES žemiau.
Sukeiskite TCP prievadus, IP ir MAC adresus. Standartinė biblioteka nepasiekiama iš XDP programos, todėl memcpy() - makrokomandą, kuri slepia Clang intrinsik.
IPv4 ir TCP kontrolinės sumos reikalauja antraštėse pridėti visus 16 bitų žodžius, o antraščių dydis yra parašytas jose, tai yra, kompiliavimo metu nežinoma. Tai problema, nes tikrintuvas nepraleis įprasto ciklo iki ribinio kintamojo. Tačiau antraščių dydis yra ribotas: kiekviena iki 64 baitų. Galite sukurti kilpą su fiksuotu iteracijų skaičiumi, kuri gali baigtis anksti.
Atkreipiu dėmesį, kad yra RFC 1624 apie tai, kaip iš dalies perskaičiuoti kontrolinę sumą, jei keičiami tik fiksuoti paketų žodžiai. Tačiau metodas nėra universalus, o jį įgyvendinti būtų sunkiau.
Kontrolinės sumos skaičiavimo funkcija:
#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;
}
Nors size patikrinta pagal iškvietimo kodą, būtina antroji išėjimo sąlyga, kad tikrintojas galėtų įrodyti ciklo pabaigą.
32 bitų žodžiams įdiegta paprastesnė versija:
INTERNAL u32
sum16_32(u32 v) {
return (v >> 16) + (v & 0xffff);
}
Iš tikrųjų perskaičiuojame kontrolines sumas ir siunčiame paketą atgal:
Funkcija carry() pagal RFC 32 sudaro kontrolinę sumą iš 16 bitų 791 bitų žodžių sumos.
TCP rankos paspaudimo patikrinimas
Filtras teisingai užmezga ryšį su netcat, praleidžiant galutinį ACK, į kurį Linux atsakė RST paketu, nes tinklo dėklas negavo SYN - jis buvo konvertuotas į SYNACK ir išsiųstas atgal - ir OS požiūriu atėjo paketas, kurio nebuvo susiję su atvirais ryšiais.
$ sudo ip netns exec xdp-test nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer
Svarbu patikrinti su visavertėmis programomis ir stebėti tcpdump apie xdp-remote nes pvz. hping3 nereaguoja į neteisingas kontrolines sumas.
SYN slapukas
XDP požiūriu pats patikrinimas yra nereikšmingas. Skaičiavimo algoritmas yra primityvus ir tikriausiai pažeidžiamas sudėtingo užpuoliko. Pavyzdžiui, „Linux“ branduolys naudoja kriptografinį „SipHash“, tačiau jo įgyvendinimas XDP aiškiai nepatenka į šio straipsnio taikymo sritį.
Atsirado naujiems TODO, susijusiems su išorine sąveika:
XDP programa negali saugoti cookie_seed (slaptoji druskos dalis) globaliame kintamajame, jums reikia branduolio parduotuvės, kurios vertė bus periodiškai atnaujinama iš patikimo generatoriaus.
Jei SYN slapukas ACK pakete sutampa, jums nereikia spausdinti pranešimo, bet atsiminti patvirtinto kliento IP, kad toliau praleistumėte paketus iš jo.
Kartais eBPF apskritai ir ypač XDP pristatomi kaip pažangus administratoriaus įrankis nei kūrimo platforma. Iš tiesų, XDP yra įrankis, trukdantis branduolio paketų apdorojimui, o ne alternatyva branduolio kaminui, kaip DPDK ir kitos branduolio apėjimo parinktys. Kita vertus, XDP leidžia įgyvendinti gana sudėtingą logiką, kurią, be to, lengva atnaujinti be pauzės srauto apdorojime. Tikriklis didelių problemų nesukelia, asmeniškai aš tokių neatsisakyčiau vartotojo erdvės kodo dalims.
Antroje dalyje, jei tema bus įdomi, užpildysime patikrintų klientų lentelę ir nutrauksime ryšius, įdiegsime skaitiklius ir parašysime vartotojo erdvės įrankį filtrui valdyti.