このパケットフィルタは /dev/bpf0 /dev/bpf1 などの キャラクタ型特殊デバイスに見えます。 このデバイスをオープンした後、ファイル記述子は BIOCSETIF ioctl によって、特定のネットワークインタフェースに結びつけなければなりません。 指示されたインタフェースは複数の監視者で共有することができ、 各記述子の下にあるフィルタは、同じパケットの流れを見ることになります。 オープンできるファイルの上限は、 カーネルの設定で与えられた値に制限されます。 上の Sx 書式 で与えられた例では、制限は 16 になっています。
それぞれのマイナデバイスには、別々のデバイスファイルが必要です。 デバイスファイルが使用中であるならば、オープンは失敗し、 errno には Er EBUSY がセットされます。
オープンされた bpf ファイルの実体それぞれに関連づけられているのが、 ユーザが設定可能なパケットフィルタです。 あるインタフェースでパケットを受信したときはいつでも、そのインタフェースを 監視しているファイル記述子はすべて自身のフィルタを適用します。 パケットを受け取る各記述子は、自分用のコピーを受け取ります。
それぞれのファイルからの入力は、フィルタでマッチしたパケットの 次のグループを返します。 性能を上げるために、read に渡すバッファは が内部で使用するバッファと同じサイズでなければなりません。 このサイズは、 BIOCGBLEN ioctl (下記を参照) で得られ、 BIOCSBLEN で設定できます。 このサイズより大きい個々のパケットは、 必然的に切り詰められてしまうことに注意して下さい。
このパケットフィルタは、固定長ヘッダであれば どのリンクレベルプロトコルでもサポートします。 今のところ、イーサネットと SLIP と PPP ドライバだけが bpf と協調して動作するように修正されています。
パケットデータはネットワークバイトオーダになっているので、 アプリケーションが複数バイトの値を引き出すためには byteorder(3) マクロを使わなければなりません。
bpf ファイル記述子に書き込むことでネットワークにパケットを 送出することができます。 書き込みはバッファリングされないので、1 回の書き込みにつき 1 つのパケットだけしか処理されません。 現在イーサネットと SLIP リンクへの書き込みだけがサポートされています。
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <net/bpf.h>
さらに、 BIOCGETIF と BIOCSETIF は、 Aq Pa sys/socket.h と Aq Pa net/if.h を必要とします。
FIONREAD と SIOCGIFADDR 以外にも、次のようなコマンドを オープンした ファイルに適用できます。 ioctl(2) への (3 番目の) 引数は、 指定の型へのポインタでなければなりません。
struct bpf_stat {
u_int bs_recv; /* 受信したパケット数 */
u_int bs_drop; /* 落としたパケット数 */
};
フィールドは次のようになります:
struct bpf_program {
int bf_len;
struct bpf_insn *bf_insns;
};
フィルタプログラムは bf_insns フィールドで指定され、 `struct bpf_insn ' の構造体中におけるプログラムの長さが bf_len フィールドで与えられます。 そして、 BIOCFLUSH の動作が実行されます。 フィルタ言語の説明については Sx フィルタマシン のセクションを見て下さい。
struct bpf_version {
u_short bv_major;
u_short bv_minor;
};
現在のバージョン番号は Aq Pa net/bpf.h 中の BPF_MAJOR_VERSION と BPF_MINOR_VERSION によって与えられます。 互換性のないフィルタでは、予期しない動作に終わるかもしれません (最もありそうなのは、 Fn ioctl によってエラーが返されるか、または偶然にパケットが一致することです)。
struct bpf_hdr {
struct timeval bh_tstamp; /* タイムスタンプ */
u_long bh_caplen; /* キャプチャされた部分の長さ */
u_long bh_datalen; /* パケットのオリジナルの長さ */
u_short bh_hdrlen; /* bpf ヘッダの長さ (この構造体
+ 境界調整パディング) */
};
フィールドはホスト順で保存されており、次のようになります:
bh_hdrlen フィールドはヘッダとリンクレベルプロトコル間の パディングのために存在します。この目的は、 パケットデータ構造の適切な境界調整を保証することです。 これは、 境界調整に厳しいアーキテクチャが必要とすることであり、 また、これによって他の多くのアーキテクチャにおける性能が向上します。 パケットフィルタは bpf_hdr とネットワーク層のヘッダがワード境界になることを保証します。 境界調整が制約されたマシン上でリンク層プロトコルをアクセス するときには、適切な注意を払わなければなりません (これはイーサネット上では問題にはなりません。なぜなら、 フィールドの型が short であり偶数オフセットに落ち着きますし、 アドレスがおそらくバイト単位でアクセスされるからです)。
さらに、個々のパケットはワード境界で始まるようにパディングされます。 これにより、アプリケーションはパケットから次のパケットを得る方法を 知っていることが要求されます。 このプロセスを手助けするために、マクロ BPF_WORDALIGN が Aq Pa net/bpf.h 中で定義されています。 引数は最も近いワード境界値 (ワードが BPF_ALIGNMENT バイト幅) に切り上げられます。
例えば `p ' がパケットの先頭を指すとき、次の表現は ポインタを次のパケットへ進めます:
p = (char *)p + BPF_WORDALIGN(p->bh_hdrlen + p->bh_caplen)
境界調整の機構を適切に動作させるために read(2) に渡されるバッファは、それ自身がワード境界になければなりません。 malloc(3) 関数は常にワード境界のバッファを返します。
次の構造体が命令フォーマットの定義です:
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) され、クラスに変換されます。 クラスとモードは Aq Pa net/bpf.h 内で定義されています。
以下は定義されたそれぞれの 命令です。 便宜的に 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'' は、 パケット長を参照します。
インタフェースは、配列の初期化を手助けする次のマクロを提供しています: Fn BPF_STMT opcode operand と Fn BPF_JUMP opcode operand true_offset false_offset
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),
};
無差別モードを要求しないファイルは、同じハードウェアインタフェース上で このモードを要求する他のファイルの副作用として、 無差別にパケットを受信するかもしれません。 これは、オーバヘッドのある処理を追加すれば、カーネル内で修正できるでしょう。 しかし、インタフェースは無差別であるとすべてのファイルがみなさなければならないような モデルの方が好まれます。必要なら、外部のパケットをはじくためのフィルタを 利用しなければなりません。
可変長ヘッダのデータリンクプロトコルは現在サポートされていません。
Enet パケットフィルタは、 1980 年にカーネギーメロン大学の Mike Accetta と Rick Rashid により作成されました。 スタンフォードの Jeffrey Mogul がコードを BSD に移植して 1983 年以降発展させました。その後 DEC の Ultrix パケットフィルタ、 SunOS 4.1 の STREAMS NIT モジュール、 BPF へと進化しました。