Linuxにおける利用が急速に増えている「Berkeley Packet Filter(BPF)」について、基礎から応用まで幅広く紹介する連載。今回は、Linuxで用いられるBPFのアーキテクチャなどを説明する。
Linuxにおける利用が急速に増えている「Berkeley Packet Filter(BPF)」について、基礎から応用まで幅広く紹介する連載「Berkeley Packet Filter(BPF)入門」。初回は、BPFの歴史や概要について解説しました。今回はBPFの基礎として、Linuxで用いられるBPFのアーキテクチャなどを説明します。
現在Linuxで主として用いられている「eBPF(extended BPF)」はオリジナルの「cBPF(classic BPF)」とは異なり、互換性がありません。しかし、LinuxにeBPFが導入された後も、LinuxからcBPFが完全になくなったわけではありません。cBPFを利用したパケットフィルタリングも可能ですし(もちろんeBPFでもできます)、システムコールフィルター(seccomp)は依然としてeBPFではなくcBPFを入力として受け取ります。
ただし、内部的にはcBPFのプログラムはeBPFのプログラムに変換されて実行されています。
以下では、cBPFとeBPF双方のアーキテクチャについて簡単にまとめます。これは今後の連載のレファレンスを意識したものであり、BPFの利用に当たって覚えておく必要はありません。必要に応じて適宜参照してください。また正式なドキュメンテーションは「Linux Socket Filtering aka Berkeley Packet Filter(BPF)」にあります。
cBPFの命令セットは64bit固定長です。フォーマットをC言語で表現すると、下記のようになります。
- struct cbpf_insn {
- u16 code; /* オペコード */
- u8 jt; /* 条件が真のときの分岐先 */
- u8 jf; /* 条件が偽のときの分岐先 */
- u32 k; /* 汎用フィールド */
- };
ジャンプ先のオフセット(jt、jf)はunsignedなので、cBPFでは負の方向へのジャンプができません。つまり、ループ処理はできません。この仕様とプログラムサイズを制限することで、一定時間内でのプログラム実行終了が保証されます。
仮想マシンは下記のレジスタとメモリ領域を持ちます。レジスタは32bit幅です。
レジスタ名 | 説明 |
---|---|
A | アキュムレータレジスタ |
X | インデックスレジスタ |
M[] | メモリ領域、32bit幅、サイズ16 |
cBPFには下記のような命令があります。
パケットデータに対するストア命令はありません。
eBPFの命令セットは64bit固定長です。フォーマットをC言語で表現すると、下記のようになります。
- struct ebpf_insn {
- u8 code; /* オペコード */
- u8 dst_reg:4; /* ディスティネーションレジスタ */
- u8 src_reg:4; /* ソースレジスタ */
- s16 off; /* オフセット */
- s32 imm; /* 即値 */
- };
オフセットや即値はsigned型になっています。従って、cBPFとは異なり、負方向へのジャンプが可能です。プログラムの一定時間以内の動作終了は検証器が検証します。また、「dst_reg」「src_reg」フィールドで使用するレジスタが指定できるようになっています。
一方で、cBPFにあった「jt」「jf」フィールドはeBPFには存在しません。eBPFのジャンプ命令は、指定した番地へのジャンプあるいはフォールスルーのどちらかになります。
仮想マシンは下記のレジスタを持ちます。レジスタは64bit幅です。
レジスタ名 | 説明 |
---|---|
R0 | 汎用レジスタ(戻り値を格納) |
R1~R5 | 汎用レジスタ(引数レジスタ) |
R6~R9 | 汎用レジスタ |
R10 | フレームポインタ(読み出し専用) |
x86_64やAArch64などのアーキテクチャにおいて、eBPFのレジスタは実際のCPUのレジスタに1対1で対応付けられるようになっています。eBPFの呼び出し規約は64bitカーネルで利用されるものと直接対応します。
cBPFとeBPFには、下記のような違いがあります。
cBPF | eBPF | |
---|---|---|
レジスタ数 | 2 | 10 |
レジスタ幅(bit) | 32 | 64 |
スタックサイズ(Byte) | 16 | 512 |
スタックアクセスサイズ(Byte) | 4 | 1、2、4、8 |
パケットアクセスサイズ(Byte) | 1、2、4 | 1、2、4、8 |
外部関数呼び出し | × | ◯ |
負方向への分岐 | × | ◯ |
アトミック加算命令 | × | ◯ |
cBPFに存在したメモリ領域の代わりに、eBPFではR10のフレームポインタを利用してプログラム用のスタック(512B)にアクセスできます。
eBPFは一部を除きcBPFと同等の命令をサポートする他、以下のような命令が追加されています。
またcBPFでは多くの命令がAレジスタに対するものであったのに対し、eBPFでは「src_reg」「dst_reg」フィールドにより使用するレジスタを柔軟に選択できるようになっています。
eBPFでは事前に登録済みのカーネル内関数をBPFプログラムから呼び出すことが可能です。このとき、呼び出し元はR1~R5に適切に引数を設定して関数を呼び出します。戻り値はR0に格納されます。
必要であればR0-R5の値は呼び出し前に退避する必要があります。R6~R9のレジスタは、関数側が退避する規約になっているため、呼び出し元が退避する必要はありません。引数が6以上の関数呼び出しはサポートされていません。
eBPFは現在ネットワーク用途のみならず、さまざまな用途で利用されています。利用場面に応じて、BPFプログラムが可能な操作(検証器が検証する内容)や呼び出し可能な外部関数は異なります。
カーネルではBPFの種類を「BPF_PROG_TYPE」で識別しています。カーネル4.18時点で以下のタイプが存在します。
- enum bpf_prog_type {
- BPF_PROG_TYPE_UNSPEC,
- BPF_PROG_TYPE_SOCKET_FILTER,
- BPF_PROG_TYPE_KPROBE,
- BPF_PROG_TYPE_SCHED_CLS,
- BPF_PROG_TYPE_SCHED_ACT,
- BPF_PROG_TYPE_TRACEPOINT,
- BPF_PROG_TYPE_XDP,
- BPF_PROG_TYPE_PERF_EVENT,
- BPF_PROG_TYPE_CGROUP_SKB,
- BPF_PROG_TYPE_CGROUP_SOCK,
- BPF_PROG_TYPE_LWT_IN,
- BPF_PROG_TYPE_LWT_OUT,
- BPF_PROG_TYPE_LWT_XMIT,
- BPF_PROG_TYPE_SOCK_OPS,
- BPF_PROG_TYPE_SK_SKB,
- BPF_PROG_TYPE_CGROUP_DEVICE,
- BPF_PROG_TYPE_SK_MSG,
- BPF_PROG_TYPE_RAW_TRACEPOINT,
- BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
- BPF_PROG_TYPE_LWT_SEG6LOCAL,
- BPF_PROG_TYPE_LIRC_MODE2,
- }
eBPFでは「eBPF map」と呼ばれるデータ構造が利用可能です。eBPF mapは、BPFプログラムからは外部関数呼び出し機能を利用して、ユーザーアプリケーションからはシステムコールを利用して、それぞれアクセス可能です。
eBPF mapを利用することで、eBPFプログラムの状態を管理できます。eBPF mapの具体的な説明は、後の連載で、実際に利用する場面で行います。
cBPFでは引数は非明示的な形で与えられ、ロード命令を利用してその中身をレジスタに読み込むことが可能です。
一方eBPFでは、最初にeBPFプログラムが実行されるときR1レジスタに引数が渡されます。これをeBPFでは「コンテキスト」と呼んでいます。非明示的な引数は存在しません。また複数のコンテキストがBPFプログラムに渡されることはありません。
コンテキストはBPF_PROG_TYPEごとに異なります。例えば、ネットワーキング関連の場合コンテキストにはカーネル内のパケットのデータ構造である「struct sk_buff」が渡されます。
「Tail Call」は他のBPFプログラムへの遷移を行う機能です。遷移後に遷移元に戻ることはありません。遷移先のBPFプログラムとはスタックフレームを共有します。
一定時間での終了を保証するために最大のTail Callの回数は32に制限されています。
Linux 4.16およびLLVM 6.0から、他のBPF関数をBPFプログラムから呼び出せるようになりました。呼び出し方法は外部関数呼び出しに準拠します。それ以前では、BPFプログラムから他のBPF関数を呼び出したい場合は、全てインラインで展開する必要がありました。
BPF関数を呼び出す場合、最大の呼び出しネスト回数は8に制限されています。引数として呼び出し元のframe pointerを渡すことは可能ですが、逆は不可能です。
この機能はTail Callと合わせて利用することはできません。
Copyright © ITmedia, Inc. All Rights Reserved.
Linux �ス�ス�ス�ス�ス�ス�ス�ス OSS 鬯ョ�ォ�ス�ェ髯区サゑスソ�ス�ス�ス�ス�コ髣包スオ隴∵コキ�ク�キ�ス�ケ隴趣ス「�ス�ス�ス�ウ鬩幢ス「�ス�ァ�ス�ス�ス�ュ鬩幢ス「隴趣ス「�ス�ス�ス�ウ鬩幢ス「�ス�ァ�ス�ス�ス�ー