Slide 1

Slide 1 text

オレオレ カーネル拡張 入門 Kernel/VM 探検隊 Tokyo #16 mumumu / 西村 啓佑 (Keisuke Nishimura) ← Twitter: @mumumu_vm eBPFを使った 2023/07/22

Slide 2

Slide 2 text

発表内容 - eBPF基礎 - eBPFを使ったカーネルの拡張 - オレオレ カーネル 拡張 2

Slide 3

Slide 3 text

eBPF 基礎:概要 - カーネル内で動作する VM (In-Kernel VM) - 実行前に検証 = 安全 - 基本的に 特定のイベントをフックして実行 - BPF_PROG_TYPE_XDP - BPF_PROG_TYPE_SK_SKB - BPF_PROG_TYPE_KPROBE - … 3 それぞれを Program Type と呼称

Slide 4

Slide 4 text

eBPF 基礎:ユースケース例 - モニタリング: カーネル内データを柔軟に参照可能 - 高速パケット処理:コンテキストスイッチ無しで - システムコールのフィルタ:安全かつ効率的な 4

Slide 5

Slide 5 text

eBPF 基礎:ユースケース例 - モニタリング: カーネル内データを柔軟に参照可能 - 高速パケット処理:コンテキストスイッチ無しで - システムコールのフィルタ:安全かつ効率的な 5 これだけじゃない!

Slide 6

Slide 6 text

いま、eBPFを使ったカーネルの拡張が熱い! MemcachedのキャッシュをeBPFでカーネルに移植 [NSDI 2021] 6

Slide 7

Slide 7 text

いま、eBPFを使ったカーネルの拡張が熱い! eBPFでスケジューラのカスタマイズ [SOSP 2021] 7

Slide 8

Slide 8 text

いま、eBPFを使ったカーネルの拡張が熱い! eBPFを用いたストレージ処理高速化 (e.g. 高速なファイルトラバースとか) [OSDI 2022] 8

Slide 9

Slide 9 text

いま、eBPFを使ったカーネルの拡張が熱い! eBFPでロック機構をApp. 毎にカスタマイズ [OSDI 2022] 9

Slide 10

Slide 10 text

10 オレオレ カーネル 拡張 作りたくない? eBPFを使って

Slide 11

Slide 11 text

eBPFでカーネルを拡張できるおもちゃ作ってみた Program Type: BPF_PROG_OREORE プログラム仕様: - eBPF: u32 を受け取って計算した答えを返却 - イベント: /sys/oreore/bpf への書き込み - 結果の出力: /sys/oreore/bpf の 読み込み - エラー処理やまともなVerificationは省略 11 ※Linux Kernel: 6.1.38 (最新のLTS)を使用

Slide 12

Slide 12 text

動作目標 12 sudo ./oreore_loader # bpfプログラムのロード

Slide 13

Slide 13 text

eBPF の機能追加レシピ 1. 新しい eBPF Program Type を定義 2. アタッチする機能を用意 3. プログラムを実行する関数定義 4. ユーザランドから呼び出し 13

Slide 14

Slide 14 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 14 BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例:

Slide 15

Slide 15 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 15 BPF_PROG_TYPEいろんな使われ方をする(8回 def/undef)

Slide 16

Slide 16 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 16 BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例:

Slide 17

Slide 17 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 17 BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: 追加したいProgram Type @/include/uapi/linux/bpf.h enum bpf_prog_type

Slide 18

Slide 18 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 18 BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: 予め作った次の2つの構造体: struct bpf_verifier_ops <このarg> ## _verifier_ops; struct bpf_prog_ops <このarg> ## _prog_ops; を参照する.(詳しくは後述)

Slide 19

Slide 19 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 19 BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: prog_ctx_type kern_ctx_type = eBPFからみた = カーネルからみた eBPFプログラムの引数の型 kern_ctx_type prog_ctx_type 変換

Slide 20

Slide 20 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 20 BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: prog_ctx_type kern_ctx_type = eBPFからみた = カーネルからみた eBPFプログラムの引数の型 kern_ctx_type prog_ctx_type 変換 今回は自動の変換で事足りるが, 複雑な場合 bpf_verifier_opsの convert_ctx_access() で変換

Slide 21

Slide 21 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 21 bpf_verifier_ops 構造体の設定 (とりあえずこの2つを登録) さっきのマクロ第二引数

Slide 22

Slide 22 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 22 bpf_verifier_ops 構造体の設定 (とりあえずこの2つを登録) 引数addrから4 bytesのみ アクセス可能

Slide 23

Slide 23 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 23 bpf_prog_ops 構造体の設定 (test_run メンバを登録するとデバッグし易い)

Slide 24

Slide 24 text

#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 24 1. enum bpf_prog_type にProg Type 追加 2. bpf_{prog|verifier}_ops 構造体 を定義 3. 適切な引数型で BPF_PROG_TYPE マクロ #1 まとめ:

Slide 25

Slide 25 text

#2 アタッチする機能を用意 25 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する bpf システムコールに当該機能を追加 今回の例: ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存

Slide 26

Slide 26 text

#2 アタッチする機能を用意 26 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する bpf システムコールに当該機能を追加 今回の例: enum bpf_attach_type (@/include/uapi/linux/bpf.h) に BPF_OREORE_ATTACH を追加 ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存

Slide 27

Slide 27 text

#2 アタッチする機能を用意 27 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する bpf システムコールに当該機能を追加 今回の例: /kernel/bpf/syscall.c の以下の関数に追加 - bpf_prog_load_check_attach - attach_type_to_prog_type - bpf_prog_at(de)tach

Slide 28

Slide 28 text

#2 アタッチする機能を用意 28 ゴール:新しいProgram Type に対応するフラグを設定 bpf_prog_load_check_attach(...) ... + case BPF_PROG_TYPE_OREORE: + if (expected_attach_type == BPF_OREORE_ATTACH) + return 0; + return -EINVAL; ... attach_type_to_prog_type(...) ... + case BPF_OREORE_ATTACH: + return BPF_PROG_TYPE_OREORE; ...

Slide 29

Slide 29 text

#2 アタッチする機能を用意 29 ゴール:新しいProgram Type に対応するフラグを設定 +struct bpf_prog __rcu *oreore_prog; +EXPORT_SYMBOL_GPL(oreore_prog); +int oreore_bpf_prog_attach(struct bpf_prog *prog) +{ + rcu_assign_pointer(oreore_prog, prog); + return 0; +} ... bpf_prog_attach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_attach(prog); + break; ...

Slide 30

Slide 30 text

#2 アタッチする機能を用意 30 ゴール:新しいProgram Type に対応するフラグを設定 +struct bpf_prog __rcu *oreore_prog; +EXPORT_SYMBOL_GPL(oreore_prog); +int oreore_bpf_prog_attach(struct bpf_prog *prog) +{ + rcu_assign_pointer(oreore_prog, prog); + return 0; +} ... bpf_prog_attach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_attach(prog); + break; ... この変数をLKM から参照し, progを実行する予定 (本来はfdからfilp を引いて 処理内容を追加)

Slide 31

Slide 31 text

#2 アタッチする機能を用意 31 ゴール:新しいProgram Type に対応するフラグを設定 +int oreore_bpf_prog_detach() +{ + rcu_assign_pointer(oreore_prog, NULL); + return 0; +} ... bpf_prog_detach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_detach(); + break; ...

Slide 32

Slide 32 text

#2 アタッチする機能を用意 32 ゴール:新しいProgram Type に対応するフラグを設定 1. enum bpf_attach_type にattach type を追加 2. bpf システムコールで使われる4つの関数に 1で追加したフラグの設定を追加 #2 まとめ: #1-2 の変更を反映済カーネルをコンパイル・実行

Slide 33

Slide 33 text

#3 プログラムを実行する関数定義 33 ゴール: bpf_prog_run()の呼び出し (#1-2で作ったカーネル上で実行) LKMを書いて /sys/oreore/bpf への write から bpf_prog_run() を呼び出し 今回の例: u32 bpf_prog_run(const struct bpf_prog *prog, const void *ctx); ● bpf_prog を実行し,その返り値を返却 ● その際ctx(#1で定義したkern_ctx_type)が引数

Slide 34

Slide 34 text

#3 プログラムを実行する関数定義 34 ゴール:bpf_prog_run()の呼び出し sysfs の show/store の ops に登録 (storeの方) attach されているか等 確認・エラー処理必要

Slide 35

Slide 35 text

#3 プログラムを実行する関数定義 35 ゴール:bpf_prog_run()の呼び出し sysfs の show/store の ops に登録 (show の方)

Slide 36

Slide 36 text

#3 プログラムを実行する関数定義 36 1. kern_ctx_typeの引数を作成 2. bpf_prog_run()をコール #1-2 の変更を行ったカーネルで実行 #3 まとめ: ゴール:bpf_prog_run()の呼び出し

Slide 37

Slide 37 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 37 1. 入力(u32)に42を足すbpf 命令を錬成(気合い) 2. libbpfの bpf_prog_load() でロード 3. libbpfのbpf_prog_attach() でアタッチ 今回の例:

Slide 38

Slide 38 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 38 入力(u32)に42を足すbpf 命令を錬成(気合い) static struct bpf_insn insns[] = { // R0 = *(r1+0) BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // R0 += 42 BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 42), // Exit BPF_EXIT_INSN(), };

Slide 39

Slide 39 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 39 入力(u32)に42を足すbpf 命令を錬成(気合い) static struct bpf_insn insns[] = { // R0 = *(r1+0) BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // R0 += 42 BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 42), // Exit BPF_EXIT_INSN(), }; 本来はtools/bpf/以下を編集し llvm でバイトコード生成 + libbpfのインフラを使用

Slide 40

Slide 40 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 40 libbpf の bpf_prog_load() でロード

Slide 41

Slide 41 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 41 bpf_prog_load() でロード オプションの指定が必要

Slide 42

Slide 42 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 42 bpf_prog_attach() でアタッチ 本来は,ターゲットの fd を指定

Slide 43

Slide 43 text

#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 43 1. 対象 bpf プログラムを錬成 2. bpf_prog_{run|attach}()をコール #1-2 の変更を行ったカーネル上で実行 #4 まとめ:

Slide 44

Slide 44 text

動作例 44 sudo insmod oreore_mod.ko # #3 /sys/oreore sudo ./oreore_loader # #4 load/attach

Slide 45

Slide 45 text

ここから先は... - エラーハンドリング(今回はほぼ無視) - libbpfからのサポートやBTF機構関連の追加 - ヘルパー関数の定義 - アタッチ動作の改善 etc... 45

Slide 46

Slide 46 text

まとめ - eBPF = In-Kernel VM - eBPF を使ったカーネルの拡張が熱い! - 意外と簡単にeBPFでカーネルを拡張可能! 46

Slide 47

Slide 47 text

47

Slide 48

Slide 48 text

Verifier の設定:メモリアクセス 自分の理解が正しければ... check_mem_access()がメモリ系の検証を担当 - is_valid_access() は ctx からのオフセットにへの制約 - check_mem_region_access() - その他... 48

Slide 49

Slide 49 text

bpf_verifier_ops

Slide 50

Slide 50 text

bpf_prog_ops