/dev/bpfをreadした場合、データグラム単位になっているか?
NetBSD 1.6.2 Releaseのソースしか見てませんが、答えを先に言うと、データグラム単位です。 はっきりした仕様なのか、実装の都合なのかはわかりません。
man bpfを見ても、いまいち載ってなかったので、 bpfドライバの実装を見ることに。
ソースはこの2つを見ました。
src/sys/net/bpf.c r1.64 src/sys/net/bpfdesc.h r1.15
まずは、read処理。
403 /* 404 * bpfread - read next chunk of packets from buffers 405 */ 406 int 407 bpfread(dev, uio, ioflag) 408 dev_t dev; 409 struct uio *uio; 410 int ioflag; 411 { 412 struct bpf_d *d = &bpf_dtab[minor(dev)]; 413 int error; 414 int s; 415 416 /* 417 * Restrict application to use a buffer the same size as 418 * as kernel buffers. 419 */ 420 if (uio->uio_resid != d->bd_bufsize) 421 return (EINVAL);
これといって変わったところはないのですが、 ユーザから指定するサイズが d->bd_bufsize でないとエラーになる仕様のようです。
どこでユーザランド側にコピーしているかというと、 bpfread の最後の方にありました。
486 /* 487 * Move data from hold buffer into user space. 488 * We know the entire buffer is transferred since 489 * we checked above that the read buffer is bpf_bufsize bytes. 490 */ 491 error = uiomove(d->bd_hbuf, d->bd_hlen, uio); 492 493 s = splnet(); 494 d->bd_fbuf = d->bd_hbuf; 495 d->bd_hbuf = 0; 496 d->bd_hlen = 0; 497 done: 498 splx(s); 499 return (error); 500 }
どうやら、d->bd_hbufそのものを返しているようです。 というわけで、d->bd_hbufを追ってみることにします。
まずは、定義から。 struct bpf_dという構造体の定義がどうなっているのか見てみます。
50 /* 51 * Descriptor associated with each open bpf file. 52 */ 53 struct bpf_d { 54 struct bpf_d *bd_next; /* Linked list of descriptors */ 55 /* 56 * Buffer slots: two mbuf clusters buffer the incoming packets. 57 * The model has three slots. Sbuf is always occupied. 58 * sbuf (store) - Receive interrupt puts packets here. 59 * hbuf (hold) - When sbuf is full, put cluster here and 60 * wakeup read (replace sbuf with fbuf). 61 * fbuf (free) - When read is done, put cluster here. 62 * On receiving, if sbuf is full and fbuf is 0, packet is dropped. 63 */ 64 caddr_t bd_sbuf; /* store slot */ 65 caddr_t bd_hbuf; /* hold slot */ 66 caddr_t bd_fbuf; /* free slot */ 67 int bd_slen; /* current length of store buffer */ 68 int bd_hlen; /* current length of hold buffer */ 69 70 int bd_bufsize; /* absolute length of buffers */ 71
定義の中に興味深いコメントを見つけました。 3っつのスロットがあり、そのうち2つはバッファを指しているようです。 バッファをコピーするのではなく、ポインタの付け替えを行うことで、 効率をUPさせているのでしょう。
定義を理解したところで、次にこのバッファの領域を確保しているところを見てみます。
1176 /* 1177 * Initialize all nonzero fields of a descriptor. 1178 */ 1179 static int 1180 bpf_allocbufs(d) 1181 struct bpf_d *d; 1182 { 1183 1184 d->bd_fbuf = (caddr_t)malloc(d->bd_bufsize, M_DEVBUF, M_WAITOK); 1185 d->bd_sbuf = (caddr_t)malloc(d->bd_bufsize, M_DEVBUF, M_WAITOK); 1186 d->bd_slen = 0; 1187 d->bd_hlen = 0; 1188 return (0); 1189 } 1190
ここ以外に malloc, realloc しているところを探してみましたが、みつかりませんでした。 どうやら、バッファのサイズは、必ず d->bd_bufsize であるようです。
ここ以外に領域を確保しているところがないということは、つまり、 2つのバッファを使いまわすだけということになります。
これまでに調べた内容だけで、このバッファがデータグラム単位であることがわかるのですが、 せっかくなのでもう少し調べてみます。
受信したデータをこのバッファに格納する処理を探してみましょう。 bpf.c のソースを眺めていくと、catchpacket という、いかにもな関数を見つけました。
1105 /* 1106 * Move the packet data from interface memory (pkt) into the 1107 * store buffer. Return 1 if it's time to wakeup a listener (buffer full), 1108 * otherwise 0. "copy" is the routine called to do the actual data 1109 * transfer. memcpy is passed in to copy contiguous chunks, while 1110 * bpf_mcpy is passed in to copy mbuf chains. In the latter case, 1111 * pkt is really an mbuf. 1112 */ 1113 static void 1114 catchpacket(d, pkt, pktlen, snaplen, cpfn) 1115 struct bpf_d *d; 1116 u_char *pkt; 1117 u_int pktlen, snaplen; 1118 void *(*cpfn) __P((void *, const void *, size_t)); 1119 { 1120 struct bpf_hdr *hp; 1121 int totlen, curlen; 1122 int hdrlen = d->bd_bif->bif_hdrlen; 1123 /* 1124 * Figure out how many bytes to move. If the packet is 1125 * greater or equal to the snapshot length, transfer that 1126 * much. Otherwise, transfer the whole packet (unless 1127 * we hit the buffer size limit). 1128 */ 1129 totlen = hdrlen + min(snaplen, pktlen); 1130 if (totlen > d->bd_bufsize) 1131 totlen = d->bd_bufsize; 1132 1133 /* 1134 * Round up the end of the previous packet to the next longword. 1135 */ 1136 curlen = BPF_WORDALIGN(d->bd_slen); 1137 if (curlen + totlen > d->bd_bufsize) { 1138 /* 1139 * This packet will overflow the storage buffer. 1140 * Rotate the buffers if we can, then wakeup any 1141 * pending reads. 1142 */ 1143 if (d->bd_fbuf == 0) { 1144 /* 1145 * We haven't completed the previous read yet, 1146 * so drop the packet. 1147 */ 1148 ++d->bd_dcount; 1149 return; 1150 } 1151 ROTATE_BUFFERS(d); 1152 bpf_wakeup(d); 1153 curlen = 0; 1154 } (略) 1162 /* 1163 * Append the bpf header. 1164 */ 1165 hp = (struct bpf_hdr *)(d->bd_sbuf + curlen); 1166 microtime(&hp->bh_tstamp); 1167 hp->bh_datalen = pktlen; 1168 hp->bh_hdrlen = hdrlen; 1169 /* 1170 * Copy the packet data into the store buffer and update its length. 1171 */ 1172 (*cpfn)((u_char *)hp + hdrlen, pkt, (hp->bh_caplen = totlen - hdrlen)); 1173 d->bd_slen = curlen + totlen; 1174 }
1162 行目からは、bpf ヘッダを編集し、cpfn にコピー処理を委託する処理になります。 cpfn と、わざわざコールバック関数になっているのは、 pkt が単純な OctetString であるか、mbuf であるかによって、コピーの処理が違うためです。 単純な OctetString であれば、cpfn は memcpy になります。
catchpacket でやっていることは、至って単純です。 パケットがバッファに収まるかどうか判定し、バッファにコピーするだけです。
受信したパケットを格納している先は、逆からたどっていくと、d->bd_sbuf のようです。 もし、d->bd_sbufに収まらないパケットを受信した場合は、ROTATE_BUFFERS(d) によって、 バッファをローテートします。 ローテートの前に free のバッファがあるかチェックし、 なければ、discard カウンタをインクリメントし、パケットを破棄しています。
受信したパケットをまるごとコピーできるか、 できないかという判断であることがわかりました。 受信したパケットの何バイトまでコピーは完了して、残りはこれこれという考えはありません。 よって、bpfドライバで管理しているバッファはデータグラム単位であるといえます。
ここで、ちょっと気になる点があるかもしれません。 『catchpacket の引数である pkt って、データグラムなのか?』です。 答えは、データグラムです。 なぜかというと、pkt は catchpacket の前にフィルター条件のロジックを通っています。 もし、ここで中途半端なサイズのパケットなのだとしたら、 フィルタリングの判定なんてできないはずだからです。 それ以前に、pkt という名前でデータグラムじゃないなんてヤクザです。