まず、どの時点でどのようなオブジェクトを確認したいかを問い、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_ACT | TC、__sk_buff | クラス分類、アクション、パケット修正 |
| KPROBE | kprobe/kretprobe、uprobe/uretprobe | 動的関数プローブ、アーキテクチャ依存レジスタコンテキスト |
| TRACEPOINT / RAW_TRACEPOINT | 定義済みイベントまたは生イベント引数 | イベント観測 |
| TRACING | fentry/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ではない) |
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の境界証明が必要である。
{"id":"xdp-variable-header-bounds","title":"固定された20バイトのチェックで、なぜポートを見つけられないのか?","summary":"IPv4ヘッダー長は4×IHLである。ポートの位置は常にEthernet+20として固定してはならない。ポートを読み取る前に、有効なIHLと対応するdata_endの境界証明が必要である。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"length\" data-i18n=\"length\"></label><output for=\"length\" id=\"length-value\"></output><input id=\"length\" type=\"range\" min=\"0\" max=\"100\" value=\"54\" step=\"1\"></div><div class=\"control\"><label for=\"ihl\" data-i18n=\"ihl\"></label><output for=\"ihl\" id=\"ihl-value\"></output><input id=\"ihl\" type=\"range\" min=\"4\" max=\"15\" value=\"5\" step=\"1\"></div><p class=\"caption\" data-i18n=\"layout\"></p><div class=\"header-map\"><div class=\"header-row\"><span data-i18n=\"eth\"></span><div><i id=\"range-eth\"></i><b class=\"end\"></b></div></div><div class=\"header-row\"><span data-i18n=\"ip\"></span><div><i id=\"range-ip\"></i><b class=\"end\"></b></div></div><div class=\"header-row\"><span data-i18n=\"ports\"></span><div><i id=\"range-ports\"></i><b class=\"end\"></b></div></div></div><div class=\"axis\"><span>0</span><span>50</span><span>100 B</span></div><div class=\"formula\" id=\"proof\"></div><div class=\"card\"><header data-i18n=\"result\"></header><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"shortEth\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"shortIP\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"badIHL\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"shortPorts\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"safe\"></p></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.header-row{margin:14px 0}.header-row span{font:12px var(--viz-font-mono)}.header-row>div{height:28px;position:relative;background:var(--surface);margin-top:7px}.header-row i{display:block;position:absolute;top:5px;bottom:5px;background:var(--accent);transition:left .2s,width .2s}.header-row b{position:absolute;top:-4px;bottom:-4px;width:2px;background:var(--second);transition:left .2s}.formula{min-height:6em;white-space:pre-line}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}function draw(){const len=+$('#length').value,ihl=+$('#ihl').value,port=14+ihl*4,end=port+4;set('length-value',len+' B');set('ihl-value',ihl+' → '+ihl*4+' B');for(const[k,start,size]of [['eth',0,14],['ip',14,ihl*4],['ports',port,4]]){$('#range-'+k).style.left=start+'%';$('#range-'+k).style.width=size+'%';}document.querySelectorAll('.end').forEach(e=>e.style.left=len+'%');set('proof','14 ≤ '+len+'; 34 ≤ '+len+'; IHL ≥ 5\\nports end = 14 + 4 × '+ihl+' + 4 = '+end+'\\n'+end+' ≤ '+len+' ?');set('explanation',t(len<14?'shortEth':len<34?'shortIP':ihl<5?'badIHL':len<end?'shortPorts':'safe'));}$('#length').oninput=$('#ihl').oninput=draw;draw();","audio":false,"strings":{"badIHL":"IHLが5未満であるため、有効なIPv4ヘッダー長ではない。","eth":"Ethernet [0,14)","heading":"固定された20バイトのチェックで、なぜポートを見つけられないのか?","ihl":"IPv4 IHL(32ビットワード数)","intro":"イーサネットフレームのアクセス可能長とIPv4 IHLを変更する。実際のデータ末尾、最小ヘッダーチェック、およびIHLで計算されたポート終了位置を比較する。","ip":"IPv4 [14,14+4×IHL)","layout":"読み取る必要がある範囲;金線はdata_end","length":"アクセス可能なデータ長 / B","ports":"Ports [14+4×IHL,18+4×IHL)","reset":"リセット","result":"現在証明できること","safe":"これらの前提条件下では、4つのポートバイトの境界が証明されている;これは完全なパケットの有効性の証明ではない。","scope":"Ethernet IIにVLANなし、EtherType=IPv4、protocol=TCP、非断片化、かつ他のプロトコルフィールドが有効であると仮定する。ソース/宛先ポートの4バイトを読み取るために必要な境界のみを証明し、完全なTCPヘッダー、IPv4 tot_len、チェックサム、または完全なパケットの有効性は証明しない。IHL=4 意図的に不正な入力として扱う。","scopeLabel":"モデル範囲と規約","shortEth":"イーサネットヘッダーが不完全であるため、そのフィールドを読み取れない。","shortIP":"最小IPv4ヘッダーが不完全であるため、完全なヘッダーとして解析を続行できない。","shortPorts":"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は、そのパケットを生成したビジネスプロセスであるとは限らない。ヘルパー呼び出しが合法であっても、観測セマンティクスが期待通りに機能しない場合がある。オブジェクトの帰属を明確にしてから統計キーを決定し、一見正確だが帰属エラーのあるチャートを作成しないようにする。