በ XDP ላይ ከ DDoS ጥቃቶች ጥበቃን እንጽፋለን. የኑክሌር ክፍል

የኢክስፕረስ ዳታ ፓዝ (ኤክስዲፒ) ቴክኖሎጂ ፓኬጆቹ ወደ የከርነል ኔትወርክ ቁልል ከመግባታቸው በፊት የዘፈቀደ ትራፊክ ሂደትን በሊኑክስ መገናኛዎች ላይ ይፈቅዳል። የ XDP መተግበሪያ - ከ DDoS ጥቃቶች ጥበቃ (CloudFlare), ውስብስብ ማጣሪያዎች, የስታቲስቲክስ ስብስብ (Netflix). የXDP ፕሮግራሞች የሚከናወኑት በ eBPF ቨርቹዋል ማሽን ነው፣ ስለዚህ በሁለቱም በኮዳቸው እና ባለው የከርነል ተግባር ላይ እንደ ማጣሪያው አይነት ገደቦች አሏቸው።

ጽሑፉ በ XDP ላይ የበርካታ ቁሳቁሶችን ድክመቶች ለመሙላት የታሰበ ነው. በመጀመሪያ ፣ የ XDP ባህሪዎችን ወዲያውኑ የሚያልፍ ዝግጁ-የተሰራ ኮድ ይሰጣሉ-ለማረጋገጥ ተዘጋጅቷል ወይም ችግሮችን ለመፍጠር በጣም ቀላል ነው። ከዚያ ኮድዎን ከባዶ ለመጻፍ ሲሞክሩ በተለመዱ ስህተቶች ምን ማድረግ እንዳለቦት አታውቁም. በሁለተኛ ደረጃ, ምንም እንኳን የራሳቸው ችግሮች ቢኖራቸውም, VM እና ሃርድዌር ሳይኖር XDPን በአገር ውስጥ ለመሞከር መንገዶች አልተሸፈኑም. ጽሑፉ ኔትዎርክን ለሚያውቁ ፕሮግራመሮች እና ሊኑክስ ለXDP እና eBPF ፍላጎት ላላቸው የታሰበ ነው።

በዚህ ክፍል ውስጥ የ XDP ማጣሪያ እንዴት እንደሚገጣጠም እና እንዴት እንደሚሞከር በዝርዝር እንረዳለን, ከዚያም በፓኬት ማቀነባበሪያ ደረጃ ላይ የታወቀው የ SYN ኩኪዎችን አሰራር ቀላል ስሪት እንጽፋለን. እስካሁን "ነጭ ዝርዝር" አንፈጥርም
የተረጋገጡ ደንበኞች ፣ ቆጣሪዎችን ያስቀምጡ እና ማጣሪያውን ያስተዳድሩ - በቂ ምዝግብ ማስታወሻዎች።

በ C ውስጥ እንጽፋለን - ፋሽን አይደለም ፣ ግን ተግባራዊ ነው። ሁሉም ኮድ በ GitHub መጨረሻ ላይ ባለው ማገናኛ በኩል ይገኛል እና በአንቀጹ ውስጥ በተገለጹት ደረጃዎች መሠረት ወደ ተግባር ይከፈላል ።

የኃላፊነት ማስተባበያ. በዚህ ጽሑፍ ጊዜ ውስጥ ፣ የ DDoS ጥቃቶችን ለመከላከል አነስተኛ መፍትሄ እዘጋጃለሁ ፣ ምክንያቱም ይህ ለ XDP እና ለሙያዬ ተጨባጭ ተግባር ነው። ይሁን እንጂ ዋናው ግቡ ቴክኖሎጂውን መረዳት ነው, ይህ ዝግጁ የሆነ ጥበቃን ለመፍጠር መመሪያ አይደለም. የማጠናከሪያው ኮድ አልተሻሻለም እና አንዳንድ ጥቃቅን ነገሮችን ይተዋል.

የ XDP አጭር መግለጫ

ሰነዶችን እና ነባር ጽሑፎችን ላለማባዛት ዋና ዋና ነጥቦቹን ብቻ እዘረዝራለሁ።

ስለዚህ, የማጣሪያው ኮድ በከርነል ውስጥ ተጭኗል. የሚመጡ እሽጎች ወደ ማጣሪያው ይተላለፋሉ። በውጤቱም, ማጣሪያው ውሳኔ መስጠት አለበት: ፓኬጁን ወደ ከርነል ውስጥ ያስተላልፉ (XDP_PASS), መጣል ፓኬት (XDP_DROP) ወይም መልሰው ይላኩት (XDP_TX). ማጣሪያው ጥቅሉን ሊለውጠው ይችላል, ይህ በተለይ ለ XDP_TX. እንዲሁም ፕሮግራሙን ማቋረጥ ይችላሉ (XDP_ABORTED) እና ጥቅሉን እንደገና ያስጀምሩ, ግን ይህ ተመሳሳይ ነው assert(0) - ለማረም.

የኢቢፒኤፍ (የተራዘመ የበርክሌይ ፓኬት ማጣሪያ) ቨርቹዋል ማሽን ሆን ተብሎ ቀላል ተደርጎ የተሰራ ሲሆን ይህም ከርነሉ ኮዱ እንደማይዞር እና የሌሎች ሰዎችን ማህደረ ትውስታ እንደማይጎዳ ማረጋገጥ ይችላል። ድምር ገደቦች እና ቼኮች፡-

  • ቀለበቶች (ወደ ኋላ) የተከለከሉ ናቸው.
  • ለመረጃ ቁልል አለ፣ ነገር ግን ምንም ተግባራት የሉም (ሁሉም የC ተግባራት ወደ ውስጥ መግባት አለባቸው)።
  • ከቁልል እና ከፓኬት ቋት ውጭ የማህደረ ትውስታ መዳረሻ የተከለከለ ነው።
  • የኮዱ መጠን የተገደበ ነው, በተግባር ግን ይህ በጣም አስፈላጊ አይደለም.
  • ወደ ልዩ የከርነል ተግባራት (eBPF አጋዥዎች) ጥሪዎች ብቻ ይፈቀዳሉ።

ማጣሪያ መንደፍ እና መጫን ይህን ይመስላል።

  1. የምንጭ ኮድ (ለምሳሌ kernel.c) ወደ ዕቃ ተሰብስቧል (kernel.o) ለ eBPF ምናባዊ ማሽን አርክቴክቸር። ከኦክቶበር 2019 ጀምሮ፣ ወደ eBPF ማጠናቀር በክላንግ የተደገፈ እና በጂሲሲ 10.1 ቃል ገብቷል።
  2. ይህ የነገር ኮድ ወደ ከርነል አወቃቀሮች (ለምሳሌ ጠረጴዛዎች እና ቆጣሪዎች) ጥሪዎችን ከያዘ መታወቂያቸው በዜሮዎች ተተክቷል፣ ይህ ማለት እንደዚህ አይነት ኮድ ሊተገበር አይችልም ማለት ነው። ወደ ከርነል ከመጫንዎ በፊት እነዚህን ዜሮዎች በከርነል ጥሪዎች (ኮዱን ያገናኙ) በተፈጠሩ ልዩ ነገሮች መታወቂያ መተካት ያስፈልግዎታል። ይህንን በውጫዊ መገልገያዎች ማድረግ ይችላሉ, ወይም አንድ የተወሰነ ማጣሪያ የሚያገናኝ እና የሚጭን ፕሮግራም መጻፍ ይችላሉ.
  3. ኮርነሉ የተጫነውን ፕሮግራም ያረጋግጣል. የዑደቶች አለመኖር እና ከጥቅል እና ከተደራራቢ ድንበሮች ማለፍ አለመቻል ተረጋግጧል። አረጋጋጩ ኮዱ ትክክል መሆኑን ማረጋገጥ ካልቻለ ፕሮግራሙ ውድቅ ተደርጓል - እሱን ማስደሰት መቻል አለብዎት።
  4. ከተሳካ ማረጋገጫ በኋላ፣ ከርነሉ የኢቢፒኤፍ አርክቴክቸር ነገር ኮድን ለስርዓቱ አርክቴክቸር (ልክ በገባ ጊዜ) ወደ ማሽን ኮድ ያጠናቅራል።
  5. ፕሮግራሙ ከመገናኛው ጋር ተያይዟል እና ፓኬቶችን ማካሄድ ይጀምራል.

XDP በከርነል ውስጥ ስለሚሰራ፣ ማረም የሚከናወነው የመከታተያ ምዝግብ ማስታወሻዎችን እና እንዲያውም ፕሮግራሙ የሚያጣራቸው ወይም የሚያመነጫቸው እሽጎች በመጠቀም ነው። ሆኖም eBPF የወረደው ኮድ ለስርዓቱ ደህንነቱ የተጠበቀ መሆኑን ያረጋግጣል፣ ስለዚህ በXDP በቀጥታ በአካባቢዎ ሊኑክስ ላይ መሞከር ይችላሉ።

አካባቢን ማዘጋጀት

መሰብሰብ

ክላንግ ለ 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

ሜክፋይል ለአርክ ሊኑክስ (ከርነል 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__ ማጣሪያው በከርነል ውስጥ ስለሚተገበር UAPI (የተጠቃሚ ቦታ ኤፒአይ) ራስጌዎች ለከርነል ኮድ ተገልጸዋል።

የቁልል ጥበቃ ሊሰናከል ይችላል (-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 የሚከናወኑት ከ 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, ነገር ግን ፓኬጆችን መቀየር አለባቸው, ይህም ለማረም የማይመች ነው. የአውታረ መረብ ስም ቦታዎችን (ከዚህ በኋላ netns) መጠቀም የተሻለ ነው.

የአውታረ መረብ ስም ቦታ ከሌሎች ኔትኖች ውስጥ ካሉ ተመሳሳይ ነገሮች የተገለሉ የበይነገጽ፣ የመሄጃ ሰንጠረዦች እና NetFilter ደንቦችን ይዟል። እያንዳንዱ ሂደት በስም ቦታ ውስጥ ነው የሚሰራው እና የኔትንሱን እቃዎች ብቻ ነው የሚደርሰው። በነባሪ, ስርዓቱ ለሁሉም እቃዎች አንድ ነጠላ የአውታረ መረብ ስም ቦታ አለው, ስለዚህ በሊኑክስ ውስጥ መስራት እና ስለ ኔትንስ ማወቅ አይችሉም.

አዲስ የስም ቦታ እንፍጠር 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 ውስጥ ያለውን በይነገጽ ለማዋቀር, ማሄድ ያስፈልግዎታል 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
<...>

እውነታው ግን 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 ጥያቄን ማሳየት እና የICMP Echo ምላሽን ማሳየት ማቆም አለበት። ግን አይታይም። ለሾል የሚሆን ነው XDP_TX በፕሮግራሙ ውስጥ xdp-local አስፈላጊ ነውወደ ጥንድ በይነገጽ xdp-remote ባዶ ቢሆንም ፕሮግራም ተመድቦለት ተነስቷል።

ይህን እንዴት አወቅሁ?

በከርነል ውስጥ የጥቅል መንገድን ይከታተሉ የፐርፍ ሁነቶች ዘዴ በነገራችን ላይ ቨርቹዋል ማሽንን በመጠቀም፣ ማለትም 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

በምትኩ ኤአርፒዎች ብቻ ከታዩ፣ ማጣሪያዎቹን ማስወገድ ያስፈልግዎታል (ይህ ያደርጋል sudo ./stand detach), እንሂድ pingከዚያ ማጣሪያዎችን ያዘጋጁ እና እንደገና ይሞክሩ። ችግሩ ማጣሪያው ነው XDP_TX በ ARP ላይ እና ቁልል ከሆነ ሁለቱም የሚሰራ
የስም ቦታዎች xdp-test የ MAC አድራሻን 192.0.2.1 "ለመርሳት" ችሏል, ይህን አይፒ መፍታት አይችልም.

የችግሩ ቀመር

ወደተጠቀሰው ተግባር እንሂድ፡ የSYN ኩኪዎችን በXDP ላይ ይፃፉ።

የSYN ጎርፍ አሁንም ታዋቂ የዲዶኤስ ጥቃት ነው፣ ዋናው ነገር የሚከተለው ነው። ግንኙነት ሲፈጠር (TCP handshake)፣ አገልጋዩ SYN ይቀበላል፣ ለወደፊት ግንኙነት ግብዓቶችን ይመድባል፣ በ SYNACK ፓኬት ምላሽ ይሰጣል እና ACK ይጠብቃል። አጥቂው በቀላሉ በሺዎች የሚቆጠሩ የSYN ፓኬቶችን በሴኮንድ ከአንድ አስተናጋጅ ከተጣሩ አድራሻዎች በብዙ ሺህ-ጠንካራ ቦትኔት ይልካል። አገልጋዩ ፓኬጁ እንደደረሰ ወዲያውኑ ሀብቶችን ለመመደብ ይገደዳል፣ ነገር ግን ብዙ ጊዜ ካለፈ በኋላ ይለቃቸዋል፤ በዚህ ምክንያት ማህደረ ትውስታ ወይም ገደቦች ተሟጠዋል፣ አዳዲስ ግንኙነቶች ተቀባይነት አያገኙም እና አገልግሎቱ አይገኝም።

በSYN ፓኬት ላይ ተመስርተህ ሃብት ካልመደብክ ነገር ግን በSYNACK ፓኬት ብቻ ምላሽ ካልሰጠህ፣ አገልጋዩ እንዴት በኋላ የደረሰው ACK ፓኬት ያልተቀመጠ የSYN ፓኬትን እንደሚያመለክት ሊረዳ ይችላል? ደግሞም አጥቂ የውሸት ኤሲኬዎችን መፍጠር ይችላል። የSYN ኩኪ ነጥቡ ወደ ውስጥ መክተት ነው። seqnum የግንኙነት መለኪያዎች እንደ አድራሻዎች ፣ ወደቦች እና ተለዋዋጭ ጨው። ጨው ከመቀየሩ በፊት ኤሲኬው መድረስ ከቻለ ሃሹን እንደገና ማስላት እና ከእሱ ጋር ማወዳደር ይችላሉ። acknum. ፎርጅ acknum አጥቂው አይችልም ፣ ምክንያቱም ጨው ምስጢሩን ስለሚጨምር እና በተወሰነ ቻናል ምክንያት እሱን ለመደርደር ጊዜ አይኖረውም።

የSYN ኩኪ በሊኑክስ ከርነል ውስጥ ለረጅም ጊዜ ሲተገበር ቆይቷል እና SYNs በፍጥነት እና በጅምላ ከደረሱ በራስ-ሰር ሊነቃ ይችላል።

በ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 ፕሮግራም የሚከተሉትን ማድረግ አለበት፡-

  • ለ SYN በ SYNACK በኩኪ ምላሽ ይስጡ;
  • ለ ACK ከ RST ጋር ምላሽ ይስጡ (ግንኙነት አቋርጥ);
  • የተቀሩትን እሽጎች ያስወግዱ.

የስልተ ቀመር የውሸት ኮድ ከጥቅል ትንተና ጋር፡-

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

አንድ (*) የስርዓቱን ሁኔታ ለማስተዳደር የሚያስፈልጉዎት ነጥቦች ምልክት ተደርጎባቸዋል - በመጀመሪያ ደረጃ የ SYN ኩኪን እንደ ተከታታይ ቁጥር በመጠቀም በቀላሉ የ TCP መጨባበጥን በመተግበር ያለ እነርሱ ማድረግ ይችላሉ።

በቦታው ላይ (**), ጠረጴዛ ከሌለን, ፓኬጁን እናልፋለን.

TCP የእጅ መጨባበጥን በመተግበር ላይ

ጥቅሉን መተንተን እና ኮዱን ማረጋገጥ

የአውታረ መረብ ራስጌ አወቃቀሮችን እንፈልጋለን፡ ኤተርኔት (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.hእና TCP (uapi/linux/tcp.h). ከ ጋር በተያያዙ ስህተቶች ምክንያት የኋለኛውን ማገናኘት አልቻልኩም atomic64_t፣ አስፈላጊዎቹን ትርጓሜዎች ወደ ኮዱ መቅዳት ነበረብኝ።

በከርነል ውስጥ ያለው eBPF አረጋጋጭ ወደ ኋላ መዞርን የሚከለክል በመሆኑ ለንባብ በ C ውስጥ የደመቁት ሁሉም ተግባራት ጥሪው በሚደረግበት ቦታ ላይ መሆን አለባቸው።

#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 ምልክት ወደተደረገባቸው ቼኮች እሳለሁ. አስተያየት ከሰጡ, ፕሮግራሙ ይገነባል, ነገር ግን በሚጫኑበት ጊዜ የማረጋገጫ ስህተት ይኖራል:

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 ወደቦችን፣ የአይፒ አድራሻዎችን እና የማክ አድራሻዎችን ይቀይሩ። መደበኛው ቤተ-መጽሐፍት ከ XDP ፕሮግራም ተደራሽ አይደለም, ስለዚህ memcpy() - Clang intrinsics የሚደብቅ ማክሮ.

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 ጎድሎታል, ሊኑክስ በ RST ፓኬት ምላሽ ሰጠ, የኔትወርክ ቁልል SYN ስላልተቀበለ - ወደ SYNACK ተቀይሮ ተመልሶ የተላከ - እና ከስርዓተ ክወናው እይታ, ከመክፈት ጋር ያልተገናኘ ፓኬት ደረሰ. ግንኙነቶች.

$ 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 ለተሳሳቱ ቼኮች ምላሽ አይሰጥም.

ከኤክስዲፒ እይታ አንጻር ማረጋገጫው ራሱ ቀላል ነው። የስሌቱ አልጎሪዝም ቀዳሚ እና ምናልባትም ለተራቀቀ አጥቂ የተጋለጠ ነው። የሊኑክስ ከርነል፣ ለምሳሌ፣ ክሪፕቶግራፊክ SipHash ይጠቀማል፣ ነገር ግን ለXDP ተግባራዊነቱ ከዚህ ጽሁፍ ወሰን በላይ እንደሆነ ግልጽ ነው።

ከውጭ ግንኙነት ጋር ለተያያዙ አዳዲስ TODOዎች አስተዋወቀ፡-

  • የXDP ፕሮግራም ማከማቸት አይችልም። cookie_seed (የጨው ሚስጥራዊ ክፍል) በአለምአቀፍ ተለዋዋጭ, በከርነል ውስጥ ማከማቻ ያስፈልግዎታል, እሴቱ በየጊዜው ከአስተማማኝ ጄነሬተር ይሻሻላል.

  • የSYN ኩኪው በኤሲኬ ፓኬት ውስጥ የሚዛመድ ከሆነ መልእክት ማተም አያስፈልግዎትም፣ ነገር ግን ከሱ ፓኬቶችን ማለፍ ለመቀጠል የተረጋገጠውን ደንበኛ አይፒ ያስታውሱ።

ህጋዊ የደንበኛ ማረጋገጫ፡-

$ 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

የተረጋገጡ አይፒዎች ዝርዝር ባይኖርም፣ ከ SYN ጎርፍ በራሱ ምንም ጥበቃ አይኖርም፣ ነገር ግን በሚከተለው ትእዛዝ ለኤሲኬ ጎርፍ የተሰጠው ምላሽ እዚህ አለ፡-

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 በጣም የተወሳሰበ ሎጂክን እንዲተገብሩ ይፈቅድልዎታል ፣ በተጨማሪም ፣ በትራፊክ ሂደት ውስጥ ያለማቋረጥ ለማዘመን ቀላል ነው። አረጋጋጩ ትልቅ ችግር አይፈጥርም፤ በግሌ ይህንን ለተጠቃሚ ቦታ ኮድ ክፍሎች አልቃወምም።

በሁለተኛው ክፍል ፣ ርዕሱ አስደሳች ከሆነ ፣ የተረጋገጡ ደንበኞችን እና ግንኙነቶችን ሰንጠረዥ እንጨርሳለን ፣ ቆጣሪዎችን እንተገብራለን እና ማጣሪያውን ለማስተዳደር የተጠቃሚ ቦታ መገልገያ እንጽፋለን።

ማጣቀሻዎች

ምንጭ: hab.com

አስተያየት ያክሉ