+#ifdef SO_ATTACH_FILTER
+static int
+fix_program(pcap_t *handle, struct sock_fprog *fcode)
+{
+ size_t prog_size;
+ register int i;
+ register struct bpf_insn *p;
+ struct bpf_insn *f;
+ int len;
+
+ /*
+ * Make a copy of the filter, and modify that copy if
+ * necessary.
+ */
+ prog_size = sizeof(*handle->fcode.bf_insns) * handle->fcode.bf_len;
+ len = handle->fcode.bf_len;
+ f = (struct bpf_insn *)malloc(prog_size);
+ if (f == NULL) {
+ snprintf(handle->errbuf, sizeof(handle->errbuf),
+ "malloc: %s", pcap_strerror(errno));
+ return -1;
+ }
+ memcpy(f, handle->fcode.bf_insns, prog_size);
+ fcode->len = len;
+ fcode->filter = (struct sock_filter *) f;
+
+ for (i = 0; i < len; ++i) {
+ p = &f[i];
+ /*
+ * What type of instruction is this?
+ */
+ switch (BPF_CLASS(p->code)) {
+
+ case BPF_RET:
+ /*
+ * It's a return instruction; is the snapshot
+ * length a constant, rather than the contents
+ * of the accumulator?
+ */
+ if (BPF_MODE(p->code) == BPF_K) {
+ /*
+ * Yes - if the value to be returned,
+ * i.e. the snapshot length, is anything
+ * other than 0, make it 65535, so that
+ * the packet is truncated by "recvfrom()",
+ * not by the filter.
+ *
+ * XXX - there's nothing we can easily do
+ * if it's getting the value from the
+ * accumulator; we'd have to insert
+ * code to force non-zero values to be
+ * 65535.
+ */
+ if (p->k != 0)
+ p->k = 65535;
+ }
+ break;
+
+ case BPF_LD:
+ case BPF_LDX:
+ /*
+ * It's a load instruction; is it loading
+ * from the packet?
+ */
+ switch (BPF_MODE(p->code)) {
+
+ case BPF_ABS:
+ case BPF_IND:
+ case BPF_MSH:
+ /*
+ * Yes; are we in cooked mode?
+ */
+ if (handle->md.cooked) {
+ /*
+ * Yes, so we need to fix this
+ * instruction.
+ */
+ if (fix_offset(p) < 0) {
+ /*
+ * We failed to do so.
+ * Return 0, so our caller
+ * knows to punt to userland.
+ */
+ return 0;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ }
+ return 1; /* we succeeded */
+}
+
+static int
+fix_offset(struct bpf_insn *p)
+{
+ /*
+ * What's the offset?
+ */
+ if (p->k >= SLL_HDR_LEN) {
+ /*
+ * It's within the link-layer payload; that starts at an
+ * offset of 0, as far as the kernel packet filter is
+ * concerned, so subtract the length of the link-layer
+ * header.
+ */
+ p->k -= SLL_HDR_LEN;
+ } else if (p->k == 14) {
+ /*
+ * It's the protocol field; map it to the special magic
+ * kernel offset for that field.
+ */
+ p->k = SKF_AD_OFF + SKF_AD_PROTOCOL;
+ } else {
+ /*
+ * It's within the header, but it's not one of those
+ * fields; we can't do that in the kernel, so punt
+ * to userland.
+ */
+ return -1;
+ }
+ return 0;
+}
+
+static int
+set_kernel_filter(pcap_t *handle, struct sock_fprog *fcode)
+{
+ int total_filter_on = 0;
+ int save_mode;
+ int ret;
+ int save_errno;
+
+ /*
+ * The socket filter code doesn't discard all packets queued
+ * up on the socket when the filter is changed; this means
+ * that packets that don't match the new filter may show up
+ * after the new filter is put onto the socket, if those
+ * packets haven't yet been read.
+ *
+ * This means, for example, that if you do a tcpdump capture
+ * with a filter, the first few packets in the capture might
+ * be packets that wouldn't have passed the filter.
+ *
+ * We therefore discard all packets queued up on the socket
+ * when setting a kernel filter. (This isn't an issue for
+ * userland filters, as the userland filtering is done after
+ * packets are queued up.)
+ *
+ * To flush those packets, we put the socket in read-only mode,
+ * and read packets from the socket until there are no more to
+ * read.
+ *
+ * In order to keep that from being an infinite loop - i.e.,
+ * to keep more packets from arriving while we're draining
+ * the queue - we put the "total filter", which is a filter
+ * that rejects all packets, onto the socket before draining
+ * the queue.
+ *
+ * This code deliberately ignores any errors, so that you may
+ * get bogus packets if an error occurs, rather than having
+ * the filtering done in userland even if it could have been
+ * done in the kernel.
+ */
+ if (setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ &total_fcode, sizeof(total_fcode)) == 0) {
+ char drain[1];
+
+ /*
+ * Note that we've put the total filter onto the socket.
+ */
+ total_filter_on = 1;
+
+ /*
+ * Save the socket's current mode, and put it in
+ * non-blocking mode; we drain it by reading packets
+ * until we get an error (which we assume is a
+ * "nothing more to be read" error).
+ */
+ save_mode = fcntl(handle->fd, F_GETFL, 0);
+ if (save_mode != -1 &&
+ fcntl(handle->fd, F_SETFL, save_mode | O_NONBLOCK) >= 0) {
+ while (recv(handle->fd, &drain, sizeof drain,
+ MSG_TRUNC) >= 0)
+ ;
+ fcntl(handle->fd, F_SETFL, save_mode);
+ }
+ }
+
+ /*
+ * Now attach the new filter.
+ */
+ ret = setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ fcode, sizeof(*fcode));
+ if (ret == -1 && total_filter_on) {
+ /*
+ * Well, we couldn't set that filter on the socket,
+ * but we could set the total filter on the socket.
+ *
+ * This could, for example, mean that the filter was
+ * too big to put into the kernel, so we'll have to
+ * filter in userland; in any case, we'll be doing
+ * filtering in userland, so we need to remove the
+ * total filter so we see packets.
+ */
+ save_errno = errno;
+
+ /*
+ * XXX - if this fails, we're really screwed;
+ * we have the total filter on the socket,
+ * and it won't come off. What do we do then?
+ */
+ reset_kernel_filter(handle);
+
+ errno = save_errno;
+ }
+ return ret;
+}
+
+static int
+reset_kernel_filter(pcap_t *handle)
+{
+ /* setsockopt() barfs unless it get a dummy parameter */
+ int dummy;
+
+ return setsockopt(handle->fd, SOL_SOCKET, SO_DETACH_FILTER,
+ &dummy, sizeof(dummy));
+}
+#endif