Linux Kernel Networking Filter Documentation
BPFによってユーザスペースがソケット上の通信にフィルタを付与できる。BSDのbpfを同じ命令が使える。
フィルタ用コードを作り SO_ATTACH_FILTER オプション経由でカーネルに送るとそのソケット上でのフィルタが始まる、というシンプルな動作である。フィルタをはずすときは SO_DETACH_FILTER で外せるが大概の場合はソケットを閉じて終わりにすることが多い。
このBPFの最も有名なユーザはlibpcapだろう。
ここではソケットについて言及しているがLinuxの中では他にもBPFを使っている。
xt_bpf for netfilter
cls_bpf in the kernel qdisc layer
SECCOMP-BPF
フィルタの構造
命令は4-tupleになっている。
code:c
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
これをアセンブルするのが
code:c
struct sock_fprog { /* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter __user *filter;
};
こんな感じでattachする。
code:c
/* ... */
/* From the example above: tcpdump -i em1 port 22 -dd */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 17, 0x00000011 },
{ 0x28, 0, 0, 0x00000036 },
{ 0x15, 14, 0, 0x00000016 },
{ 0x28, 0, 0, 0x00000038 },
{ 0x15, 12, 13, 0x00000016 },
{ 0x15, 0, 12, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 8, 0x00000011 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x45, 6, 0, 0x00001fff },
{ 0xb1, 0, 0, 0x0000000e },
{ 0x48, 0, 0, 0x0000000e },
{ 0x15, 2, 0, 0x00000016 },
{ 0x48, 0, 0, 0x00000010 },
{ 0x15, 0, 1, 0x00000016 },
{ 0x06, 0, 0, 0x0000ffff },
{ 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);
ETH_P_ALL なんだろう。Ethernetの protocol に入ってくる種別を絞れるらしい。ARPとかIPv4とか。
上記のプログラムはIPv4,v6でport 22を通るパケットをソケットに通す。
PF_PACKET ソケットだけでなく他のファミリーでも使うことができる。
まあ普通はlibpcapで事足りるだろう。
下記の場合はby handでfilterを書いてくれ。
libpcapをlinkしない
libpcapでサポートしないBPFの拡張を使う
libpcapではコンパイルできないような複雑なフィルタが必要
filterコードを手動で最適化したい
xt_bpf や cls_bpf などは複雑なフィルタを必要としている。
BPF engine and instruction set
BPFの構成要素
A : 32 bitのアキュムレータ
X : 32 bitのXレジスタ
M[] : 16 x 32 bit wide misc registers aka "scratch memory store", addressable from 0 to 15
命令は下記のようなタプルになる
code:text
op:16, jt:8, jf:8, k:32
op
16bit opcode
jt
8bit jump if true のときのターゲット
jf
8bit jump if false のときのターゲット
k
32bit opcodeによってことなる色々な引数
BPF命令いろいろ
命令セットは主に
load
store
branch
alu
misc
return
でできている。 ( push pop がないからレジスタマシン的なセマンティクスなのかな )
アドレス参照構文のBHWってなに
BHW: byte, half-word, or word
load
word (32 bit), half-word (16 bit), byte (8 bit) をレジスタに読み込む
store
branch
ジャンプ命令、引数に指定されている数字が何指しているか分からない、相対位置かな?
alu
misc
return
Examples
code:text
** ARP packets:
先頭から12byte offsetとるとprotocol (タグがなければ)
0x806 の即値は ARP を表す。
返却値が -1 なのはなんで? -> わからない。。BSDのmanでは0がパケット処理をskipさせるというのは理解できたが、-1の説明はない。
なるほど、ここでld命令で見ているのはBPFが対象にしているデータなので構造は一様ではない。
ソケットならskbだしseccompならseccomp_dataとかいうもののバイト配列表現。
eBPF opcode encoding
eBPFでは通常のBPFのopcodeを再利用するが算術とジャンプについては8bitのcodeフィールドを3つに分割する。
code:text
+----------------+--------+--------------------+
| 4 bits | 1 bit | 3 bits |
| operation code | source | instruction class |
+----------------+--------+--------------------+
(MSB) (LSB)
BPF kernel internals
カーネル内部ではBPFと似た異なる命令セットを使っている。これはマシン固有の命令セットに似せたものである。これにより少しパフォーマンスがよくなっている。この新しい命令セットを eBPF と呼ぶ。
GCCとかLLVMで最適化したコードが生成しやすいよいにネイティブな命令セットに似せてある。
この新しい命令セットは制限されたCでプログラムを書くようなことを目的として設計された。
現在このフォーマットはユーザのBPFプログラムを実行するために使われている。それらのプログラムはカーネル内でインタープリタに渡される。
classic BPFはeBPFから32 bitアーキテクチャに変換する時に使われている。
eBPFによる変更点
レジスタ数の増加
AとXというレジスタのみだったが、eBPFではR0-R10までのレジスタに増えた。
R0
in-kernel関数の返却値またはeBPFプログラムの返却値
レジスタサイズの増加
32bitから64bitへ
実際の走らせ方
こんな感じでロードしたらアタッチすればいいのか。 setsockopt
他のkprobeやperf_eventだと perf_event_open でイベントのfdつくって
code:c
ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
でアタッチする