ကျွန်ုပ်တို့သည် XDP တွင် DDoS တိုက်ခိုက်မှုများကို ကာကွယ်ရန် ရေးသားထားသည်။ နူကလီးယားအပိုင်း

eXpress Data Path (XDP) နည်းပညာသည် packets kernel network stack သို့မဝင်မီ Linux interfaces တွင် အသွားအလာများကို မထင်မှတ်ဘဲ လုပ်ဆောင်ခြင်းကို ခွင့်ပြုပါသည်။ XDP ၏လျှောက်လွှာ - DDoS တိုက်ခိုက်မှုများ (CloudFlare)၊ ရှုပ်ထွေးသောစစ်ထုတ်မှုများ၊ စာရင်းအင်းစုဆောင်းခြင်း (Netflix) မှကာကွယ်ခြင်း။ XDP ပရိုဂရမ်များကို eBPF virtual machine မှ လုပ်ဆောင်ပြီးဖြစ်သောကြောင့် စစ်ထုတ်မှုအမျိုးအစားပေါ် မူတည်၍ ၎င်းတို့၏ကုဒ်နှင့် ရရှိနိုင်သော kernel လုပ်ဆောင်ချက်များကို ကန့်သတ်ချက်များရှိသည်။

ဆောင်းပါးသည် XDP ရှိ များပြားလှသော ပစ္စည်းများ၏ ချို့ယွင်းချက်များအတွက် ရည်ရွယ်ပါသည်။ ပထမဦးစွာ၊ ၎င်းတို့သည် XDP ၏အင်္ဂါရပ်များကိုချက်ချင်းရှောင်လွှဲနိုင်သည့်အဆင်သင့်လုပ်ကုဒ်ကိုပေးသည်- အတည်ပြုခြင်းအတွက်ပြင်ဆင်သည် သို့မဟုတ် ပြဿနာများဖြစ်စေရန် ရိုးရှင်းလွန်းသည်။ နောက်ပိုင်းတွင် သင့်ကိုယ်ပိုင်ကုဒ်ကို အစမှ ရေးရန် ကြိုးစားသောအခါ၊ ပုံမှန်အမှားများနှင့် မည်သို့လုပ်ဆောင်ရမည်ကို နားမလည်ပါ။ ဒုတိယအနေနှင့်၊ ၎င်းတို့တွင် ၎င်းတို့၏ကိုယ်ပိုင်အခက်အခဲများရှိလင့်ကစား VM နှင့် ဟာ့ဒ်ဝဲမပါဘဲ XDP ကို ​​ပြည်တွင်းတွင် စမ်းသပ်ရန် နည်းလမ်းများကို အကျုံးမဝင်ပေ။ စာသားသည် XDP နှင့် eBPF ကို စိတ်ဝင်စားသော ကွန်ရက်များနှင့် Linux နှင့် ရင်းနှီးသော ပရိုဂရမ်မာများအတွက် ရည်ရွယ်ပါသည်။

ဤအပိုင်းတွင်၊ ကျွန်ုပ်တို့သည် XDP filter ကိုမည်ကဲ့သို့စုဝေးပြီး ၎င်းကိုစမ်းသပ်နည်းကို အသေးစိတ်နားလည်မည်ဖြစ်ပြီး၊ ထို့နောက် packet processing အဆင့်တွင် နာမည်ကြီး SYN cookies ယန္တရား၏ ရိုးရှင်းသောဗားရှင်းကို ရေးပါမည်။ “ဝှိုက်စာရင်း” မဖွဲ့မချင်း၊
စိစစ်ပြီးသော ဖောက်သည်များ၊ ကောင်တာများထားရှိကာ စစ်ထုတ်မှုကို စီမံပါ - လုံလောက်သောမှတ်တမ်းများ။

ကျွန်ုပ်တို့သည် C ဖြင့်ရေးပါလိမ့်မည် - ဤအရာသည်ခေတ်ဆန်သည်မဟုတ်သော်လည်းလက်တွေ့ဖြစ်သည်။ ကုဒ်အားလုံးကို အဆုံးရှိလင့်ခ်တွင် GitHub တွင်ရနိုင်ပြီး ဆောင်းပါးတွင်ဖော်ပြထားသည့်အဆင့်များအလိုက် commits များအဖြစ် ပိုင်းခြားထားသည်။

မသက်ဆိုင်ကြောင်းရှင်းလင်းချက်။ ဆောင်းပါး၏သင်တန်းတွင်၊ ဤသည်မှာ XDP နှင့် ကျွန်ုပ်၏ဧရိယာအတွက် လက်တွေ့ကျသောအလုပ်ဖြစ်သောကြောင့်၊ DDoS တိုက်ခိုက်မှုများကို ချေမှုန်းရန်အတွက် အသေးစားဖြေရှင်းချက်တစ်ခု တီထွင်မည်ဖြစ်သည်။ သို့သော်၊ အဓိက ပန်းတိုင်မှာ နည်းပညာကို နားလည်ရန်ဖြစ်ပြီး၊ ၎င်းသည် အဆင်သင့်လုပ်ထားသော ကာကွယ်မှုကို ဖန်တီးရန် လမ်းညွှန်ချက်မဟုတ်ပါ။ သင်ခန်းစာကုဒ်ကို အကောင်းဆုံးမွမ်းမံထားပြီး အချို့သော ကွဲပြားချက်များကို ချန်လှပ်ထားသည်။

XDP ၏ အကျဉ်းချုပ်

စာရွက်စာတမ်းနှင့် လက်ရှိဆောင်းပါးများကို မပွားမိစေရန် အဓိကအချက်များကိုသာ ဖော်ပြပါမည်။

ထို့ကြောင့်၊ filter ကုဒ်ကို kernel ထဲသို့ထည့်ထားသည်။ Filter သည် ဝင်လာသော ပက်ကတ်များကို ကျော်သွားသည် ။ ရလဒ်အနေဖြင့်၊ filter သည် ဆုံးဖြတ်ချက်တစ်ခုချရမည်- packet ကို kernel သို့ပေးပို့ရန် (XDP_PASS) ပက်ကေ့ချဲ (XDP_DROP) သို့မဟုတ် ပြန်ပို့ပါ (XDP_TX) filter သည် package ကိုပြောင်းလဲနိုင်သည်၊ ၎င်းသည်အထူးသဖြင့်မှန်သည်။ XDP_TX. ပရိုဂရမ်ကိုလည်း ပျက်သွားစေနိုင်သည် (XDP_ABORTED) ပက်ကေ့ချ်ချလိုက်ပါ၊ ဒါပေမယ့် ဒါက တူညီပါတယ်။ assert(0) - အမှားရှာပြင်ခြင်းအတွက်။

eBPF (extended Berkley Packet Filter) virtual machine ကို kernel မှ စစ်ဆေးနိုင်စေရန်အတွက် ကုဒ်သည် အခြားသူများ၏ မှတ်ဉာဏ်ကို မထိခိုက်စေကြောင်း စစ်ဆေးနိုင်ရန် ရိုးရှင်းပါသည်။ ကန့်သတ်ချက်များနှင့် စစ်ဆေးမှုများ-

  • လှည့်ပတ်ခြင်း (နောက်သို့ခုန်ခြင်း) ကို တားမြစ်ထားသည်။
  • ဒေတာအတွက် stack တစ်ခုရှိသော်လည်း လုပ်ဆောင်ချက်များ မရှိပါ (C လုပ်ဆောင်ချက်အားလုံးကို မျဉ်းသားထားရမည်)။
  • stack ပြင်ပရှိ memory သို့ ဝင်ရောက်ခွင့်နှင့် packet ကြားခံများကို တားမြစ်ထားသည်။
  • ကုဒ်၏ အရွယ်အစားမှာ အကန့်အသတ်ရှိသော်လည်း လက်တွေ့တွင် ၎င်းသည် အလွန်ထူးခြားမှုမရှိပါ။
  • အထူး kernel လုပ်ဆောင်ချက်များ (eBPF အကူအညီပေးသူများ) ကိုသာ ခွင့်ပြုထားသည်။

စစ်ထုတ်မှုကို တီထွင်ခြင်းနှင့် ထည့်သွင်းခြင်းမှာ ဤကဲ့သို့ ဖြစ်သည်-

  1. အရင်းအမြစ်ကုဒ် (ဥပမာ။ kernel.c) compiles to object (kernel.o) eBPF virtual machine architecture အတွက်။ 2019 ခုနှစ် အောက်တိုဘာလအထိ၊ eBPF သို့ စုစည်းခြင်းကို Clang မှပံ့ပိုးထားပြီး GCC 10.1 တွင် ကတိပြုထားသည်။
  2. အကယ်၍ ဤအရာဝတ္တုကုဒ်တွင် kernel တည်ဆောက်ပုံများ (ဥပမာ၊ ဇယားများနှင့် ကောင်တာများသို့) ခေါ်ဆိုမှုများရှိနေပါက၊ ၎င်းတို့၏ ID များအစား သုညများပါရှိသည်၊ ဆိုလိုသည်မှာ၊ ထိုကုဒ်ကို လုပ်ဆောင်၍မရပါ။ kernel ထဲသို့ မတင်မီ၊ kernel ခေါ်ဆိုမှုများမှတစ်ဆင့် ဖန်တီးထားသော သီးခြားအရာများ၏ ID များကို ဤသုည (ကုဒ်ကို လင့်ခ်) ဖြင့် အစားထိုးရပါမည်။ သင်သည် ၎င်းကို ပြင်ပအသုံးအဆောင်များဖြင့် လုပ်ဆောင်နိုင်သည်၊ သို့မဟုတ် လင့်ခ်နှင့် သီးခြား filter တစ်ခုကို တင်မည့် ပရိုဂရမ်တစ်ခု ရေးနိုင်သည်။
  3. kernel သည် loaded ပရိုဂရမ်ကိုစစ်ဆေးသည်။ ၎င်းသည် ပက်ကေ့ဂျ်နှင့် အစုအစည်းများ၏ ထွက်ပေါက်မရှိခြင်းနှင့် သံသရာမရှိခြင်းတို့ကို စစ်ဆေးသည်။ စိစစ်သူသည် ကုဒ်မှန်ကန်ကြောင်း သက်သေမပြနိုင်ပါက ပရိုဂရမ်ကို ပယ်ချသည် - တစ်စုံတစ်ယောက်သည် သူ့အား ကျေနပ်နိုင်စေရမည်။
  4. အောင်မြင်စွာအတည်ပြုပြီးနောက်၊ kernel သည် eBPF ဗိသုကာအရာဝတ္ထုကုဒ်ကို စနစ်ဗိသုကာစက်ကုဒ် (အချိန်နှင့်တပြေးညီ) တွင်စုစည်းသည်။
  5. ပရိုဂရမ်သည် အင်တာဖေ့စ်သို့ ချိတ်ဆက်ထားပြီး ပက်ကတ်များကို စတင်လုပ်ဆောင်သည်။

XDP သည် kernel တွင်အလုပ်လုပ်သောကြောင့်၊ အမှားရှာခြင်းအား ခြေရာခံမှတ်တမ်းများဖြင့် လုပ်ဆောင်ပြီး အမှန်တကယ်အားဖြင့် ပရိုဂရမ်မှ စစ်ထုတ်ခြင်း သို့မဟုတ် ထုတ်ပေးသည့် ပက်ကတ်များဖြင့် ပြုလုပ်ခြင်းဖြစ်သည်။ သို့သော်၊ eBPF သည် စနစ်အတွက် ဒေါင်းလုဒ်လုပ်ထားသောကုဒ်ကို လုံခြုံစွာ ထိန်းသိမ်းထားသောကြောင့် သင်သည် သင်၏ local Linux တွင် XDP နှင့် စမ်းသပ်နိုင်သည်။

ပတ်ဝန်းကျင်ပြင်ဆင်ခြင်း။

အစည်းအဝေး

Clang သည် eBPF ဗိသုကာအတွက် အရာဝတ္ထုကုဒ်ကို တိုက်ရိုက်ထုတ်မပေးနိုင်သောကြောင့် လုပ်ငန်းစဉ်တွင် အဆင့်နှစ်ဆင့်ပါရှိသည်-

  1. C ကုဒ်ကို LLVM bytecode သို့ စုစည်းပါ (clang -emit-llvm).
  2. bytecode ကို eBPF အရာဝတ္ထုကုဒ်သို့ ပြောင်းပါ (llc -march=bpf -filetype=obj).

စစ်ထုတ်မှုတစ်ခုရေးသောအခါ၊ အရန်လုပ်ဆောင်ချက်များနှင့် မက်ခရိုများပါသည့် ဖိုင်အချို့သည် အဆင်ပြေလာပါလိမ့်မည်။ kernel စမ်းသပ်မှုများမှ. ၎င်းတို့သည် kernel ဗားရှင်းနှင့် ကိုက်ညီရန် အရေးကြီးသည် (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 (kernel 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 kernel ခေါင်းစီးများဆီသို့ လမ်းကြောင်းပါ၀င်သည်၊ ARCH - စနစ်ဗိသုကာ။ လမ်းကြောင်းများနှင့် ကိရိယာများသည် ဖြန့်ဝေမှုများကြားတွင် အနည်းငယ်ကွဲပြားနိုင်သည်။

Debian 10 အတွက် ကွာခြားချက် ဥပမာ (kernel 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 ခေါင်းစီးများပါရှိသော လမ်းညွှန်များ ပါဝင်သည်။ သင်္ကေတ __KERNEL__ စစ်ထုတ်မှုကို kernel တွင်လုပ်ဆောင်သောကြောင့် UAPI (userspace API) ခေါင်းစီးများကို kernel ကုဒ်အတွက် သတ်မှတ်ထားကြောင်း ဆိုလိုသည်။

Stack ကာကွယ်မှုကို ပိတ်ထားနိုင်သည် (-fno-stack-protector) အဘယ်ကြောင့်ဆိုသော် eBPF ကုဒ်အတည်ပြုသူသည် stack boundaries မှမဟုတ်ကြောင်းစစ်ဆေးသောကြောင့်ဖြစ်သည်။ eBPF bytecode ၏အရွယ်အစားမှာ အကန့်အသတ်ရှိသောကြောင့် ပိုမိုကောင်းမွန်အောင်ပြုလုပ်ခြင်းကို သင်ချက်ချင်းဖွင့်သင့်သည်။

ပက်ကေ့ဂျ်များအားလုံးကို ကျော်သွားကာ ဘာမှမလုပ်ဘဲ စစ်ထုတ်မှုတစ်ခုဖြင့် စတင်ကြပါစို့။

#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. အခုဘယ်မှာစမ်းသပ်နိုင်မလဲ။

စမ်းသပ်မှုရပ်

ရပ်တည်ချက်တွင် အင်တာဖေ့စ်နှစ်ခုပါဝင်သင့်သည်- စစ်ထုတ်မှုတစ်ခုရှိမည်ဖြစ်ပြီး မည်သည့်ပက်ကတ်များကို ပေးပို့မည်နည်း။ ကျွန်ုပ်တို့၏ filter နှင့် ပုံမှန်အပလီကေးရှင်းများ မည်သို့အလုပ်လုပ်သည်ကို စစ်ဆေးရန်အတွက် ၎င်းတို့သည် ၎င်းတို့၏ကိုယ်ပိုင် IP များဖြင့် အပြည့်အဝ Linux စက်ပစ္စည်းများ ဖြစ်ရပါမည်။

veth (virtual Ethernet) ကဲ့သို့သော စက်ပစ္စည်းများသည် ကျွန်ုပ်တို့အတွက် သင့်လျော်သည်- ၎င်းတို့သည် တစ်ခုနှင့်တစ်ခု တိုက်ရိုက် "ချိတ်ဆက်ထားသည့်" virtual network interfaces တစ်စုံဖြစ်သည်။ သင်ဤကဲ့သို့သောသူတို့ကိုဖန်တီးနိုင်သည် (ဤကဏ္ဍတွင်၊ အားလုံးသောအမိန့်များ 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ဒါပေမယ့် အမှားရှာပြင်တဲ့အခါ အဆင်မပြေတဲ့ ပက်ကေ့ခ်ျတွေကို ပြောင်းရပါလိမ့်မယ်။ network namespaces (network namespaces, further netns) ကိုသုံးတာက ပိုကောင်းပါတယ်။

ကွန်ရက် namespace တွင် အခြား netns ရှိ အလားတူအရာဝတ္ထုများမှ သီးခြားခွဲထုတ်ထားသော အင်တာဖေ့စ်များ၊ လမ်းကြောင်းဇယားများနှင့် NetFilter စည်းမျဉ်းများပါရှိသည်။ လုပ်ငန်းစဉ်တစ်ခုစီသည် အချို့သော namespace တွင်အလုပ်လုပ်ပြီး ဤ netns ၏အရာဝတ္ထုများကိုသာ ၎င်းတွင်ရနိုင်သည်။ ပုံမှန်အားဖြင့်၊ စနစ်တွင် အရာဝတ္ထုအားလုံးအတွက် ကွန်ရက်အမည်ကွက်တစ်ခု ရှိသည်၊ ထို့ကြောင့် သင်သည် Linux တွင် အလုပ်လုပ်နိုင်ပြီး netns အကြောင်း မသိပါ။

namespace အသစ်တစ်ခုဖန်တီးကြပါစို့ 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 သို့ packet တစ်ခုကို ပေးပို့သည့်အခါ ၎င်းကို ကျော်သွားမည်ဖြစ်ပါသည်။ xdp-remoteအဘယ်ကြောင့်ဆိုသော် ၎င်းသည် ဤလုပ်ငန်းစဉ်အတွက် 192.0.2.0/24 ရှိ တစ်ခုတည်းသော အင်တာဖေ့စ်ဖြစ်သည်။ ဒါလည်း ပြောင်းပြန်အလုပ်လုပ်တယ်။

netns များကြား ရွှေ့သောအခါ၊ အင်တာဖေ့စ်သည် ကျသွားပြီး လိပ်စာကို ဆုံးရှုံးသွားသည်။ netns တွင် အင်တာဖေ့စ်ကို စနစ်ထည့်သွင်းရန်၊ သင် run ရန် လိုအပ်သည်။ ip ... ဤ command namespace တွင် 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

သင်မြင်သည့်အတိုင်း၊ ၎င်းသည် setting နှင့်မတူပါ။ xdp-local မူရင်း namespace တွင်-

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

ပြေးရင် tcpdump -tnevi xdp-localပို့လိုက်တဲ့ packets တွေကို တွေ့နိုင်ပါတယ်။ xdp-test၊ ဤအင်တာဖေ့စ်သို့ ပို့ပေးသည်-

ip netns exec xdp-test   ping 192.0.2.1

အခွံကို ပြေးရတာ အဆင်ပြေတယ်။ xdp-test. repository တွင် stand ဖြင့် အလိုအလျောက်အလုပ်လုပ်သော script တစ်ခုပါရှိသည်၊ ဥပမာအားဖြင့်၊ သင်သည် stand ကို command ဖြင့်သတ်မှတ်နိုင်သည်။ sudo ./stand up ဖယ်ရှားလိုက်ပါ။ sudo ./stand down.

ခြေရာခံခြင်း။

Filter ကို ဤကဲ့သို့သော စက်တွင် တွဲထားသည်။

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

သော့ -force နောက်တစ်ခုက လင့်ခ်ချိတ်ထားပြီးသားဆိုရင် ပရိုဂရမ်အသစ်တစ်ခုကို လင့်ခ်ချိတ်ဖို့ လိုအပ်တယ်။ "No news is good news" သည် ဤ command နှင့် ပတ်သတ်၍ မဟုတ်ဘဲ output သည် မည်သို့ပင်ဖြစ်စေ တောက်ပနေပါသည်။ ညွှန်ပြ verbose စိတ်ကြိုက်ရွေးချယ်နိုင်သော်လည်း ၎င်းနှင့်အတူ တပ်ဆင်သူစာရင်းနှင့်အတူ ကုဒ်အတည်ပြုစနစ်၏အလုပ်အကြောင်း အစီရင်ခံစာတစ်ခုပေါ်လာသည်-

Verifier analysis:

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

ပရိုဂရမ်ကို အင်တာဖေ့စ်မှ ဖယ်ထုတ်ပါ-

ip link set dev xdp-local xdp off

script တွင်၊ ဤအရာများသည် command များဖြစ်သည်။ sudo ./stand attach и sudo ./stand detach.

Filter ကို ချည်နှောင်ခြင်းဖြင့်၊ သင်သည် ၎င်းကို သေချာစေနိုင်သည်။ 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;
   }

အထွက်သည် ဖွင့်ထားရန်လိုအပ်သည့် kernel ခြေရာခံချန်နယ်သို့ သွားသည်-

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

Verifier ၏ output ကို အနီးကပ်ကြည့်လျှင် ထူးဆန်းသော တွက်ချက်မှုများကို သင် သတိပြုမိနိုင်သည်-

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 command များ၏ ချက်ခြင်းငြင်းခုံခြင်းဖြစ်သည်-

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

ထို့ကြောင့်၊ debug output သည် ရလဒ်ထွက်ကုဒ်ကို အလွန်အကျည်းတန်စေသည်။

XDP Packets များ ပေးပို့ခြင်း။

စစ်ထုတ်မှုကို ပြောင်းလဲလိုက်ရအောင်- အဝင်ထုပ်ပိုးမှုအားလုံးကို ပြန်ပို့ပေးပါစေ။ ခေါင်းစီးများရှိ လိပ်စာများကို ပြောင်းလဲရန် လိုအပ်သော်လည်း ယခုမူအရ အလုပ်သည် အရေးကြီးသောကြောင့် ကွန်ရက်အမြင်အရ ၎င်းသည် မှားယွင်းနေပါသည်။

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

စတင်လိုက်ပါ tcpdump အပေါ် xdp-remote. ၎င်းသည် တူညီသောအထွက်နှင့်အဝင် ICMP Echo တောင်းဆိုမှုကိုပြသပြီး ICMP Echo Reply ကိုပြသခြင်းကိုရပ်တန့်သင့်သည်။ ဒါပေမယ့် မပြဘူး။ အလုပ်မှထွက်သည်။ XDP_TX အစီအစဉ်အတွက် xdp-local interface ကိုတွဲချိတ်ရန် xdp-remote ပရိုဂရမ်ကို အလွတ်ရနေရင်တောင်မှ တာဝန်ပေးထားတယ်၊

ငါဘယ်လိုသိတာလဲ။

Kernel ရှိ ပက်ကေ့ခ်ျတစ်ခု၏ လမ်းကြောင်းကို ခြေရာခံပါ။ perf ဖြစ်ရပ်များ ယန္တရားသည် eBPF နှင့် ဖြုတ်တပ်ရန်အတွက် eBPF ကို တူညီသော virtual machine ကို အသုံးပြု၍ ခွင့်ပြုသည်။

အခြားမည်သည့်အရာမှမရှိသောကြောင့်၊ မကောင်းမှုမှ ကောင်းကျိုးကို ဆောင်ရမည်။

$ 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])
                                     <...>

Code 6 ဆိုတာဘာလဲ။

$ errno 6
ENXIO 6 No such device or address

လုပ်ဆောင်ချက် veth_xdp_flush_bq() error code မှ ရရှိသည်။ 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ထို့နောက် စစ်ထုတ်မှုများကို ထည့်သွင်းပြီး ထပ်စမ်းကြည့်ပါ။ ပြဿနာကတော့ filter ပါ။ XDP_TX ARP ကိုလည်း သက်ရောက်မှုရှိပြီး stack ဖြစ်ရင်
အမည်နေရာများ xdp-test MAC လိပ်စာ 192.0.2.1 ကို "မေ့" ရန် စီမံထားပြီး၊ သူသည် ဤ IP ကို ​​ဖြေရှင်းနိုင်မည် မဟုတ်ပါ။

ပြဿနာကိုပုံဖော်ခြင်း

XDP တွင် SYN ကွက်ကီးယန္တရားတစ်ခုရေးရန် ဖော်ပြထားသည့်တာဝန်သို့ ဆက်သွားကြပါစို့။

ယခုအချိန်အထိ SYN ရေလွှမ်းမိုးမှုသည် လူကြိုက်များသော DDoS တိုက်ခိုက်မှုတစ်ခုအဖြစ် ကျန်ရှိနေသည်၊ ယင်း၏အနှစ်သာရမှာ အောက်ပါအတိုင်းဖြစ်သည်။ ချိတ်ဆက်မှုတစ်ခုတည်ဆောက်သောအခါ (TCP လက်ဆွဲနှုတ်ဆက်ခြင်း) ဆာဗာသည် SYN ကိုလက်ခံရရှိပြီး အနာဂတ်ချိတ်ဆက်မှုအတွက် အရင်းအမြစ်များကို ခွဲဝေပေးကာ SYNACK ပက်ကတ်တစ်ခုဖြင့် တုံ့ပြန်ပြီး ACK ကိုစောင့်ဆိုင်းသည်။ တိုက်ခိုက်သူသည် ထောင်ပေါင်းများစွာသော botnet တစ်ခုတွင် host တစ်ခုစီမှ တစ်စက္ကန့်လျှင် ထောင်ပေါင်းများစွာသော လိပ်စာအတုများမှ SYN packet များကို ရိုးရှင်းစွာ ပေးပို့ပါသည်။ ဆာဗာသည် ပက်ကက်ရောက်ရှိချိန်တွင် အရင်းအမြစ်များကို ချက်ခြင်းခွဲဝေပေးရန် အတင်းအကြပ်ခိုင်းစေသော်လည်း အချိန်အတော်ကြာပြီးနောက် ၎င်းကို ထုတ်လွှတ်လိုက်ရာ ရလဒ်အနေဖြင့် မန်မိုရီ သို့မဟုတ် ကန့်သတ်ချက်များ ကုန်ဆုံးသွားကာ ချိတ်ဆက်မှုအသစ်များကို လက်ခံခြင်းမရှိတော့ဘဲ ဝန်ဆောင်မှုကို မရနိုင်ပါ။

SYN ပက်ကတ်တွင် အရင်းအမြစ်များကို ခွဲဝေမပေးပါက SYNACK ပက်ကေ့ခ်ျဖြင့်သာ တုံ့ပြန်ပါက၊ နောက်မှလာသော ACK ပက်ကတ်သည် မသိမ်းဆည်းရသေးသော SYN ပက်ကတ်မှဖြစ်ကြောင်း ဆာဗာက မည်သို့နားလည်နိုင်မည်နည်း။ နောက်ဆုံးတွင်၊ တိုက်ခိုက်သူသည် ACK အတုများကိုလည်း ဖန်တီးနိုင်သည်။ SYN cookie ၏ အနှစ်သာရမှာ ကုဒ်သွင်းရန် ဖြစ်သည်။ seqnum ချိတ်ဆက်မှုဘောင်များသည် လိပ်စာများ၊ ဆိပ်ကမ်းများနှင့် ဆားပြောင်းလဲခြင်း၏ hash အဖြစ်။ ဆားမပြောင်းလဲမီ ACK ရောက်ရှိလာပါက၊ hash ကို ထပ်မံတွက်ချက်ပြီး နှိုင်းယှဉ်နိုင်ပါသည်။ acknum. အတု acknum ဆားတွင် လျှို့ဝှက်ချက်ပါ၀င်သောကြောင့် တိုက်ခိုက်သူသည် အကန့်အသတ်ရှိသောချန်နယ်ကြောင့် ၎င်းကို စီရန်အချိန်မရှိသောကြောင့်ဖြစ်သည်။

SYN cookies များကို Linux kernel တွင် အချိန်အတော်ကြာ အသုံးပြုထားပြီး SYN များ လျင်မြန်စွာနှင့် အစုလိုက်ရောက်ရှိပါက အလိုအလျောက်ဖွင့်ပေးနိုင်ပါသည်။

TCP လက်ဆွဲခြင်းဆိုင်ရာ ပညာပေးအစီအစဉ်

TCP သည် ဘိုက်များစီးကြောင်းအဖြစ် ဒေတာလွှဲပြောင်းခြင်းကို ပံ့ပိုးပေးသည်၊ ဥပမာ၊ HTTP တောင်းဆိုချက်များကို TCP မှတဆင့် ပေးပို့ပါသည်။ စမ်းချောင်းသည် ထုပ်ပိုးမှုများတွင် အပိုင်းတစ်ခုပြီးတစ်ခု ထုတ်လွှင့်သည်။ TCP ပက်ကေ့ဂျ်အားလုံးတွင် ယုတ္တိတန်သောအလံများနှင့် 32-ဘစ်ဆင့်ကိန်းနံပါတ်များ ရှိသည်-

  • အလံများ၏ ပေါင်းစပ်မှုသည် သီးခြား package တစ်ခု၏ အခန်းကဏ္ဍကို သတ်မှတ်သည်။ SYN အလံသည် ဤချိတ်ဆက်မှုတွင် ပေးပို့သူ၏ ပထမဆုံး ပက်ကတ်ကို ဆိုလိုသည်။ ACK အလံသည် ပေးပို့သူသည် တစ်ဘိုက်အထိ ချိတ်ဆက်မှုဒေတာအားလုံးကို လက်ခံရရှိထားကြောင်း ဆိုလိုသည်။ acknum. ပက်ကေ့ခ်ျတစ်ခုတွင် အလံများစွာပါရှိနိုင်ပြီး၊ ဥပမာ၊ SYNACK ပက်ကတ်ကို ၎င်းတို့ပေါင်းစပ်ပြီးနောက် အမည်ပေးထားသည်။

  • နံပါတ်စဉ် (seqnum) သည် ဤပက်ကတ်တွင် ပေးပို့သည့် ပထမဆုံး byte အတွက် ဒေတာစီးကြောင်းတွင် ထေရကို သတ်မှတ်သည်။ ဥပမာအားဖြင့်၊ ဒေတာ X bytes ပါသော ပထမပက်ကတ်တွင် ဤနံပါတ်သည် N ဖြစ်ပါက၊ ဒေတာအသစ်ပါသော နောက်ထုပ်ပိုးတွင် N+X ဖြစ်လိမ့်မည်။ ချိတ်ဆက်မှုအစတွင်၊ ပါတီတစ်ခုစီသည် ဤနံပါတ်ကို ကျပန်းရွေးချယ်သည်။

  • အသိအမှတ်ပြုနံပါတ် (acknum) - seqnum နှင့် တူညီသော offset ဖြစ်သော်လည်း၊ ၎င်းသည် ပို့လွှတ်သော byte အရေအတွက်ကို မသတ်မှတ်ထားဘဲ၊ လက်ခံသူထံမှ ပထမဆုံး byte အရေအတွက်၊

ချိတ်ဆက်မှုအစတွင် နှစ်ဖက်သဘောတူရမည်။ seqnum и acknum. client သည် ၎င်းနှင့်အတူ SYN packet တစ်ခုကို ပေးပို့သည်။ seqnum = X. ဆာဗာသည် ၎င်း၏ကိုယ်ပိုင်ရေးသားသည့် SYNACK ပက်ကတ်ဖြင့် တုံ့ပြန်သည်။ seqnum = Y ပြီးရော acknum = X + 1. client သည် ACK packet ဖြင့် SYNACK ကို တုံ့ပြန်သည်။ seqnum = X + 1, acknum = Y + 1. ထို့နောက်၊ အမှန်တကယ်ဒေတာလွှဲပြောင်းခြင်းစတင်သည်။

အပြန်အလှန်ပြောဆိုသူသည် packet ၏လက်ခံရရှိမှုကို အသိအမှတ်မပြုပါက TCP သည် အချိန်လွန်ပြီး ၎င်းကိုပြန်လည်ပေးပို့ပါသည်။

SYN ကွက်ကီးများကို အဘယ်ကြောင့် အမြဲအသုံးမပြုရသနည်း။

ပထမဦးစွာ SYNACK သို့မဟုတ် ACK ပျောက်ဆုံးသွားပါက ပြန်လည်ပေးပို့မှုကို စောင့်ရပါမည် - ချိတ်ဆက်မှု တည်ဆောက်မှု နှေးကွေးသွားပါသည်။ ဒုတိယအချက်မှာ SYN packet တွင် - ၎င်းတွင်သာ။ - ချိတ်ဆက်မှု၏နောက်ထပ်လုပ်ဆောင်မှုကိုအကျိုးသက်ရောက်စေသည့်ရွေးချယ်စရာများစွာကိုထုတ်လွှင့်သည်။ အဝင် SYN ပက်ကေ့ခ်ျများကို မမှတ်မိဘဲ၊ ထို့ကြောင့် ဆာဗာသည် ဤရွေးချယ်မှုများကို လျစ်လျူရှုကာ၊ အောက်ပါ ထုပ်ပိုးမှုများတွင် သုံးစွဲသူက ၎င်းတို့ကို ပေးပို့တော့မည် မဟုတ်ပါ။ TCP ဤကိစ္စတွင်အလုပ်လုပ်နိုင်သော်လည်းအနည်းဆုံးကနဦးအဆင့်တွင်၊ ချိတ်ဆက်မှုအရည်အသွေးကျဆင်းလိမ့်မည်။

ပက်ကေ့ဂျ်များနှင့်ပတ်သက်၍ XDP ပရိုဂရမ်တစ်ခုသည် အောက်ပါအတိုင်းလုပ်ဆောင်သင့်သည်-

  • cookie ဖြင့် SYN ကို SYNACK ဖြင့် တုံ့ပြန်ပါ။
  • RST ဖြင့် ACK ဖြေပါ (ချိတ်ဆက်မှုကို ချိုးဖျက်ပါ);
  • တခြား packet တွေကို ချလိုက်ပါ။

ပက်ကက်ခွဲခြမ်းစိတ်ဖြာမှုနှင့်အတူ algorithm ၏ Pseudocode

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

တစ်မျိုး (*) စနစ်၏အခြေအနေကို စီမံခန့်ခွဲရန် လိုအပ်သည့်အချက်များကို အမှတ်အသားပြုထားပါသည် - ပထမအဆင့်တွင်၊ SYN cookie တစ်ခုကို seqnum အဖြစ်ဖန်တီးခြင်းဖြင့် TCP လက်ဆွဲခြင်းကို အကောင်အထည်ဖော်ခြင်းဖြင့် ၎င်းတို့မပါဘဲ သင်လုပ်ဆောင်နိုင်သည်။

ထိုနေရာမှာပဲ (**)ကျွန်ုပ်တို့တွင် ဇယားမရှိသော်လည်း၊ ပက်ကတ်ကို ကျော်သွားပါမည်။

TCP လက်ဆွဲ အကောင်အထည်ဖော်ခြင်း။

ပက်ကေ့ဂျ်ခွဲခြမ်းစိတ်ဖြာခြင်းနှင့် ကုဒ်အတည်ပြုခြင်း။

ကျွန်ုပ်တို့သည် ကွန်ရက်ခေါင်းစီးတည်ဆောက်ပုံများ လိုအပ်သည်- Ethernet (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) နှင့် TCP (uapi/linux/tcp.h) ဆက်စပ်အမှားအယွင်းကြောင့် ကျွန်တော် ဆက်သွယ်၍မရခဲ့သော နောက်ဆုံးတစ်ခု atomic64_tလိုအပ်သောအဓိပ္ပါယ်ဖွင့်ဆိုချက်များကို ကုဒ်ထဲသို့ ကူးယူခဲ့ပါသည်။

kernel ရှိ eBPF အတည်ပြုသူသည် back jumps များကို တားမြစ်ထားပြီး၊ အမှန်မှာ၊ loops နှင့် function calls များကို C တွင် ခွဲခြားသိမြင်နိုင်စေရန်အတွက် လုပ်ဆောင်ချက်အားလုံးကို ခေါ်ဆိုမှုဆိုက်တွင် ထည့်သွင်းရပါမည်။

#define INTERNAL static __attribute__((always_inline))

မက္ကရို LOG() ထုတ်ဝေမှုတည်ဆောက်မှုတွင် ပုံနှိပ်ခြင်းကို ပိတ်သည်။

ပရိုဂရမ်သည် လုပ်ငန်းဆောင်တာများ၏ ပိုက်လိုင်းတစ်ခုဖြစ်သည်။ တစ်ခုစီသည် သက်ဆိုင်ရာအဆင့်၏ ခေါင်းစီးကို မီးမောင်းထိုးပြထားသည့် packet တစ်ခုကို လက်ခံရရှိသည်၊ ဥပမာ၊ process_ether() ဖြည့်ဖို့စောင့်နေတယ်။ ether. နယ်ပယ်ခွဲခြမ်းစိတ်ဖြာမှုရလဒ်များအပေါ် အခြေခံ၍ လုပ်ဆောင်ချက်သည် ပက်ကတ်ကို ပိုမိုမြင့်မားသောအဆင့်သို့ လွှဲပြောင်းပေးနိုင်သည်။ လုပ်ဆောင်ချက်၏ရလဒ်သည် XDP လုပ်ဆောင်ချက်ဖြစ်သည်။ SYN နှင့် ACK handlers များသည် packet အားလုံးကို ဖြတ်သန်းခွင့်ပေးသော်လည်း၊

struct Packet {
    struct xdp_md* ctx;

    struct ethhdr* ether;
    struct iphdr* ip;
    struct tcphdr* tcp;
};

INTERNAL int process_tcp_syn(struct Packet* packet) { return XDP_PASS; }
INTERNAL int process_tcp_ack(struct Packet* packet) { return XDP_PASS; }
INTERNAL int process_tcp(struct Packet* packet) { ... }
INTERNAL int process_ip(struct Packet* packet) { ... }

INTERNAL int
process_ether(struct Packet* packet) {
    struct ethhdr* ether = packet->ether;

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

    if (ether->h_proto != bpf_ntohs(ETH_P_IP)) {
        return XDP_PASS;
    }

    // B
    struct iphdr* ip = (struct iphdr*)(ether + 1);
    if ((void*)(ip + 1) > (void*)packet->ctx->data_end) {
        return XDP_DROP; /* malformed packet */
    }

    packet->ip = ip;
    return process_ip(packet);
}

SEC("prog")
int xdp_main(struct xdp_md* ctx) {
    struct Packet packet;
    packet.ctx = ctx;

    // A
    struct ethhdr* ether = (struct ethhdr*)(void*)ctx->data;
    if ((void*)(ether + 1) > (void*)ctx->data_end) {
        return XDP_PASS;
    }

    packet.ether = ether;
    return process_ether(&packet);
}

A နှင့် B ဟု အမှတ်အသားပြုထားသည့် ချက်လက်မှတ်များကို ကျွန်ုပ် အာရုံစိုက်ပါသည်။ အကယ်၍ သင်သည် A ကို မှတ်ချက်ပေးပါက၊ ပရိုဂရမ်က တည်ဆောက်မည်၊ သို့သော် ဖွင့်သည့်အခါ အတည်ပြုချက် အမှားအယွင်း ရှိလိမ့်မည်-

Verifier analysis:

<...>
11: (7b) *(u64 *)(r10 -48) = r1
12: (71) r3 = *(u8 *)(r7 +13)
invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0)
R7 offset is outside of the packet
processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

Error fetching program/map!

သော့ကြိုး invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): ကြားခံ၏အစမှ 12th byte သည် packet အပြင်ဘက်တွင် ရှိနေသောအခါတွင် လုပ်ဆောင်မှုလမ်းကြောင်းများရှိပါသည်။ ကျွန်ုပ်တို့ပြောနေသည့်စာကြောင်းစာရင်းမှပြောပြရန်ခက်ခဲသော်လည်း၊ ညွှန်ကြားချက်နံပါတ် (၁၂) နှင့် source code ၏လိုင်းများကိုပြသသည့် disassembler တစ်ခုရှိသည်-

llvm-objdump -S xdp_filter.o | less

ဤကိစ္စတွင်၊ မျဉ်းကြောင်းကိုညွှန်ပြသည်။

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

အဲဒါက ပြဿနာဖြစ်တယ်ဆိုတာ ရှင်းပါတယ်။ ether. အမြဲတမ်း ဒီလိုပါပဲ။

SYN ကို အကြောင်းပြန်ပါ။

ဤအဆင့်တွင် ရည်ရွယ်ချက်မှာ မှန်ကန်သော SYNACK packet တစ်ခုကို ပုံသေတစ်ခုဖြင့် ထုတ်လုပ်ရန်ဖြစ်သည်။ seqnumအနာဂတ်တွင် SYN ကွတ်ကီးဖြင့် အစားထိုးပါမည်။ အပြောင်းအလဲအားလုံးသည် တွင်ရှိနေသည်။ process_tcp_syn() နှင့်ပတ်ဝန်းကျင်။

အထုပ်ကိုစစ်ဆေးခြင်း။

ထူးထူးခြားခြား၊ ဤနေရာတွင် အထူးခြားဆုံး စာကြောင်းတစ်ခု သို့မဟုတ် ၎င်းအတွက် မှတ်ချက်တစ်ခုဖြစ်သည်။

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

ကုဒ်၏ပထမဗားရှင်းကိုရေးသောအခါ၊ 5.1 kernel ကိုအသုံးပြုခဲ့သည်၊ အကြားကွာခြားချက်ရှိသောအရာကိုအတည်ပြုရန် data_end и (const void*)ctx->data_end. စာရေးချိန်၌ 5.3.1 kernel တွင် ဤပြဿနာမရှိပါ။ compiler သည် အကွက်တစ်ခုထက် ကွဲပြားသော local variable တစ်ခုကို ဝင်ရောက်နေခြင်းဖြစ်နိုင်သည်။ ကိုယ်ကျင့်တရား - ကြီးမားသောအသိုက်တွင်၊ ကုဒ်ကိုရိုးရှင်းအောင်ပြုလုပ်ခြင်းဖြင့်ကူညီနိုင်သည်။

Verifier ၏ ဂုဏ်ကျက်သရေအတွက် နောက်ထပ် ပုံမှန်စစ်ဆေးမှုများ။ အို 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 и acknumACK ကို သတ်မှတ်ပါ (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 intrinsik ကို ဖုံးကွယ်ထားသည့် မက်ခရိုတစ်ခု။

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

Checksum ပြန်လည်တွက်ချက်ခြင်း။

IPv4 နှင့် TCP စစ်ဆေးမှုများသည် ခေါင်းစီးရှိ 16-bit စကားလုံးများအားလုံးကို ထပ်ပေါင်းရန် လိုအပ်ပြီး ခေါင်းစီး၏အရွယ်အစားကို ၎င်းတို့တွင် ရေးထားသည်၊ ဆိုလိုသည်မှာ စုစည်းချိန်တွင် မသိရသေးပါ။ နယ်နိမိတ်မပြောင်းမချင်း အတည်ပြုသူသည် ပုံမှန်လှည့်ပတ်ကို ကျော်သွားမည်မဟုတ်သောကြောင့် ၎င်းမှာ ပြဿနာတစ်ခုဖြစ်သည်။ သို့သော် ခေါင်းစီးများ၏ အရွယ်အစားကို ကန့်သတ်ထားသည်- တစ်ခုလျှင် 64 bytes အထိရှိသည်။ စောစောအဆုံးသတ်နိုင်သည့် ပုံသေအကြိမ်အရေအတွက်ဖြင့် ကွင်းဆက်တစ်ခုကို သင်ပြုလုပ်နိုင်သည်။

ရှိတယ်ဆိုတာ သတိပြုမိပါတယ်။ RFC 1624 packet များ၏ ပုံသေစကားလုံးများသာ ပြောင်းလဲပါက checksum ကို တစ်စိတ်တစ်ပိုင်း ပြန်လည်တွက်ချက်နည်း။ သို့သော် အဆိုပါနည်းလမ်းသည် လူတိုင်းမဟုတ်ပါ၊ အကောင်အထည်ဖော်ရန် ပိုမိုခက်ခဲမည်ဖြစ်သည်။

Checksum တွက်ချက်မှုလုပ်ဆောင်ချက်

#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 ခေါ်ဆိုမှုကုဒ်ဖြင့် စစ်ဆေးထားသော၊ သို့မှသာ verifier သည် loop ၏အဆုံးကို သက်သေပြနိုင်စေရန် ဒုတိယထွက်ပေါက်အခြေအနေ လိုအပ်ပါသည်။

32-bit စကားလုံးများအတွက်၊ ပိုမိုရိုးရှင်းသောဗားရှင်းကို အကောင်အထည်ဖော်သည်-

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

အမှန်တကယ် checksums များကို ပြန်လည်တွက်ချက်ပြီး packet ကို ပြန်ပို့ပေးသည်-

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-bit စကားလုံးများ၏ 791-bit ပေါင်းလဒ်ကို checksum ပြုလုပ်သည်။

TCP လက်ဆွဲစစ်ဆေးခြင်း။

filter သည် မှန်ကန်စွာချိတ်ဆက်မှုတစ်ခုနှင့် ထူထောင်သည်။ netcatNetwork stack သည် SYN မရရှိခဲ့သောကြောင့် Linux မှ RST packet ဖြင့် တုံ့ပြန်ခဲ့သည့် နောက်ဆုံး ACK ကို ကျော်သွားသည် - ၎င်းကို SYNACK အဖြစ်ပြောင်းလဲပြီး ပြန်လည်ပေးပို့ခြင်းဖြစ်သည် - OS ၏အမြင်အရ၊ မဟုတ်သော packet တစ်ခုရောက်ရှိလာပါသည်။ ပွင့်လင်းသောချိတ်ဆက်မှုများနှင့်သက်ဆိုင်သည်။

$ 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 မမှန်သော checksum များကို မတုံ့ပြန်ပါ။

XDP ၏အမြင်မှ၊ စစ်ဆေးမှုကိုယ်တိုင်ကအသေးအဖွဲဖြစ်သည်။ တွက်ချက်မှု အယ်လဂိုရီသမ်သည် ရှေးရိုးဆန်ပြီး ခေတ်မီဆန်းပြားသော တိုက်ခိုက်သူအတွက် အားနည်းချက် ဖြစ်နိုင်သည်။ ဥပမာအားဖြင့် Linux kernel သည် cryptographic SipHash ကိုအသုံးပြုသည်၊ သို့သော် XDP အတွက် ၎င်း၏အကောင်အထည်ဖော်မှုသည် ဤဆောင်းပါး၏နယ်ပယ်ထက်ကျော်လွန်နေပါသည်။

ပြင်ပ အပြန်အလှန်ဆက်သွယ်မှုနှင့် ဆက်စပ်သော TODO အသစ်များအတွက် ပေါ်လာသည်-

  • XDP ပရိုဂရမ်ကို သိမ်းဆည်း၍မရပါ။ cookie_seed (ဆား၏လျှို့ဝှက်အစိတ်အပိုင်း) ကမ္ဘာလုံးဆိုင်ရာပြောင်းလဲမှုတစ်ခုတွင်၊ သင်သည် ယုံကြည်စိတ်ချရသော မီးစက်မှ အချိန်အခါအလိုက် အပ်ဒိတ်လုပ်မည့် တန်ဖိုးရှိသော kernel စတိုးတစ်ခု လိုအပ်ပါသည်။

  • ACK packet ရှိ SYN ကွတ်ကီးသည် ကိုက်ညီပါက၊ သင်သည် မက်ဆေ့ချ်ကို ပရင့်ထုတ်ရန် မလိုအပ်သော်လည်း ၎င်းမှ packet များကို ကျော်သွားရန်အတွက် အတည်ပြုထားသော client ၏ 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 သည် kernel packet လုပ်ဆောင်ခြင်းကို အနှောင့်အယှက်ပေးသည့်ကိရိယာတစ်ခုဖြစ်ပြီး DPDK နှင့် အခြားသော kernel bypass ရွေးချယ်မှုများကဲ့သို့ kernel stack ၏အခြားရွေးချယ်စရာမဟုတ်ပါ။ အခြားတစ်ဖက်တွင်၊ XDP သည် သင့်အား အသွားအလာလုပ်ဆောင်ရာတွင် ခေတ္တရပ်နားခြင်းမရှိဘဲ အပ်ဒိတ်လုပ်ရန် လွယ်ကူသော ရှုပ်ထွေးသောယုတ္တိကို အကောင်အထည်ဖော်နိုင်စေပါသည်။ verifier သည် ပြဿနာကြီးကြီးမားမားမဖန်တီးထားပေ၊ ကျွန်ုပ်သည် userspace code ၏ အစိတ်အပိုင်းများအတွက် ယင်းကဲ့သို့ ငြင်းဆိုမည်မဟုတ်ပါ။

ဒုတိယအပိုင်းတွင်၊ အကြောင်းအရာသည် စိတ်ဝင်စားစရာကောင်းပါက၊ ကျွန်ုပ်တို့သည် အတည်ပြုထားသော client များ၏ဇယားကို အပြီးသတ်ပြီး ချိတ်ဆက်မှုများကို ဖြတ်တောက်ကာ၊ ကောင်တာများကို အကောင်အထည်ဖော်ကာ filter ကိုစီမံခန့်ခွဲရန် userspace utility တစ်ခုကို ရေးသားပါမည်။

လင့်ခ်များ

source: www.habr.com

မှတ်ချက် Add