BPF(4)

名称
bpf - バークレイパケットフィルタ

書式
pseudo-device bpfilter

解説
バークレイパケットフィルタは、データリンク層においてプロトコル非依存の 生の形のインターフェースを提供します。 ネットワーク上のパケットは、たとえ他のホストに向けられた ものであっても、すべてこの機構を通してアクセスすることができます。

このパケットフィルタは /dev/bpf0, /dev/bpf1 などの キャラクタ型特殊デバイスに見えます。 このデバイスをオープンした後、ファイル記述子は BIOCSETIF ioctl によって、特定のネットワークインタフェースに結びつけなければなりません。 指示されたインタフェースは複数の監視者で共有することができ、 各記述子の下にあるフィルタは、同じパケットの流れを見ることになります。 オープンできるファイルの上限は、 カーネルの設定で与えられた値に制限されます。 上の 書式 で与えられた例では、制限は 16 になっています。

それぞれのマイナデバイスには、別々のデバイスファイルが必要です。 デバイスファイルが使用中であるならば、オープンは失敗し、 errno には EBUSY がセットされます。

オープンされた bpf ファイルの実体それぞれに関連づけられているのが、 ユーザが設定可能なパケットフィルタです。 あるインタフェースでパケットを受信したときはいつでも、そのインタフェースを 監視しているファイル記述子はすべて自身のフィルタを適用します。 パケットを受け取る各記述子は、自分用のコピーを受け取ります。

それぞれのファイルからの入力は、フィルタでマッチしたパケットの 次のグループを返します。 性能を上げるために、read に渡すバッファは bpf が内部で使用するバッファと同じサイズでなければなりません。 このサイズは、 BIOCGBLEN ioctl (下記を参照) で得られ、 BIOCSBLEN で設定できます。 このサイズより大きい個々のパケットは、 必然的に切り詰められてしまうことに注意して下さい。

このパケットフィルタは、固定長ヘッダであれば どのリンクレベルプロトコルでもサポートします。 今のところ、イーサネットと SLIP と PPP ドライバだけが bpf と協調して動作するように修正されています。

パケットデータはネットワークバイトオーダになっているので、 アプリケーションが複数バイトの値を引き出すためには byteorder(3) マクロを使わなければなりません。

bpf ファイル記述子に書き込むことでネットワークにパケットを 送出することができます。 書き込みはバッファリングされないので、1 回の書き込みにつき 1 つのパケットだけしか処理されません。 現在イーサネットと SLIP リンクへの書き込みだけがサポートされています。

IOCTL
次のような ioctl(2) コマンドコードが <net/bpf.h> で定義されています。すべてのコマンドには 次のようなインクルードファイルが必要です。

    	#include <sys/types.h>
    	#include <sys/time.h>
    	#include <sys/ioctl.h>
    	#include <net/bpf.h>

さらに、 BIOCGETIF と BIOCSETIF は、 <sys/socket.h> と <net/if.h> を必要とします。 FIONREAD と SIOCGIFADDR 以外にも、次のようなコマンドを オープンした bpf ファイルに適用できます。 ioctl(2) への (3 番目の) 引数は、 指定の型へのポインタでなければなりません。

BIOCGBLEN
    (u_int) bpf ファイル上で読み込みを行うために必要なバッファ長を返します。

BIOCSBLEN
    (u_int) bpf ファイル上で読み込みを行うためのバッファ長を設定します。バッファは、ファイルが BIOCSETIF によってインタフェースに接続される前に設定されなければなりません。 要求されたバッファサイズが適用できなかった場合は、 許容できるサイズで最も近いものに設定され、それが引数の中に返されます。 このサイズでないパケットがバッファを通ったときには、 読み込みコールは EIO の値で終了します。

BIOCGDLT
    (u_int) 接続されたインタフェースの下にあるデータリンク層の型を返します。 インタフェースが指定されていなかった場合 EINVAL が返されます。 デバイスの型は、 “DLT_” が前に付いた形であり、 <net/bpf.h> で定義されています。

BIOCPROMISC
    強制的にインタフェースを無差別 (promiscuous) モードにします。 ローカルホストに向けられたもののみならずすべてのパケットが処理されます。 複数のファイルが与えられたインタフェースを監視することができるので、 そのインタフェースを無差別でないモードでオープンした監視者でも パケットを無差別に受信することができてしまいます。 この問題は適当なフィルタで矯正することができます。

BIOCFLUSH
    到着パケットのバッファをフラッシュし、BIOCGSTATS で返される 集計値をリセットします。

BIOCGETIF
    (struct ifreq) ファイルが監視しているハードウェアインタフェースの名前を返します。 名前は ifreq 構造体の ifr_name フィールドに返されます。 他のフィールドはすべて未定義となります。

BIOCSETIF
    (struct ifreq) ファイルに関連付けるハードウェアインタフェースを設定します。 このコマンドは、どんなパケットを読み込むよりも前に 実行されなければいけません。 デバイスは、 ifreq 構造体の ifr_name フィールドを使って名前で示されます。 さらに BIOCFLUSH の動作を実行します。

BIOCSRTIMEOUT

BIOCGRTIMEOUT
    (struct timeval) タイムアウトパラメータを設定または取得します。 引数には、読み込み要求でタイムアウトするまでの 待ち時間の長さを指定します。 このパラメータは、 open(2) により 0 に初期化され、タイムアウトしないことを指示します。

BIOCGSTATS
    (struct bpf_stat) パケット集計値の次の構造体を返します:

        struct bpf_stat {
        	u_int bs_recv;    /* 受信したパケット数 */
        	u_int bs_drop;    /* 落としたパケット数 */
        };

    フィールドは次のようになります:

    bs_recv
        オープンまたはリセット後に、この記述子によって受信したパケット数 (最後の読み込みコールからバッファされているものを含みます)。

    bs_drop
        フィルタが受け取りはしたが、バッファのオーバフローにより カーネルが落としたパケットの数 (つまり、アプリケーションの読み込みがパケットの流量に追いついていない ということです)。

BIOCIMMEDIATE
    (u_int) 引数の真偽値に基づいて ``直接モード'' を有効または無効にします。 直接モードが有効なとき、パケットを受け取ると読み込みは ただちに返されます。無効なとき、入力はカーネルバッファがいっぱいになるか、 またはタイムアウトが起こるまでブロックされます。 これは、リアルタイムにメッセージに応答しなければならない rarpd(8) のようなプログラムには便利です。 新しいファイルに対しては、デフォルトではオフになります。

BIOCSETF
    (struct bpf_program) 興味のないパケットを捨てるために カーネルが使うフィルタプログラムを設定します。 次の構造体を通して命令を並べた配列とその長さが渡されます:

        struct bpf_program {
        	int bf_len;
        	struct bpf_insn *bf_insns;
        };

    フィルタプログラムは bf_insns フィールドで指定され、 ‘struct bpf_insn’ の構造体中におけるプログラムの長さが bf_len フィールドで与えられます。 そして、 BIOCFLUSH の動作が実行されます。 フィルタ言語の説明については フィルタマシン のセクションを見て下さい。

BIOCVERSION
    (struct bpf_version) 現在カーネルに認識されているフィルタ言語のメジャーおよび マイナバージョン番号を返します。フィルタをインストールする前に、 アプリケーションは、動作しているカーネルと現在のバージョンとが 互換性があるかどうかを調べなければなりません。 メジャー番号が一致し、アプリケーションのマイナ番号が カーネルのマイナ番号に等しいか、それ以下ならバージョン番号は互換性が あります。 カーネルのバージョン番号は以下の構造体で返されます:

        struct bpf_version {
                u_short bv_major;
                u_short bv_minor;
        };

    現在のバージョン番号は <net/bpf.h> 中の BPF_MAJOR_VERSION と BPF_MINOR_VERSION によって与えられます。 互換性のないフィルタでは、予期しない動作に終わるかもしれません (最もありそうなのは、 ioctl() によってエラーが返されるか、または偶然にパケットが一致することです)。

BPF ヘッダ
read(2) によって返される各パケットの先頭には、 次のような構造体がつけられています:

    struct bpf_hdr {
            struct timeval bh_tstamp;     /* タイムスタンプ */
            u_long bh_caplen;             /* キャプチャされた部分の長さ */
            u_long bh_datalen;            /* パケットのオリジナルの長さ */
            u_short bh_hdrlen;            /* bpf ヘッダの長さ (この構造体
    					 + 境界調整パディング) */
    };

フィールドはホスト順で保存されており、次のようになります:

bh_tstamp
    パケットフィルタによって、そのパケットが処理された時刻
bh_caplen
    パケットのキャプチャされた部分の長さ。 これは切り詰め量の最小値で、 フィルタとパケット長によって指定されています。
bh_datalen
    経路から離れたパケット長。 この値はフィルタで指定された切り詰め量には依存しません。
bh_hdrlen
    bpf ヘッダの長さ。これは sizeof(struct bpf_hdr) に等しいとは限りません。

bh_hdrlen フィールドはヘッダとリンクレベルプロトコル間の パディングのために存在します。この目的は、 パケットデータ構造の適切な境界調整を保証することです。 これは、 境界調整に厳しいアーキテクチャが必要とすることであり、 また、これによって他の多くのアーキテクチャにおける性能が向上します。 パケットフィルタは bpf_hdr とネットワーク層のヘッダがワード境界になることを保証します。 境界調整が制約されたマシン上でリンク層プロトコルをアクセス するときには、適切な注意を払わなければなりません (これはイーサネット上では問題にはなりません。なぜなら、 フィールドの型が short であり偶数オフセットに落ち着きますし、 アドレスがおそらくバイト単位でアクセスされるからです)。

さらに、個々のパケットはワード境界で始まるようにパディングされます。 これにより、アプリケーションはパケットから次のパケットを得る方法を 知っていることが要求されます。 このプロセスを手助けするために、マクロ BPF_WORDALIGN が <net/bpf.h> 中で定義されています。 引数は最も近いワード境界値 (ワードが BPF_ALIGNMENT バイト幅) に切り上げられます。

例えば ‘p’ がパケットの先頭を指すとき、次の表現は ポインタを次のパケットへ進めます:
    p = (char *)p + BPF_WORDALIGN(p->bh_hdrlen + p->bh_caplen)

境界調整の機構を適切に動作させるために read(2) に渡されるバッファは、それ自身がワード境界になければなりません。 malloc(3) 関数は常にワード境界のバッファを返します。

フィルタマシン
フィルタプログラムは命令を並べた配列であり、すべての分岐が コードの前方に向かうものであり、また、 return 命令で終わるものです。 各命令は、アキュムレータ、インデックスレジスタ、一時メモリ記憶、 および暗黙のプログラムカウンタから成る疑似マシン状態上で 何らかの動作を行ないます。 次の構造体が命令フォーマットの定義です:

    struct bpf_insn {
    	u_short	code;
    	u_char 	jt;
    	u_char 	jf;
    	u_long k;
    };

k フィールドは命令によって異なる用法で用いられ、 jt と jf フィールドは分岐命令によってオフセットとして用いられます。 操作コードは半階層的な形で符号化されます。 命令には 8 つのクラス BPF_LD, BPF_LDX, BPF_ST, BPF_STX, BPF_ALU, BPF_JMP, BPF_RET, BPF_MISC があります。 他のいろいろなモードと操作ビットは実際の命令を与えるために ビット加算 (or) され、クラスに変換されます。 クラスとモードは <net/bpf.h> 内で定義されています。 以下は定義されたそれぞれの bpf 命令です。 便宜的に A がアキュムレータ、X がインデックスレジスタ、 P[] がパケットデータ、M[] が寄せ集めの一時メモリ記憶であるとします。 P[i:n] はパケット内の ``i'' バイトオフセットのデータを指し、 ワード (n=4)、符号無し半ワード (n=2)、符号無しバイト (n=1) に翻訳されます。 M[i] は、一時メモリ記憶の中で i 番目のワードを指し、ワード単位の アドレスだけが割り振られます。メモリ記憶は 0 から BPF_MEMWORDS - 1 に番号付けされます。 k, jt, jf は、命令定義の中で対応するフィールドになります。``len'' は、 パケット長を参照します。

BPF_LD
    これらの命令は、値をアキュムレータに複写します。 ソースオペランドの型は、``アドレッシングモード'' で指定され、 定数 (BPF_IMM) でも、固定オフセットのパケットデータ (BPF_ABS) 、可変オフセットのパケットデータ (BPF_IND) 、パケット長 (BPF_LEN) 、一時メモリ記憶内のワード (BPF_MEM) をとり得ます。 BPF_IND と BPF_ABS の場合、データサイズは、ワード (BPF_W) 、ハーフワード (BPF_H) 、バイト (BPF_B) のいずれかでなければなりません。 使用可能な全 BPF_LD 命令の意味は次の通りです。

    BPF_LD+BPF_W+BPF_ABS
        A <- P[k:4]
    BPF_LD+BPF_H+BPF_ABS
        A <- P[k:2]
    BPF_LD+BPF_B+BPF_ABS
        A <- P[k:1]
    BPF_LD+BPF_W+BPF_IND
        A <- P[X+k:4]
    BPF_LD+BPF_H+BPF_IND
        A <- P[X+k:2]
    BPF_LD+BPF_B+BPF_IND
        A <- P[X+k:1]
    BPF_LD+BPF_W+BPF_LEN
        A <- len
    BPF_LD+BPF_IMM
        A <- k
    BPF_LD+BPF_MEM
        A <- M[k]
BPF_LDX
    これらの命令は値をインデックスレジスタにロードします。 この命令のときのアドレッシングモードは、アキュムレータへのロード 時よりも厳密ですが、IP ヘッダ長をロードする効果的な方法である BPF_MSH を含んでいることに注目して下さい。
    BPF_LDX+BPF_W+BPF_IMM
        X <- k
    BPF_LDX+BPF_W+BPF_MEM
        X <- M[k]
    BPF_LDX+BPF_W+BPF_LEN
        X <- len
    BPF_LDX+BPF_B+BPF_MSH
        X <- 4*(P[k:1]&0xf)
BPF_ST
    この命令はアキュムレータを一時メモリに退避します。 行き先の可能性が一つしかないのでアドレッシングモードは不要です。
    BPF_ST
        M[k] <- A
BPF_STX
    この命令はインデックスレジスタを一時メモリに退避します。
    BPF_STX
        M[k] <- X
BPF_ALU
    alu 命令は、アキュムレータとインデックスレジスタまたは定数間の 操作を実行し、結果をアキュムレータに戻します。 バイナリ操作のためにはソースモードが必要です ( BPF_K または BPF_X
    BPF_ALU+BPF_ADD+BPF_K
        A <- A + k
    BPF_ALU+BPF_SUB+BPF_K
        A <- A - k
    BPF_ALU+BPF_MUL+BPF_K
        A <- A * k
    BPF_ALU+BPF_DIV+BPF_K
        A <- A / k
    BPF_ALU+BPF_AND+BPF_K
        A <- A & k
    BPF_ALU+BPF_OR+BPF_K
        A <- A | k
    BPF_ALU+BPF_LSH+BPF_K
        A <- A << k
    BPF_ALU+BPF_RSH+BPF_K
        A <- A >> k
    BPF_ALU+BPF_ADD+BPF_X
        A <- A + X
    BPF_ALU+BPF_SUB+BPF_X
        A <- A - X
    BPF_ALU+BPF_MUL+BPF_X
        A <- A * X
    BPF_ALU+BPF_DIV+BPF_X
        A <- A / X
    BPF_ALU+BPF_AND+BPF_X
        A <- A & X
    BPF_ALU+BPF_OR+BPF_X
        A <- A | X
    BPF_ALU+BPF_LSH+BPF_X
        A <- A << X
    BPF_ALU+BPF_RSH+BPF_X
        A <- A >> X
    BPF_ALU+BPF_NEG
        A <- -A
BPF_JMP
    ジャンプ命令はフロー制御を変更します。条件ジャンプは アキュムレータと、定数 (BPF_K) またはインデックスレジスタ (BPF_X) 間の比較を行ないます。 結果が真 (つまり非 0) であった場合に真の分岐が選択され、 そうでなければ偽の分岐が選択されます。 ジャンプオフセットは 8 ビットに符号化されるので、 最長ジャンプは 256 命令分です。 しかし、常時ジャンプ操作コード (BPF_JA) は、オフセットとして 32 ビットの k フィールドを使用し、離れた任意の行き先を許します。 すべての条件は慣習的に符号無し比較を用います。
    BPF_JMP+BPF_JA
        pc += k
    BPF_JMP+BPF_JGT+BPF_K
        pc += (A > k) ? jt : jf
    BPF_JMP+BPF_JGE+BPF_K
        pc += (A >= k) ? jt : jf
    BPF_JMP+BPF_JEQ+BPF_K
        pc += (A == k) ? jt : jf
    BPF_JMP+BPF_JSET+BPF_K
        pc += (A & k) ? jt : jf
    BPF_JMP+BPF_JGT+BPF_X
        pc += (A > X) ? jt : jf
    BPF_JMP+BPF_JGE+BPF_X
        pc += (A >= X) ? jt : jf
    BPF_JMP+BPF_JEQ+BPF_X
        pc += (A == X) ? jt : jf
    BPF_JMP+BPF_JSET+BPF_X
        pc += (A & X) ? jt : jf
BPF_RET
    リターン命令はフィルタプログラムを終了し、受信するパケットの量を 指定します (すなわち、切り詰め量を返します)。 戻り値 0 は、そのパケットが無視されるべきであることを表しています。 戻り値は定数 (BPF_K) またはアキュムレータ (BPF_A) のいずれかです。
    BPF_RET+BPF_A
        A バイト受信
    BPF_RET+BPF_K
        k バイト受信
BPF_MISC
    上記のクラスに適合しないものや、追加されるべき新しい命令 のために、その他のカテゴリが作られました。 現在、インデックスレジスタをアキュムレータに複写する、 またはその逆を行なうレジスタ転送命令があります。
    BPF_MISC+BPF_TAX
        X <- A
    BPF_MISC+BPF_TXA
        A <- X

    bpf インタフェースは、配列の初期化を手助けする次のマクロを提供しています: BPF_STMT(opcode, operand) と BPF_JUMP(opcode, operand, true_offset, false_offset)

使用例
以下のフィルタが RARP デーモンから取得されます。フィルタは、 RARP 要求のみを受信します。

    struct bpf_insn insns[] = {
    	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_REVARP, 0, 3),
    	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 20),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, REVARP_REQUEST, 0, 1),
    	BPF_STMT(BPF_RET+BPF_K, sizeof(struct ether_arp) +
    		 sizeof(struct ether_header)),
    	BPF_STMT(BPF_RET+BPF_K, 0),
    };

このフィルタは 128.3.112.15 と 128.3.112.35 の間の IP パケット だけを受け取ります。

    struct bpf_insn insns[] = {
    	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 0, 8),
    	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 26),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x8003700f, 0, 2),
    	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 30),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x80037023, 3, 4),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x80037023, 0, 3),
    	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 30),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x8003700f, 0, 1),
    	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
    	BPF_STMT(BPF_RET+BPF_K, 0),
    };

最後に、このフィルタは TCP finger パケットだけを返します。 TCP ヘッダにたどり着くためには IP ヘッダを解析しなければなりません。 BPF_JSET 命令は IP フラグメントオフセットが 0 であることを調べます。 それで TCP ヘッダであることを確認します。

    struct bpf_insn insns[] = {
    	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 0, 10),
    	BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 23),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 0, 8),
    	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 20),
    	BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, 0x1fff, 6, 0),
    	BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 14),
    	BPF_STMT(BPF_LD+BPF_H+BPF_IND, 14),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 79, 2, 0),
    	BPF_STMT(BPF_LD+BPF_H+BPF_IND, 16),
    	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 79, 0, 1),
    	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
    	BPF_STMT(BPF_RET+BPF_K, 0),
    };

関連項目
tcpdump(1), ioctl(2), byteorder(3) McCanne, S., Jacobson V., An efficient, extensible, and portable network monitor.

関連ファイル
/dev/bpfn
    パケットフィルタデバイス

バグ
読み込みバッファは固定長 ( BIOCGBLEN ioctl で返される値) でなければなりません。

無差別モードを要求しないファイルは、同じハードウェアインタフェース上で このモードを要求する他のファイルの副作用として、 無差別にパケットを受信するかもしれません。 これは、オーバヘッドのある処理を追加すれば、カーネル内で修正できるでしょう。 しかし、インタフェースは無差別であるとすべてのファイルがみなさなければならないような モデルの方が好まれます。必要なら、外部のパケットをはじくためのフィルタを 利用しなければなりません。

可変長ヘッダのデータリンクプロトコルは現在サポートされていません。

歴史

Enet パケットフィルタは、 1980 年にカーネギーメロン大学の Mike Accetta と Rick Rashid により作成されました。 スタンフォードの Jeffrey Mogul がコードを BSD に移植して 1983 年以降発展させました。その後 DEC の Ultrix パケットフィルタ、 SunOS 4.1 の STREAMS NIT モジュール、 BPF へと進化しました。

著者
Lawrence Berkeley 研究所の Steven McCanne が 1990 年夏に BPF をインプリメントしました。 多くは Van Jacobson によってデザインされました。