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 で与えられる付随する引き数をとる。 attrbpf_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 が返され、 errnoEINVAL, EPERM, ENOMEM のいずれかが設定される。
key_sizevalue_size 属性は、プログラムのロード時に検証器が使用し、プログラムが、 正しく初期化された keybpf_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 に格納される。 valuevalue_size バイトのバッファを指していなければならない。
要素が見つからない場合、この操作では -1 が返され、 errnoENOENT に設定される。
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 が返され、 errnoEINVAL, EPERM, ENOMEM, E2BIG のいずれかに設定される。 E2BIG はマップの要素数が、マップの作成時に指定した max_entries の制限に達したことを表す。 EEXIST は、 flagsBPF_NOEXIST が指定されていて、かつマップに key の要素が既に存在する場合に返される。 ENOENT は、 flagsBPF_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 が返され、 errnoENOENT に設定される。
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 が返され、 errnoENOENT に設定される。 その他に 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_sizevalue_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 の残りのフィールドは以下のように設定される:

*
insnsstruct bpf_insn 命令の配列である。
*
insn_cntinsns で参照されるプログラムの命令数である。
*
license はライセンス文字列であり、 gpl_only とマークされているヘルパー関数を呼ぶために、GPL 互換でなければならない。 (ライセンシングのルールは、カーネルモジュールと同じであり、 "Dual BSD/GPL" のようなデュアルライセンスも使える。)
*
log_buf は、呼び出し側によって確保されたバッファーであり、 カーネル内の検証器が検証ログを格納できる。 ログは、複数行の文字列であり、 検証器がどのようにして eBPF プログラムが安全でないとの結論にしたかを、プログラムの作者が知るために チェックできる。 出力の形式は、検証器の開発が進むにつれて、変更される可能性がある。
*
log_sizelog_buf で指されるバッファーのサイズである。 バッファーサイズが、検証器のすべてのメッセージを格納するのに 充分でない場合、-1 が返され、 errnoENOSPC に設定される。
*
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 に紐づける。 ここで、 sockfdsocket(2) の呼び出しで前もって作られたものである:

setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_BPF,
           &prog_fd, sizeof(prog_fd));

Linux 4.1 以降では、以下の呼び出しで、 ファイルディスクリプター prog_fd で参照される eBPF プログラムを、 perf イベントディスクリプター event_fd に紐づける。 ここで、 event_fdperf_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