BPF
Section: Linux Programmer's Manual (2)
Updated: 2020-06-09
Index
Return to Main Contents
名前
bpf - 拡張 (extended) BPF マップまたはプログラム上で、コマンドを実行する
書式
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
説明
bpf()
システムコールは、extended Berkeley Packet Filters に関連する
さまざまな操作を実行する。
extended BPF (または eBPF) は、
元々の ("classic") BPF (cBPF) と同じく、
ネットワークパケットをフィルタするために使われる。
cBPF と eBPF のプログラムの両方に対して、
カーネルはプログラムをロードする前に統計的に解析し、
プログラムが実行中のシステムを害することできないように保証する。
eBPF は cBPF をいろいろな方法で拡張しており、
(eBPF で提供される
BPF_CALL
オペコード拡張を使って)
カーネル内ヘルパー関数のセットを呼び出したり、
eBPF マップのような共有データ構造体にアクセスできたりする。
extended BPF のデザイン/アーキテクチャ
eBPF マップは、さまざまなデータ型を格納するための、基本的なデータ構造体である。
データ型は、一般的にバイナリ blob として扱われるので、
ユーザはマップの作成時にキーのサイズと値のサイズを指定するだけでよい。
言い換えれば、指定されたマップのキー/値には、任意の構造体を含めることが
できる。
ユーザプロセスは (データのバイト数が不定なキー/値のペアを持つ) 複数のマップを作成し、
ファイルディスクリプターを使ってマップにアクセスできる。
異なる eBPF プログラムが、同じマップに同時並行にアクセスできる。
マップの中に何を格納するかは、ユーザプロセスと eBPF プログラムが決定できる。
プログラム配列と呼ばれる特別なマップ型がある。
この型のマップは、他の eBPF プログラムを参照するファイルディスクリプターを格納する。
マップの参照が行われると、プログラムフローが
その場で他の eBPF プログラムの最初にリダイレクトされ、
呼び出したプログラムには戻らない。
入れ子のレベルは 32 に制限されているので、
無限ループは作れない。
マップに格納されたファイルディスクリプターは、実行時に変更できるので、
プログラムの機能は、特定の要件に基づいて変更できる。
プログラム配列マップで参照されるすべてのプログラムは、
bpf()
で前もってカーネルにロードされていなればならない。
マップの参照が失敗した場合、現在のプログラムが実行を継続する。
より詳細は、下記の
BPF_MAP_TYPE_PROG_ARRAY
を参照すること。
一般的には、eBPF プログラムはユーザプロセスによってロードされ、
プロセスが終了すると自動的にアンロードされる。
いくつかの場合、例えば
tc-bpf(8)
では、プログラムをロードしたプロセスが終了した後でも、
プログラムをカーネル内部に生かし続けることができる。
この場合、ファイルディスクリプターがユーザ空間プログラムによって
クローズされた後も、tc サブシステムが eBPF プログラムの参照を持ちづづける。
よって、特定のプログラムをカーネル内部で生かし続けるか否かは、
bpf()
でロードされた後に、指定されたカーネルサブシステムにどのように
アタッチされるかに依存する。
各 eBPF プログラムは、命令 (instruction) の集合であり、
その命令は完了まで安全に実行することができる。
カーネル内の検証器 (verifier) は、eBPF プログラムが終了するかと、
安全に実行できるかを、統計的に決定する。
検証の過程で、カーネルは eBPF プログラムが使う各マップの参照カウントを増やすので、
アタッチされたマップは、プログラムがアンロードされるまで削除されない。
eBPF プログラムは、さまざまなイベントにアタッチすることができる。
これらのイベントには、ネットワークパケットの到達、追跡 (tracing) イベント、
(tc(8)
classifier にアタッチされた eBPF プログラムについての)
ネットワーク待ちの規則 (queueing discipline) による分類イベント、
将来追加される可能性のある他のタイプのイベントがある。
新しいイベントが起きると、eBPF マップにそのイベントについての情報を格納している、
eBPF マップが実行される。
eBPF プログラムは、データを格納するだけでなく、
固定のカーネルヘルパー関数のセットを呼び出すことができる。
eBPF プログラムを複数のイベントにアタッチでき、
複数の eBPF プログラムから同じマップにアクセスできる。
tracing tracing tracing packet packet packet
event A event B event C on eth0 on eth1 on eth2
| | | | | ^
| | | | v |
--> tracing <-- tracing socket tc ingress tc egress
prog_1 prog_2 prog_3 classifier action
| | | | prog_4 prog_5
|--- -----| |------| map_3 | |
map_1 map_2 --| map_4 |--
引き数
bpf()
システムコールで実行される操作は、
cmd
引き数で決定される。
各操作は、
attr
で与えられる付随する引き数をとる。
attr
は
bpf_attr
型の共用体 (union) へのポインタである (下記参照)。
size
引き数は、
attr
で指される共用体のサイズである。
cmd
に指定可能な値は、以下のいずれかである:
- BPF_MAP_CREATE
-
マップを作成し、マップを参照するファイルディスクリプターを返す。
新しいファイルディスクリプターについては、
close-on-exec ファイルディスクリプターフラグ
(fcntl(2)
を参照) が自動的に有効にされる。
- BPF_MAP_LOOKUP_ELEM
-
指定されたマップでキーで要素を探し、その値を返す。
- BPF_MAP_UPDATE_ELEM
-
指定されたマップで要素 (キー/値のペア) を作成または更新する。
- BPF_MAP_DELETE_ELEM
-
指定されたマップでキーで要素を探し、削除する。
- BPF_MAP_GET_NEXT_KEY
-
指定されたマップでキーで要素を探し、次の要素のキーを返す。
- BPF_PROG_LOAD
-
eBPF プログラムを検証してロードし、
プログラムに紐づけられた新しいファイルディスクリプターを返す。
close-on-exec ファイルディスクリプターフラグ
(fcntl(2)
を参照) が自動的に有効にされる。
-
bpf_attr
共用体は、
いろいろな
bpf()
コマンドで使われるさまざまな無名 (anonymous) の構造体から構成される :
union bpf_attr {
struct { /* Used by BPF_MAP_CREATE */
__u32 map_type;
__u32 key_size; /* size of key in bytes */
__u32 value_size; /* size of value in bytes */
__u32 max_entries; /* maximum number of entries
in a map */
};
struct { /* Used by BPF_MAP_*_ELEM and BPF_MAP_GET_NEXT_KEY
commands */
__u32 map_fd;
__aligned_u64 key;
union {
__aligned_u64 value;
__aligned_u64 next_key;
};
__u64 flags;
};
struct { /* Used by BPF_PROG_LOAD */
__u32 prog_type;
__u32 insn_cnt;
__aligned_u64 insns; /* 'const struct bpf_insn *' */
__aligned_u64 license; /* 'const char *' */
__u32 log_level; /* verbosity level of verifier */
__u32 log_size; /* size of user buffer */
__aligned_u64 log_buf; /* user supplied 'char *'
buffer */
__u32 kern_version;
/* checked when prog_type=kprobe
(since Linux 4.1) */
};
} __attribute__((aligned(8)));
eBPF マップ
マップは、いろいろタイプのデータを格納するための、基本的な
データ構造である。
マップによって、eBPF カーネルプログラム間でデータを共有したり、
カーネルとユーザ空間アプリケーションでデータを共有したりできる。
各マップタイプには以下の属性がある:
- *
-
型
- *
-
最大の要素数
- *
-
キーのサイズ (バイト単位)
- *
-
値のサイズ (バイト単位)
以下のラッパー関数は、さまざまな
bpf()
コマンドが、どのようにマップへのアクセスに使えるかを示している。
これらの関数は、
cmd
引き数を使って、別々の操作を呼び出している。
- BPF_MAP_CREATE
-
BPF_MAP_CREATE
コマンドは新しいマップを作成し、
そのマップを参照する新しいファイルディスクリプターを返す。
-
int
bpf_create_map(enum bpf_map_type map_type,
unsigned int key_size,
unsigned int value_size,
unsigned int max_entries)
{
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
}
-
新しいマップは
map_type
で指定された型で、
key_size,
value_size,
max_entries
で指定された属性を持つ。
成功した場合、この操作はファイルディスクリプターを返す。
エラーの場合、-1 が返され、
errno
に
EINVAL,
EPERM,
ENOMEM
のいずれかが設定される。
-
key_size
と
value_size
属性は、プログラムのロード時に検証器が使用し、プログラムが、
正しく初期化された
key
で
bpf_map_*_elem()
ヘルパー関数を呼び出しているかのチェックと、
value_size
で指定された範囲を超えてマップ要素
value
にアクセスしていないかのチェックを行う。
例えば、
key_size
を 8 としてマップが作成されて、eBPF プログラムが
-
bpf_map_lookup_elem(map_fd, fp - 4)
-
を呼び出した場合、プログラムは拒否される。
なぜなら、カーネル内のヘルパー関数
-
bpf_map_lookup_elem(map_fd, void *key)
-
は、
key
で指される位置から、8 バイトを読み込むことを想定しているが、
開始アドレス
fp - 4
(ここで
fp
はスタックのトップ) は、範囲外のスタックアスクセスを起こしてしまうからである。
-
同様に、
value_size
を 1 としてマップが作成された場合、eBPF プログラムに
-
value = bpf_map_lookup_elem(...);
*(u32 *) value = 1;
-
というコードが含まれていた場合、プログラムは拒否される。
なぜなら、
value
ポインターが指定した 1 バイトの
value_size
の制限を越えてしまうからである。
-
現在のところ、
map_type
として以下の値がサポートされている:
-
enum bpf_map_type {
BPF_MAP_TYPE_UNSPEC, /* Reserve 0 as invalid map type */
BPF_MAP_TYPE_HASH,
BPF_MAP_TYPE_ARRAY,
BPF_MAP_TYPE_PROG_ARRAY,
BPF_MAP_TYPE_PERF_EVENT_ARRAY,
BPF_MAP_TYPE_PERCPU_HASH,
BPF_MAP_TYPE_PERCPU_ARRAY,
BPF_MAP_TYPE_STACK_TRACE,
BPF_MAP_TYPE_CGROUP_ARRAY,
BPF_MAP_TYPE_LRU_HASH,
BPF_MAP_TYPE_LRU_PERCPU_HASH,
BPF_MAP_TYPE_LPM_TRIE,
BPF_MAP_TYPE_ARRAY_OF_MAPS,
BPF_MAP_TYPE_HASH_OF_MAPS,
BPF_MAP_TYPE_DEVMAP,
BPF_MAP_TYPE_SOCKMAP,
BPF_MAP_TYPE_CPUMAP,
BPF_MAP_TYPE_XSKMAP,
BPF_MAP_TYPE_SOCKHASH,
BPF_MAP_TYPE_CGROUP_STORAGE,
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
BPF_MAP_TYPE_QUEUE,
BPF_MAP_TYPE_STACK,
/* See /usr/include/linux/bpf.h for the full list. */
};
-
map_type
として、カーネルでの利用可能なマップ実装の 1 つを選択する。
すべてのマップ型について、eBPF プログラムは
bpf_map_lookup_elem()
と
bpf_map_update_elem()
ヘルパー関数でマップにアクセスできる。
いろいろなマップ型のより詳細については、下記を参照すること。
- BPF_MAP_LOOKUP_ELEM
-
BPF_MAP_LOOKUP_ELEM
コマンドは、ファイルディスクリプター
fd
で参照されるマップから、指定された
key
で要素を検索する。
-
int
bpf_lookup_elem(int fd, const void *key, void *value)
{
union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
};
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}
-
要素が見つかった場合、この操作では 0 が返され、要素の値が
value
に格納される。
value
は
value_size
バイトのバッファを指していなければならない。
-
要素が見つからない場合、この操作では -1 が返され、
errno
が
ENOENT
に設定される。
- BPF_MAP_UPDATE_ELEM
-
BPF_MAP_UPDATE_ELEM
コマンドは、ファイルディスクリプター
fd
で参照されるマップに、指定した
key/value
の要素を作成または更新する。
-
int
bpf_update_elem(int fd, const void *key, const void *value,
uint64_t flags)
{
union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
.flags = flags,
};
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
-
flags
引き数は、以下のうち 1 つを指定しなければならない:
-
- BPF_ANY
-
新しい要素を作成するか、既存の要素を更新する。
- BPF_NOEXIST
-
指定した要素が存在しない場合にのみ、新しい要素として
作成する。
- BPF_EXIST
-
既存の要素を更新する。
-
成功した場合、この操作は 0 を返す。
エラーの場合、-1 が返され、
errno
が
EINVAL,
EPERM,
ENOMEM,
E2BIG
のいずれかに設定される。
E2BIG
はマップの要素数が、マップの作成時に指定した
max_entries
の制限に達したことを表す。
EEXIST
は、
flags
に
BPF_NOEXIST
が指定されていて、かつマップに
key
の要素が既に存在する場合に返される。
ENOENT
は、
flags
に
BPF_EXIST
が指定されていて、かつマップに
key
の要素が存在しない場合に返される。
- BPF_MAP_DELETE_ELEM
-
BPF_MAP_DELETE_ELEM
コマンドは、ファイルディスクリプター
fd
で参照されるマップから、キーが
key
の要素を削除する。
-
int
bpf_delete_elem(int fd, const void *key)
{
union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
};
return bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
}
-
成功した場合、0 が返される。
要素が見つからない場合、-1 が返され、
errno
が
ENOENT
に設定される。
- BPF_MAP_GET_NEXT_KEY
-
BPF_MAP_GET_NEXT_KEY
コマンドは、ファイルディスクリプター
fd
で参照されるマップから
key
で要素を検索し、次の要素のキーを
next_key
ポインタに設定する。
-
int
bpf_get_next_key(int fd, const void *key, void *next_key)
{
union bpf_attr attr = {
.map_fd = fd,
.key = ptr_to_u64(key),
.next_key = ptr_to_u64(next_key),
};
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
}
-
key
が見つかった場合、この操作は 0 を返し、次の要素のキーが
next_key
ポインタに設定される。
key
が見つからなかった場合、この操作は 0 を返し、最初の要素のキーが
next_key
ポインタに設定される。
key
が最後の要素の場合、-1 が返され、
errno
が
ENOENT
に設定される。
その他に
errno
に設定される可能性のある値は、
ENOMEM,
EFAULT,
EPERM,
EINVAL
である。
このメソッドは、マップのすべての要素を辿るために使われる。
- close(map_fd)
-
ファイルディスクリプター
map_fd
で参照されるマップを削除する。
マップを作成したユーザ空間プログラムが存在する場合、
すべてのマップは自動的に削除される (「注意」を参照すること)。
eBPF マップ型
以下のマップ型がサポートされる:
- BPF_MAP_TYPE_HASH
-
ハッシュテーブルマップには、以下の特徴がある。
-
- *
-
マップはユーザ空間プログラムによって、作成または破壊される。
ユーザ空間プログラムと eBPF プログラムの両方が、
検索、更新、削除の操作を行える。
- *
-
カーネルがキー/値のペアの確保と解放に責任を持つ。
- *
-
map_update_elem()
ヘルパーは、
max_entries
の制限に到達した場合、新しい要素の挿入に失敗する。
(これにより、eBPF プログラムがメモリを使い果たさないようにできる。)
- *
-
map_update_elem()
は、既存の要素を自動的に置き換える。
-
ハッシュテーブルマップは、検索の速度に最適化されている。
- BPF_MAP_TYPE_ARRAY
-
配列マップには、以下の特徴がある。
-
- *
-
検索を最速にするために最適化されている。
将来的に、検証器と JIT コンパイラーは、
固定のキーが使われている lookup() 操作を認識し、
固定のポインタに最適化できるかもしれない。
eBPF プログラムの生存期間中、
ポインタと
value_size
は一定であるので、固定でないキーでも、直接のポインタ計算に
最適化できる。
言い換えると、
array_map_lookup_elem()
は、ユーザ空間からの並行アクセスを保持しながら、
検証器と JIT コンパイラによって「インライン」にできる。
- *
-
すべての配列要素はあらかじめ確保され、最初に 0 で初期化される。
- *
-
キーは配列のインデックスであり、必ず 4 バイトでなければならない。
- *
-
map_delete_elem()
は、要素が削除できないので、エラー
EINVAL
で失敗する。
- *
-
map_update_elem()
は要素を
アトミックでない
やり方で置き換える。
アトミックな更新をするためには、ハッシュテーブルマップを
使うべきである。
しかし、特別な場合には、(アトミックな更新に) 配列を使うことができる:
アトミックな組み込みの
__sync_fetch_and_add()
は 32 ビットと 64 ビットのアトミックなカウンターで使うことができる。
例えば、この関数は、値が 1 つのカウンターである場合、値全体に適用することができる。
また、この関数は、構造体が複数のカウンターを含んでいる場合、
個々のカウンターに使うことができる。
これは、しばしばイベントの集約や集計にとても役立つ。
-
配列マップの使い方は、以下の通りである:
-
- *
-
「グローバルな」eBPF 変数として:要素が 1 の配列で、キーを (インデックス) 0 とする。
ここで値は、「グローバルな」変数の集合であり、
eBPF プログラムはイベント間で状態を保持するのに使う。
- *
-
トレーシングイベントを、バケットの固定の集合に集約する。
- *
-
ネットワーキングイベント、例えば、パケット数または
パケットサイズを集計する。
-
-
BPF_MAP_TYPE_PROG_ARRAY (Linux 4.2 以降)
プログラム配列マップは、配列マップの特別な形式であり、
マップの値は、他の eBPF プログラムを参照するファイルディスクリプターのみを保持する。
よって、
key_size
と
value_size
は、必ず 4 バイトでなければならない。
このマップは、
bpf_tail_call()
ヘルパーと併せて使われる。
-
これは、プログラム配列マップをつけた eBPF プログラムは、
カーネルサイドから以下のように呼び出すことができるということである。
-
void bpf_tail_call(void *context, void *prog_map,
unsigned int index);
-
そして、自身のプログラムフローを、指定したプログラム配列の
スロットにプログラムがあれば、それに置き換えることができる。
これは、別の eBPF プログラムへのジャンプテーブルの一種として扱うことができる。
そして、起動されたプログラムは、同じスタックを再利用する。
新しいプログラムへのジャンプが実行された場合、前のプログラムには
戻らない。
-
プログラム配列の指定されたインデックスに eBPF プログラムがない場合
(マップスロットに有効なプログラムファイルディスクリプターがない
場合、
または指定された検索インデックス/キーが範囲外である場合、
または入れ子の呼び出しが 32 の制限を肥えた場合)
現在の eBPF プログラムで実行が継続される。
これは、失敗した場合のデフォルトとして、使われる。
-
プログラム配列マップは、例えば、トレーシング、または
ネットワーキングで役に立つ。
プログラム配列マップ自身のサブプログラムで各システムコールや
プロトコルを扱ったり、それらの識別子を各マップのインデックスとして
使うことが出来る。
この方法は性能上の利点になり、単一の eBPF プログラムの
命令数の制限の最大値を超えることもできる。
動的な環境では、ユーザ空間のデーモンは、実行時に各サブプログラムを
新バージョンに自動的に置き換え、たとえば、グローバルなポリシーを
変更することで、プログラムの挙動全体を変更することができる。
eBPF プログラム
BPF_PROG_LOAD
コマンドを使って、eBPF プログラムをカーネルにロードすることができる。
このコマンドの返り値は、この eBPF プログラムに関連づけられた
新しいファイルディスクリプターである。
char bpf_log_buf[LOG_BUF_SIZE];
int
bpf_prog_load(enum bpf_prog_type type,
const struct bpf_insn *insns, int insn_cnt,
const char *license)
{
union bpf_attr attr = {
.prog_type = type,
.insns = ptr_to_u64(insns),
.insn_cnt = insn_cnt,
.license = ptr_to_u64(license),
.log_buf = ptr_to_u64(bpf_log_buf),
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};
return bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
}
prog_type
は以下の利用可能なプログラムタイプのうちのいずれかである:
-
enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC, /* Reserve 0 as invalid
program type */
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,
BPF_PROG_TYPE_SK_REUSEPORT,
BPF_PROG_TYPE_FLOW_DISSECTOR,
/* See /usr/include/linux/bpf.h for the full list. */
};
eBPF プログラム型のより詳細については、下記を参照すること。
bpf_attr
の残りのフィールドは以下のように設定される:
- *
-
insns
は
struct bpf_insn
命令の配列である。
- *
-
insn_cnt
は
insns
で参照されるプログラムの命令数である。
- *
-
license
はライセンス文字列であり、
gpl_only
とマークされているヘルパー関数を呼ぶために、GPL 互換でなければならない。
(ライセンシングのルールは、カーネルモジュールと同じであり、
"Dual BSD/GPL" のようなデュアルライセンスも使える。)
- *
-
log_buf
は、呼び出し側によって確保されたバッファーであり、
カーネル内の検証器が検証ログを格納できる。
ログは、複数行の文字列であり、
検証器がどのようにして eBPF プログラムが安全でないとの結論にしたかを、プログラムの作者が知るために
チェックできる。
出力の形式は、検証器の開発が進むにつれて、変更される可能性がある。
- *
-
log_size
は
log_buf
で指されるバッファーのサイズである。
バッファーサイズが、検証器のすべてのメッセージを格納するのに
充分でない場合、-1 が返され、
errno
が
ENOSPC
に設定される。
- *
-
log_level
は検証器の詳細レベルである。
値を 0 にすると、検証器はログを出力しない。
この場合、
log_buf
は NULL ポインターでなければならず、
log_size
は 0 でなければならない。
BPF_PROG_LOAD
で返されるファイルディスクリプターを
close(2)
を適用すると、eBPF プログラムをアンロードする
(「注意」を参照すること)。
マップは eBPF プログラムからアクセス可能であり、
eBPF プログラム間や、eBPF プログラムとユーザ空間プログラムの間で、
データを交換するのに使える。
例えば、eBPF プログラムは (kprobe, パケットのような) さまざまなイベントを処理可能で、
そのデータをマップに格納できる。
そして、ユーザ空間プログラムはマップからそのデータを取得できる。
逆に、ユーザ空間プログラムはマップを設定の仕組みとして使うことができる。
eBPF プログラムがチェックする値を含むマップを渡すことで、
この値に基づいて、即時にプログラムの挙動を変更することができる。
eBPF プログラム型
eBPF プログラム型
(prog_type)
は、プログラムが呼ばれるカーネルヘルパー関数のサブセットを決定する。
プログラム型は、プログラムの入力 (コンテキスト)---
つまり
struct bpf_context
のフォーマットも決定する。
(struct bpf_context
は、データ blob で、eBPF プログラムの最初の引き数として渡される)。
例えば、トレーシングプログラムは、ソケットフィルタープログラムとは、
厳密には同じヘルパー関数のサブセットを持たない
(しかし、いくつかのヘルパー関数は共通に持つ)。
同様に、トレーシングプログラムの入力 (コンテキスト) はレジスター値の集合であるが、
ソケットフィルターの入力 (コンテキスト) はネットワークパケットである。
指定された型の eBPF プログラムで利用可能な関数のセットは、
将来増えるかもしれない。
以下のプログラム型がサポートされている:
-
-
BPF_PROG_TYPE_SOCKET_FILTER (Linux 3.19 以降)
現在、
BPF_PROG_TYPE_SOCKET_FILTER
で利用可能な関数のセットは以下の通り:
-
bpf_map_lookup_elem(map_fd, void *key)
/* look up key in a map_fd */
bpf_map_update_elem(map_fd, void *key, void *value)
/* update key/value */
bpf_map_delete_elem(map_fd, void *key)
/* delete key in a map_fd */
-
bpf_context
引き数は、
struct __sk_buff
へのポインターである。
-
-
BPF_PROG_TYPE_KPROBE (Linux 4.1 以降)
[ドキュメント化が必要]
-
-
BPF_PROG_TYPE_SCHED_CLS (Linux 4.1 以降)
[ドキュメント化が必要]
-
-
BPF_PROG_TYPE_SCHED_ACT (Linux 4.1 以降)
[ドキュメント化が必要]
イベント
プログラムが 1 度ロードされると、イベントに紐づけられる。
いろいろなカーネルサブシステムが、さまざまな方法でこれを行う。
Linux 3.19 以降では、以下の呼び出しで、プログラム
prog_fd
をソケット
sockfd
に紐づける。
ここで、
sockfd
は
socket(2)
の呼び出しで前もって作られたものである:
setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_BPF,
&prog_fd, sizeof(prog_fd));
Linux 4.1 以降では、以下の呼び出しで、
ファイルディスクリプター
prog_fd
で参照される eBPF プログラムを、
perf イベントディスクリプター
event_fd
に紐づける。
ここで、
event_fd
は
perf_event_open(2)
の呼び出しで前もって作られたものである:
ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
例
/* bpf とソケットの例:
* 1. 要素数 256 の配列マップを作成する。
* 2. 受信したパケット数をカウントするプログラムをロードする。
* r0 = skb->data[ETH_HLEN + offsetof(struct iphdr, protocol)]
* map[r0]++
* 3. setsockopt() で prog_fd を raw ソケットに紐づける。
* 4. 受信した TCP/UDP パケット数を毎秒表示する。
*/
int
main(int argc, char **argv)
{
int sock, map_fd, prog_fd, key;
long long value = 0, tcp_cnt, udp_cnt;
map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key),
sizeof(value), 256);
if (map_fd < 0) {
printf("failed to create map '%s'\n", strerror(errno));
/* likely not run as root */
return 1;
}
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), /* r6 = r1 */
BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol)),
/* r0 = ip->proto */
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),
/* *(u32 *)(fp - 4) = r0 */
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), /* r2 = fp */
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = r2 - 4 */
BPF_LD_MAP_FD(BPF_REG_1, map_fd), /* r1 = map_fd */
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),
/* r0 = map_lookup(r1, r2) */
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
/* if (r0 == 0) goto pc+2 */
BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */
BPF_XADD(BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0),
/* lock *(u64 *) r0 += r1 */
BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */
BPF_EXIT_INSN(), /* return r0 */
};
prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog,
sizeof(prog) / sizeof(prog[0]), "GPL");
sock = open_raw_sock("lo");
assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,
sizeof(prog_fd)) == 0);
for (;;) {
key = IPPROTO_TCP;
assert(bpf_lookup_elem(map_fd, &key, &tcp_cnt) == 0);
key = IPPROTO_UDP;
assert(bpf_lookup_elem(map_fd, &key, &udp_cnt) == 0);
printf("TCP %lld UDP %lld packets\n", tcp_cnt, udp_cnt);
sleep(1);
}
return 0;
}
動作している完全なコードは、カーネルソースツリーの
samples/bpf
にある。
返り値
呼び出しに成功した場合、返り値は操作に依存する:
- BPF_MAP_CREATE
-
eBPF マップに関連づけられた新しいファイルディスクリプター。
- BPF_PROG_LOAD
-
eBPF プログラムに関連づけられた新しいファイルディスクリプター。
-
-
他のコマンド
0。
エラーの場合、-1 が返され、
errno
が適切に設定される。
エラー
- E2BIG
-
eBPF プログラムが大きすぎる、またはマップが
max_entries
の制限 (要素数の最大値) に達した。
- EACCES
-
BPF_PROG_LOAD
の場合に、すべてのプログラム命令が有効であるが、
プログラムが安全でないと思われて、拒否された。
これは、許可されていないメモリ領域または初期化されていない
スタック/レジスタにアクセスする可能性がある場合、
関数の制限が実際のタイプにマッチしない場合、
配置を間違えたメモリアクセスがある場合などである。
この場合、
bpf()
を
log_level = 1
としてもう一度呼んで、検証器が提供する特定の理由を
log_buf
で調べることをお勧めする。
- EBADF
-
fd
がオープンされたファイルディスクリプターでない。
- EFAULT
-
ポインター
(key,
value,
log_buf,
insns)
のいずれかが、アクセス可能はアドレス空間の外にある。
- EINVAL
-
cmd
に指定された値が、カーネルに認識されない。
- EINVAL
-
BPF_MAP_CREATE
の場合に、
map_type
または属性が無効である。
- EINVAL
-
BPF_MAP_*_ELEM
コマンドの場合に、このコマンドで使われない
union bpf_attr
のどれかのフィールドが 0 に設定されていない。
- EINVAL
-
BPF_PROG_LOAD
の場合に、無効なプログラムをロードしようとしたことを表す。
eBPF プログラムは、認識されない命令、予約されたフィールドの利用、
範囲外へのジャンプ、無限ループ、不明な関数呼び出しの場合に、
無効であると認識される。
- ENOENT
-
BPF_MAP_LOOKUP_ELEM
または
BPF_MAP_DELETE_ELEM
の場合、指定された
key
の要素が見つからないことを示す。
- ENOMEM
-
十分なメモリが確保できない。
- EPERM
-
十分な権限
(CAP_SYS_ADMIN
ケーパビリティ) なしで呼び出しが行われた。
バージョン
bpf()
は Linux 3.18 で最初に登場した。
準拠
bpf()
システムコールは Linux 固有である。
注意
Linux 4.4 より前では、すべての
bpf()
コマンドで、呼び出し元が
CAP_SYS_ADMIN
ケーパビリティを持つ必要があった。
Linux 4.4 以降では、特権のないユーザでもタイプ
BPF_PROG_TYPE_SOCKET_FILTER
と関連するマップの限定されたプログラムを作れるようになった。
しかし、マップにカーネルのポインターを格納できず、現在のところ下記の
ヘルパー関数に限定されている:
- *
-
get_random
- *
-
get_smp_processor_id
- *
-
tail_call
- *
-
ktime_get_ns
特権のないアクセスは、sysctl
/proc/sys/kernel/unprivileged_bpf_disabled
を設定することで、ブロックできる。
eBPF オブジェクト (マップとプログラム) は、プロセス間で共有できる。
例えば、
fork(2)
の後、子プロセスは、同じ eBPF オブジェクトを参照するファイルディスクリプターを継承する。
加えて、eBPF オブジェクトを参照するファイルディスクリプターは、
UNIX ドメインソケットを使って転送できる。
eBPF オブジェクトを参照するファイルディスクリプターは、
dup(2)
や同様の呼び出しで、通常の方法で複製できる。
eBPF オブジェクトは、オブジェクトを参照するすべてのファイルディスクリプターが
閉じられた場合に、解放される。
eBPF プログラムは、制限付きの C で書くことができて、
(clang
コンパイラーを使って) eBPF バイトコードにコンパイルされる。
制限付きの C では、ループ、グローバル変数、可変長期引き数の関数、
浮動小数点小数、構造体の関数引き数への受け渡しなど、
さまざまな機能が省略されている。
カーネルソースコードツリーの
samples/bpf/*_kern.c
ファイルに、いくつかの例が書かれている。
カーネルは just-in-time (JIT) を含んでおり、
eBPF バイトコードをネイティブのマシンコードに変換し、
性能を良くすることができる。
Linux 4.15 より前のカーネルでは、
デフォルトでは JIT コンパイラーは無効化されているが、
下記の整数文字列のいずれかを
/proc/sys/net/core/bpf_jit_enable
に書き込むことにより、操作をコントロールできる。
- 0
-
JIT コンパイルを無効化する (デフォルト)。
- 1
-
通常のコンパイル。
- 2
-
デバッグモード。
生成されたオペコード (opcode) はカーネルログに 16 進数でダンプされる。
これらのオペコードは、カーネルソースツリーの
tools/net/bpf_jit_disasm.c
で提供されるプログラムで逆アセンブルできる。
Linux 4.15 以降では、カーネルが
CONFIG_BPF_JIT_ALWAYS_ON
オプション付きで設定されているかもしれない。
この場合、JIT コンパイラーは常に有効になっており、
bpf_jit_enable
は 1 で初期化されており、変更できない。
(このカーネル設定オプションは、BPF インタプリターへのスペクター (Spectre) アタックを
緩和するために提供されている。)
JIT コンパイラーは、現在のところ下記のアーキテクチャーで提供されている:
- *
-
x86-64 (Linux 3.18 以降; cBPF は Linux 3.0 以降);
- *
-
ARM32 (Linux 3.18 以降; cBPF は Linux 3.4 以降);
- *
-
SPARC 32 (Linux 3.18 以降; cBPF は Linux 3.5 以降);
- *
-
ARM-64 (Linux 3.18 以降);
- *
-
s390 (Linux 4.1 以降; cBPF は Linux 3.7 以降);
- *
-
PowerPC 64 (Linux 4.8 以降; cBPF は Linux 3.1 以降);
- *
-
SPARC 64 (Linux 4.12 以降);
- *
-
x86-32 (Linux 4.18 以降);
- *
-
MIPS 64 (Linux 4.18 以降; cBPF は Linux 3.16 以降);
- *
-
riscv (Linux 5.1 以降).
関連項目
seccomp(2),
bpf-helpers(7),
socket(7),
tc(8),
tc-bpf(8)
classic BPF と extended BPF の両方が、カーネルソースコード
Documentation/networking/filter.txt
で説明されている。
この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.07 の一部である。
プロジェクトの説明、バグ報告に関する情報、このページの最新版は、
http://www.kernel.org/doc/man-pages/ に書かれている。
Index
- 名前
-
- 書式
-
- 説明
-
- extended BPF のデザイン/アーキテクチャ
-
- 引き数
-
- eBPF マップ
-
- eBPF マップ型
-
- eBPF プログラム
-
- eBPF プログラム型
-
- イベント
-
- 例
-
- 返り値
-
- エラー
-
- バージョン
-
- 準拠
-
- 注意
-
- 関連項目
-
- この文書について
-
This document was created by
man2html,
using the manual pages.
Time: 15:58:34 GMT, January 04, 2021