読者です 読者をやめる 読者になる 読者になる

/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 という名前でデータグラムじゃないなんてヤクザです。