changeset 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 92569921a974
children d09c879cc4d5
files src/arp.c src/arp.h src/bpf.c src/bpf.h src/control.c src/dhcp.c src/dhcp.h src/dhcp6.c src/dhcp6.h src/dhcpcd.c src/dhcpcd.h src/eloop.c src/if-bsd.c src/if.c src/if.h src/ipv4.c src/ipv4.h src/ipv6.c src/ipv6nd.c src/ipv6nd.h src/privsep-bpf.c src/privsep-bpf.h src/privsep-inet.c src/privsep-inet.h src/privsep-root.c src/privsep.c src/privsep.h
diffstat 27 files changed, 792 insertions(+), 747 deletions(-) [+]
line wrap: on
line diff
--- a/src/arp.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/arp.c	Tue May 19 16:19:05 2020 +0100
@@ -67,14 +67,15 @@
 __CTASSERT(sizeof(struct arphdr) == 8);
 
 static ssize_t
-arp_request(const struct interface *ifp,
-    const struct in_addr *sip, const struct in_addr *tip)
+arp_request(const struct arp_state *astate,
+    const struct in_addr *sip)
 {
+	const struct interface *ifp = astate->iface;
+	const struct in_addr *tip = &astate->addr;
 	uint8_t arp_buffer[ARP_LEN];
 	struct arphdr ar;
 	size_t len;
 	uint8_t *p;
-	const struct iarp_state *state;
 
 	ar.ar_hrd = htons(ifp->hwtype);
 	ar.ar_pro = htons(ETHERTYPE_IP);
@@ -107,12 +108,11 @@
 
 #ifdef PRIVSEP
 	if (ifp->ctx->options & DHCPCD_PRIVSEP)
-		return ps_bpf_sendarp(ifp, arp_buffer, len);
+		return ps_bpf_sendarp(ifp, tip, arp_buffer, len);
 #endif
-	state = ARP_CSTATE(ifp);
 	/* Note that well formed ethernet will add extra padding
 	 * to ensure that the packet is at least 60 bytes (64 including FCS). */
-	return bpf_send(ifp, state->bpf_fd, ETHERTYPE_ARP, arp_buffer, len);
+	return bpf_send(astate->bpf, ETHERTYPE_ARP, arp_buffer, len);
 
 eexit:
 	errno = ENOBUFS;
@@ -179,7 +179,7 @@
 	    eloop_timespec_diff(&now, &astate->defend, NULL) < DEFEND_INTERVAL)
 		logwarnx("%s: %d second defence failed for %s",
 		    ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr));
-	else if (arp_request(ifp, &astate->addr, &astate->addr) == -1)
+	else if (arp_request(astate, &astate->addr) == -1)
 		logerr(__func__);
 	else {
 		logdebugx("%s: defended address %s",
@@ -222,7 +222,7 @@
 }
 
 void
-arp_packet(struct interface *ifp, uint8_t *data, size_t len)
+arp_packet(struct interface *ifp, uint8_t *data, size_t len, uint8_t bpf_flags)
 {
 	size_t fl = bpf_frame_header_len(ifp), falen;
 	const struct interface *ifn;
@@ -292,108 +292,39 @@
 		if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) ||
 		    (IN_IS_ADDR_UNSPECIFIED(&arm.sip) &&
 		    IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr) &&
-		    state->bpf_flags & BPF_BCAST))
+		    bpf_flags & BPF_BCAST))
 			arp_found(astate, &arm);
 	}
 }
 
 static void
-arp_close(struct interface *ifp)
-{
-	struct dhcpcd_ctx *ctx = ifp->ctx;
-	struct iarp_state *state;
-
-#ifdef PRIVSEP
-	if (IN_PRIVSEP(ctx)) {
-		if (IN_PRIVSEP_SE(ctx) &&
-		    ps_bpf_closearp(ifp) == -1)
-			logerr(__func__);
-		return;
-	}
-#endif
-
-	if ((state = ARP_STATE(ifp)) == NULL)
-		return;
-
-	if (state->bpf_fd == -1)
-		return;
-	eloop_event_delete(ctx->eloop, state->bpf_fd);
-	bpf_close(ifp, state->bpf_fd);
-	state->bpf_fd = -1;
-	state->bpf_flags |= BPF_EOF;
-}
-
-static void
-arp_tryfree(struct iarp_state *state)
-{
-	struct interface *ifp = state->ifp;
-
-	/* If there are no more ARP states, close the socket. */
-	if (TAILQ_FIRST(&state->arp_states) == NULL) {
-		arp_close(ifp);
-		if (state->bpf_flags & BPF_READING)
-			state->bpf_flags |= BPF_EOF;
-		else {
-			free(state);
-			ifp->if_data[IF_DATA_ARP] = NULL;
-		}
-	} else if (state->bpf_fd != -1) {
-		if (bpf_arp(ifp, state->bpf_fd) == -1)
-			logerr(__func__);
-	}
-}
-
-static void
 arp_read(void *arg)
 {
-	struct iarp_state *state = arg;
-	struct interface *ifp = state->ifp;
+	struct arp_state *astate = arg;
+	struct bpf *bpf = astate->bpf;
+	struct interface *ifp = astate->iface;
 	uint8_t buf[ARP_LEN];
 	ssize_t bytes;
+	struct in_addr addr = astate->addr;
 
 	/* Some RAW mechanisms are generic file descriptors, not sockets.
 	 * This means we have no kernel call to just get one packet,
 	 * so we have to process the entire buffer. */
-	state->bpf_flags &= ~BPF_EOF;
-	state->bpf_flags |= BPF_READING;
-	while (!(state->bpf_flags & BPF_EOF)) {
-		bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf),
-				 &state->bpf_flags);
+	bpf->bpf_flags &= ~BPF_EOF;
+	while (!(bpf->bpf_flags & BPF_EOF)) {
+		bytes = bpf_read(bpf, buf, sizeof(buf));
 		if (bytes == -1) {
 			logerr("%s: %s", __func__, ifp->name);
-			arp_close(ifp);
-			break;
+			arp_free(astate);
+			return;
 		}
-		arp_packet(ifp, buf, (size_t)bytes);
+		arp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags);
 		/* Check we still have a state after processing. */
-		if ((state = ARP_STATE(ifp)) == NULL)
+		if ((astate = arp_find(ifp, &addr)) == NULL)
+			break;
+		if ((bpf = astate->bpf) == NULL)
 			break;
 	}
-	if (state != NULL) {
-		state->bpf_flags &= ~BPF_READING;
-		/* Try and free the state if nothing left to do. */
-		arp_tryfree(state);
-	}
-}
-
-static int
-arp_open(struct interface *ifp)
-{
-	struct iarp_state *state;
-
-#ifdef PRIVSEP
-	if (IN_PRIVSEP_SE(ifp->ctx))
-		return ps_bpf_openarp(ifp) == -1 ? -1 : 0;
-#endif
-
-	state = ARP_STATE(ifp);
-	if (state->bpf_fd == -1) {
-		state->bpf_fd = bpf_open(ifp, bpf_arp);
-		if (state->bpf_fd == -1)
-			return -1;
-		eloop_event_add(ifp->ctx->eloop, state->bpf_fd, arp_read, state);
-	}
-	return state->bpf_fd;
 }
 
 static void
@@ -425,7 +356,7 @@
 	    ifp->name, inet_ntoa(astate->addr),
 	    astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM,
 	    (float)delay / MSEC_PER_SEC);
-	if (arp_request(ifp, NULL, &astate->addr) == -1)
+	if (arp_request(astate, NULL) == -1)
 		logerr(__func__);
 }
 
@@ -436,11 +367,6 @@
 	astate->probes = 0;
 	logdebugx("%s: probing for %s",
 	    astate->iface->name, inet_ntoa(astate->addr));
-	if (!(IN_PRIVSEP(astate->iface->ctx)) && arp_open(astate->iface) == -1)
-	{
-		logerr(__func__);
-		return;
-	}
 	arp_probe1(astate);
 }
 #endif	/* ARP */
@@ -501,7 +427,7 @@
 		goto skip_request;
 #endif
 
-	if (arp_request(ifp, &astate->addr, &astate->addr) == -1)
+	if (arp_request(astate, &astate->addr) == -1)
 		logerr(__func__);
 
 #ifndef __linux__
@@ -524,12 +450,6 @@
 	struct arp_state *a2;
 	int r;
 
-	if (!(IN_PRIVSEP(astate->iface->ctx)) && arp_open(astate->iface) == -1)
-	{
-		logerr(__func__);
-		return;
-	}
-
 	/* Cancel any other ARP announcements for this address. */
 	TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) {
 		state = ARP_STATE(ifp);
@@ -608,22 +528,12 @@
 	struct arp_state *astate;
 
 	if ((state = ARP_STATE(ifp)) == NULL) {
-#ifdef PRIVSEP
-		/* We need to ensure ARP is spawned so we can add to it. */
-		if (IN_PRIVSEP_SE(ifp->ctx) && arp_open(ifp) == -1) {
-			logerr(__func__);
-			return NULL;
-		}
-#endif
 	        ifp->if_data[IF_DATA_ARP] = malloc(sizeof(*state));
 		state = ARP_STATE(ifp);
 		if (state == NULL) {
 			logerr(__func__);
 			return NULL;
 		}
-		state->ifp = ifp;
-		state->bpf_fd = -1;
-		state->bpf_flags = 0;
 		TAILQ_INIT(&state->arp_states);
 	} else {
 		if (addr && (astate = arp_find(ifp, addr)))
@@ -635,12 +545,31 @@
 		return NULL;
 	}
 	astate->iface = ifp;
+	astate->addr = *addr;
+
+#ifdef PRIVSEP
+	if (IN_PRIVSEP(ifp->ctx)) {
+		if (ps_bpf_openarp(ifp, addr) == -1) {
+			logerr(__func__);
+			free(astate);
+			return NULL;
+		}
+	} else
+#endif
+	{
+		astate->bpf = bpf_open(ifp, bpf_arp, addr);
+		if (astate->bpf == NULL) {
+			logerr(__func__);
+			free(astate);
+			return NULL;
+		}
+		eloop_event_add(ifp->ctx->eloop, astate->bpf->bpf_fd,
+		    arp_read, astate);
+	}
+
+
 	state = ARP_STATE(ifp);
 	TAILQ_INSERT_TAIL(&state->arp_states, astate, next);
-	if (state->bpf_fd != -1) {
-		if (bpf_arp(ifp, state->bpf_fd) == -1)
-			logerr(__func__); /* try and continue */
-	}
 	return astate;
 }
 
@@ -655,19 +584,36 @@
 arp_free(struct arp_state *astate)
 {
 	struct interface *ifp;
+	struct dhcpcd_ctx *ctx;
 	struct iarp_state *state;
 
 	if (astate == NULL)
 		return;
 
 	ifp = astate->iface;
-	eloop_timeout_delete(ifp->ctx->eloop, NULL, astate);
+	ctx = ifp->ctx;
+	eloop_timeout_delete(ctx->eloop, NULL, astate);
+
 	state =	ARP_STATE(ifp);
 	TAILQ_REMOVE(&state->arp_states, astate, next);
 	if (astate->free_cb)
 		astate->free_cb(astate);
+
+#ifdef PRIVSEP
+	if (IN_PRIVSEP(ctx) && ps_bpf_closearp(ifp, &astate->addr) == -1)
+		logerr(__func__);
+#endif
+	if (astate->bpf != NULL) {
+		eloop_event_delete(ctx->eloop, astate->bpf->bpf_fd);
+		bpf_close(astate->bpf);
+	}
+
 	free(astate);
-	arp_tryfree(state);
+
+	if (TAILQ_FIRST(&state->arp_states) == NULL) {
+		free(state);
+		ifp->if_data[IF_DATA_ARP] = NULL;
+	}
 }
 
 void
@@ -688,6 +634,4 @@
 	while ((state = ARP_STATE(ifp)) != NULL &&
 	    (astate = TAILQ_FIRST(&state->arp_states)) != NULL)
 		arp_free(astate);
-
-	/* No need to close because the last free will close */
 }
--- a/src/arp.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/arp.h	Tue May 19 16:19:05 2020 +0100
@@ -41,6 +41,7 @@
 #define RATE_LIMIT_INTERVAL	60
 #define DEFEND_INTERVAL		10
 
+#include "bpf.h"
 #include "dhcpcd.h"
 #include "if.h"
 
@@ -66,24 +67,22 @@
 struct arp_state {
 	TAILQ_ENTRY(arp_state) next;
 	struct interface *iface;
+	struct in_addr addr;
+	struct bpf *bpf;
+
+	int probes;
+	int claims;
+	struct timespec defend;
 
 	void (*found_cb)(struct arp_state *, const struct arp_msg *);
 	void (*not_found_cb)(struct arp_state *);
 	void (*announced_cb)(struct arp_state *);
 	void (*defend_failed_cb)(struct arp_state *);
 	void (*free_cb)(struct arp_state *);
-
-	struct in_addr addr;
-	int probes;
-	int claims;
-	struct timespec defend;
 };
 TAILQ_HEAD(arp_statehead, arp_state);
 
 struct iarp_state {
-	struct interface *ifp;
-	int bpf_fd;
-	unsigned int bpf_flags;
 	struct arp_statehead arp_states;
 };
 
@@ -93,7 +92,7 @@
 	((const struct iarp_state *)(ifp)->if_data[IF_DATA_ARP])
 
 #ifdef ARP
-void arp_packet(struct interface *, uint8_t *, size_t);
+void arp_packet(struct interface *, uint8_t *, size_t, uint8_t);
 struct arp_state *arp_new(struct interface *, const struct in_addr *);
 void arp_probe(struct arp_state *);
 void arp_announce(struct arp_state *);
--- a/src/bpf.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/bpf.c	Tue May 19 16:19:05 2020 +0100
@@ -57,8 +57,6 @@
 #include "if.h"
 #include "logerr.h"
 
-#define	ARP_ADDRS_MAX	3
-
 /* BPF helper macros */
 #ifdef __linux__
 #define	BPF_WHOLEPACKET		0x7fffffff /* work around buggy LPF filters */
@@ -129,12 +127,12 @@
     { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
 
 int
-bpf_frame_bcast(const struct interface *ifp, const char *frame)
+bpf_frame_bcast(const struct interface *ifp, const void *frame)
 {
 
 	switch (ifp->hwtype) {
 	case ARPHRD_ETHER:
-		return memcmp(frame +
+		return memcmp((const char *)frame +
 		    offsetof(struct ether_header, ether_dhost),
 		    etherbcastaddr, sizeof(etherbcastaddr));
 	default:
@@ -148,15 +146,15 @@
 
 const char *bpf_name = "Berkley Packet Filter";
 
-int
-bpf_open(struct interface *ifp, int (*filter)(struct interface *, int))
+struct bpf *
+bpf_open(const struct interface *ifp,
+    int (*filter)(const struct bpf *, const struct in_addr *),
+    const struct in_addr *ia)
 {
-	struct ipv4_state *state;
-	int fd = -1;
-	struct ifreq ifr;
+	struct bpf *bpf;
+	struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 };
+	struct ifreq ifr = { .ifr_flags = 0 };
 	int ibuf_len = 0;
-	size_t buf_len;
-	struct bpf_version pv;
 #ifdef BIOCIMMEDIATE
 	unsigned int flags;
 #endif
@@ -174,29 +172,31 @@
 	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++);
-		fd = open(device, O_RDWR | O_NONBLOCK
+		bpf->bpf_fd = open(device, O_RDWR | O_NONBLOCK
 #ifdef O_CLOEXEC
 				| O_CLOEXEC
 #endif
 		);
-	} while (fd == -1 && errno == EBUSY);
+	} while (bpf->bpf_fd == -1 && errno == EBUSY);
 #endif
 
-	if (fd == -1)
-		return -1;
+	if (bpf->bpf_fd == -1)
+		goto eexit;
 
 #ifndef O_CLOEXEC
-	if ((fd_opts = fcntl(fd, F_GETFD)) == -1 ||
-	    fcntl(fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1) {
-		close(fd);
-		return -1;
-	}
+	if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 ||
+	    fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1)
+		goto eexit;
 #endif
 
-	memset(&pv, 0, sizeof(pv));
-	if (ioctl(fd, BIOCVERSION, &pv) == -1)
+	if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1)
 		goto eexit;
 	if (pv.bv_major != BPF_MAJOR_VERSION ||
 	    pv.bv_minor < BPF_MINOR_VERSION) {
@@ -204,59 +204,49 @@
 		goto eexit;
 	}
 
-	if (filter(ifp, fd) != 0)
+	strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+	if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1)
 		goto eexit;
 
-	memset(&ifr, 0, sizeof(ifr));
-	strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
-	if (ioctl(fd, BIOCSETIF, &ifr) == -1)
+#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(fd, BIOCGBLEN, &ibuf_len) == -1)
-		goto eexit;
-	buf_len = (size_t)ibuf_len;
-	state = ipv4_getstate(ifp);
-	if (state == NULL)
+	if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1)
 		goto eexit;
-	if (state->buffer_size != buf_len) {
-		void *nb;
-
-		if ((nb = realloc(state->buffer, buf_len)) == NULL)
-			goto eexit;
-		state->buffer = nb;
-		state->buffer_size = buf_len;
-	}
-
-#ifdef BIOCIMMEDIATE
-	flags = 1;
-	if (ioctl(fd, BIOCIMMEDIATE, &flags) == -1)
+	bpf->bpf_size = (size_t)ibuf_len;
+	bpf->bpf_buffer = malloc(bpf->bpf_size);
+	if (bpf->bpf_buffer == NULL)
 		goto eexit;
-#endif
-
-	return fd;
+	return bpf;
 
 eexit:
-	close(fd);
-	return -1;
+	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 interface *ifp, int fd, void *data, size_t len,
-    unsigned int *flags)
+bpf_read(struct bpf *bpf, void *data, size_t len)
 {
 	ssize_t bytes;
-	struct ipv4_state *state = IPV4_STATE(ifp);
-
 	struct bpf_hdr packet;
 	const char *payload;
 
-	*flags &= ~BPF_EOF;
+	bpf->bpf_flags &= ~BPF_EOF;
 	for (;;) {
-		if (state->buffer_len == 0) {
-			bytes = read(fd, state->buffer, state->buffer_size);
+		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. */
@@ -267,31 +257,31 @@
 #endif
 			if (bytes == -1 || bytes == 0)
 				return bytes;
-			state->buffer_len = (size_t)bytes;
-			state->buffer_pos = 0;
+			bpf->bpf_len = (size_t)bytes;
+			bpf->bpf_pos = 0;
 		}
 		bytes = -1;
-		memcpy(&packet, state->buffer + state->buffer_pos,
-		    sizeof(packet));
-		if (state->buffer_pos + packet.bh_caplen + packet.bh_hdrlen >
-		    state->buffer_len)
+		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 = state->buffer + state->buffer_pos + packet.bh_hdrlen;
-		if (bpf_frame_bcast(ifp, payload) == 0)
-			*flags |= BPF_BCAST;
-		else
-			*flags &= ~BPF_BCAST;
+		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:
-		state->buffer_pos += BPF_WORDALIGN(packet.bh_hdrlen +
+		bpf->bpf_pos += BPF_WORDALIGN(packet.bh_hdrlen +
 		    packet.bh_caplen);
-		if (state->buffer_pos >= state->buffer_len) {
-			state->buffer_len = state->buffer_pos = 0;
-			*flags |= BPF_EOF;
+		if (bpf->bpf_pos >= bpf->bpf_len) {
+			bpf->bpf_len = bpf->bpf_pos = 0;
+			bpf->bpf_flags |= BPF_EOF;
 		}
 		if (bytes != -1)
 			return bytes;
@@ -324,16 +314,17 @@
 #ifndef __sun
 /* SunOS is special too - sending via BPF goes nowhere. */
 ssize_t
-bpf_send(const struct interface *ifp, int fd, uint16_t protocol,
+bpf_send(const struct bpf *bpf, uint16_t protocol,
     const void *data, size_t len)
 {
 	struct iovec iov[2];
 	struct ether_header eh;
 
-	switch(ifp->hwtype) {
+	switch(bpf->bpf_ifp->hwtype) {
 	case ARPHRD_ETHER:
 		memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost));
-		memcpy(&eh.ether_shost, ifp->hwaddr, sizeof(eh.ether_shost));
+		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);
@@ -345,19 +336,105 @@
 	}
 	iov[1].iov_base = UNCONST(data);
 	iov[1].iov_len = len;
-	return writev(fd, iov, 2);
+	return writev(bpf->bpf_fd, iov, 2);
 }
 #endif
 
-int
-bpf_close(struct interface *ifp, int fd)
+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 ipv4_state *state = IPV4_STATE(ifp);
+	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;
+
+	}
 
-	/* Rewind the buffer on closing. */
-	state->buffer_len = state->buffer_pos = 0;
-	return close(fd);
+	/* 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 [] = {
@@ -401,22 +478,22 @@
 };
 #define BPF_ARP_FILTER_LEN	__arraycount(bpf_arp_filter)
 
-#define BPF_ARP_ADDRS_LEN	1 + (ARP_ADDRS_MAX * 2) + 3 + \
-				(ARP_ADDRS_MAX * 2) + 1
-
-#define BPF_ARP_LEN		BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN
+/* One address is two checks of two statements. */
+#define BPF_NADDRS		1
+#define BPF_ARP_ADDRS_LEN	5 + ((BPF_NADDRS * 2) * 2)
 
-int
-bpf_arp(struct interface *ifp, int fd)
+#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)
 {
-	struct bpf_insn bpf[BPF_ARP_LEN + 1];
+	const struct interface *ifp = bpf->bpf_ifp;
+	struct bpf_insn buf[BPF_ARP_LEN + 1];
 	struct bpf_insn *bp;
 	uint16_t arp_len;
 
-	if (fd == -1)
-		return 0;
-
-	bp = bpf;
+	bp = buf;
 	/* Check frame header. */
 	switch(ifp->hwtype) {
 	case ARPHRD_ETHER:
@@ -433,20 +510,60 @@
 	memcpy(bp, bpf_arp_filter, sizeof(bpf_arp_filter));
 	bp += BPF_ARP_FILTER_LEN;
 
-	/* Past the filer so return the packet. */
+	/* 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 (bpf_attach(fd, bpf, (unsigned int)(bp - bpf)) == -1)
-		return -1;
+	/* 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 (bpf_wattach(fd, bpf, (unsigned int)(bp - bpf)) == -1 ||
-	    ioctl(fd, BIOCLOCK) == -1)
-		return -1;
+	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
 
@@ -523,17 +640,14 @@
 				BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4
 
 static int
-bpf_bootp_rw(struct interface *ifp, int fd, bool read)
+bpf_bootp_rw(const struct bpf *bpf, bool read)
 {
-	struct bpf_insn bpf[BPF_BOOTP_LEN + 1];
+	struct bpf_insn buf[BPF_BOOTP_LEN + 1];
 	struct bpf_insn *bp;
 
-	if (fd == -1)
-		return 0;
-
-	bp = bpf;
+	bp = buf;
 	/* Check frame header. */
-	switch(ifp->hwtype) {
+	switch(bpf->bpf_ifp->hwtype) {
 #ifdef ARPHRD_NONE
 	case ARPHRD_NONE:
 		memcpy(bp, bpf_bootp_none, sizeof(bpf_bootp_none));
@@ -562,7 +676,7 @@
 		BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
 		bp++;
 
-		return bpf_wattach(fd, bpf, (unsigned int)(bp - bpf));
+		return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
 	}
 #else
 	UNUSED(read);
@@ -575,20 +689,20 @@
 	BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
 	bp++;
 
-	return bpf_attach(fd, bpf, (unsigned int)(bp - bpf));
+	return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
 }
 
 int
-bpf_bootp(struct interface *ifp, int fd)
+bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia)
 {
 
 #ifdef BIOCSETWF
-	if (bpf_bootp_rw(ifp, fd, true) == -1 ||
-	    bpf_bootp_rw(ifp, fd, false) == -1 ||
-	    ioctl(fd, BIOCLOCK) == -1)
+	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(ifp, fd, true);
+	return bpf_bootp_rw(bpf, true);
 #endif
 }
--- a/src/bpf.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/bpf.h	Tue May 19 16:19:05 2020 +0100
@@ -29,10 +29,9 @@
 #ifndef BPF_HEADER
 #define BPF_HEADER
 
-#define	BPF_READING		(1U << 0)
-#define	BPF_EOF			(1U << 1)
-#define	BPF_PARTIALCSUM		(1U << 2)
-#define	BPF_BCAST		(1U << 3)
+#define	BPF_EOF			0x01
+#define	BPF_PARTIALCSUM		0x02
+#define	BPF_BCAST		0x04
 
 /*
  * Even though we program the BPF filter should we trust it?
@@ -55,16 +54,28 @@
 
 #include "dhcpcd.h"
 
+struct bpf {
+	const struct interface *bpf_ifp;
+	int bpf_fd;
+	uint8_t bpf_flags;
+	void *bpf_buffer;
+	size_t bpf_size;
+	size_t bpf_len;
+	size_t bpf_pos;
+};
+
 extern const char *bpf_name;
 size_t bpf_frame_header_len(const struct interface *);
 void *bpf_frame_header_src(const struct interface *, void *, size_t *);
 void *bpf_frame_header_dst(const struct interface *, void *, size_t *);
-int bpf_frame_bcast(const struct interface *, const char *frame);
-int bpf_open(struct interface *, int (*)(struct interface *, int));
-int bpf_close(struct interface *, int);
+int bpf_frame_bcast(const struct interface *, const void *);
+struct bpf * bpf_open(const struct interface *,
+    int (*)(const struct bpf *, const struct in_addr *),
+    const struct in_addr *);
+void bpf_close(struct bpf *);
 int bpf_attach(int, void *, unsigned int);
-ssize_t bpf_send(const struct interface *, int, uint16_t, const void *, size_t);
-ssize_t bpf_read(struct interface *, int, void *, size_t, unsigned int *);
-int bpf_arp(struct interface *, int);
-int bpf_bootp(struct interface *, int);
+ssize_t bpf_send(const struct bpf *, uint16_t, const void *, size_t);
+ssize_t bpf_read(struct bpf *, void *, size_t);
+int bpf_arp(const struct bpf *, const struct in_addr *);
+int bpf_bootp(const struct bpf *, const struct in_addr *);
 #endif
--- a/src/control.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/control.c	Tue May 19 16:19:05 2020 +0100
@@ -198,10 +198,8 @@
 {
 	int fd;
 
-#define SOCK_FLAGS	SOCK_CLOEXEC | SOCK_NONBLOCK
-	if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_FLAGS, 0)) == -1)
+	if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0)) == -1)
 		return -1;
-#undef SOCK_FLAGS
 	memset(sa, 0, sizeof(*sa));
 	sa->sun_family = AF_UNIX;
 	if (unpriv)
--- a/src/dhcp.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/dhcp.c	Tue May 19 16:19:05 2020 +0100
@@ -1443,6 +1443,7 @@
 	if (get_option_uint32(ctx, &lease->renewaltime,
 	    bootp, len, DHO_RENEWALTIME) != 0)
 		lease->renewaltime = 0;
+	lease->renewaltime = 5;
 	if (get_option_uint32(ctx, &lease->rebindtime,
 	    bootp, len, DHO_REBINDTIME) != 0)
 		lease->rebindtime = 0;
@@ -1537,16 +1538,15 @@
 	}
 #endif
 
-	if (state->bpf_fd != -1) {
-		eloop_event_delete(ctx->eloop, state->bpf_fd);
-		bpf_close(ifp, state->bpf_fd);
-		state->bpf_fd = -1;
-		state->bpf_flags |= BPF_EOF;
+	if (state->bpf != NULL) {
+		eloop_event_delete(ctx->eloop, state->bpf->bpf_fd);
+		bpf_close(state->bpf);
+		state->bpf = NULL;
 	}
-	if (state->udp_fd != -1) {
-		eloop_event_delete(ctx->eloop, state->udp_fd);
-		close(state->udp_fd);
-		state->udp_fd = -1;
+	if (state->udp_rfd != -1) {
+		eloop_event_delete(ctx->eloop, state->udp_rfd);
+		close(state->udp_rfd);
+		state->udp_rfd = -1;
 	}
 
 	state->interval = 0;
@@ -1559,7 +1559,7 @@
 	struct sockaddr_in sin;
 	int n;
 
-	if ((s = xsocket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP)) == -1)
+	if ((s = xsocket(PF_INET, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP)) == -1)
 		return -1;
 
 	n = 1;
@@ -1572,6 +1572,11 @@
 	if (setsockopt(s, IPPROTO_IP, IP_RECVPKTINFO, &n, sizeof(n)) == -1)
 		goto errexit;
 #endif
+#ifdef SO_RERROR
+	if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1)
+		goto errexit;
+#endif
+
 	memset(&sin, 0, sizeof(sin));
 	sin.sin_family = AF_INET;
 	sin.sin_port = htons(BOOTPC);
@@ -1669,33 +1674,28 @@
 		.sin_len = sizeof(sin),
 #endif
 	};
+	struct udphdr udp = {
+	    .uh_sport = htons(BOOTPC),
+	    .uh_dport = htons(BOOTPS),
+	    .uh_ulen = htons(sizeof(udp) + len),
+	};
 	struct iovec iov[] = {
-		{ .iov_base = data, .iov_len = len }
+	    { .iov_base = &udp, .iov_len = sizeof(udp), },
+	    { .iov_base = data, .iov_len = len, },
 	};
 	struct msghdr msg = {
 		.msg_name = (void *)&sin,
 		.msg_namelen = sizeof(sin),
 		.msg_iov = iov,
-		.msg_iovlen = 1,
+		.msg_iovlen = __arraycount(iov),
 	};
-	struct dhcp_state *state = D_STATE(ifp);
-	ssize_t r;
-	int fd;
+	struct dhcpcd_ctx *ctx = ifp->ctx;
 
 #ifdef PRIVSEP
-	if (ifp->ctx->options & DHCPCD_PRIVSEP)
-		return ps_inet_sendbootp(state->addr, &msg);
+	if (ctx->options & DHCPCD_PRIVSEP)
+		return ps_inet_sendbootp(ifp, &msg);
 #endif
-	fd = state->udp_fd;
-	if (fd == -1) {
-		fd = dhcp_openudp(&state->addr->addr);
-		if (fd == -1)
-			return -1;
-	}
-	r = sendmsg(fd, &msg, 0);
-	if (state->udp_fd == -1)
-		close(fd);
-	return r;
+	return sendmsg(ctx->udp_wfd, &msg, 0);
 }
 
 static void
@@ -1771,8 +1771,7 @@
 	 * interface we want to configure because we can't dictate the
 	 * interface via IP_PKTINFO unlike for IPv6.
 	 */
-	if (to.s_addr != INADDR_BROADCAST)
-	{
+	if (to.s_addr != INADDR_BROADCAST) {
 		if (dhcp_sendudp(ifp, &to, bootp, len) != -1)
 			goto out;
 		logerr("%s: dhcp_sendudp", ifp->name);
@@ -1791,8 +1790,7 @@
 		free(udp);
 #endif
 	} else {
-		r = bpf_send(ifp, state->bpf_fd,
-		    ETHERTYPE_IP, (uint8_t *)udp, ulen);
+		r = bpf_send(state->bpf, ETHERTYPE_IP, udp, ulen);
 		free(udp);
 	}
 	/* If we failed to send a raw packet this normally means
@@ -2073,30 +2071,84 @@
 }
 #endif
 
-#if defined(ARP) && (!defined(KERNEL_RFC5227) || defined(ARPING))
+#ifdef ARP
+#ifndef KERNEL_RFC5227
+static void
+dhcp_arp_defend_failed(struct arp_state *astate)
+{
+	struct interface *ifp = astate->iface;
+
+	dhcp_drop(ifp, "EXPIRED");
+	dhcp_start1(ifp);
+}
+#endif
+
+#if !defined(KERNEL_RFC5227) || defined(ARPING)
+static void dhcp_arp_not_found(struct arp_state *);
+
+static struct arp_state *
+dhcp_arp_new(struct interface *ifp, struct in_addr *addr)
+{
+	struct arp_state *astate;
+
+	astate = arp_new(ifp, addr);
+	if (astate == NULL)
+		return NULL;
+
+	astate->found_cb = dhcp_arp_found;
+	astate->not_found_cb = dhcp_arp_not_found;
+#ifdef KERNEL_RFC5227
+	astate->announced_cb = dhcp_arp_announced;
+#else
+	astate->announced_cb = NULL;
+	astate->defend_failed_cb = dhcp_arp_defend_failed;
+#endif
+	return astate;
+}
+#endif
+
+#ifdef ARPING
+static int
+dhcp_arping(struct interface *ifp)
+{
+	struct dhcp_state *state;
+	struct if_options *ifo;
+	struct arp_state *astate;
+	struct in_addr addr;
+
+	state = D_STATE(ifp);
+	ifo = ifp->options;
+
+	if (ifo->arping_len == 0 || state->arping_index > ifo->arping_len)
+		return 0;
+
+	if (state->arping_index + 1 == ifo->arping_len) {
+		state->arping_index++;
+		dhcpcd_startinterface(ifp);
+		return 1;
+	}
+
+	addr.s_addr = ifo->arping[++state->arping_index];
+	astate = dhcp_arp_new(ifp, &addr);
+	if (astate == NULL) {
+		logerr(__func__);
+		return -1;
+	}
+	arp_probe(astate);
+	return 1;
+}
+#endif
+
+#if !defined(KERNEL_RFC5227) || defined(ARPING)
 static void
 dhcp_arp_not_found(struct arp_state *astate)
 {
 	struct interface *ifp;
-#ifdef ARPING
-	struct dhcp_state *state;
-	struct if_options *ifo;
-#endif
 
 	ifp = astate->iface;
 #ifdef ARPING
-	state = D_STATE(ifp);
-	ifo = ifp->options;
-	if (ifo->arping_len && state->arping_index < ifo->arping_len) {
-		/* We didn't find a profile for this
-		 * address or hwaddr, so move to the next
-		 * arping profile */
-		if (++state->arping_index < ifo->arping_len) {
-			arp_probe(astate);
-			return;
-		}
+	if (dhcp_arping(ifp) == 1) {
 		arp_free(astate);
-		dhcpcd_startinterface(ifp);
 		return;
 	}
 #endif
@@ -2146,6 +2198,7 @@
 	arp_free(astate);
 	dhcp_addr_duplicated(ifp, &addr);
 }
+#endif
 
 #ifdef KERNEL_RFC5227
 static void
@@ -2304,8 +2357,8 @@
 	}
 #endif
 
-	state->udp_fd = dhcp_openudp(&state->addr->addr);
-	if (state->udp_fd == -1) {
+	state->udp_rfd = dhcp_openudp(&state->addr->addr);
+	if (state->udp_rfd == -1) {
 		logerr(__func__);
 		/* Address sharing without master mode is not supported.
 		 * It's also possible another DHCP client could be running,
@@ -2314,7 +2367,7 @@
 		dhcp_openbpf(ifp);
 		return;
 	}
-	eloop_event_add(ctx->eloop, state->udp_fd, dhcp_handleifudp, ifp);
+	eloop_event_add(ctx->eloop, state->udp_rfd, dhcp_handleifudp, ifp);
 }
 
 static void
@@ -2361,41 +2414,6 @@
 	return sizeof(**bootp);
 }
 
-#ifdef ARP
-#ifndef KERNEL_RFC5227
-static void
-dhcp_arp_defend_failed(struct arp_state *astate)
-{
-	struct interface *ifp = astate->iface;
-
-	dhcp_drop(ifp, "EXPIRED");
-	dhcp_start1(ifp);
-}
-#endif
-
-#if !defined(KERNEL_RFC5227) || defined(ARPING)
-static struct arp_state *
-dhcp_arp_new(struct interface *ifp, struct in_addr *addr)
-{
-	struct arp_state *astate;
-
-	astate = arp_new(ifp, addr);
-	if (astate == NULL)
-		return NULL;
-
-	astate->found_cb = dhcp_arp_found;
-	astate->not_found_cb = dhcp_arp_not_found;
-#ifdef KERNEL_RFC5227
-	astate->announced_cb = dhcp_arp_announced;
-#else
-	astate->announced_cb = NULL;
-	astate->defend_failed_cb = dhcp_arp_defend_failed;
-#endif
-	return astate;
-}
-#endif
-#endif /* ARP */
-
 #if defined(ARP) || defined(KERNEL_RFC5227)
 static int
 dhcp_arp_address(struct interface *ifp)
@@ -3231,7 +3249,7 @@
 			state->reason = "TEST";
 			script_runreason(ifp, state->reason);
 			eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
-			state->bpf_flags |= BPF_EOF;
+			state->bpf->bpf_flags |= BPF_EOF;
 			return;
 		}
 		eloop_timeout_delete(ifp->ctx->eloop, send_discover, ifp);
@@ -3446,15 +3464,15 @@
 }
 
 void
-dhcp_packet(struct interface *ifp, uint8_t *data, size_t len)
+dhcp_packet(struct interface *ifp, uint8_t *data, size_t len, uint8_t bpf_flags)
 {
 	struct bootp *bootp;
 	struct in_addr from;
 	size_t udp_len;
+	size_t fl = bpf_frame_header_len(ifp);
+#ifdef PRIVSEP
 	const struct dhcp_state *state = D_CSTATE(ifp);
-	size_t fl = bpf_frame_header_len(ifp);
-
-#ifdef PRIVSEP
+
 	/* Ignore double reads */
 	if (IN_PRIVSEP(ifp->ctx)) {
 		switch (state->state) {
@@ -3470,8 +3488,8 @@
 	/* Trim frame header */
 	if (fl != 0) {
 		if (len < fl) {
-			logerrx("%s: %s: short frame header",
-			    __func__, ifp->name);
+			logerrx("%s: %s: short frame header %zu",
+			    __func__, ifp->name, len);
 			return;
 		}
 		len -= fl;
@@ -3487,7 +3505,7 @@
 		return;
 	}
 
-	if (!checksums_valid(data, &from, state->bpf_flags)) {
+	if (!checksums_valid(data, &from, bpf_flags)) {
 		logerrx("%s: checksum failure from %s",
 		    ifp->name, inet_ntoa(from));
 		return;
@@ -3511,15 +3529,11 @@
 	uint8_t buf[FRAMELEN_MAX];
 	ssize_t bytes;
 	struct dhcp_state *state = D_STATE(ifp);
-
-	/* Some RAW mechanisms are generic file descriptors, not sockets.
-	 * This means we have no kernel call to just get one packet,
-	 * so we have to process the entire buffer. */
-	state->bpf_flags &= ~BPF_EOF;
-	state->bpf_flags |= BPF_READING;
-	while (!(state->bpf_flags & BPF_EOF)) {
-		bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf),
-				 &state->bpf_flags);
+	struct bpf *bpf = state->bpf;
+
+	bpf->bpf_flags &= ~BPF_EOF;
+	while (!(bpf->bpf_flags & BPF_EOF)) {
+		bytes = bpf_read(bpf, buf, sizeof(buf));
 		if (bytes == -1) {
 			if (state->state != DHS_NONE) {
 				logerr("%s: %s", __func__, ifp->name);
@@ -3527,13 +3541,13 @@
 			}
 			break;
 		}
-		dhcp_packet(ifp, buf, (size_t)bytes);
+		dhcp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags);
 		/* Check we still have a state after processing. */
 		if ((state = D_STATE(ifp)) == NULL)
 			break;
+		if ((bpf = state->bpf) == NULL)
+			break;
 	}
-	if (state != NULL)
-		state->bpf_flags &= ~BPF_READING;
 }
 
 void
@@ -3557,7 +3571,7 @@
 		return;
 	}
 
-	if (state->bpf_fd != -1) {
+	if (state->bpf != NULL) {
 		/* Avoid a duplicate read if BPF is open for the interface. */
 		return;
 	}
@@ -3610,9 +3624,9 @@
 
 	if (ifp != NULL) {
 		state = D_CSTATE(ifp);
-		s = state->udp_fd;
+		s = state->udp_rfd;
 	} else
-		s = ctx->udp_fd;
+		s = ctx->udp_rfd;
 
 	bytes = recvmsg(s, &msg, 0);
 	if (bytes == -1) {
@@ -3657,11 +3671,11 @@
 	}
 #endif
 
-	if (state->bpf_fd != -1)
+	if (state->bpf != NULL)
 		return 0;
 
-	state->bpf_fd = bpf_open(ifp, bpf_bootp);
-	if (state->bpf_fd == -1) {
+	state->bpf = bpf_open(ifp, bpf_bootp, NULL);
+	if (state->bpf == NULL) {
 		if (errno == ENOENT) {
 			logerrx("%s not found", bpf_name);
 			/* May as well disable IPv4 entirely at
@@ -3673,7 +3687,7 @@
 	}
 
 	eloop_event_add(ifp->ctx->eloop,
-	    state->bpf_fd, dhcp_readbpf, ifp);
+	    state->bpf->bpf_fd, dhcp_readbpf, ifp);
 	return 0;
 }
 
@@ -3707,10 +3721,14 @@
 		}
 	}
 	if (ifp == NULL) {
-		if (ctx->udp_fd != -1) {
-			eloop_event_delete(ctx->eloop, ctx->udp_fd);
-			close(ctx->udp_fd);
-			ctx->udp_fd = -1;
+		if (ctx->udp_rfd != -1) {
+			eloop_event_delete(ctx->eloop, ctx->udp_rfd);
+			close(ctx->udp_rfd);
+			ctx->udp_rfd = -1;
+		}
+		if (ctx->udp_wfd != -1) {
+			close(ctx->udp_wfd);
+			ctx->udp_wfd = -1;
 		}
 
 		free(ctx->opt_buffer);
@@ -3734,8 +3752,7 @@
 
 	state->state = DHS_NONE;
 	/* 0 is a valid fd, so init to -1 */
-	state->bpf_fd = -1;
-	state->udp_fd = -1;
+	state->udp_rfd = -1;
 #ifdef ARPING
 	state->arping_index = -1;
 #endif
@@ -3849,14 +3866,21 @@
 	 * Only do this in master mode so we don't swallow messages
 	 * for dhcpcd running on another interface. */
 	if ((ctx->options & (DHCPCD_MASTER|DHCPCD_PRIVSEP)) == DHCPCD_MASTER
-	    && ctx->udp_fd == -1)
+	    && ctx->udp_rfd == -1)
 	{
-		ctx->udp_fd = dhcp_openudp(NULL);
-		if (ctx->udp_fd == -1) {
+		ctx->udp_rfd = dhcp_openudp(NULL);
+		if (ctx->udp_rfd == -1) {
 			logerr(__func__);
 			return;
 		}
-		eloop_event_add(ctx->eloop, ctx->udp_fd, dhcp_handleudp, ctx);
+		eloop_event_add(ctx->eloop, ctx->udp_rfd, dhcp_handleudp, ctx);
+	}
+	if (!IN_PRIVSEP(ctx) && ctx->udp_wfd == -1) {
+		ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW|SOCK_CXNB,IPPROTO_UDP);
+		if (ctx->udp_wfd == -1) {
+			logerr(__func__);
+			return;
+		}
 	}
 
 	if (dhcp_init(ifp) == -1) {
@@ -3873,11 +3897,7 @@
 
 #ifdef ARPING
 	if (ifo->arping_len && state->arping_index < ifo->arping_len) {
-		struct arp_state *astate;
-
-		astate = dhcp_arp_new(ifp, NULL);
-		if (astate)
-			dhcp_arp_not_found(astate);
+		dhcp_arping(ifp);
 		return;
 	}
 #endif
--- a/src/dhcp.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/dhcp.h	Tue May 19 16:19:05 2020 +0100
@@ -41,6 +41,7 @@
 #include <stdint.h>
 
 #include "arp.h"
+#include "bpf.h"
 #include "auth.h"
 #include "dhcp-common.h"
 
@@ -221,9 +222,8 @@
 	uint32_t xid;
 	int socket;
 
-	int bpf_fd;
-	unsigned int bpf_flags;
-	int udp_fd;
+	struct bpf *bpf;
+	int udp_rfd;
 	struct ipv4_addr *addr;
 	uint8_t added;
 
@@ -256,7 +256,7 @@
 ssize_t print_rfc3442(FILE *, const uint8_t *, size_t);
 
 int dhcp_openudp(struct in_addr *);
-void dhcp_packet(struct interface *, uint8_t *, size_t);
+void dhcp_packet(struct interface *, uint8_t *, size_t, uint8_t);
 void dhcp_recvmsg(struct dhcpcd_ctx *, struct msghdr *);
 void dhcp_printoptions(const struct dhcpcd_ctx *,
     const struct dhcp_opt *, size_t);
--- a/src/dhcp6.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/dhcp6.c	Tue May 19 16:19:05 2020 +0100
@@ -27,8 +27,10 @@
  */
 
 #include <sys/utsname.h>
+#include <sys/types.h>
 
 #include <netinet/in.h>
+#include <netinet/ip6.h>
 
 #include <assert.h>
 #include <ctype.h>
@@ -1197,22 +1199,26 @@
 }
 #endif
 
+static const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT;
 static int
 dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *))
 {
 	struct dhcp6_state *state = D6_STATE(ifp);
 	struct dhcpcd_ctx *ctx = ifp->ctx;
+	unsigned int RT;
+	bool broadcast = true;
 	struct sockaddr_in6 dst = {
 	    .sin6_family = AF_INET6,
 	    .sin6_port = htons(DHCP6_SERVER_PORT),
 	};
-	unsigned int RT;
-	const char *broad_uni;
-	const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT;
-	struct ipv6_addr *lla;
-	int s;
-	struct iovec iov = {
-	    .iov_base = state->send, .iov_len = state->send_len,
+	struct udphdr udp = {
+	    .uh_sport = htons(DHCP6_CLIENT_PORT),
+	    .uh_dport = htons(DHCP6_SERVER_PORT),
+	    .uh_ulen = htons(sizeof(udp) + state->send_len),
+	};
+	struct iovec iov[] = {
+	    { .iov_base = &udp, .iov_len = sizeof(udp), },
+	    { .iov_base = state->send, .iov_len = state->send_len, },
 	};
 	union {
 		struct cmsghdr hdr;
@@ -1220,39 +1226,39 @@
 	} cmsgbuf = { .buf = { 0 } };
 	struct msghdr msg = {
 	    .msg_name = &dst, .msg_namelen = sizeof(dst),
-	    .msg_iov = &iov, .msg_iovlen = 1,
+	    .msg_iov = iov, .msg_iovlen = __arraycount(iov),
 	};
+	char uaddr[INET6_ADDRSTRLEN];
 
 	if (!callback && ifp->carrier <= LINK_DOWN)
 		return 0;
 
-#ifdef HAVE_SA_LEN
-	dst.sin6_len = sizeof(dst);
-#endif
-
-	lla = ipv6_linklocal(ifp);
-	/* We need to ensure we have sufficient scope to unicast the address */
-	/* XXX FIXME: We should check any added addresses we have like from
-	 * a Router Advertisement */
-	if (IN6_IS_ADDR_UNSPECIFIED(&state->unicast) ||
-	    (state->state == DH6S_REQUEST &&
-	    (!IN6_IS_ADDR_LINKLOCAL(&state->unicast) || lla == NULL)))
-	{
-		dst.sin6_addr = alldhcp;
-		broad_uni = "broadcasting";
-	} else {
-		dst.sin6_addr = state->unicast;
-		broad_uni = "unicasting";
+	if (!IN6_IS_ADDR_UNSPECIFIED(&state->unicast)) {
+		switch (state->send->type) {
+		case DHCP6_SOLICIT:	/* FALLTHROUGH */
+		case DHCP6_CONFIRM:	/* FALLTHROUGH */
+		case DHCP6_REBIND:
+			/* Unicasting is denied for these types. */
+			break;
+		default:
+			broadcast = false;
+			inet_ntop(AF_INET6, &state->unicast, uaddr,
+			    sizeof(uaddr));
+			break;
+		}
 	}
+	dst.sin6_addr = broadcast ? alldhcp : state->unicast;
 
 	if (!callback) {
-		logdebugx("%s: %s %s with xid 0x%02x%02x%02x",
+		logdebugx("%s: %s %s with xid 0x%02x%02x%02x%s%s",
 		    ifp->name,
-		    broad_uni,
+		    broadcast ? "broadcasting" : "unicasting",
 		    dhcp6_get_op(state->send->type),
 		    state->send->xid[0],
 		    state->send->xid[1],
-		    state->send->xid[2]);
+		    state->send->xid[2],
+		    !broadcast ? " " : "",
+		    !broadcast ? uaddr : "");
 		RT = 0;
 	} else {
 		if (state->IMD &&
@@ -1266,7 +1272,6 @@
 			 * 1 second grace seems to be the sweet spot. */
 			if (ifp->flags & IFF_POINTOPOINT)
 				state->RT += MSEC_PER_SEC;
-			broad_uni = "delaying";
 		} else if (state->RTC == 0)
 			state->RT = state->IRT * MSEC_PER_SEC;
 
@@ -1287,14 +1292,17 @@
 		    * ((float)lr / DHCP6_RAND_DIV));
 
 		if (ifp->carrier > LINK_DOWN)
-			logdebugx("%s: %s %s (xid 0x%02x%02x%02x),"
+			logdebugx("%s: %s %s (xid 0x%02x%02x%02x)%s%s,"
 			    " next in %0.1f seconds",
 			    ifp->name,
-			    broad_uni,
+			    state->IMD != 0 ? "delaying" :
+			    broadcast ? "broadcasting" : "unicasting",
 			    dhcp6_get_op(state->send->type),
 			    state->send->xid[0],
 			    state->send->xid[1],
 			    state->send->xid[2],
+			    state->IMD == 0 && !broadcast ? " " : "",
+			    state->IMD == 0 && !broadcast ? uaddr : "",
 			    (float)RT / MSEC_PER_SEC);
 
 		/* Wait the initial delay */
@@ -1321,7 +1329,7 @@
 #endif
 
 	/* Set the outbound interface */
-	if (IN6_ARE_ADDR_EQUAL(&dst.sin6_addr, &alldhcp)) {
+	if (broadcast) {
 		struct cmsghdr *cm;
 		struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
 
@@ -1339,46 +1347,13 @@
 
 #ifdef PRIVSEP
 	if (IN_PRIVSEP(ifp->ctx)) {
-		struct ipv6_addr *ia;
-
-		if (IN6_ARE_ADDR_EQUAL(&dst.sin6_addr, &alldhcp))
-			ia = lla;
-		else {
-			/* Find an IA to send from */
-			TAILQ_FOREACH(ia, &state->addrs, next) {
-				if (ia->flags & IPV6_AF_STALE)
-					continue;
-				if (ia->addr_flags & IN6_IFF_NOTUSEABLE)
-					continue;
-				if (ia->ia_type == D6_OPTION_IA_PD)
-					continue;
-				break;
-			}
-		}
-		if (ia == NULL) {
-			if (lla == NULL) {
-				logerrx("%s: no address to send from",
-				    ifp->name);
-				return -1;
-			}
-			ia = lla;
-		}
-		if (ps_inet_senddhcp6(ia, &msg) == -1)
+		if (ps_inet_senddhcp6(ifp, &msg) == -1)
 			logerr(__func__);
 		goto sent;
 	}
 #endif
 
-	if (ctx->dhcp6_fd != -1)
-		s = ctx->dhcp6_fd;
-	else if (lla != NULL && lla->dhcp6_fd != -1)
-		s = lla->dhcp6_fd;
-	else {
-		logerrx("%s: no socket to send from", ifp->name);
-		return -1;
-	}
-
-	if (sendmsg(s, &msg, 0) == -1) {
+	if (sendmsg(ctx->dhcp6_wfd, &msg, 0) == -1) {
 		logerr("%s: %s: sendmsg", __func__, ifp->name);
 		/* Allow DHCPv6 to continue .... the errors
 		 * would be rate limited by the protocol.
@@ -3722,7 +3697,7 @@
 	int s;
 	ssize_t bytes;
 
-	s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_fd;
+	s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_rfd;
 	bytes = recvmsg(s, &msg, 0);
 	if (bytes == -1) {
 		logerr(__func__);
@@ -3750,21 +3725,35 @@
 }
 
 int
+dhcp6_openraw(void)
+{
+	int fd, v;
+
+	fd = socket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
+	if (fd == -1)
+		return -1;
+
+	v = 1;
+	if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &v, sizeof(v)) == -1)
+		return -1;
+
+	v = offsetof(struct udphdr, uh_sum);
+	if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &v, sizeof(v)) == -1)
+		return -1;
+
+	return fd;
+}
+
+int
 dhcp6_openudp(unsigned int ifindex, struct in6_addr *ia)
 {
 	struct sockaddr_in6 sa;
 	int n, s;
 
-#define SOCK_FLAGS	SOCK_CLOEXEC | SOCK_NONBLOCK
-	s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_FLAGS, IPPROTO_UDP);
-#undef SOCK_FLAGS
+	s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP);
 	if (s == -1)
 		goto errexit;
 
-	n = 1;
-	if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n)) == -1)
-		goto errexit;
-
 	memset(&sa, 0, sizeof(sa));
 	sa.sin6_family = AF_INET6;
 	sa.sin6_port = htons(DHCP6_CLIENT_PORT);
@@ -3784,6 +3773,12 @@
 	if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &n, sizeof(n)) == -1)
 		goto errexit;
 
+#ifdef SO_RERROR
+	n = 1;
+	if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1)
+		goto errexit;
+#endif
+
 	return s;
 
 errexit:
@@ -3836,14 +3831,22 @@
 	const struct dhcp_compat *dhc;
 
 	if ((ctx->options & (DHCPCD_MASTER|DHCPCD_PRIVSEP)) == DHCPCD_MASTER &&
-	    ctx->dhcp6_fd == -1)
+	    ctx->dhcp6_rfd == -1)
 	{
-		ctx->dhcp6_fd = dhcp6_openudp(0, NULL);
-		if (ctx->dhcp6_fd == -1) {
+		ctx->dhcp6_rfd = dhcp6_openudp(0, NULL);
+		if (ctx->dhcp6_rfd == -1) {
 			logerr(__func__);
 			return;
 		}
-		eloop_event_add(ctx->eloop, ctx->dhcp6_fd, dhcp6_recvctx, ctx);
+		eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, dhcp6_recvctx, ctx);
+	}
+
+	if (!IN_PRIVSEP(ctx) && ctx->dhcp6_wfd == -1) {
+		ctx->dhcp6_wfd = dhcp6_openraw();
+		if (ctx->dhcp6_wfd == -1) {
+			logerr(__func__);
+			return;
+		}
 	}
 
 	state = D6_STATE(ifp);
@@ -4055,10 +4058,10 @@
 				break;
 		}
 	}
-	if (ifp == NULL && ctx->dhcp6_fd != -1) {
-		eloop_event_delete(ctx->eloop, ctx->dhcp6_fd);
-		close(ctx->dhcp6_fd);
-		ctx->dhcp6_fd = -1;
+	if (ifp == NULL && ctx->dhcp6_rfd != -1) {
+		eloop_event_delete(ctx->eloop, ctx->dhcp6_rfd);
+		close(ctx->dhcp6_rfd);
+		ctx->dhcp6_rfd = -1;
 	}
 }
 
--- a/src/dhcp6.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/dhcp6.h	Tue May 19 16:19:05 2020 +0100
@@ -226,6 +226,7 @@
 	(D6_CSTATE((ifp)) &&						       \
 	D6_CSTATE((ifp))->reason && dhcp6_dadcompleted((ifp)))
 
+int dhcp6_openraw(void);
 int dhcp6_openudp(unsigned int, struct in6_addr *);
 void dhcp6_recvmsg(struct dhcpcd_ctx *, struct msghdr *, struct ipv6_addr *);
 void dhcp6_printoptions(const struct dhcpcd_ctx *,
--- a/src/dhcpcd.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/dhcpcd.c	Tue May 19 16:19:05 2020 +0100
@@ -71,6 +71,9 @@
 #include "privsep.h"
 #include "script.h"
 
+#ifdef HAVE_CAPSICUM
+#include <sys/capsicum.h>
+#endif
 #ifdef HAVE_UTIL_H
 #include <util.h>
 #endif
@@ -379,6 +382,7 @@
 static void
 dhcpcd_drop(struct interface *ifp, int stop)
 {
+	return;
 
 #ifdef DHCP6
 	dhcp6_drop(ifp, stop ? NULL : "EXPIRE6");
@@ -1844,13 +1848,15 @@
 	ctx.dev_fd = -1;
 #endif
 #ifdef INET
-	ctx.udp_fd = -1;
+	ctx.udp_rfd = -1;
+	ctx.udp_wfd = -1;
 #endif
 #if defined(INET6) && !defined(__sun)
 	ctx.nd_fd = -1;
 #endif
 #ifdef DHCP6
-	ctx.dhcp6_fd = -1;
+	ctx.dhcp6_rfd = -1;
+	ctx.dhcp6_wfd = -1;
 #endif
 #ifdef PRIVSEP
 	ctx.ps_root_fd = ctx.ps_data_fd = -1;
--- a/src/dhcpcd.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/dhcpcd.h	Tue May 19 16:19:05 2020 +0100
@@ -206,7 +206,8 @@
 	struct dhcp_opt *dhcp_opts;
 	size_t dhcp_opts_len;
 
-	int udp_fd;
+	int udp_rfd;
+	int udp_wfd;
 
 	/* Our aggregate option buffer.
 	 * We ONLY use this when options are split, which for most purposes is
@@ -223,11 +224,11 @@
 #endif
 	struct ra_head *ra_routers;
 
-	int dhcp6_fd;
-
 	struct dhcp_opt *nd_opts;
 	size_t nd_opts_len;
 #ifdef DHCP6
+	int dhcp6_rfd;
+	int dhcp6_wfd;
 	struct dhcp_opt *dhcp6_opts;
 	size_t dhcp6_opts_len;
 #endif
--- a/src/eloop.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/eloop.c	Tue May 19 16:19:05 2020 +0100
@@ -1023,12 +1023,13 @@
 			continue;
 		}
 
-		eloop_reduce_timers(eloop);
-
 		t = TAILQ_FIRST(&eloop->timeouts);
 		if (t == NULL && eloop->events_len == 0)
 			break;
 
+		if (t != NULL)
+			eloop_reduce_timers(eloop);
+
 		if (t != NULL && t->seconds == 0 && t->nseconds == 0) {
 			TAILQ_REMOVE(&eloop->timeouts, t, next);
 			t->callback(t->arg);
--- a/src/if-bsd.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/if-bsd.c	Tue May 19 16:19:05 2020 +0100
@@ -168,12 +168,16 @@
 	priv->pf_inet6_fd = -1;
 #endif
 
-#define SOCK_FLAGS	(SOCK_CLOEXEC | SOCK_NONBLOCK)
-	ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_FLAGS, AF_UNSPEC);
-#undef SOCK_FLAGS
+	ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_CXNB, AF_UNSPEC);
 	if (ctx->link_fd == -1)
 		return -1;
 
+#ifdef SO_RERROR
+	n = 1;
+	if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RERROR, &n,sizeof(n)) == -1)
+		goto errexit;
+#endif
+
 	/* Ignore our own route(4) messages.
 	 * Sadly there is no way of doing this for route(4) messages
 	 * generated from addresses we add/delete. */
--- a/src/if.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/if.c	Tue May 19 16:19:05 2020 +0100
@@ -394,7 +394,7 @@
 	}
 	TAILQ_INIT(ifs);
 
-#ifdef HAVE_CAPSICUM
+#if defined(PRIVSEP) && defined(HAVE_CAPSICUM)
 	if (ctx->options & DHCPCD_PRIVSEP) {
 		if (ps_root_getifaddrs(ctx, ifaddrs) == -1) {
 			logerr("ps_root_getifaddrs");
@@ -911,13 +911,6 @@
 		goto out;
 #endif
 
-#ifdef SO_RERROR
-	/* Tell recvmsg(2) to return ENOBUFS if the receiving socket overflows. */
-	on = 1;
-	if (setsockopt(s, SOL_SOCKET, SO_RERROR, &on, sizeof(on)) == -1)
-		logerr("%s: SO_RERROR", __func__);
-#endif
-
 	return s;
 
 #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
--- a/src/if.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/if.h	Tue May 19 16:19:05 2020 +0100
@@ -210,6 +210,9 @@
 #else
 # define SOCK_NONBLOCK	0x20000000
 #endif
+#ifndef SOCK_CXNB
+#define	SOCK_CXNB	SOCK_CLOEXEC | SOCK_NONBLOCK
+#endif
 
 int if_route(unsigned char, const struct rt *rt);
 int if_initrt(struct dhcpcd_ctx *, rb_tree_t *, int);
--- a/src/ipv4.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/ipv4.c	Tue May 19 16:19:05 2020 +0100
@@ -565,8 +565,6 @@
 			return NULL;
 		}
 		TAILQ_INIT(&state->addrs);
-		state->buffer_size = state->buffer_len = state->buffer_pos = 0;
-		state->buffer = NULL;
 	}
 	return state;
 }
@@ -963,6 +961,5 @@
 		TAILQ_REMOVE(&state->addrs, ia, next);
 		free(ia);
 	}
-	free(state->buffer);
 	free(state);
 }
--- a/src/ipv4.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/ipv4.h	Tue May 19 16:19:05 2020 +0100
@@ -110,10 +110,6 @@
 
 struct ipv4_state {
 	struct ipv4_addrhead addrs;
-
-	/* Buffer for BPF */
-	size_t buffer_size, buffer_len, buffer_pos;
-	char *buffer;
 };
 
 #define IPV4_STATE(ifp)							       \
--- a/src/ipv6.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/ipv6.c	Tue May 19 16:19:05 2020 +0100
@@ -140,7 +140,10 @@
 #ifndef __sun
 	ctx->nd_fd = -1;
 #endif
-	ctx->dhcp6_fd = -1;
+#ifdef DHCP6
+	ctx->dhcp6_rfd = -1;
+	ctx->dhcp6_wfd = -1;
+#endif
 	return 0;
 }
 
--- a/src/ipv6nd.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/ipv6nd.c	Tue May 19 16:19:05 2020 +0100
@@ -188,36 +188,45 @@
 	}
 }
 
-static int
-ipv6nd_open0(void)
+int
+ipv6nd_open(bool recv)
 {
 	int fd, on;
 	struct icmp6_filter filt;
 
-#define SOCK_FLAGS	SOCK_CLOEXEC | SOCK_NONBLOCK
-	fd = xsocket(PF_INET6, SOCK_RAW | SOCK_FLAGS, IPPROTO_ICMPV6);
-#undef SOCK_FLAGS
+	fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6);
 	if (fd == -1)
 		return -1;
 
+	ICMP6_FILTER_SETBLOCKALL(&filt);
+
 	/* RFC4861 4.1 */
 	on = 255;
 	if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
 	    &on, sizeof(on)) == -1)
 		goto eexit;
 
-	on = 1;
-	if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
-	    &on, sizeof(on)) == -1)
-		goto eexit;
+	if (recv) {
+		on = 1;
+		if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+		    &on, sizeof(on)) == -1)
+			goto eexit;
+
+		on = 1;
+		if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+		    &on, sizeof(on)) == -1)
+			goto eexit;
 
-	on = 1;
-	if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
-	    &on, sizeof(on)) == -1)
-		goto eexit;
+		ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
 
-	ICMP6_FILTER_SETBLOCKALL(&filt);
-	ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+#ifdef SO_RERROR
+		on = 1;
+		if (setsockopt(fd, SOL_SOCKET, SO_RERROR,
+		    &on, sizeof(on)) == -1)
+			goto eexit;
+#endif
+	}
+
 	if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER,
 	    &filt, sizeof(filt)) == -1)
 		goto eexit;
@@ -231,7 +240,7 @@
 
 #ifdef __sun
 int
-ipv6nd_open(struct interface *ifp)
+ipv6nd_openif(struct interface *ifp)
 {
 	int fd;
 	struct ipv6_mreq mreq = {
@@ -244,7 +253,7 @@
 	if (state->nd_fd != -1)
 		return state->nd_fd;
 
-	fd = ipv6nd_open0();
+	fd = ipv6nd_open0(true);
 	if (fd == -1)
 		return -1;
 
@@ -266,24 +275,6 @@
 	eloop_event_add(ifp->ctx->eloop, fd, ipv6nd_handledata, ifp);
 	return fd;
 }
-#else
-int
-ipv6nd_open(struct dhcpcd_ctx *ctx)
-{
-	int fd;
-
-	if (ctx->nd_fd != -1)
-		return ctx->nd_fd;
-
-	fd = ipv6nd_open0();
-	if (fd == -1)
-		return -1;
-
-	ctx->nd_fd = fd;
-	if (!(IN_PRIVSEP(ctx)))
-		eloop_event_add(ctx->eloop, fd, ipv6nd_handledata, ctx);
-	return fd;
-}
 #endif
 
 static int
@@ -340,6 +331,7 @@
 	struct cmsghdr *cm;
 	struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
 	int s;
+	struct dhcpcd_ctx *ctx = ifp->ctx;
 
 	if (ipv6_linklocal(ifp) == NULL) {
 		logdebugx("%s: delaying Router Solicitation for LL address",
@@ -372,6 +364,14 @@
 #ifdef __sun
 	s = state->nd_fd;
 #else
+	if (ctx->nd_fd == -1) {
+		ctx->nd_fd = ipv6nd_open(true);
+		if (ctx->nd_fd == -1) {
+			logerr(__func__);
+			return;
+		}
+		eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx);
+	}
 	s = ifp->ctx->nd_fd;
 #endif
 	if (sendmsg(s, &msg, 0) == -1) {
@@ -2002,20 +2002,6 @@
 #endif
 	}
 
-	if (!(IN_PRIVSEP(ifp->ctx))) {
-#ifdef __sun
-		if (ipv6nd_open(ifp) == -1) {
-			logerr(__func__);
-			return;
-		}
-#else
-		if (ipv6nd_open(ifp->ctx) == -1) {
-			logerr(__func__);
-			return;
-		}
-#endif
-	}
-
 	/* Always make a new probe as the underlying hardware
 	 * address could have changed. */
 	ipv6nd_makersprobe(ifp);
--- a/src/ipv6nd.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/ipv6nd.h	Tue May 19 16:19:05 2020 +0100
@@ -98,10 +98,9 @@
 #define	RETRANS_TIMER			1000	/* milliseconds */
 #define	DELAY_FIRST_PROBE_TIME		5	/* seconds */
 
+int ipv6nd_open(bool);
 #ifdef __sun
-int ipv6nd_open(struct interface *);
-#else
-int ipv6nd_open(struct dhcpcd_ctx *);
+int ipv6nd_openif(struct interface *);
 #endif
 void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *);
 int ipv6nd_rtpref(struct ra *);
--- a/src/privsep-bpf.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep-bpf.c	Tue May 19 16:19:05 2020 +0100
@@ -62,26 +62,28 @@
 ps_bpf_recvbpf(void *arg)
 {
 	struct ps_process *psp = arg;
-	unsigned int flags;
-	uint8_t buf[FRAMELEN_MAX];
+	struct bpf *bpf = psp->psp_bpf;
+	uint8_t buf[sizeof(bpf->bpf_flags) + FRAMELEN_MAX];
 	ssize_t len;
 	struct ps_msghdr psm = {
 		.ps_id = psp->psp_id,
 		.ps_cmd = psp->psp_id.psi_cmd,
 	};
 
+	bpf->bpf_flags &= ~BPF_EOF;
 	/* A BPF read can read more than one filtered packet at time.
 	 * This mechanism allows us to read each packet from the buffer. */
-	flags = 0;
-	while (!(flags & BPF_EOF)) {
-		len = bpf_read(&psp->psp_ifp, psp->psp_work_fd,
-		    buf, sizeof(buf), &flags);
+	while (!(bpf->bpf_flags & BPF_EOF)) {
+		len = bpf_read(bpf,
+		    buf + sizeof(bpf->bpf_flags),
+		    sizeof(buf) - sizeof(bpf->bpf_flags));
 		if (len == -1)
 			logerr(__func__);
 		if (len == -1 || len == 0)
 			break;
+		memcpy(buf, &bpf->bpf_flags, sizeof(bpf->bpf_flags));
 		len = ps_sendpsmdata(psp->psp_ctx, psp->psp_ctx->ps_data_fd,
-		    &psm, buf, (size_t)len);
+		    &psm, buf, (size_t)len + sizeof(bpf->bpf_flags));
 		if (len == -1 && errno != ECONNRESET)
 			logerr(__func__);
 		if (len == -1 || len == 0)
@@ -89,61 +91,13 @@
 	}
 }
 
-#ifdef ARP
-#if !defined(HAVE_CAPSICUM) && !defined(HAVE_PLEDGE)
 static ssize_t
-ps_bpf_arp_addr(uint16_t cmd, struct ps_process *psp, struct msghdr *msg)
-{
-	struct interface *ifp = &psp->psp_ifp;
-	struct iovec *iov = msg->msg_iov;
-	struct in_addr addr;
-	struct arp_state *astate;
-
-	if (psp == NULL) {
-		errno = ESRCH;
-		return -1;
-	}
-
-	assert(msg->msg_iovlen == 1);
-	assert(iov->iov_len == sizeof(addr));
-	memcpy(&addr, iov->iov_base, sizeof(addr));
-	if (cmd & PS_START) {
-		astate = arp_new(ifp, &addr);
-		if (astate == NULL)
-			return -1;
-	} else if (cmd & PS_DELETE) {
-		astate = arp_find(ifp, &addr);
-		if (astate == NULL) {
-			errno = ESRCH;
-			return -1;
-		}
-		arp_free(astate);
-	} else {
-		errno = EINVAL;
-		return -1;
-	}
-
-	return bpf_arp(ifp, psp->psp_work_fd);
-}
-#endif
-#endif
-
-static ssize_t
-ps_bpf_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+ps_bpf_recvmsgcb(void *arg, __unused struct ps_msghdr *psm, struct msghdr *msg)
 {
 	struct ps_process *psp = arg;
 	struct iovec *iov = msg->msg_iov;
 
-#ifdef ARP
-	if (psm->ps_cmd & (PS_START | PS_DELETE))
-#if !defined(HAVE_CAPSICUM) && !defined(HAVE_PLEDGE)
-		return ps_bpf_arp_addr(psm->ps_cmd, psp, msg);
-#else
-		return 0;
-#endif
-#endif
-
-	return bpf_send(&psp->psp_ifp, psp->psp_work_fd, psp->psp_proto,
+	return bpf_send(psp->psp_bpf, psp->psp_proto,
 	    iov->iov_base, iov->iov_len);
 }
 
@@ -162,6 +116,8 @@
 {
 	struct ps_process *psp = arg;
 	struct dhcpcd_ctx *ctx = psp->psp_ctx;
+	char *addr;
+	struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr;
 #ifdef HAVE_CAPSICUM
 	cap_rights_t rights;
 
@@ -170,23 +126,30 @@
 	cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_IOCTL);
 #endif
 
-	setproctitle("[BPF %s] %s", psp->psp_protostr, psp->psp_ifname);
-
+	if (ia->s_addr == INADDR_ANY) {
+		ia = NULL;
+		addr = NULL;
+	} else
+		addr = inet_ntoa(*ia);
+	setproctitle("[BPF %s] %s%s%s", psp->psp_protostr, psp->psp_ifname,
+	    addr != NULL ? " " : "", addr != NULL ? addr : "");
 	ps_freeprocesses(ctx, psp);
 
-	psp->psp_work_fd = bpf_open(&psp->psp_ifp, psp->psp_filter);
-	if (psp->psp_work_fd == -1)
+	psp->psp_bpf = bpf_open(&psp->psp_ifp, psp->psp_filter, ia);
+	if (psp->psp_bpf == NULL)
 		logerr("%s: bpf_open",__func__);
 #ifdef HAVE_CAPSICUM
-	else if (cap_rights_limit(psp->psp_work_fd, &rights) == -1 &&
+	else if (cap_rights_limit(psp->psp_bpf->bpf_fd, &rights) == -1 &&
 	    errno != ENOSYS)
 		logerr("%s: cap_rights_limit", __func__);
 #endif
 	else if (eloop_event_add(ctx->eloop,
-	    psp->psp_work_fd, ps_bpf_recvbpf, psp) == -1)
+	    psp->psp_bpf->bpf_fd, ps_bpf_recvbpf, psp) == -1)
 		logerr("%s: eloop_event_add", __func__);
-	else
+	else {
+		psp->psp_work_fd = psp->psp_bpf->bpf_fd;
 		return 0;
+	}
 
 	eloop_exit(ctx->eloop, EXIT_FAILURE);
 	return -1;
@@ -306,17 +269,24 @@
 {
 	struct iovec *iov = msg->msg_iov;
 	struct interface *ifp;
+	uint8_t bpf_flags, *bpf;
+	size_t bpf_len;
 
 	ifp = if_findindex(ctx->ifaces, psm->ps_id.psi_ifindex);
+	bpf = iov->iov_base;
+	bpf_len = iov->iov_len;
+	memcpy(&bpf_flags, bpf, sizeof(bpf_flags));
+	bpf += sizeof(bpf_flags);
+	bpf_len -= sizeof(bpf_flags);
 
 	switch (psm->ps_cmd) {
 #ifdef ARP
 	case PS_BPF_ARP:
-		arp_packet(ifp, iov->iov_base, iov->iov_len);
+		arp_packet(ifp, bpf, bpf_len, bpf_flags);
 		break;
 #endif
 	case PS_BPF_BOOTP:
-		dhcp_packet(ifp, iov->iov_base, iov->iov_len);
+		dhcp_packet(ifp, bpf, bpf_len, bpf_flags);
 		break;
 	default:
 		errno = ENOTSUP;
@@ -327,59 +297,48 @@
 }
 
 static ssize_t
-ps_bpf_send(const struct interface *ifp, uint16_t cmd,
-    const void *data, size_t len)
+ps_bpf_send(const struct interface *ifp, const struct in_addr *ia,
+    uint16_t cmd, const void *data, size_t len)
 {
 	struct dhcpcd_ctx *ctx = ifp->ctx;
 	struct ps_msghdr psm = {
 		.ps_cmd = cmd,
 		.ps_id = {
 			.psi_ifindex = ifp->index,
-			.psi_cmd = (uint8_t)(cmd &
-			    ~(PS_START | PS_STOP | PS_DELETE)),
+			.psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)),
 		},
 	};
 
-	if (psm.ps_id.psi_cmd == PS_BPF_ARP_ADDR)
-		psm.ps_id.psi_cmd = PS_BPF_ARP;
+	if (ia != NULL)
+		psm.ps_id.psi_addr.psa_in_addr = *ia;
 
 	return ps_sendpsmdata(ctx, ctx->ps_root_fd, &psm, data, len);
 }
 
 #ifdef ARP
 ssize_t
-ps_bpf_openarp(const struct interface *ifp)
+ps_bpf_openarp(const struct interface *ifp, const struct in_addr *ia)
 {
 
-	return ps_bpf_send(ifp, PS_BPF_ARP | PS_START, ifp, sizeof(*ifp));
-}
-
-ssize_t
-ps_bpf_addaddr(const struct interface *ifp, const struct in_addr *addr)
-{
-
-	return ps_bpf_send(ifp, PS_BPF_ARP_ADDR | PS_START, addr, sizeof(*addr));
+	assert(ia != NULL);
+	return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_START,
+	    ifp, sizeof(*ifp));
 }
 
 ssize_t
-ps_bpf_deladdr(const struct interface *ifp, const struct in_addr *addr)
+ps_bpf_closearp(const struct interface *ifp, const struct in_addr *ia)
 {
 
-	return ps_bpf_send(ifp, PS_BPF_ARP_ADDR | PS_DELETE, addr, sizeof(*addr));
+	return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_STOP, NULL, 0);
 }
 
 ssize_t
-ps_bpf_closearp(const struct interface *ifp)
+ps_bpf_sendarp(const struct interface *ifp, const struct in_addr *ia,
+    const void *data, size_t len)
 {
 
-	return ps_bpf_send(ifp, PS_BPF_ARP | PS_STOP, NULL, 0);
-}
-
-ssize_t
-ps_bpf_sendarp(const struct interface *ifp, const void *data, size_t len)
-{
-
-	return ps_bpf_send(ifp, PS_BPF_ARP, data, len);
+	assert(ia != NULL);
+	return ps_bpf_send(ifp, ia, PS_BPF_ARP, data, len);
 }
 #endif
 
@@ -387,19 +346,20 @@
 ps_bpf_openbootp(const struct interface *ifp)
 {
 
-	return ps_bpf_send(ifp, PS_BPF_BOOTP | PS_START, ifp, sizeof(*ifp));
+	return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_START,
+	    ifp, sizeof(*ifp));
 }
 
 ssize_t
 ps_bpf_closebootp(const struct interface *ifp)
 {
 
-	return ps_bpf_send(ifp, PS_BPF_BOOTP | PS_STOP, NULL, 0);
+	return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_STOP, NULL, 0);
 }
 
 ssize_t
 ps_bpf_sendbootp(const struct interface *ifp, const void *data, size_t len)
 {
 
-	return ps_bpf_send(ifp, PS_BPF_BOOTP, data, len);
+	return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP, data, len);
 }
--- a/src/privsep-bpf.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep-bpf.h	Tue May 19 16:19:05 2020 +0100
@@ -35,11 +35,10 @@
     struct ps_msghdr *, struct msghdr *);
 
 #ifdef ARP
-ssize_t ps_bpf_openarp(const struct interface *);
-ssize_t ps_bpf_addaddr(const struct interface *, const struct in_addr *);
-ssize_t ps_bpf_deladdr(const struct interface *, const struct in_addr *);
-ssize_t ps_bpf_closearp(const struct interface *);
-ssize_t ps_bpf_sendarp(const struct interface *, const void *, size_t);
+ssize_t ps_bpf_openarp(const struct interface *, const struct in_addr *);
+ssize_t ps_bpf_closearp(const struct interface *, const struct in_addr *);
+ssize_t ps_bpf_sendarp(const struct interface *, const struct in_addr *,
+    const void *, size_t);
 #endif
 
 ssize_t ps_bpf_openbootp(const struct interface *);
--- a/src/privsep-inet.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep-inet.c	Tue May 19 16:19:05 2020 +0100
@@ -55,7 +55,7 @@
 {
 	struct dhcpcd_ctx *ctx = arg;
 
-	if (ps_recvmsg(ctx, ctx->udp_fd, PS_BOOTP, ctx->ps_inet_fd) == -1)
+	if (ps_recvmsg(ctx, ctx->udp_rfd, PS_BOOTP, ctx->ps_inet_fd) == -1)
 		logerr(__func__);
 }
 #endif
@@ -86,7 +86,7 @@
 {
 	struct dhcpcd_ctx *ctx = arg;
 
-	if (ps_recvmsg(ctx, ctx->dhcp6_fd, PS_DHCP6, ctx->ps_inet_fd) == -1)
+	if (ps_recvmsg(ctx, ctx->dhcp6_rfd, PS_DHCP6, ctx->ps_inet_fd) == -1)
 		logerr(__func__);
 }
 #endif
@@ -99,7 +99,7 @@
 #ifdef HAVE_CAPSICUM
 	cap_rights_t rights;
 
-	cap_rights_init(&rights, CAP_RECV, CAP_CONNECT, CAP_SEND, CAP_EVENT);
+	cap_rights_init(&rights, CAP_RECV, CAP_EVENT);
 #endif
 
 	if (ctx->options & DHCPCD_MASTER)
@@ -120,31 +120,32 @@
 	if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MASTER)) ==
 	    (DHCPCD_IPV4 | DHCPCD_MASTER))
 	{
-		ctx->udp_fd = dhcp_openudp(NULL);
-		if (ctx->udp_fd == -1)
+		ctx->udp_rfd = dhcp_openudp(NULL);
+		if (ctx->udp_rfd == -1)
 			logerr("%s: dhcp_open", __func__);
 #ifdef HAVE_CAPSICUM
-		else if (cap_rights_limit(ctx->udp_fd, &rights) == -1
+		else if (cap_rights_limit(ctx->udp_rfd, &rights) == -1
 		    && errno != ENOSYS)
 		{
 			logerr("%s: cap_rights_limit", __func__);
-			close(ctx->udp_fd);
-			ctx->udp_fd = -1;
+			close(ctx->udp_rfd);
+			ctx->udp_rfd = -1;
 		}
 #endif
-		else if (eloop_event_add(ctx->eloop, ctx->udp_fd,
+		else if (eloop_event_add(ctx->eloop, ctx->udp_rfd,
 		    ps_inet_recvbootp, ctx) == -1)
 		{
 			logerr("%s: eloop_event_add DHCP", __func__);
-			close(ctx->udp_fd);
-			ctx->udp_fd = -1;
+			close(ctx->udp_rfd);
+			ctx->udp_rfd = -1;
 		} else
 			ret++;
 	}
 #endif
 #if defined(INET6) && !defined(__sun)
 	if (ctx->options & DHCPCD_IPV6) {
-		if (ipv6nd_open(ctx) == -1)
+		ctx->nd_fd = ipv6nd_open(true);
+		if (ctx->nd_fd == -1)
 			logerr("%s: ipv6nd_open", __func__);
 #ifdef HAVE_CAPSICUM
 		else if (cap_rights_limit(ctx->nd_fd, &rights) == -1
@@ -169,24 +170,24 @@
 	if ((ctx->options & (DHCPCD_DHCP6 | DHCPCD_MASTER)) ==
 	    (DHCPCD_DHCP6 | DHCPCD_MASTER))
 	{
-		ctx->dhcp6_fd = dhcp6_openudp(0, NULL);
-		if (ctx->dhcp6_fd == -1)
+		ctx->dhcp6_rfd = dhcp6_openudp(0, NULL);
+		if (ctx->dhcp6_rfd == -1)
 			logerr("%s: dhcp6_open", __func__);
 #ifdef HAVE_CAPSICUM
-		else if (cap_rights_limit(ctx->dhcp6_fd, &rights) == -1
+		else if (cap_rights_limit(ctx->dhcp6_rfd, &rights) == -1
 		    && errno != ENOSYS)
 		{
 			logerr("%s: cap_rights_limit", __func__);
-			close(ctx->dhcp6_fd);
-			ctx->dhcp6_fd = -1;
+			close(ctx->dhcp6_rfd);
+			ctx->dhcp6_rfd = -1;
 		}
 #endif
-		else if (eloop_event_add(ctx->eloop, ctx->dhcp6_fd,
+		else if (eloop_event_add(ctx->eloop, ctx->dhcp6_rfd,
 		    ps_inet_recvdhcp6, ctx) == -1)
 		{
 			logerr("%s: eloop_event_add DHCP6", __func__);
-			close(ctx->dhcp6_fd);
-			ctx->dhcp6_fd = -1;
+			close(ctx->dhcp6_rfd);
+			ctx->dhcp6_rfd = -1;
 		} else
 			ret++;
 	}
@@ -200,23 +201,22 @@
 }
 
 static ssize_t
-ps_inet_recvmsg_cb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+ps_inet_sendmsg(struct dhcpcd_ctx *ctx,
+    struct ps_msghdr *psm, struct msghdr *msg)
 {
-	struct dhcpcd_ctx *ctx = arg;
 	struct ps_process *psp;
 	int s;
 
 	psp = ps_findprocess(ctx, &psm->ps_id);
 	if (psp != NULL) {
 		s = psp->psp_work_fd;
-		logerrx("psp found fd %d", s);
 		goto dosend;
 	}
 
 	switch (psm->ps_cmd) {
 #ifdef INET
 	case PS_BOOTP:
-		s = ctx->udp_fd;
+		s = ctx->udp_wfd;
 		break;
 #endif
 #if defined(INET6) && !defined(__sun)
@@ -226,7 +226,7 @@
 #endif
 #ifdef DHCP6
 	case PS_DHCP6:
-		s = ctx->dhcp6_fd;
+		s = ctx->dhcp6_wfd;
 		break;
 #endif
 	default:
@@ -235,17 +235,16 @@
 	}
 
 dosend:
-
 	return sendmsg(s, msg, 0);
 }
 
-/* Receive from state engine, send message on wire. */
 static void
 ps_inet_recvmsg(void *arg)
 {
 	struct dhcpcd_ctx *ctx = arg;
 
-	if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, ps_inet_recvmsg_cb, ctx) == -1)
+	/* Receive shutdown */
+	if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, NULL, NULL) == -1)
 		logerr(__func__);
 }
 
@@ -310,11 +309,9 @@
 	    PSF_DROPPRIVS);
 
 #ifdef HAVE_CAPSICUM
-#if 0		/* This breaks sendmsg() */
-	if (cap_enter() == -1 && errno != ENOSYS)
+	if (pid == 0 && cap_enter() == -1 && errno != ENOSYS)
 		logerr("%s: cap_enter", __func__);
 #endif
-#endif
 #ifdef HAVE_PLEDGE
 	if (pid == 0 && pledge("stdio inet", NULL) == -1)
 		logerr("%s: pledge", __func__);
@@ -350,7 +347,7 @@
 #ifdef HAVE_CAPSICUM
 	cap_rights_t rights;
 
-	cap_rights_init(&rights, CAP_RECV, CAP_CONNECT, CAP_SEND, CAP_EVENT);
+	cap_rights_init(&rights, CAP_RECV, CAP_EVENT);
 #endif
 
 	inet_ntop(AF_INET, ia, buf, sizeof(buf));
@@ -401,7 +398,7 @@
 #ifdef HAVE_CAPSICUM
 	cap_rights_t rights;
 
-	cap_rights_init(&rights, CAP_RECV, CAP_CONNECT, CAP_SEND, CAP_EVENT);
+	cap_rights_init(&rights, CAP_RECV, CAP_EVENT);
 #endif
 
 	setproctitle("[ND network proxy]");
@@ -453,7 +450,7 @@
 #ifdef HAVE_CAPSICUM
 	cap_rights_t rights;
 
-	cap_rights_init(&rights, CAP_RECV, CAP_CONNECT, CAP_SEND, CAP_EVENT);
+	cap_rights_init(&rights, CAP_RECV, CAP_EVENT);
 #endif
 
 	inet_ntop(AF_INET6, ia, buf, sizeof(buf));
@@ -486,28 +483,18 @@
 }
 #endif
 
-static ssize_t
-ps_inet_recvmsgpsp_cb(void *arg, __unused struct ps_msghdr *psm,
-    struct msghdr *msg)
-{
-	struct ps_process *psp = arg;
-
-	return sendmsg(psp->psp_work_fd, msg, 0);
-}
-
 static void
 ps_inet_recvmsgpsp(void *arg)
 {
 	struct ps_process *psp = arg;
 
-	if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd,
-	    ps_inet_recvmsgpsp_cb, psp) == -1)
+	/* Receive shutdown. */
+	if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, NULL, NULL) == -1)
 		logerr(__func__);
 }
 
 ssize_t
-ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm,
-    __unused struct msghdr *msg)
+ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg)
 {
 	uint16_t cmd;
 	struct ps_process *psp;
@@ -515,6 +502,9 @@
 	pid_t start;
 
 	cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
+	if (cmd == psm->ps_cmd)
+		return ps_inet_sendmsg(ctx, psm, msg);
+
 	psp = ps_findprocess(ctx, &psm->ps_id);
 
 #ifdef PRIVSEP_DEBUG
@@ -573,11 +563,9 @@
 		return -1;
 	case 0:
 #ifdef HAVE_CAPSICUM
-#if 0		/* This breaks sendmsg() */
 		if (cap_enter() == -1 && errno != ENOSYS)
 			logerr("%s: cap_enter", __func__);
 #endif
-#endif
 #ifdef HAVE_PLEDGE
 		if (pledge("stdio inet", NULL) == -1)
 			logerr("%s: pledge", __func__);
@@ -622,13 +610,10 @@
 }
 
 ssize_t
-ps_inet_sendbootp(struct ipv4_addr *ia, const struct msghdr *msg)
+ps_inet_sendbootp(struct interface *ifp, const struct msghdr *msg)
 {
-	struct dhcpcd_ctx *ctx = ia->iface->ctx;
 
-	if (ctx->options & DHCPCD_MASTER)
-		return ps_sendmsg(ctx, ctx->ps_inet_fd, PS_BOOTP, 0, msg);
-	return ps_inet_in_docmd(ia, PS_BOOTP, msg);
+	return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_BOOTP, 0, msg);
 }
 #endif /* INET */
 
@@ -674,7 +659,7 @@
 ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg)
 {
 
-	return ps_sendmsg(ifp->ctx, ifp->ctx->ps_inet_fd, PS_ND, 0, msg);
+	return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_ND, 0, msg);
 }
 #endif
 
@@ -710,13 +695,10 @@
 }
 
 ssize_t
-ps_inet_senddhcp6(struct ipv6_addr *ia, const struct msghdr *msg)
+ps_inet_senddhcp6(struct interface *ifp, const struct msghdr *msg)
 {
-	struct dhcpcd_ctx *ctx = ia->iface->ctx;
 
-	if (ctx->options & DHCPCD_MASTER)
-		return ps_sendmsg(ctx, ctx->ps_inet_fd, PS_DHCP6, 0, msg);
-	return ps_inet_in6_docmd(ia, PS_DHCP6, msg);
+	return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_DHCP6, 0, msg);
 }
 #endif /* DHCP6 */
 #endif /* INET6 */
--- a/src/privsep-inet.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep-inet.h	Tue May 19 16:19:05 2020 +0100
@@ -38,7 +38,7 @@
 struct ipv4_addr;
 ssize_t ps_inet_openbootp(struct ipv4_addr *);
 ssize_t ps_inet_closebootp(struct ipv4_addr *);
-ssize_t ps_inet_sendbootp(struct ipv4_addr *, const struct msghdr *);
+ssize_t ps_inet_sendbootp(struct interface *, const struct msghdr *);
 #endif
 
 #ifdef INET6
@@ -51,7 +51,7 @@
 #ifdef DHCP6
 ssize_t ps_inet_opendhcp6(struct ipv6_addr *);
 ssize_t ps_inet_closedhcp6(struct ipv6_addr *);
-ssize_t ps_inet_senddhcp6(struct ipv6_addr *, const struct msghdr *);
+ssize_t ps_inet_senddhcp6(struct interface *, const struct msghdr *);
 #endif /* DHCP6 */
 #endif /* INET6 */
 #endif
--- a/src/privsep-root.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep-root.c	Tue May 19 16:19:05 2020 +0100
@@ -38,14 +38,17 @@
 #include <fcntl.h>
 #include <pwd.h>
 #include <signal.h>
+#include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
 #include "common.h"
 #include "dhcpcd.h"
+#include "dhcp6.h"
 #include "eloop.h"
 #include "if.h"
+#include "ipv6nd.h"
 #include "logerr.h"
 #include "privsep.h"
 #include "sa.h"
@@ -393,16 +396,14 @@
 	ssize_t err;
 	bool free_rdata= false;
 
-	cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP | PS_DELETE));
+	cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
 	psp = ps_findprocess(ctx, &psm->ps_id);
 
 #ifdef PRIVSEP_DEBUG
 	logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
 #endif
 
-	if ((!(psm->ps_cmd & PS_START) || cmd == PS_BPF_ARP_ADDR) &&
-	    psp != NULL)
-	{
+	if (psp != NULL) {
 		if (psm->ps_cmd & PS_STOP) {
 			int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
 
@@ -412,7 +413,7 @@
 		return ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg);
 	}
 
-	if (psm->ps_cmd & (PS_STOP | PS_DELETE) && psp == NULL)
+	if (psm->ps_cmd & PS_STOP && psp == NULL)
 		return 0;
 
 	/* All these should just be PS_START */
@@ -517,6 +518,27 @@
 		    ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
 	ctx->ps_root_pid = getpid();
 	ctx->options |= DHCPCD_PRIVSEPROOT;
+
+	/* Open network sockets for sending.
+	 * This is a small bit wasteful for non sandboxed OS's
+	 * but makes life very easy for unicasting DHCPv6 in non master
+	 * mode as we no longer care about address selection. */
+#ifdef INET
+	ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
+	if (ctx->udp_wfd == -1)
+		return -1;
+#endif
+#ifdef INET6
+	ctx->nd_fd = ipv6nd_open(false);
+	if (ctx->nd_fd == -1)
+		return -1;
+#endif
+#ifdef DHCP6
+	ctx->dhcp6_wfd = dhcp6_openraw();
+	if (ctx->dhcp6_wfd == -1)
+		return -1;
+#endif
+
 	return 0;
 }
 
@@ -567,7 +589,6 @@
 	int fd[2];
 	pid_t pid;
 
-#define	SOCK_CXNB	SOCK_CLOEXEC | SOCK_NONBLOCK
 	if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1)
 		return -1;
 
@@ -586,20 +607,16 @@
 	close(fd[1]);
 	if (eloop_event_add(ctx->eloop, ctx->ps_data_fd,
 	    ps_root_dispatch, ctx) == -1)
-		logerr(__func__);
+		return -1;
 
-	if ((ctx->ps_eloop = eloop_new()) == NULL) {
-		logerr(__func__);
+	if ((ctx->ps_eloop = eloop_new()) == NULL)
 		return -1;
-	}
 
 	if (eloop_signal_set_cb(ctx->ps_eloop,
 	    dhcpcd_signals, dhcpcd_signals_len,
 	    ps_root_readerrorsig, ctx) == -1)
-	{
-		logerr(__func__);
 		return -1;
-	}
+
 	return pid;
 }
 
--- a/src/privsep.c	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep.c	Tue May 19 16:19:05 2020 +0100
@@ -322,6 +322,7 @@
 
 	switch (pid = ps_root_start(ctx)) {
 	case -1:
+		logerr("ps_root_start");
 		return -1;
 	case 0:
 		return 0;
@@ -378,14 +379,6 @@
 void
 ps_freeprocess(struct ps_process *psp)
 {
-#ifdef INET
-	struct ipv4_state *istate = IPV4_STATE(&psp->psp_ifp);
-
-	if (istate != NULL) {
-		free(istate->buffer);
-		free(istate);
-	}
-#endif
 
 	TAILQ_REMOVE(&psp->psp_ctx->ps_processes, psp, next);
 	if (psp->psp_fd != -1) {
@@ -396,6 +389,10 @@
 		eloop_event_delete(psp->psp_ctx->eloop, psp->psp_work_fd);
 		close(psp->psp_work_fd);
 	}
+#ifdef INET
+	if (psp->psp_bpf != NULL)
+		bpf_close(psp->psp_bpf);
+#endif
 	free(psp);
 }
 
@@ -460,21 +457,20 @@
 ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd,
     struct ps_msghdr *psm, const struct msghdr *msg)
 {
-	assert(msg == NULL || msg->msg_iovlen == 1);
-
 	struct iovec iov[] = {
 		{ .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) },
 		{ .iov_base = NULL, },	/* name */
 		{ .iov_base = NULL, },	/* control */
-		{ .iov_base = NULL, },	/* payload */
+		{ .iov_base = NULL, },	/* payload 1 */
+		{ .iov_base = NULL, },	/* payload 2 */
+		{ .iov_base = NULL, },	/* payload 3 */
 	};
-	int iovlen = __arraycount(iov);
+	int iovlen;
 	ssize_t len;
 
 	if (msg != NULL) {
 		struct iovec *iovp = &iov[1];
-
-		assert(msg->msg_iovlen == 1);
+		size_t i;
 
 		psm->ps_namelen = msg->msg_namelen;
 		psm->ps_controllen = (socklen_t)msg->msg_controllen;
@@ -484,10 +480,18 @@
 		iovp++;
 		iovp->iov_base = msg->msg_control;
 		iovp->iov_len = msg->msg_controllen;
-		iovp++;
-		iovp->iov_base = msg->msg_iov[0].iov_base;
-		iovp->iov_len = msg->msg_iov[0].iov_len;
-		iovlen = __arraycount(iov);
+		iovlen = 3;
+
+		for (i = 0; i < (size_t)msg->msg_iovlen; i++) {
+			if ((size_t)iovlen + i > __arraycount(iov)) {
+				errno =	ENOBUFS;
+				return -1;
+			}
+			iovp++;
+			iovp->iov_base = msg->msg_iov[i].iov_base;
+			iovp->iov_len = msg->msg_iov[i].iov_len;
+		}
+		iovlen += i;
 	} else
 		iovlen = 1;
 
@@ -519,15 +523,16 @@
 ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
     const struct msghdr *msg)
 {
-	assert(msg->msg_iovlen == 1);
-
 	struct ps_msghdr psm = {
 		.ps_cmd = cmd,
 		.ps_flags = flags,
 		.ps_namelen = msg->msg_namelen,
 		.ps_controllen = (socklen_t)msg->msg_controllen,
-		.ps_datalen = msg->msg_iov[0].iov_len,
 	};
+	size_t i;
+
+	for (i = 0; i < (size_t)msg->msg_iovlen; i++)
+		psm.ps_datalen += msg->msg_iov[i].iov_len;
 
 #if 0	/* For debugging structure padding. */
 	logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family));
@@ -699,6 +704,9 @@
 	if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1)
 		return -1;
 
+	if (callback == NULL)
+		return 0;
+
 	errno = 0;
 	return callback(cbctx, &psm.psm_hdr, &msg);
 }
--- a/src/privsep.h	Sat May 16 12:28:56 2020 +0100
+++ b/src/privsep.h	Tue May 19 16:19:05 2020 +0100
@@ -40,7 +40,6 @@
 #define	PS_DHCP6		0x0003
 #define	PS_BPF_BOOTP		0x0004
 #define	PS_BPF_ARP		0x0005
-#define	PS_BPF_ARP_ADDR		0x0006
 
 /* Generic commands */
 #define	PS_IOCTL		0x0010
@@ -62,7 +61,6 @@
 #define	PS_WRITEPATHUINT	0x0201
 
 /* Process commands */
-#define	PS_DELETE		0x2000
 #define	PS_START		0x4000
 #define	PS_STOP			0x8000
 
@@ -119,6 +117,7 @@
 	uint8_t psm_data[PS_BUFLEN];
 };
 
+struct bpf;
 struct ps_process {
 	TAILQ_ENTRY(ps_process) next;
 	struct dhcpcd_ctx *psp_ctx;
@@ -132,8 +131,9 @@
 	const char *psp_protostr;
 
 #ifdef INET
-	int (*psp_filter)(struct interface *, int);
+	int (*psp_filter)(const struct bpf *, const struct in_addr *);
 	struct interface psp_ifp; /* Move BPF gubbins elsewhere */
+	struct bpf *psp_bpf;
 #endif
 };
 TAILQ_HEAD(ps_process_head, ps_process);