NetBSD Advent Calendar 2015
BPFを使ってパケットを送受信
Dec 8, 2015
Masaru OKI @masaru0714
BPF?
Berkeley Packet Filter。
昔から*BSDにある、パケットキャプチャの仕組み。
injectといってパケットを送信することもできる。らしい。
bpf(4)にいろいろ説明が書いてある。
以降、C言語を使ったBPFでのパケット送受信について大雑把に解説する。
自分がハマったところは太字にしている。あちこちにある。うっかりさんである。
BPFによるキャプチャの準備
1. fd = open(“/dev/bpf”, O_RDWR); で、スペシャルファイルをオープンする。
2. ioctl(fd, BIOCSBLEN, &len); で、バッファサイズを設定する。
バッファには複数パケットの情報が詰めて書き込まれる。
インタフェースを指定する前に設定する必要がある。
3. ioctl(fd, BIOCSSEESENT, &onoff); で送信方向をキャプチャするか指定。
受信方向については常にキャプチャされる。デフォルトは送信方向もキャプチャ。
※FreeBSDではBIOCSDIECTIONを使う。送信方向のみキャプチャもできる。
4. ioctl(fd, BIOCSETIF, &ifreq); で対象インタフェース名を指定。
ifreq.ifr_nameにインタフェース名を書き込んでおく。
5. 必要に応じて ioctl(fd, BIOCPROMISC, NULL); しておく。
パケットの受信
1. read(fd, buf, buflen); する。buflenはBIOCSBLENと同じ値でないとエラーになる。
2. bufの中身はstruct bpf_hdr + パケットデータ、が収まるだけ連続している。
3. パケットデータのサイズは bh_caplenにホストバイトオーダで書かれている。
bpf_hdr bpf_hdr bpf_hdr bpf_hdr
struct bpf_hdr {
struct bpf_timeval bh_tstamp; /* time stamp */
uint32_t bh_caplen; /* length of captured portion */
uint32_t bh_datalen; /* original length of packet */
uint16_t bh_hdrlen; /* length of bpf header (this struct
plus alignment padding) */
};
パケットの送信
ふつうに生データをwriteするだけ。1回のwriteで1パケット。bpf_hdrはつけない。
FCSはカーネルが計算してくれる。
ioctl(fd, BIOCSSEESENT, &onoff); で送信方向のキャプチャを切っておかないと、送った
データそのものを受信する。tcpdumpみたいに全部キャプチャするのであれば問題ない
が、パケット送受信するプログラムの場合はハマるので注意。
パケットの受信待ち
fdをふつうにselectやpollで待てる。
送信でブロックを避けたい場合もselectやpollを使う。
OSによるBPF実装の差異
● BPFではfilterを設定できるが、NetBSDではfilterのJITコンパイラが使える。
● FreeBSDではzero copy bpfなるものが用意されている。
● 他は未調査……
● スライドで解説したコードはNetBSD, FreeBSD共通で使える。
● 昔の*BSDは “/dev/bpf0”, “/dev/bpf1”, … を使っていた(重複open不可)らしいが、
NetBSDやFreeBSDは “/dev/bpf” を複数回openして別I/Fを送受信できる。
おしまい。

Net bsd advent calendar 2015 bpf