view src/bpf.c @ 5231:a2c342295221 draft

privsep: Enable Capsicum for all processes. Except for the priviledged process. This is quite an in-depth change: * ARP is now one process per address * BPF flags are now returned via privsep * BPF write filters are locked when supported * The root process sends to the network The last step is done by opening RAW sockets and then sending a UDP header (where applicable) to avoid binding to an address which is already in use by the reader sockets. This is slightly wasteful for OS's without sandboxing but does have the very nice side effect of not needing a source address to unicast DHCPs replies from which makes the code smaller.
author Roy Marples <roy@marples.name>
date Tue, 19 May 2020 16:19:05 +0100
parents bb468c1a3b46
children d09c879cc4d5
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * dhcpcd: BPF arp and bootp filtering
 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
 * All rights reserved

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/ioctl.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#ifdef __linux__
/* Special BPF snowflake. */
#include <linux/filter.h>
#define	bpf_insn		sock_filter
#else
#include <net/bpf.h>
#endif

#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include "common.h"
#include "arp.h"
#include "bpf.h"
#include "dhcp.h"
#include "if.h"
#include "logerr.h"

/* BPF helper macros */
#ifdef __linux__
#define	BPF_WHOLEPACKET		0x7fffffff /* work around buggy LPF filters */
#else
#define	BPF_WHOLEPACKET		~0U
#endif

/* Macros to update the BPF structure */
#define	BPF_SET_STMT(insn, c, v) {				\
	(insn)->code = (c);					\
	(insn)->jt = 0;						\
	(insn)->jf = 0;						\
	(insn)->k = (uint32_t)(v);				\
};

#define	BPF_SET_JUMP(insn, c, v, t, f) {			\
	(insn)->code = (c);					\
	(insn)->jt = (t);					\
	(insn)->jf = (f);					\
	(insn)->k = (uint32_t)(v);				\
};

size_t
bpf_frame_header_len(const struct interface *ifp)
{

	switch (ifp->hwtype) {
	case ARPHRD_ETHER:
		return sizeof(struct ether_header);
	default:
		return 0;
	}
}

void *
bpf_frame_header_src(const struct interface *ifp, void *fh, size_t *len)
{
	uint8_t *f = fh;

	switch (ifp->hwtype) {
	case ARPHRD_ETHER:
		*len = sizeof(((struct ether_header *)0)->ether_shost);
		return f + offsetof(struct ether_header, ether_shost);
	default:
		*len = 0;
		errno =	ENOTSUP;
		return NULL;
	}
}

void *
bpf_frame_header_dst(const struct interface *ifp, void *fh, size_t *len)
{
	uint8_t *f = fh;

	switch (ifp->hwtype) {
	case ARPHRD_ETHER:
		*len = sizeof(((struct ether_header *)0)->ether_dhost);
		return f + offsetof(struct ether_header, ether_dhost);
	default:
		*len = 0;
		errno =	ENOTSUP;
		return NULL;
	}
}

static const uint8_t etherbcastaddr[] =
    { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

int
bpf_frame_bcast(const struct interface *ifp, const void *frame)
{

	switch (ifp->hwtype) {
	case ARPHRD_ETHER:
		return memcmp((const char *)frame +
		    offsetof(struct ether_header, ether_dhost),
		    etherbcastaddr, sizeof(etherbcastaddr));
	default:
		return -1;
	}
}

#ifndef __linux__
/* Linux is a special snowflake for opening, attaching and reading BPF.
 * See if-linux.c for the Linux specific BPF functions. */

const char *bpf_name = "Berkley Packet Filter";

struct bpf *
bpf_open(const struct interface *ifp,
    int (*filter)(const struct bpf *, const struct in_addr *),
    const struct in_addr *ia)
{
	struct bpf *bpf;
	struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 };
	struct ifreq ifr = { .ifr_flags = 0 };
	int ibuf_len = 0;
#ifdef BIOCIMMEDIATE
	unsigned int flags;
#endif
#ifndef O_CLOEXEC
	int fd_opts;
#endif

#ifdef _PATH_BPF
	fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK
#ifdef O_CLOEXEC
		| O_CLOEXEC
#endif
	);
#else
	char device[32];
	int n = 0;

	bpf = calloc(1, sizeof(*bpf));
	if (bpf == NULL)
		return NULL;
	bpf->bpf_ifp = ifp;

	do {
		snprintf(device, sizeof(device), "/dev/bpf%d", n++);
		bpf->bpf_fd = open(device, O_RDWR | O_NONBLOCK
#ifdef O_CLOEXEC
				| O_CLOEXEC
#endif
		);
	} while (bpf->bpf_fd == -1 && errno == EBUSY);
#endif

	if (bpf->bpf_fd == -1)
		goto eexit;

#ifndef O_CLOEXEC
	if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 ||
	    fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1)
		goto eexit;
#endif

	if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1)
		goto eexit;
	if (pv.bv_major != BPF_MAJOR_VERSION ||
	    pv.bv_minor < BPF_MINOR_VERSION) {
		logerrx("BPF version mismatch - recompile");
		goto eexit;
	}

	strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
	if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1)
		goto eexit;

#ifdef BIOCIMMEDIATE
	flags = 1;
	if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1)
		goto eexit;
#endif

	if (filter(bpf, ia) != 0)
		goto eexit;

	/* Get the required BPF buffer length from the kernel. */
	if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1)
		goto eexit;
	bpf->bpf_size = (size_t)ibuf_len;
	bpf->bpf_buffer = malloc(bpf->bpf_size);
	if (bpf->bpf_buffer == NULL)
		goto eexit;
	return bpf;

eexit:
	if (bpf->bpf_fd != -1)
		close(bpf->bpf_fd);
	free(bpf);
	return NULL;
}

/* BPF requires that we read the entire buffer.
 * So we pass the buffer in the API so we can loop on >1 packet. */
ssize_t
bpf_read(struct bpf *bpf, void *data, size_t len)
{
	ssize_t bytes;
	struct bpf_hdr packet;
	const char *payload;

	bpf->bpf_flags &= ~BPF_EOF;
	for (;;) {
		if (bpf->bpf_len == 0) {
			bytes = read(bpf->bpf_fd, bpf->bpf_buffer,
			    bpf->bpf_size);
#if defined(__sun)
			/* After 2^31 bytes, the kernel offset overflows.
			 * To work around this bug, lseek 0. */
			if (bytes == -1 && errno == EINVAL) {
				lseek(fd, 0, SEEK_SET);
				continue;
			}
#endif
			if (bytes == -1 || bytes == 0)
				return bytes;
			bpf->bpf_len = (size_t)bytes;
			bpf->bpf_pos = 0;
		}
		bytes = -1;
		payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos;
		memcpy(&packet, payload, sizeof(packet));
		if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen >
		    bpf->bpf_len)
			goto next; /* Packet beyond buffer, drop. */
		payload += packet.bh_hdrlen;
		if (packet.bh_caplen > len)
			bytes = (ssize_t)len;
		else
			bytes = (ssize_t)packet.bh_caplen;
		if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0)
			bpf->bpf_flags |= BPF_BCAST;
		else
			bpf->bpf_flags &= ~BPF_BCAST;
		memcpy(data, payload, (size_t)bytes);
next:
		bpf->bpf_pos += BPF_WORDALIGN(packet.bh_hdrlen +
		    packet.bh_caplen);
		if (bpf->bpf_pos >= bpf->bpf_len) {
			bpf->bpf_len = bpf->bpf_pos = 0;
			bpf->bpf_flags |= BPF_EOF;
		}
		if (bytes != -1)
			return bytes;
	}

	/* NOTREACHED */
}

int
bpf_attach(int fd, void *filter, unsigned int filter_len)
{
	struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };

	/* Install the filter. */
	return ioctl(fd, BIOCSETF, &pf);
}

#ifdef BIOCSETWF
static int
bpf_wattach(int fd, void *filter, unsigned int filter_len)
{
	struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };

	/* Install the filter. */
	return ioctl(fd, BIOCSETWF, &pf);
}
#endif
#endif

#ifndef __sun
/* SunOS is special too - sending via BPF goes nowhere. */
ssize_t
bpf_send(const struct bpf *bpf, uint16_t protocol,
    const void *data, size_t len)
{
	struct iovec iov[2];
	struct ether_header eh;

	switch(bpf->bpf_ifp->hwtype) {
	case ARPHRD_ETHER:
		memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost));
		memcpy(&eh.ether_shost, bpf->bpf_ifp->hwaddr,
		    sizeof(eh.ether_shost));
		eh.ether_type = htons(protocol);
		iov[0].iov_base = &eh;
		iov[0].iov_len = sizeof(eh);
		break;
	default:
		iov[0].iov_base = NULL;
		iov[0].iov_len = 0;
		break;
	}
	iov[1].iov_base = UNCONST(data);
	iov[1].iov_len = len;
	return writev(bpf->bpf_fd, iov, 2);
}
#endif

void
bpf_close(struct bpf *bpf)
{

	close(bpf->bpf_fd);
	free(bpf->bpf_buffer);
	free(bpf);
}

#ifdef ARP
#define BPF_CMP_HWADDR_LEN	((((HWADDR_LEN / 4) + 2) * 2) + 1)
static unsigned int
bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off,
    bool equal, const uint8_t *hwaddr, size_t hwaddr_len)
{
	struct bpf_insn *bp;
	size_t maclen, nlft, njmps;
	uint32_t mac32;
	uint16_t mac16;
	uint8_t jt, jf;

	/* Calc the number of jumps */
	if ((hwaddr_len / 4) >= 128) {
		errno = EINVAL;
		return 0;
	}
	njmps = (hwaddr_len / 4) * 2; /* 2 instructions per check */
	/* We jump after the 1st check. */
	if (njmps)
		njmps -= 2;
	nlft = hwaddr_len % 4;
	if (nlft) {
		njmps += (nlft / 2) * 2;
		nlft = nlft % 2;
		if (nlft)
			njmps += 2;

	}

	/* Skip to positive finish. */
	njmps++;
	if (equal) {
		jt = (uint8_t)njmps;
		jf = 0;
	} else {
		jt = 0;
		jf = (uint8_t)njmps;
	}

	bp = bpf;
	for (; hwaddr_len > 0;
	     hwaddr += maclen, hwaddr_len -= maclen, off += maclen)
	{
		if (bpf_len < 3) {
			errno = ENOBUFS;
			return 0;
		}
		bpf_len -= 3;

		if (hwaddr_len >= 4) {
			maclen = sizeof(mac32);
			memcpy(&mac32, hwaddr, maclen);
			BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, off);
			bp++;
			BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
			             htonl(mac32), jt, jf);
		} else if (hwaddr_len >= 2) {
			maclen = sizeof(mac16);
			memcpy(&mac16, hwaddr, maclen);
			BPF_SET_STMT(bp, BPF_LD + BPF_H + BPF_IND, off);
			bp++;
			BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
			             htons(mac16), jt, jf);
		} else {
			maclen = sizeof(*hwaddr);
			BPF_SET_STMT(bp, BPF_LD + BPF_B + BPF_IND, off);
			bp++;
			BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
			             *hwaddr, jt, jf);
		}
		if (jt)
			jt = (uint8_t)(jt - 2);
		if (jf)
			jf = (uint8_t)(jf - 2);
		bp++;
	}

	/* Last step is always return failure.
	 * Next step is a positive finish. */
	BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
	bp++;

	return (unsigned int)(bp - bpf);
}
#endif

#ifdef ARP
static const struct bpf_insn bpf_arp_ether [] = {
	/* Check this is an ARP packet. */
	BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
	         offsetof(struct ether_header, ether_type)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),

	/* Load frame header length into X */
	BPF_STMT(BPF_LDX + BPF_W + BPF_IMM, sizeof(struct ether_header)),

	/* Make sure the hardware type matches. */
	BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_hrd)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),

	/* Make sure the hardware length matches. */
	BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_hln)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K,
	         sizeof(((struct ether_arp *)0)->arp_sha), 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),
};
#define BPF_ARP_ETHER_LEN	__arraycount(bpf_arp_ether)

static const struct bpf_insn bpf_arp_filter [] = {
	/* Make sure this is for IP. */
	BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_pro)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),
	/* Make sure this is an ARP REQUEST. */
	BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_op)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),
	/* or ARP REPLY. */
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),
	/* Make sure the protocol length matches. */
	BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_pln)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(in_addr_t), 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),
};
#define BPF_ARP_FILTER_LEN	__arraycount(bpf_arp_filter)

/* One address is two checks of two statements. */
#define BPF_NADDRS		1
#define BPF_ARP_ADDRS_LEN	5 + ((BPF_NADDRS * 2) * 2)

#define BPF_ARP_LEN		BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN + \
				BPF_CMP_HWADDR_LEN + BPF_ARP_ADDRS_LEN

static int
bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv)
{
	const struct interface *ifp = bpf->bpf_ifp;
	struct bpf_insn buf[BPF_ARP_LEN + 1];
	struct bpf_insn *bp;
	uint16_t arp_len;

	bp = buf;
	/* Check frame header. */
	switch(ifp->hwtype) {
	case ARPHRD_ETHER:
		memcpy(bp, bpf_arp_ether, sizeof(bpf_arp_ether));
		bp += BPF_ARP_ETHER_LEN;
		arp_len = sizeof(struct ether_header)+sizeof(struct ether_arp);
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	/* Copy in the main filter. */
	memcpy(bp, bpf_arp_filter, sizeof(bpf_arp_filter));
	bp += BPF_ARP_FILTER_LEN;

	/* Ensure it's not from us. */
	bp += bpf_cmp_hwaddr(bp, BPF_CMP_HWADDR_LEN, sizeof(struct arphdr),
	                     !recv, ifp->hwaddr, ifp->hwlen);

	/* Match sender protocol address */
	BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND,
	    sizeof(struct arphdr) + ifp->hwlen);
	bp++;
	BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1);
	bp++;
	BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len);
	bp++;

	/* If we didn't match sender, then we're only interested in
	 * ARP probes to us, so check the null host sender. */
	BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, INADDR_ANY, 1, 0);
	bp++;
	BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
	bp++;

	/* Match target protocol address */
	BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, (sizeof(struct arphdr) +
	    (size_t)(ifp->hwlen * 2) + sizeof(in_addr_t)));
	bp++;
	BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1);
	bp++;
	BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len);
	bp++;

	/* No match, drop it */
	BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
	bp++;

#ifdef BIOCSETWF
	if (!recv)
		return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
#endif

	return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
}

int
bpf_arp(const struct bpf *bpf, const struct in_addr *ia)
{

#ifdef BIOCSETWF
	if (bpf_arp_rw(bpf, ia, true) == -1 ||
	    bpf_arp_rw(bpf, ia, false) == -1 ||
	    ioctl(bpf->bpf_fd, BIOCLOCK) == -1)
		return -1;
	return 0;
#else
	return bpf_arp_rw(bpf, ia, true);
#endif
}
#endif

#ifdef ARPHRD_NONE
static const struct bpf_insn bpf_bootp_none[] = {
};
#define BPF_BOOTP_NONE_LEN	__arraycount(bpf_bootp_none)
#endif

static const struct bpf_insn bpf_bootp_ether[] = {
	/* Make sure this is an IP packet. */
	BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
	         offsetof(struct ether_header, ether_type)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),

	/* Advance to the IP header. */
	BPF_STMT(BPF_LDX + BPF_K, sizeof(struct ether_header)),
};
#define BPF_BOOTP_ETHER_LEN	__arraycount(bpf_bootp_ether)

#define BOOTP_MIN_SIZE		sizeof(struct ip) + sizeof(struct udphdr) + \
				sizeof(struct bootp)

static const struct bpf_insn bpf_bootp_base[] = {
	/* Make sure it's an IPv4 packet. */
	BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0),
	BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0xf0),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x40, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),

	/* Make sure it's a UDP packet. */
	BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct ip, ip_p)),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),

	/* Make sure this isn't a fragment. */
	BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct ip, ip_off)),
	BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 0, 1),
	BPF_STMT(BPF_RET + BPF_K, 0),

	/* Advance to the UDP header. */
	BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0),
	BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x0f),
	BPF_STMT(BPF_ALU + BPF_MUL + BPF_K, 4),
	BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0),
	BPF_STMT(BPF_MISC + BPF_TAX, 0),
};
#define BPF_BOOTP_BASE_LEN	__arraycount(bpf_bootp_base)

static const struct bpf_insn bpf_bootp_read[] = {
	/* Make sure it's from and to the right port. */
	BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),
};
#define BPF_BOOTP_READ_LEN	__arraycount(bpf_bootp_read)

#ifdef BIOCSETWF
static const struct bpf_insn bpf_bootp_write[] = {
	/* Make sure it's from and to the right port. */
	BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPC << 16) + BOOTPS, 1, 0),
	BPF_STMT(BPF_RET + BPF_K, 0),
};
#define BPF_BOOTP_WRITE_LEN	__arraycount(bpf_bootp_write)
#endif

#define BPF_BOOTP_CHADDR_LEN	((BOOTP_CHADDR_LEN / 4) * 3)
#define	BPF_BOOTP_XID_LEN	4 /* BOUND check is 4 instructions */

#define BPF_BOOTP_LEN		BPF_BOOTP_ETHER_LEN + \
				BPF_BOOTP_BASE_LEN + BPF_BOOTP_READ_LEN + \
				BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4

static int
bpf_bootp_rw(const struct bpf *bpf, bool read)
{
	struct bpf_insn buf[BPF_BOOTP_LEN + 1];
	struct bpf_insn *bp;

	bp = buf;
	/* Check frame header. */
	switch(bpf->bpf_ifp->hwtype) {
#ifdef ARPHRD_NONE
	case ARPHRD_NONE:
		memcpy(bp, bpf_bootp_none, sizeof(bpf_bootp_none));
		bp += BPF_BOOTP_NONE_LEN;
		break;
#endif
	case ARPHRD_ETHER:
		memcpy(bp, bpf_bootp_ether, sizeof(bpf_bootp_ether));
		bp += BPF_BOOTP_ETHER_LEN;
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	/* Copy in the main filter. */
	memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base));
	bp += BPF_BOOTP_BASE_LEN;

#ifdef BIOCSETWF
	if (!read) {
		memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write));
		bp += BPF_BOOTP_WRITE_LEN;

		/* All passed, return the packet. */
		BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
		bp++;

		return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
	}
#else
	UNUSED(read);
#endif

	memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read));
	bp += BPF_BOOTP_READ_LEN;

	/* All passed, return the packet. */
	BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
	bp++;

	return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
}

int
bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia)
{

#ifdef BIOCSETWF
	if (bpf_bootp_rw(bpf, true) == -1 ||
	    bpf_bootp_rw(bpf, false) == -1 ||
	    ioctl(bpf->bpf_fd, BIOCLOCK) == -1)
		return -1;
	return 0;
#else
	return bpf_bootp_rw(bpf, true);
#endif
}