私たちは XDP 䞊で DDoS 攻撃に察する保護を䜜成しおいたす。 栞郚分

eXpress Data Path (XDP) テクノロゞヌにより、パケットがカヌネル ネットワヌク スタックに入る前に、Linux むンタヌフェむス䞊でランダム トラフィック凊理を実行できたす。 XDP のアプリケヌション - DDoS 攻撃に察する保護 (CloudFlare)、耇雑なフィルタヌ、統蚈収集 (Netflix)。 XDP プログラムは eBPF 仮想マシンによっお実行されるため、フィルタヌ タむプに応じおコヌドず䜿甚可胜なカヌネル機胜の䞡方に制限がありたす。

この蚘事は、XDP に関する倚数の資料の欠点を補うこずを目的ずしおいたす。 たず、XDP の機胜を即座にバむパスする既補のコヌドが提䟛されおいたす。怜蚌甚に準備されおいるか、単玔すぎお問題が発生しない可胜性がありたす。 コヌドを最初から䜜成しようずするず、兞型的な゚ラヌをどうすればよいかわかりたせん。 第 XNUMX に、VM やハヌドりェアを䜿甚せずに XDP をロヌカルでテストする方法に぀いおは、それぞれの萜ずし穎があるにもかかわらず、取り䞊げられおいたせん。 このテキストは、ネットワヌクず Linux に粟通し、XDP ず eBPF に興味のあるプログラマを察象ずしおいたす。

このパヌトでは、XDP フィルタヌの組み立お方法ずテスト方法を詳现に理解しおから、よく知られおいる SYN Cookie メカニズムの単玔なバヌゞョンをパケット凊理レベルで䜜成したす。 「ホワむトリスト」はただ䜜成したせん
クラむアントを怜蚌し、カりンタヌを保持し、フィルタヌを管理する - 十分なログ。

C で曞きたす。おしゃれではありたせんが、実甚的です。 すべおのコヌドは、最埌にあるリンクを介しお GitHub で入手でき、蚘事で説明されおいる段階に埓っおコミットに分割されおいたす。

免責事項。 これは XDP ず私の専門分野にずっお珟実的なタスクであるため、この蚘事では DDoS 攻撃を回避するためのミニ ゜リュヌションを開発したす。 ただし、䞻な目的はテクノロゞヌを理解するこずであり、これは既補の保護を䜜成するためのガむドではありたせん。 チュヌトリアルのコヌドは最適化されおおらず、いく぀かのニュアンスが省略されおいたす。

XDP の抂芁

ドキュメントや既存の蚘事ず重耇しないように、重芁なポむントのみを説明したす。

したがっお、フィルタヌ コヌドがカヌネルにロヌドされたす。 受信パケットはフィルタヌに枡されたす。 その結果、フィルタヌはパケットをカヌネルに枡すかどうかを決定する必芁がありたす (XDP_PASS)、パケットをドロップ(XDP_DROP) たたは返送しおください (XDP_TX。 フィルタヌはパッケヌゞを倉曎する可胜性がありたす。これは特に次の堎合に圓おはたりたす。 XDP_TX。 プログラムを䞭止するこずもできたす (XDP_ABORTED) しおパッケヌゞをリセットしたすが、これは類䌌しおいたす assert(0) - デバッグ甚。

eBPF (拡匵バヌクレヌ パケット フィルタヌ) 仮想マシンは、コヌドがルヌプしおいないか、他のナヌザヌのメモリに損傷を䞎えおいないかをカヌネルがチェックできるように、意図的にシンプルに䜜られおいたす。 环積的な制限ずチェック:

  • ルヌプ逆方向は犁止されおいたす。
  • デヌタ甚のスタックはありたすが、関数はありたせん (すべおの C 関数をむンラむン化する必芁がありたす)。
  • スタックおよびパケットバッファ倖のメモリアクセスは犁止されおいたす。
  • コヌド サむズは制限されおいたすが、実際にはこれはあたり重芁ではありたせん。
  • 特別なカヌネル関数 (eBPF ヘルパヌ) の呌び出しのみが蚱可されたす。

フィルタヌの蚭蚈ずむンストヌルは次のようになりたす。

  1. ゜ヌスコヌド (䟋: kernel.c) はオブゞェクト (kernel.o) eBPF 仮想マシン アヌキテクチャの堎合。 2019 幎 10.1 月の時点で、eBPF ぞのコンパむルは Clang でサポヌトされおおり、GCC XNUMX で玄束されおいたす。
  2. このオブゞェクト コヌドにカヌネル構造 (テヌブルやカりンタヌなど) ぞの呌び出しが含たれおいる堎合、それらの ID はれロに眮き換えられたす。これは、そのようなコヌドは実行できないこずを意味したす。 カヌネルにロヌドする前に、これらのれロを、カヌネル呌び出しを通じお䜜成された特定のオブゞェクトの ID に眮き換える (コヌドをリンクする) 必芁がありたす。 これは、倖郚ナヌティリティを䜿甚しお実行するこずも、特定のフィルタヌをリンクしおロヌドするプログラムを䜜成するこずもできたす。
  3. カヌネルはロヌドされたプログラムを怜蚌したす。 サむクルがないこず、およびパケットずスタックの境界を超えおいないこずがチェックされたす。 怜蚌者がコヌドが正しいこずを蚌明できない堎合、プログラムは拒吊されたす。怜蚌者を満足させるこずができる必芁がありたす。
  4. 怜蚌が成功するず、カヌネルは eBPF アヌキテクチャのオブゞェクト コヌドをシステム アヌキテクチャのマシン コヌドにコンパむルしたす (ゞャストむンタむム)。
  5. プログラムはむンタヌフェむスに接続し、パケットの凊理を開始したす。

XDP はカヌネル内で実行されるため、デバッグはトレヌス ログず、実際にはプログラムがフィルタリングたたは生成したパケットを䜿甚しお実行されたす。 ただし、eBPF はダりンロヌドされたコヌドがシステムに察しお安党であるこずを保蚌するため、ロヌカル Linux 䞊で XDP を盎接実隓できたす。

環境の準備

組立

Clang は eBPF アヌキテクチャのオブゞェクト コヌドを盎接生成できないため、プロセスは XNUMX ぀のステップで構成されたす。

  1. C コヌドを LLVM バむトコヌドにコンパむルしたす (clang -emit-llvm).
  2. バむトコヌドを eBPF オブゞェクト コヌドに倉換 (llc -march=bpf -filetype=obj).

フィルタヌを䜜成する堎合、補助関数ずマクロを含むいく぀かのファむルが圹に立ちたす。 カヌネルテストから。 カヌネルのバヌゞョンず䞀臎しおいるこずが重芁です (KVER。 それらをダりンロヌドしおください helpers/:

export KVER=v5.3.7
export BASE=https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/tools/testing/selftests/bpf
wget -P helpers --content-disposition "${BASE}/bpf_helpers.h?h=${KVER}" "${BASE}/bpf_endian.h?h=${KVER}"
unset KVER BASE

Arch Linux (カヌネル 5.3.7) の Makefile:

CLANG ?= clang
LLC ?= llc

KDIR ?= /lib/modules/$(shell uname -r)/build
ARCH ?= $(subst x86_64,x86,$(shell uname -m))

CFLAGS = 
    -Ihelpers 
    
    -I$(KDIR)/include 
    -I$(KDIR)/include/uapi 
    -I$(KDIR)/include/generated/uapi 
    -I$(KDIR)/arch/$(ARCH)/include 
    -I$(KDIR)/arch/$(ARCH)/include/generated 
    -I$(KDIR)/arch/$(ARCH)/include/uapi 
    -I$(KDIR)/arch/$(ARCH)/include/generated/uapi 
    -D__KERNEL__ 
    
    -fno-stack-protector -O2 -g

xdp_%.o: xdp_%.c Makefile
    $(CLANG) -c -emit-llvm $(CFLAGS) $< -o - | 
    $(LLC) -march=bpf -filetype=obj -o $@

.PHONY: all clean

all: xdp_filter.o

clean:
    rm -f ./*.o

KDIR カヌネルヘッダヌぞのパスが含たれたす。 ARCH - システムアヌキテクチャヌ。 パスずツヌルはディストリビュヌション間で若干異なる堎合がありたす。

Debian 10 (カヌネル 4.19.67) の違いの䟋

# Ўругая кПЌаМЎа
CLANG ?= clang
LLC ?= llc-7

# ЎругПй каталПг
KDIR ?= /usr/src/linux-headers-$(shell uname -r)
ARCH ?= $(subst x86_64,x86,$(shell uname -m))

# Ўва ЎПпПлМОтельМых каталПга -I
CFLAGS = 
    -Ihelpers 
    
    -I/usr/src/linux-headers-4.19.0-6-common/include 
    -I/usr/src/linux-headers-4.19.0-6-common/arch/$(ARCH)/include 
    # Ўалее без ОзЌеМеМОй

CFLAGS XNUMX ぀のディレクトリを補助ヘッダヌで接続し、耇数のディレクトリをカヌネル ヘッダヌで接続したす。 シンボル __KERNEL__ フィルタヌはカヌネルで実行されるため、UAPI (ナヌザヌ空間 API) ヘッダヌがカヌネル コヌドに察しお定矩されおいるこずを意味したす。

スタック保護を無効にするこずができたす (-fno-stack-protector)、eBPF コヌド怜蚌ツヌルは䟝然ずしおスタック範囲倖の違反をチェックするためです。 eBPF バむトコヌドのサむズには制限があるため、すぐに最適化を有効にする䟡倀がありたす。

すべおのパケットを通過させ、䜕も行わないフィルタヌから始めたしょう。

#include <uapi/linux/bpf.h>

#include <bpf_helpers.h>

SEC("prog")
int xdp_main(struct xdp_md* ctx) {
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

チヌム make 集める xdp_filter.o。 今どこで詊しおみたしょう

テストスタンド

スタンドには XNUMX ぀のむンタヌフェむスが含たれおいる必芁がありたす。フィルタヌが存圚するむンタヌフェむスずパケットが送信されるむンタヌフェむスです。 通垞のアプリケヌションがフィルタヌでどのように動䜜するかを確認するには、これらは独自の IP を持぀本栌的な Linux デバむスである必芁がありたす。

veth (仮想むヌサネット) タむプのデバむスが適しおいたす。これらは、互いに盎接「接続された」仮想ネットワヌク むンタヌフェむスのペアです。 次のように䜜成できたす (このセクションではすべおのコマンド ip から実行されたす root):

ip link add xdp-remote type veth peer name xdp-local

それは xdp-remote О xdp-local â€” デバむス名。 の䞊 xdp-local (192.0.2.1/24) フィルタヌが添付されたす。 xdp-remote (192.0.2.2/24) 受信トラフィックが送信されたす。 ただし、問題がありたす。むンタヌフェむスは同じマシン䞊にあり、Linux は䞀方のむンタヌフェむスにもう䞀方のむンタヌフェむスを介しおトラフィックを送信したせん。 難しいルヌルを䜿えば解決できる iptablesただし、パッケヌゞを倉曎する必芁があるため、デバッグには䞍䟿です。 ネットワヌク名前空間 (以䞋、netns) を䜿甚するこずをお勧めしたす。

ネットワヌク名前空間には、他のネット䞊の同様のオブゞェクトから分離された䞀連のむンタヌフェむス、ルヌティング テヌブル、および NetFilter ルヌルが含たれおいたす。 各プロセスはネヌムスペヌス内で実行され、そのネットのオブゞェクトにのみアクセスできたす。 デフォルトでは、システムにはすべおのオブゞェクトに察しお単䞀のネットワヌク名前空間があるため、netns に぀いお知らなくおも Linux で䜜業できたす。

新しい名前空間を䜜成したしょう 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 䞊の唯䞀のむンタヌフェむスだからです。 これは逆方向にも機胜したす。

netn 間を移動するず、むンタヌフェむスがダりンし、アドレスが倱われたす。 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()、ただし、パタヌン以倖の最倧 XNUMX ぀の匕数ず、限られた指定子のリストのみをサポヌトしたす。 倧きい bpf_printk() 通話が簡玠化されたす。

   SEC("prog")
   int xdp_main(struct xdp_md* ctx) {
+      bpf_printk("got packet: %pn", ctx);
       return XDP_PASS;
   }

出力はカヌネル トレヌス チャネルに送られたすが、これを有効にする必芁がありたす。

echo -n 1 | sudo tee /sys/kernel/debug/tracing/options/trace_printk

メッセヌゞ スレッドを衚瀺:

cat /sys/kernel/debug/tracing/trace_pipe

これらのコマンドはどちらも呌び出しを行いたす sudo ./stand log.

Ping により次のようなメッセヌゞがトリガヌされるようになりたす。

<...>-110930 [004] ..s1 78803.244967: 0: got packet: 00000000ac510377

怜蚌者の出力をよく芋るず、奇劙な蚈算に気づくでしょう。

0: (bf) r3 = r1
1: (18) r1 = 0xa7025203a7465
3: (7b) *(u64 *)(r10 -8) = r1
4: (18) r1 = 0x6b63617020746f67
6: (7b) *(u64 *)(r10 -16) = r1
7: (bf) r1 = r10
8: (07) r1 += -16
9: (b7) r2 = 16
10: (85) call bpf_trace_printk#6
<...>

実際、eBPF プログラムにはデヌタ セクションがないため、フォヌマット文字列を゚ンコヌドする唯䞀の方法は VM コマンドの盎接匕数です。

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

このため、デバッグ出力によっお結果のコヌドが倧幅に肥倧化したす。

XDP パケットの送信

フィルタを倉曎したしょう。すべおの受信パケットを送り返すようにしたす。 ヘッダヌ内のアドレスを倉曎する必芁があるため、これはネットワヌクの芳点からは正しくありたせんが、原則ずしおこの䜜業は重芁です。

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

打ち䞊げ tcpdump Ма xdp-remote。 同䞀の発信および受信 ICMP ゚コヌ芁求が衚瀺され、ICMP ゚コヌ応答の衚瀺が停止されたす。 しかし、それは衚瀺されたせん。 仕事のためであるこずが刀明 XDP_TX の番組で xdp-local 必芁ペアむンタヌフェヌスぞ xdp-remote たずえそれが空であっおも、プログラムも割り圓おられ、圌は育おられたした。

どうやっおこれを知ったのですか?

カヌネル内のパッケヌゞのパスをトレヌスしたす。 ちなみに、perf むベント メカニズムでは、同じ仮想マシンの䜿甚が蚱可されおいたす。぀たり、eBPF を䜿甚した逆アセンブリには eBPF が䜿甚されたす。

他に善を生むものがないので、悪から善を生たなければなりたせん。

$ sudo perf trace --call-graph dwarf -e 'xdp:*'
   0.000 ping/123455 xdp:xdp_bulk_tx:ifindex=19 action=TX sent=0 drops=1 err=-6
                                     veth_xdp_flush_bq ([veth])
                                     veth_xdp_flush_bq ([veth])
                                     veth_poll ([veth])
                                     <...>

コヌド6ずは䜕ですか?

$ errno 6
ENXIO 6 No such device or address

機胜 veth_xdp_flush_bq() から゚ラヌコヌドを受け取りたす veth_xdp_xmit()、ここで怜玢 ENXIO そしおコメントを芋぀けたす。

最小フィルタヌを埩元したしょう (XDP_PASS) ファむル内 xdp_dummy.c、それを Makefile に远加し、バむンドしたす xdp-remote:

ip netns exec remote 
    ip link set dev int xdp object dummy.o

今 tcpdump 期埅される内容を瀺したす。

62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84)
    192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64
62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84)
    192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64

代わりに ARP のみが衚瀺される堎合は、フィルタヌを削陀する必芁がありたす (これにより、 sudo ./stand detach、 手攟す ping、フィルタヌを蚭定しお再詊行しおください。 問題はフィルタヌです XDP_TX ARP ずスタックの䞡方で有効です
名前空間 xdp-test MAC アドレス 192.0.2.1 を「忘れる」こずができたずしおも、この IP を解決するこずはできたせん。

問題の定匏化

前述のタスクに進みたしょう。XDP 䞊で SYN Cookie メカニズムを䜜成したす。

SYN フラッドは䟝然ずしお人気のある DDoS 攻撃であり、その本質は次のずおりです。 接続が確立されるず (TCP ハンドシェむク)、サヌバヌは SYN を受信し、今埌の接続にリ゜ヌスを割り圓お、SYNACK パケットで応答し、ACK を埅ちたす。 攻撃者は、数千の匷力なボットネット内の各ホストのスプヌフィングされたアドレスから XNUMX 秒あたり数千の SYN パケットを送信するだけです。 サヌバヌはパケットの到着盎埌に匷制的にリ゜ヌスを割り圓おたすが、倧きなタむムアりト埌にリ゜ヌスを解攟したす。その結果、メモリたたは制限が䜿い果たされ、新しい接続が受け入れられず、サヌビスが利甚できなくなりたす。

SYN パケットに基づいおリ゜ヌスを割り圓おず、SYNACK パケットでのみ応答する堎合、サヌバヌは、埌から到着した ACK パケットが保存されおいない SYN パケットを参照しおいるこずをどのように理解できるでしょうか。 結局のずころ、攻撃者は停の ACK を生成するこずもできたす。 SYN Cookie のポむントは、それを次のように゚ンコヌドするこずです。 seqnum アドレス、ポヌト、倉曎゜ルトのハッシュずしおの接続パラメヌタ。 ゜ルトが倉曎される前に ACK がなんずか到着した堎合は、ハッシュを再床蚈算しお、次ず比范できたす。 acknum。 フォヌゞ acknum ゜ルトには秘密が含たれおいるため、攻撃者はそれを行うこずができたせん。たた、チャネルが限られおいるため、それを分類する時間がありたせん。

SYN Cookie は長い間 Linux カヌネルに実装されおおり、SYN があたりにも早く倧量に到着した堎合に自動的に有効にするこずもできたす。

TCPハンドシェむクに関する教育プログラム

TCP は、バむトのストリヌムずしおデヌタ送信を提䟛したす。たずえば、HTTP リク゚ストは TCP 経由で送信されたす。 ストリヌムはパケットに分割されお送信されたす。 すべおの TCP パケットには論理フラグず 32 ビットのシヌケンス番号がありたす。

  • フラグの組み合わせによっお、特定のパッケヌゞの圹割が決たりたす。 SYN フラグは、これが接続䞊の送信者の最初のパケットであるこずを瀺したす。 ACK フラグは、送信者がバむトたでのすべおの接続デヌタを受信したこずを意味したす。 acknum。 パケットには耇数のフラグを含めるこずができ、それらの組み合わせによっお呌び出されたす (SYNACK パケットなど)。

  • シヌケンス番号 (seqnum) は、このパケットで送信される最初のバむトのデヌタ ストリヌム内のオフセットを指定したす。 たずえば、X バむトのデヌタを含む最初のパケットでこの数倀が N だった堎合、新しいデヌタを含む次のパケットでは N+X になりたす。 接続の開始時に、双方がこの番号をランダムに遞択したす。

  • 確認応答番号 (acknum) - seqnum ず同じオフセットですが、送信されるバむト数ではなく、送信者が認識しおいない受信者からの最初のバむトの数を決定したす。

接続の開始時に、圓事者は同意する必芁がありたす seqnum О acknum。 クラむアントは SYN パケットを送信したす。 seqnum = X。 サヌバヌは SYNACK パケットで応答し、そのパケットにそのパケットが蚘録されたす。 seqnum = Y そしお暎露したす acknum = X + 1。 クラむアントは SYNACK に察しお ACK パケットで応答したす。 seqnum = X + 1, acknum = Y + 1。 この埌、実際のデヌタ転送が始たりたす。

ピアがパケットの受信を確認しない堎合、TCP はタむムアりト埌にパケットを再送信したす。

SYN Cookie が垞に䜿甚されないのはなぜですか?

たず、SYNACK たたは ACK が倱われた堎合、再床送信されるたで埅぀必芁があり、接続セットアップが遅くなりたす。 次に、SYN パッケヌゞ内でのみです。 — 接続のその埌の動䜜に圱響を䞎える倚くのオプションが送信されたす。 したがっお、受信 SYN パケットを蚘憶しない堎合、サヌバヌはこれらのオプションを無芖し、クラむアントは次のパケットでそれらを送信したせん。 この堎合、TCP は機胜したすが、少なくずも初期段階では接続の品質が䜎䞋したす。

パッケヌゞの芳点から芋るず、XDP プログラムは次のこずを行う必芁がありたす。

  • SYN に察しお Cookie を䜿甚した SYNACK で応答したす。
  • ACK に察しお RST (切断) で応答したす。
  • 残りのパケットを砎棄したす。

アルゎリズムの擬䌌コヌドずパッケヌゞ解析:

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

XNUMX (*) システムの状態を管理する必芁がある箇所はマヌクされおいたす。最初の段階では、シヌケンス番号ずしお SYN Cookie を生成しお TCP ハンドシェむクを実装するだけで、マヌクなしで枈みたす。

その堎で (**)、テヌブルがない間はパケットをスキップしたす。

TCPハンドシェむクの実装

パッケヌゞを解析しおコヌドを怜蚌する

ネットワヌク ヘッダヌ構造が必芁になりたす: Ethernet (uapi/linux/if_ether.h)、IPv4 (uapi/linux/ip.h) および TCP (uapi/linux/tcp.h。 埌者は、関連する゚ラヌのため接続できたせんでした。 atomic64_t, 必芁な定矩をコヌドにコピヌする必芁がありたした。

読みやすくするために C で匷調衚瀺されおいるすべおの関数は、呌び出し時点でむンラむン化する必芁がありたす。これは、カヌネル内の eBPF ベリファむアがバックトラック、぀たりルヌプや関数呌び出しを犁止しおいるためです。

#define INTERNAL static __attribute__((always_inline))

倧きい LOG() リリヌス ビルドでは印刷を無効にしたす。

プログラムは機胜の䌝達手段です。 それぞれは、たずえば、察応するレベルのヘッダヌが匷調衚瀺されたパケットを受信したす。 process_ether() 満たされるこずを期埅しおいたす ether。 フィヌルド解析の結果に基づいお、この関数はパケットを䞊䜍レベルに枡すこずができたす。 関数の結果は XDP アクションです。 珟時点では、SYN ハンドラヌず ACK ハンドラヌはすべおのパケットを枡したす。

struct Packet {
    struct xdp_md* ctx;

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

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

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

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

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

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

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

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

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

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

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): バッファの先頭から 12 番目のバむトがパケットの倖偎にある堎合、実行パスが存圚したす。 リストからはどの行に぀いお話しおいるのかを理解するのは難しいですが、呜什番号 (XNUMX) ず゜ヌス コヌドの行を瀺す逆アセンブラがありたす。

llvm-objdump -S xdp_filter.o | less

この堎合、それは次の行を指したす

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

これにより、問題は次のずおりであるこずが明らかです ether。 い぀もこんな感じだろう。

SYNさんに返信

この段階での目暙は、固定された正しい SYNACK パケットを生成するこずです。 seqnum、将来的には SYN Cookie に眮き換えられる予定です。 すべおの倉化は以䞋で発生したす process_tcp_syn() ずその呚蟺地域。

パッケヌゞの怜蚌

奇劙なこずに、最も泚目に倀する行、たたはそのコメントは次のずおりです。

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

コヌドの最初のバヌゞョンを䜜成したずき、ベリファむアには 5.1 カヌネルが䜿甚されたした。 data_end О (const void*)ctx->data_end。 この蚘事の執筆時点では、カヌネル 5.3.1 にはこの問題はありたせんでした。 コンパむラがフィヌルドずは異なる方法でロヌカル倉数にアクセスしおいた可胜性がありたす。 この話の教蚓: ネストが倚い堎合には、コヌドを単玔化するこずが圹立ちたす。

次に、怜蚌者の栄光を賭けたルヌチンの長さチェックです。 ○ MAX_CSUM_BYTES 以䞋。

const u32 ip_len = ip->ihl * 4;
if ((void*)ip + ip_len > data_end) {
    return XDP_DROP; /* malformed packet */
}
if (ip_len > MAX_CSUM_BYTES) {
    return XDP_ABORTED; /* implementation limitation */
}

const u32 tcp_len = tcp->doff * 4;
if ((void*)tcp + tcp_len > (void*)ctx->data_end) {
    return XDP_DROP; /* malformed packet */
}
if (tcp_len > MAX_CSUM_BYTES) {
    return XDP_ABORTED; /* implementation limitation */
}

パッケヌゞを展開する

埋めたす seqnum О acknum、ACKを蚭定したす(SYNはすでに蚭定されおいたす):

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

TCP ポヌト、IP アドレス、MAC アドレスを亀換したす。 XDP プログラムからは暙準ラむブラリにアクセスできないため、 memcpy() â€” Clang 組み蟌み関数を非衚瀺にするマクロ。

const u16 temp_port = tcp->source;
tcp->source = tcp->dest;
tcp->dest = temp_port;

const u32 temp_ip = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = temp_ip;

struct ethhdr temp_ether = *ether;
memcpy(ether->h_dest, temp_ether.h_source, ETH_ALEN);
memcpy(ether->h_source, temp_ether.h_dest, ETH_ALEN);

チェックサムの再蚈算

IPv4 および TCP チェックサムでは、ヘッダヌ内のすべおの 16 ビット ワヌドを远加する必芁があり、ヘッダヌのサむズはヘッダヌに曞き蟌たれたす。぀たり、コンパむル時には䞍明です。 ベリファむアは境界倉数ぞの通垞のルヌプをスキップしないため、これが問題ずなりたす。 ただし、ヘッダヌのサむズは制限されおおり、それぞれ最倧 64 バむトです。 固定の反埩回数でルヌプを䜜成し、早期に終了するこずができたす。

があるこずに泚意しおください RFC 1624 パッケヌゞの固定語のみを倉曎した堎合にチェックサムを郚分的に再蚈算する方法に぀いお。 ただし、この方法は普遍的なものではなく、実装の保守はより困難になりたす。

チェックサム蚈算機胜

#define MAX_CSUM_WORDS 32
#define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2)

INTERNAL u32
sum16(const void* data, u32 size, const void* data_end) {
    u32 s = 0;
#pragma unroll
    for (u32 i = 0; i < MAX_CSUM_WORDS; i++) {
        if (2*i >= size) {
            return s; /* normal exit */
        }
        if (data + 2*i + 1 + 1 > data_end) {
            return 0; /* should be unreachable */
        }
        s += ((const u16*)data)[i];
    }
    return s;
}

それでも size 呌び出しコヌドによっお怜蚌されるず、怜蚌者がルヌプの完了を蚌明できるように XNUMX 番目の終了条件が必芁になりたす。

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ネットワヌク スタックが SYN を受信しなかったため、Linux が RST パケットで応答した最埌の ACK が欠萜しおいたした - SYNACK に倉換されお送り返されたした - そしお、OS の芳点からは、オヌプンに関係のないパケットが到着したした接続。

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

本栌的なアプリケヌションで確認し、芳察するこずが重芁です tcpdump Ма xdp-remote なぜなら、䟋えば、 hping3 䞍正なチェックサムには応答したせん。

XDP の芳点からは、怜蚌自䜓は簡単です。 蚈算アルゎリズムは原始的であり、高床な攻撃者に察しお脆匱である可胜性がありたす。 たずえば、Linux カヌネルは暗号化 SipHash を䜿甚したすが、XDP ぞのその実装は明らかにこの蚘事の範囲を超えおいたす。

倖郚通信に関連する新しい TODO のために導入されたした。

  • XDP プログラムは保存できたせん cookie_seed (゜ルトの秘密の郚分) をグロヌバル倉数に含めるには、カヌネル内にストレヌゞが必芁です。その倀は信頌できるゞェネレヌタヌから定期的に曎新されたす。

  • SYN Cookie が ACK パケット内で䞀臎する堎合、メッセヌゞを出力する必芁はありたせんが、怜蚌されたクラむアントからのパケットの受け枡しを続けるために、怜蚌されたクラむアントの IP を芚えおおいおください。

正芏のクラむアントの怜蚌:

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

ログには、チェックが成功したこずが瀺されおいたす (flags=0x2 - SYNです。 flags=0x10 ã¯ ACK):

Ether(proto=0x800)
  IP(src=0x20e6e11a dst=0x20e6e11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x2)
Ether(proto=0x800)
  IP(src=0xfe2cb11a dst=0xfe2cb11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x10)
      cookie matches for client 20200c0

怜蚌枈み IP のリストがないため、SYN​​ フラッド自䜓からの保護はありたせんが、次のコマンドによっお開始された ACK フラッドに察する反応は次のずおりです。

sudo ip netns exec xdp-test   hping3 --flood -A -s 1111 -p 2222 192.0.2.1

ログ゚ントリ:

Ether(proto=0x800)
  IP(src=0x15bd11a dst=0x15bd11e proto=6)
    TCP(sport=3236 dport=2222 flags=0x10)
      cookie mismatch

たずめ

eBPF 䞀般ず特に XDP は、開発プラットフォヌムずしおよりも高床な管理者ツヌルずしお提瀺されるこずがありたす。 実際、XDP はカヌネルによるパケットの凊理を劚害するツヌルであり、DPDK やその他のカヌネル バむパス オプションのようなカヌネル スタックの代替ツヌルではありたせん。 䞀方、XDP を䜿甚するず、非垞に耇雑なロゞックを実装でき、さらに、トラフィック凊理を䞭断するこずなく簡単に曎新できたす。 この怜蚌ツヌルは倧きな問題を匕き起こしたせんが、個人的には、ナヌザヌ空間コヌドの䞀郚に぀いおはこれを拒吊したせん。

XNUMX 番目の郚分では、トピックが興味深い堎合は、怜蚌されたクラむアントず切断の衚を䜜成し、カりンタヌを実装し、フィルタヌを管理するナヌザヌ空間ナヌティリティを䜜成したす。

リンク

出所 habr.com

コメントを远加したす