eBPF プログラムタイプ

このページの目次

まず、どの時点でどのようなオブジェクトを確認したいかを問い、BPFタイプを選択する。コンテキスト、ヘルパー/kfunc、戻り値にはそれぞれ契約が存在し、ELFセクション名を変更するだけではタイプ変換は完了しない。

以下は一般的なタイプのナビゲーション表であり、完全な対応関係はlibbpfプログラムタイプ表を参照のこと。

参照目標読書位置
タイプの全体像とXDP実験第1~2節
ネットワーク層と関数イベント追跡第3~4節
LSM、struct_opsおよびインターフェース制限第5~6節

1. 一般的なタイプのクイックリファレンス

タイプ/ファミリー実行位置とコンテキスト主な目的
SOCKET_FILTERソケット受信フィルタリング、__sk_buffソケットに渡すデータ長を決定
XDPサポートされるXDPデータパス、xdp_md早期パケット処理とリダイレクト
SCHED_CLS / SCHED_ACTTC、__sk_buffクラス分類、アクション、パケット修正
KPROBEkprobe/kretprobe、uprobe/uretprobe動的関数プローブ、アーキテクチャ依存レジスタコンテキスト
TRACEPOINT / RAW_TRACEPOINT定義済みイベントまたは生イベント引数イベント観測
TRACINGfentry/fexit、tp_btfなどBTF型付けされた関数/イベント観測
PERF_EVENTソフトウェア/ハードウェアパフォーマンスイベント周期またはイベント駆動サンプリング
CGROUP_SKB / SOCK_ADDR などcgroup関連ネットワーク操作ソケット/アドレス/トラフィックポリシー
SOCK_OPS / SK_MSG / SK_SKBソケットコールバックまたはメッセージパス接続処理、メッセージリダイレクトなど
SK_LOOKUPソケット検索サポートされる検索パス内でターゲットソケットを選択
LSMサポートされるLSMフック監査とアクセス制御拡張
STRUCT_OPS特定のカーネル操作テーブルサポートされるコールバックの実装(例:輻輳制御、スケジューラ拡張)
SYSCALLユーザー空間から対応するBPF実行インターフェース経由で呼び出し特定のBPF操作/ロードロジック(seccompではない)
戻り値の意味はアタッチされるインターフェースによって決まる XDP return XDP_PASS プロトコルスタックへ継続 TC direct-action return TC_ACT_OK TCアクションセマンティクスに従って処理 LSM MAC hook return 0 今回のチェックで拒否しない プログラムタイプ、アタッチタイプ、コンテキスト、および戻り値の契約は、一緒に選択しなければならない。

2. XDP:モードが「どの程度早期か」を決定する

native/driver XDPは、通常、ドライバ受信パスで、通常のskb構築の前に実行される。generic/SKBモードはより遅いskbパスを使用し、ハードウェアオフロードはデバイスのサポート状況に制限される。したがって、すべてのXDPを「NAPI内でskb割り当てなし、かつ関数呼び出しなし」として記述することはできない。

アクション後の意味
XDP_ABORTED / DROP異常終了 / 破棄の対応処理
XDP_PASS後のスタックパスへ継続
XDP_TX受信デバイスへ送信
XDP_REDIRECT準備されたターゲットへリダイレクト

REDIRECTは通常、ヘルパーなどを通じてデバイス、CPU、またはAF_XDPなどのターゲットを事前に設定する必要がある。定数を返すだけでは、ターゲットを自動生成したり、転送プロトコルを補完したりすることはできない。

最小境界チェックの例

以下は、現在直接アクセス可能なデータにイーサネットヘッダーが少なくとも1つ含まれていることをチェックするのみである。VLAN、IP、またはマルチバッファの完全なパケットは解析せず、デプロイ可能な完全なフィルタリングポリシーでもない。

#include <linux/bpf.h>
#include <linux/if_ether.h>

__attribute__((section("xdp"), used))
int check_ethernet_header(struct xdp_md *ctx)
{
    void *data = (void *)(long)ctx->data;
    void *end = (void *)(long)ctx->data_end;
    struct ethhdr *eth = data;

    if ((void *)(eth + 1) > end)
        return XDP_DROP;
    return XDP_PASS;
}

char bpf_license[] __attribute__((section("license"), used)) = "GPL";

header.bpf.c として保存し、Linux UAPIヘッダーとBPFバックエンドを持つClang環境でコンパイル可能である:

clang -O2 -g -target bpf -Wall -Wextra -Werror -c header.bpf.c -o header.bpf.o

生成されたELFはコンパイルプロセスのみを検証する。実際のロードにはverifierが必要であり、アタッチには権限とデバイスのサポートが必要である。一部のディストリビューションでは、対応するアーキテクチャのUAPI includeパスを追加する必要がある。packet headなどのヘルパーを調整するとデータポインタの有効性が変わる可能性があるため、後のアクセスではポインタを再取得し、境界を再証明する必要がある。

以下は、ソースと宛先のポートの4バイトのみを取得し、それらに必要な境界を個別に計算するものである。IHLが変更されると、以前の最小IPv4ヘッダーに対するチェックが、新しいポートの位置をカバーしない可能性がある。

図を準備しています
固定された20バイトのチェックで、なぜポートを見つけられないのか?

IPv4ヘッダー長は4×IHLである。ポートの位置は常にEthernet+20として固定してはならない。ポートを読み取る前に、有効なIHLと対応するdata_endの境界証明が必要である。

3. TCとcgroupは同一階層のネットワークフックではない

TCはskbパスに位置し、より豊富なネットワークコンテキストを利用できる。SCHED_CLSの戻り値の解釈はアタッチモードに依存し、direct-actionはTC_ACT_*セマンティクスを使用する。従来のclassifierの分類結果を同じ解釈に直接適用することはできない。アタッチメントの更新時には、ターゲットシステムが従来のTCを使用しているか、サポートされる新しいlink/TCXなどのインターフェースを使用しているかを考慮する必要がある。

cgroupネットワークプログラムは、cgroupとソケットの関連付けに基づいてチェックを提供する。例えば、作成、bind/connectアドレス、送信受信トラフィックなどである。ソケットの関連付けライフサイクルと継承ルールは、それがどのcgroupに属するか会影响する。プロセスを移動しても、既存のすべてのソケットが直感的に即時再帰属するわけではない。

CGROUP_SKBはcgroup v1のnet_clsコントローラにアタッチされるものではなく、「コンテナの全パケットが通過する唯一のフック」として一般化することもできない。実際のネットワーク製品では、TC、XDP、cgroup、およびソケット層プログラムを組み合わせることが多く、製品名だけでその全ポリシーが1つのタイプによって実現されると断言することはできない。

4. 関数とイベント追跡:安定性とオーバーヘッドは別々に見る

kprobeはアーキテクチャ固有のレジスタ状態を使用する。BPFのR0–R10仮想レジスタは、プローブ対象のC関数の元の引数レジスタではない。引数にアクセスするには、対応するtracingマクロ/コンテキストを使用すべきであり、「R0–R5が関数引数である」と記述してはならない。

kretprobeは戻りを観測するが、エントリ状態との関連付けが必要な場合がある。関数のインライン化、プローブ禁止位置、末尾呼び出し、およびターゲット実装の変化は、カバレッジに影響を与える可能性がある。uprobe/uretprobeはユーザー空間コードに対して同様のプローブを行い、その適否はトリガー頻度と測定されたオーバーヘッドに依存し、必ずしも生産環境で使用できないわけではない。

tracepointは明示的に名前付けられたイベントとフィールドを提供し、任意の内部関数に依存するよりも保守が容易であることが多い。ただし、trace eventフォーマットはすべてのバージョンで不変の安定したUAPIではない。実際のフィールドはtracefsのevents/.../formatで確認できる。生tracepoint、通常tracepoint、およびtp_btfのコンテキスト表現は異なる。

型付けされた追跡:BTFがあるだけでは不十分

fentry/fexitはBPF trampolineとターゲットBTFを利用し、通常、型に基づいて関数引数と戻り情報を読み取りやすいが、対応するカーネル、ターゲット関数、およびBTFのサポートが必要である。「BTFがある」ということは、すべての関数をアタッチできることを意味しない。追跡とデバッグを参照。

5. LSM、struct_ops、およびSYSCALL

LSMプログラムは具体的なフック引数に基づいてコンテキストを定義し、すべてのフックフィールドをカバーできる汎用のbpf_lsm_ctx構造体は存在しない。システム全体のMACとcgroup LSMのアタッチと戻り値セマンティクスは別々に参照すべきであり、0/1/-errnoの意味を混用してはならない。LSM BPFを参照。

struct_opsは、カーネルが明示的にサポートする操作テーブルとコールバックのみを実装し、任意のカーネル関数ポインタを任意に置き換えることは許可されない。コールバックがスリープを許可するかどうか、オブジェクトがどのように検証されるか、登録寿命を誰が管理するかは、ターゲットインターフェースによって規定される。

BPF_PROG_TYPE_SYSCALLという名前は誤解を招きやすい:これは各システムコールのエントリでユーザープロセスをフィルタリングするものではなく、ユーザー空間から対応する実行インターフェース経由で実行され、特定のhelper/kfunc能力を持つプログラムカテゴリである。ソースコードはkernel/bpf/syscall.cのbpf_syscall_prog_opsとtest-runパスから確認できる。システムコールの制限についてはseccompを参照すること。システムコールの観測にはtracepointなどを選択できる。

6. Helper/kfuncの利用可能性は名前だけを見て判断できない

同じヘルパーが許可されるかどうかは、プログラムタイプ、アタッチタイプ、コンテキスト、権限、およびカーネル機能に依存する。kfuncには、さらに強力な内部インターフェース制約がある場合もある。sleepable BPFも、サポートされるプログラムと呼び出しポイントでのみ合法である。

帰属の境界:現在のタスクが必ずしもパケットのビジネスオーナーではないとは限らない

ネットワークソフトIRQで取得されたcurrent PID/commは、そのパケットを生成したビジネスプロセスであるとは限らない。ヘルパー呼び出しが合法であっても、観測セマンティクスが期待通りに機能しない場合がある。オブジェクトの帰属を明確にしてから統計キーを決定し、一見正確だが帰属エラーのあるチャートを作成しないようにする。