Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
eBPFを使ったオレオレカーネル拡張入門
Search
Keisuke Nishimura
July 22, 2023
Programming
1
820
eBPFを使ったオレオレカーネル拡張入門
Kernel/VM探検隊@東京 No16
Keisuke Nishimura
July 22, 2023
Tweet
Share
More Decks by Keisuke Nishimura
See All by Keisuke Nishimura
Xenのスケジューラがぜんぜんわからん
mu_mu_mu
0
1.1k
C言語プログラムの構造とほんの少し解釈
mu_mu_mu
3
1.5k
Intel MPK入門
mu_mu_mu
0
570
Other Decks in Programming
See All in Programming
採用事例の少ないSvelteを選んだ理由と それを正解にするためにやっていること
oekazuma
2
1k
Keeping it Ruby: Why Your Product Needs a Ruby SDK - RubyWorld 2024
envek
0
190
今年のアップデートで振り返るCDKセキュリティのシフトレフト/2024-cdk-security-shift-left
tomoki10
0
200
fs2-io を試してたらバグを見つけて直した話
chencmd
0
230
선언형 UI에서의 상태관리
l2hyunwoo
0
160
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
330
KMP와 kotlinx.rpc로 서버와 클라이언트 동기화
kwakeuijin
0
140
コンテナをたくさん詰め込んだシステムとランタイムの変化
makihiro
1
130
HTTP compression in PHP and Symfony apps
dunglas
2
1.7k
php-conference-japan-2024
tasuku43
0
280
103 Early Hints
sugi_0000
1
230
開発者とQAの越境で自動テストが増える開発プロセスを実現する
92thunder
1
180
Featured
See All Featured
How GitHub (no longer) Works
holman
311
140k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
665
120k
The Power of CSS Pseudo Elements
geoffreycrofte
73
5.4k
A better future with KSS
kneath
238
17k
GraphQLとの向き合い方2022年版
quramy
44
13k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
28
900
Typedesign – Prime Four
hannesfritz
40
2.4k
Gamification - CAS2011
davidbonilla
80
5.1k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
44
9.3k
Transcript
オレオレ カーネル拡張 入門 Kernel/VM 探検隊 Tokyo #16 mumumu / 西村
啓佑 (Keisuke Nishimura) ← Twitter: @mumumu_vm eBPFを使った 2023/07/22
発表内容 - eBPF基礎 - eBPFを使ったカーネルの拡張 - オレオレ カーネル 拡張 2
eBPF 基礎:概要 - カーネル内で動作する VM (In-Kernel VM) - 実行前に検証 =
安全 - 基本的に 特定のイベントをフックして実行 - BPF_PROG_TYPE_XDP - BPF_PROG_TYPE_SK_SKB - BPF_PROG_TYPE_KPROBE - … 3 それぞれを Program Type と呼称
eBPF 基礎:ユースケース例 - モニタリング: カーネル内データを柔軟に参照可能 - 高速パケット処理:コンテキストスイッチ無しで - システムコールのフィルタ:安全かつ効率的な 4
eBPF 基礎:ユースケース例 - モニタリング: カーネル内データを柔軟に参照可能 - 高速パケット処理:コンテキストスイッチ無しで - システムコールのフィルタ:安全かつ効率的な 5
これだけじゃない!
いま、eBPFを使ったカーネルの拡張が熱い! MemcachedのキャッシュをeBPFでカーネルに移植 [NSDI 2021] 6
いま、eBPFを使ったカーネルの拡張が熱い! eBPFでスケジューラのカスタマイズ [SOSP 2021] 7
いま、eBPFを使ったカーネルの拡張が熱い! eBPFを用いたストレージ処理高速化 (e.g. 高速なファイルトラバースとか) [OSDI 2022] 8
いま、eBPFを使ったカーネルの拡張が熱い! eBFPでロック機構をApp. 毎にカスタマイズ [OSDI 2022] 9
10 オレオレ カーネル 拡張 作りたくない? eBPFを使って
eBPFでカーネルを拡張できるおもちゃ作ってみた Program Type: BPF_PROG_OREORE プログラム仕様: - eBPF: u32 を受け取って計算した答えを返却 -
イベント: /sys/oreore/bpf への書き込み - 結果の出力: /sys/oreore/bpf の 読み込み - エラー処理やまともなVerificationは省略 11 ※Linux Kernel: 6.1.38 (最新のLTS)を使用
動作目標 12 sudo ./oreore_loader # bpfプログラムのロード
eBPF の機能追加レシピ 1. 新しい eBPF Program Type を定義 2. アタッチする機能を用意
3. プログラムを実行する関数定義 4. ユーザランドから呼び出し 13
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 14
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例:
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 15
BPF_PROG_TYPEいろんな使われ方をする(8回 def/undef)
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 16
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例:
#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
#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; を参照する.(詳しくは後述)
#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 変換
#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() で変換
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 21 bpf_verifier_ops 構造体の設定
(とりあえずこの2つを登録) さっきのマクロ第二引数
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 22 bpf_verifier_ops 構造体の設定
(とりあえずこの2つを登録) 引数addrから4 bytesのみ アクセス可能
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 23 bpf_prog_ops 構造体の設定
(test_run メンバを登録するとデバッグし易い)
#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 まとめ:
#2 アタッチする機能を用意 25 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する
bpf システムコールに当該機能を追加 今回の例: ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存
#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 非依存にプログラムを適当なコンテキストに保存
#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
#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; ...
#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; ...
#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 を引いて 処理内容を追加)
#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; ...
#2 アタッチする機能を用意 32 ゴール:新しいProgram Type に対応するフラグを設定 1. enum bpf_attach_type にattach
type を追加 2. bpf システムコールで使われる4つの関数に 1で追加したフラグの設定を追加 #2 まとめ: #1-2 の変更を反映済カーネルをコンパイル・実行
#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)が引数
#3 プログラムを実行する関数定義 34 ゴール:bpf_prog_run()の呼び出し sysfs の show/store の ops に登録
(storeの方) attach されているか等 確認・エラー処理必要
#3 プログラムを実行する関数定義 35 ゴール:bpf_prog_run()の呼び出し sysfs の show/store の ops に登録
(show の方)
#3 プログラムを実行する関数定義 36 1. kern_ctx_typeの引数を作成 2. bpf_prog_run()をコール #1-2 の変更を行ったカーネルで実行 #3
まとめ: ゴール:bpf_prog_run()の呼び出し
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 37 1. 入力(u32)に42を足すbpf 命令を錬成(気合い)
2. libbpfの bpf_prog_load() でロード 3. libbpfのbpf_prog_attach() でアタッチ 今回の例:
#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(), };
#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のインフラを使用
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 40 libbpf の bpf_prog_load()
でロード
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 41 bpf_prog_load() でロード オプションの指定が必要
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 42 bpf_prog_attach() でアタッチ 本来は,ターゲットの
fd を指定
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 43 1. 対象 bpf
プログラムを錬成 2. bpf_prog_{run|attach}()をコール #1-2 の変更を行ったカーネル上で実行 #4 まとめ:
動作例 44 sudo insmod oreore_mod.ko # #3 /sys/oreore sudo ./oreore_loader
# #4 load/attach
ここから先は... - エラーハンドリング(今回はほぼ無視) - libbpfからのサポートやBTF機構関連の追加 - ヘルパー関数の定義 - アタッチ動作の改善 etc...
45
まとめ - eBPF = In-Kernel VM - eBPF を使ったカーネルの拡張が熱い! -
意外と簡単にeBPFでカーネルを拡張可能! 46
47
Verifier の設定:メモリアクセス 自分の理解が正しければ... check_mem_access()がメモリ系の検証を担当 - is_valid_access() は ctx からのオフセットにへの制約 -
check_mem_region_access() - その他... 48
bpf_verifier_ops
bpf_prog_ops