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
810
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
560
Other Decks in Programming
See All in Programming
Kaigi on Rails 2024 〜運営の裏側〜
krpk1900
1
210
役立つログに取り組もう
irof
28
9.6k
RubyLSPのマルチバイト文字対応
notfounds
0
120
Outline View in SwiftUI
1024jp
1
330
ヤプリ新卒SREの オンボーディング
masaki12
0
130
Compose 1.7のTextFieldはPOBox Plusで日本語変換できない
tomoya0x00
0
190
LLM生成文章の精度評価自動化とプロンプトチューニングの効率化について
layerx
PRO
2
190
【Kaigi on Rails 2024】YOUTRUST スポンサーLT
krpk1900
1
330
タクシーアプリ『GO』のリアルタイムデータ分析基盤における機械学習サービスの活用
mot_techtalk
4
1.4k
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.7k
『ドメイン駆動設計をはじめよう』のモデリングアプローチ
masuda220
PRO
8
540
[Do iOS '24] Ship your app on a Friday...and enjoy your weekend!
polpielladev
0
100
Featured
See All Featured
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
31
2.7k
Facilitating Awesome Meetings
lara
50
6.1k
jQuery: Nuts, Bolts and Bling
dougneiner
61
7.5k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
Designing for Performance
lara
604
68k
Writing Fast Ruby
sferik
627
61k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
16
2.1k
Thoughts on Productivity
jonyablonski
67
4.3k
Happy Clients
brianwarren
98
6.7k
Automating Front-end Workflow
addyosmani
1366
200k
Git: the NoSQL Database
bkeepers
PRO
427
64k
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