Import dhcpleased(8) - a dhcp daemon to acquire IPv4 address leases
authorflorian <florian@openbsd.org>
Fri, 26 Feb 2021 16:16:37 +0000 (16:16 +0000)
committerflorian <florian@openbsd.org>
Fri, 26 Feb 2021 16:16:37 +0000 (16:16 +0000)
from servers.

dhcpleased(8) follows the well known three process design of all our
privsep daemons. It uses pledge(2) and unveil(2) to restrict access
further. In particular the "engine" process, responsible for parsing
of untrusted data, is pledge'd "stdio". It cannot access the outside
world nor the filesystem at all.

Like slaacd(8) for IPv6 it will be always running and acquire addresses
for all interface with the autoconf4 flag set.
The flag can be set by "ifconfig $if inet autoconf" or by adding
"inet autoconf" to /etc/hostname.if. An existing "dhcp" line should
be removed.

Various iterations tested by deraadt@
The hardest part, finding a name, was handled by jmatthew@ & otto@

"get to it :)" deraadt@

21 files changed:
sbin/dhcpleased/Makefile [new file with mode: 0644]
sbin/dhcpleased/bpf.c [new file with mode: 0644]
sbin/dhcpleased/bpf.h [new file with mode: 0644]
sbin/dhcpleased/checksum.c [new file with mode: 0644]
sbin/dhcpleased/checksum.h [new file with mode: 0644]
sbin/dhcpleased/control.c [new file with mode: 0644]
sbin/dhcpleased/control.h [new file with mode: 0644]
sbin/dhcpleased/dhcpleased.8 [new file with mode: 0644]
sbin/dhcpleased/dhcpleased.c [new file with mode: 0644]
sbin/dhcpleased/dhcpleased.h [new file with mode: 0644]
sbin/dhcpleased/engine.c [new file with mode: 0644]
sbin/dhcpleased/engine.h [new file with mode: 0644]
sbin/dhcpleased/frontend.c [new file with mode: 0644]
sbin/dhcpleased/frontend.h [new file with mode: 0644]
sbin/dhcpleased/log.c [new file with mode: 0644]
sbin/dhcpleased/log.h [new file with mode: 0644]
usr.sbin/dhcpleasectl/Makefile [new file with mode: 0644]
usr.sbin/dhcpleasectl/dhcpleasectl.8 [new file with mode: 0644]
usr.sbin/dhcpleasectl/dhcpleasectl.c [new file with mode: 0644]
usr.sbin/dhcpleasectl/parser.c [new file with mode: 0644]
usr.sbin/dhcpleasectl/parser.h [new file with mode: 0644]

diff --git a/sbin/dhcpleased/Makefile b/sbin/dhcpleased/Makefile
new file mode 100644 (file)
index 0000000..8b18a7a
--- /dev/null
@@ -0,0 +1,19 @@
+#      $OpenBSD: Makefile,v 1.1 2021/02/26 16:16:37 florian Exp $
+
+PROG=  dhcpleased
+SRCS=  bpf.c checksum.c control.c dhcpleased.c engine.c frontend.c log.c
+
+MAN=   dhcpleased.8
+
+#DEBUG=        -g -DDEBUG=3 -O0
+
+CFLAGS+= -Wall -I${.CURDIR}
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare
+YFLAGS=
+LDADD+=        -levent -lutil
+DPADD+= ${LIBEVENT} ${LIBUTIL}
+
+.include <bsd.prog.mk>
diff --git a/sbin/dhcpleased/bpf.c b/sbin/dhcpleased/bpf.c
new file mode 100644 (file)
index 0000000..15ba041
--- /dev/null
@@ -0,0 +1,187 @@
+/*     $OpenBSD: bpf.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */
+
+/* BPF socket interface code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1998, 1999
+ * The Internet Software Consortium.    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.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ *    of its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/ethertypes.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bpf.h"
+#include "log.h"
+
+#define        CLIENT_PORT     68
+
+/*
+ * Packet filter program.
+ */
+struct bpf_insn dhcp_bpf_filter[] = {
+       /* Make sure this is an IP packet. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+       /* Make sure it's a UDP packet. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+       /* Make sure this isn't a fragment. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+       /* Get the IP header length. */
+       BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+       /* Make sure it's to the right port. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_PORT, 0, 1),
+
+       /* If we passed all the tests, ask for the whole packet. */
+       BPF_STMT(BPF_RET+BPF_K, (unsigned int)-1),
+
+       /* Otherwise, drop it. */
+       BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+/*
+ * Packet write filter program:
+ * 'ip and udp and src port bootpc and dst port bootps'
+ */
+struct bpf_insn dhcp_bpf_wfilter[] = {
+       BPF_STMT(BPF_LD + BPF_B + BPF_IND, 14),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (IPVERSION << 4) + 5, 0, 12),
+
+       /* Make sure this is an IP packet. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 10),
+
+       /* Make sure it's a UDP packet. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8),
+
+       /* Make sure this isn't a fragment. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0),     /* patched */
+
+       /* Get the IP header length. */
+       BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+       /* Make sure it's from the right port. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 68, 0, 3),
+
+       /* Make sure it is to the right ports. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1),
+
+       /* If we passed all the tests, ask for the whole packet. */
+       BPF_STMT(BPF_RET+BPF_K, (unsigned int)-1),
+
+       /* Otherwise, drop it. */
+       BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int
+get_bpf_sock(const char *name)
+{
+       struct bpf_program       p;
+       struct ifreq             ifr;
+       u_int                    sz;
+       int                      flag = 1, fildrop = BPF_FILDROP_CAPTURE;
+       int                      bpffd;
+
+       if ((bpffd = open("/dev/bpf", O_RDWR | O_CLOEXEC | O_NONBLOCK)) == -1)
+               fatal("open(/dev/bpf)");
+
+       sz = BPFLEN;
+       /* Set the BPF buffer length. */
+       if (ioctl(bpffd, BIOCSBLEN, &sz) == -1)
+               fatal("BIOCSBLEN");
+       if (sz != BPFLEN)
+               fatal("BIOCSBLEN, expected %u, got %u", BPFLEN, sz);
+
+       strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+       if (ioctl(bpffd, BIOCSETIF, &ifr) == -1) {
+               log_warn("BIOCSETIF"); /* interface might have disappeared */
+               close(bpffd);
+               return -1;
+       }
+
+       /*
+        * Set immediate mode so that reads return as soon as a packet
+        * comes in, rather than waiting for the input buffer to fill
+        * with packets.
+        */
+       if (ioctl(bpffd, BIOCIMMEDIATE, &flag) == -1)
+               fatal("BIOCIMMEDIATE");
+
+       if (ioctl(bpffd, BIOCSFILDROP, &fildrop) == -1)
+               fatal("BIOCSFILDROP");
+
+       /* Set up the bpf filter program structure. */
+       p.bf_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn);
+       p.bf_insns = dhcp_bpf_filter;
+
+       if (ioctl(bpffd, BIOCSETF, &p) == -1)
+               fatal("BIOCSETF");
+
+       /* Set up the bpf write filter program structure. */
+       p.bf_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn);
+       p.bf_insns = dhcp_bpf_wfilter;
+
+       if (dhcp_bpf_wfilter[7].k == 0x1fff)
+               dhcp_bpf_wfilter[7].k = htons(IP_MF|IP_OFFMASK);
+
+       if (ioctl(bpffd, BIOCSETWF, &p) == -1)
+               fatal("BIOCSETWF");
+
+       if (ioctl(bpffd, BIOCLOCK, NULL) == -1)
+               fatal("BIOCLOCK");
+
+       return bpffd;
+}
diff --git a/sbin/dhcpleased/bpf.h b/sbin/dhcpleased/bpf.h
new file mode 100644 (file)
index 0000000..b5a60f6
--- /dev/null
@@ -0,0 +1,45 @@
+/*     $OpenBSD: bpf.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */
+
+/* BPF socket interface code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1998, 1999
+ * The Internet Software Consortium.    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.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ *    of its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#define        BPFLEN 2048
+
+int     get_bpf_sock(const char *);
diff --git a/sbin/dhcpleased/checksum.c b/sbin/dhcpleased/checksum.c
new file mode 100644 (file)
index 0000000..9e26a7e
--- /dev/null
@@ -0,0 +1,80 @@
+/*     $OpenBSD: checksum.c,v 1.1 2021/02/26 16:16:37 florian Exp $    */
+
+/* Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
+ * 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.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ *    of its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <arpa/inet.h>
+
+#include <stdint.h>
+
+#include "checksum.h"
+
+uint32_t
+checksum(uint8_t *buf, uint32_t nbytes, uint32_t sum)
+{
+       unsigned int     i;
+
+       /* Checksum all the pairs of bytes first. */
+       for (i = 0; i < (nbytes & ~1U); i += 2) {
+               sum += (uint16_t)ntohs(*((uint16_t *)(buf + i)));
+               if (sum > 0xFFFF)
+                       sum -= 0xFFFF;
+       }
+
+       /*
+        * If there's a single byte left over, checksum it, too.
+        * Network byte order is big-endian, so the remaining byte is
+        * the high byte.
+        */
+       if (i < nbytes) {
+               sum += buf[i] << 8;
+               if (sum > 0xFFFF)
+                       sum -= 0xFFFF;
+       }
+
+       return sum;
+}
+
+uint32_t
+wrapsum(uint32_t sum)
+{
+       sum = ~sum & 0xFFFF;
+       return htons(sum);
+}
diff --git a/sbin/dhcpleased/checksum.h b/sbin/dhcpleased/checksum.h
new file mode 100644 (file)
index 0000000..fe5e7ca
--- /dev/null
@@ -0,0 +1,44 @@
+/*     $OpenBSD: checksum.h,v 1.1 2021/02/26 16:16:37 florian Exp $    */
+
+/* Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
+ * 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.
+ * 3. Neither the name of The Internet Software Consortium nor the names
+ *    of its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+uint32_t        checksum(uint8_t *, uint32_t, uint32_t);
+uint32_t        wrapsum(uint32_t);
diff --git a/sbin/dhcpleased/control.c b/sbin/dhcpleased/control.c
new file mode 100644 (file)
index 0000000..d413cb3
--- /dev/null
@@ -0,0 +1,305 @@
+/*     $OpenBSD: control.c,v 1.1 2021/02/26 16:16:37 florian Exp $     */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <md5.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "dhcpleased.h"
+#include "control.h"
+#include "frontend.h"
+
+#define        CONTROL_BACKLOG 5
+
+struct {
+       struct event    ev;
+       struct event    evt;
+       int             fd;
+} control_state = {.fd = -1};
+
+struct ctl_conn {
+       TAILQ_ENTRY(ctl_conn)   entry;
+       struct imsgev           iev;
+};
+
+struct ctl_conn        *control_connbyfd(int);
+struct ctl_conn        *control_connbypid(pid_t);
+void            control_close(int);
+
+TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
+
+int
+control_init(char *path)
+{
+       struct sockaddr_un       sun;
+       int                      fd;
+       mode_t                   old_umask;
+
+       if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           0)) == -1) {
+               log_warn("%s: socket", __func__);
+               return (-1);
+       }
+
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+       strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+
+       if (unlink(path) == -1)
+               if (errno != ENOENT) {
+                       log_warn("%s: unlink %s", __func__, path);
+                       close(fd);
+                       return (-1);
+               }
+
+       old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+       if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+               log_warn("%s: bind: %s", __func__, path);
+               close(fd);
+               umask(old_umask);
+               return (-1);
+       }
+       umask(old_umask);
+
+       if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+               log_warn("%s: chmod", __func__);
+               close(fd);
+               (void)unlink(path);
+               return (-1);
+       }
+
+       return (fd);
+}
+
+int
+control_listen(int fd)
+{
+       if (control_state.fd != -1)
+               fatalx("%s: received unexpected controlsock", __func__);
+
+       control_state.fd = fd;
+       if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+               log_warn("%s: listen", __func__);
+               return (-1);
+       }
+
+       event_set(&control_state.ev, control_state.fd, EV_READ,
+           control_accept, NULL);
+       event_add(&control_state.ev, NULL);
+       evtimer_set(&control_state.evt, control_accept, NULL);
+
+       return (0);
+}
+
+void
+control_accept(int listenfd, short event, void *bula)
+{
+       int                      connfd;
+       socklen_t                len;
+       struct sockaddr_un       sun;
+       struct ctl_conn         *c;
+
+       event_add(&control_state.ev, NULL);
+       if ((event & EV_TIMEOUT))
+               return;
+
+       len = sizeof(sun);
+       if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
+           SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) {
+               /*
+                * Pause accept if we are out of file descriptors, or
+                * libevent will haunt us here too.
+                */
+               if (errno == ENFILE || errno == EMFILE) {
+                       struct timeval evtpause = { 1, 0 };
+
+                       event_del(&control_state.ev);
+                       evtimer_add(&control_state.evt, &evtpause);
+               } else if (errno != EWOULDBLOCK && errno != EINTR &&
+                   errno != ECONNABORTED)
+                       log_warn("%s: accept4", __func__);
+               return;
+       }
+
+       if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
+               log_warn("%s: calloc", __func__);
+               close(connfd);
+               return;
+       }
+
+       imsg_init(&c->iev.ibuf, connfd);
+       c->iev.handler = control_dispatch_imsg;
+       c->iev.events = EV_READ;
+       event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+           c->iev.handler, &c->iev);
+       event_add(&c->iev.ev, NULL);
+
+       TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+       struct ctl_conn *c;
+
+       TAILQ_FOREACH(c, &ctl_conns, entry) {
+               if (c->iev.ibuf.fd == fd)
+                       break;
+       }
+
+       return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+       struct ctl_conn *c;
+
+       TAILQ_FOREACH(c, &ctl_conns, entry) {
+               if (c->iev.ibuf.pid == pid)
+                       break;
+       }
+
+       return (c);
+}
+
+void
+control_close(int fd)
+{
+       struct ctl_conn *c;
+
+       if ((c = control_connbyfd(fd)) == NULL) {
+               log_warnx("%s: fd %d: not found", __func__, fd);
+               return;
+       }
+
+       msgbuf_clear(&c->iev.ibuf.w);
+       TAILQ_REMOVE(&ctl_conns, c, entry);
+
+       event_del(&c->iev.ev);
+       close(c->iev.ibuf.fd);
+
+       /* Some file descriptors are available again. */
+       if (evtimer_pending(&control_state.evt, NULL)) {
+               evtimer_del(&control_state.evt);
+               event_add(&control_state.ev, NULL);
+       }
+
+       free(c);
+}
+
+void
+control_dispatch_imsg(int fd, short event, void *bula)
+{
+       struct ctl_conn *c;
+       struct imsg      imsg;
+       ssize_t          n;
+       int              verbose;
+
+       if ((c = control_connbyfd(fd)) == NULL) {
+               log_warnx("%s: fd %d: not found", __func__, fd);
+               return;
+       }
+
+       if (event & EV_READ) {
+               if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
+                   n == 0) {
+                       control_close(fd);
+                       return;
+               }
+       }
+       if (event & EV_WRITE) {
+               if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
+                       control_close(fd);
+                       return;
+               }
+       }
+
+       for (;;) {
+               if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+                       control_close(fd);
+                       return;
+               }
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_CTL_LOG_VERBOSE:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
+                               break;
+                       c->iev.ibuf.pid = imsg.hdr.pid;
+                       /* Forward to all other processes. */
+                       frontend_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid,
+                           imsg.data, IMSG_DATA_SIZE(imsg));
+                       frontend_imsg_compose_engine(imsg.hdr.type, 0,
+                           imsg.hdr.pid, imsg.data, IMSG_DATA_SIZE(imsg));
+
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_setverbose(verbose);
+                       break;
+               case IMSG_CTL_SHOW_INTERFACE_INFO:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(uint32_t))
+                               break;
+                       c->iev.ibuf.pid = imsg.hdr.pid;
+                       frontend_imsg_compose_engine(imsg.hdr.type, 0,
+                           imsg.hdr.pid, imsg.data, IMSG_DATA_SIZE(imsg));
+                       break;
+               case IMSG_CTL_SEND_REQUEST:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(uint32_t))
+                               break;
+                       c->iev.ibuf.pid = imsg.hdr.pid;
+                       frontend_imsg_compose_engine(imsg.hdr.type, 0,
+                           imsg.hdr.pid, imsg.data, IMSG_DATA_SIZE(imsg));
+                       break;
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+
+       imsg_event_add(&c->iev);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+       struct ctl_conn *c;
+
+       if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+               return (0);
+
+       return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
+           -1, imsg->data, IMSG_DATA_SIZE(*imsg)));
+}
diff --git a/sbin/dhcpleased/control.h b/sbin/dhcpleased/control.h
new file mode 100644 (file)
index 0000000..5ad6237
--- /dev/null
@@ -0,0 +1,25 @@
+/*     $OpenBSD: control.h,v 1.1 2021/02/26 16:16:37 florian Exp $     */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef        SMALL
+int    control_init(char *);
+int    control_listen(int);
+void   control_accept(int, short, void *);
+void   control_dispatch_imsg(int, short, void *);
+int    control_imsg_relay(struct imsg *);
+#endif /* SMALL */
diff --git a/sbin/dhcpleased/dhcpleased.8 b/sbin/dhcpleased/dhcpleased.8
new file mode 100644 (file)
index 0000000..fb6219a
--- /dev/null
@@ -0,0 +1,105 @@
+.\"    $OpenBSD: dhcpleased.8,v 1.1 2021/02/26 16:16:37 florian Exp $
+.\"
+.\" Copyright (c) 2021 Florian Obser <florian@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: February 26 2021 $
+.Dt DHCPLEASED 8
+.Os
+.Sh NAME
+.Nm dhcpleased
+.Nd a dynamic host configuration protocol daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dv
+.Op Fl s Ar socket
+.Sh DESCRIPTION
+.Nm
+is a dynamic host configuration protocol (DHCP) daemon for clients.
+It requests IP configuration information from for example those offered by
+.Xr dhcpd 8 ,
+on interfaces with the
+.Sy AUTOCONF4
+flag set.
+See
+.Xr hostname.if 5
+and
+.Xr ifconfig 8
+on how to enable auto configuration on an interface.
+.Pp
+.Nm
+monitors network interface states (interface going up or down,
+auto configuration enabled or disabled etc.) and sends requests
+when necessary.
+.Pp
+A running
+.Nm
+can be controlled with the
+.Xr dhcpleasectl 8
+utility.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl s Ar socket
+Use an alternate location for the default control socket.
+.It Fl v
+Produce more verbose output.
+Multiple
+.Fl v
+options increase the verbosity.
+.El
+.Sh FILES
+.Bl -tag -width "/dev/dhcpleased.sockXX" -compact
+.It Pa /dev/dhcpleased.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr dhcpleasectl 8 .
+.El
+.Sh SEE ALSO
+.Xr hostname.if 5 ,
+.Xr dhcpleasectl 8
+.Xr dhcpd 8 ,
+.Xr ifconfig 8 ,
+.Sh STANDARDS
+.Rs
+.%A R. Droms
+.%D March 1997
+.%R RFC 2131
+.%T Dynamic Host Configuration Protocol
+.Re
+.Pp
+.Rs
+.%A S. Alexander
+.%A R. Droms
+.%D March 1997
+.%R RFC 2132
+.%T DHCP Options and BOOTP Vendor Extensions
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 6.9 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Florian Obser Aq Mt florian@openbsd.org .
diff --git a/sbin/dhcpleased/dhcpleased.c b/sbin/dhcpleased/dhcpleased.c
new file mode 100644 (file)
index 0000000..38a95d2
--- /dev/null
@@ -0,0 +1,979 @@
+/*     $OpenBSD: dhcpleased.c,v 1.1 2021/02/26 16:16:37 florian Exp $  */
+
+/*
+ * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/syslog.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/in_var.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <event.h>
+#include <ifaddrs.h>
+#include <imsg.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "bpf.h"
+#include "log.h"
+#include "dhcpleased.h"
+#include "frontend.h"
+#include "engine.h"
+#include "control.h"
+
+enum dhcpleased_process {
+       PROC_MAIN,
+       PROC_ENGINE,
+       PROC_FRONTEND
+};
+
+__dead void    usage(void);
+__dead void    main_shutdown(void);
+
+void   main_sig_handler(int, short, void *);
+
+static pid_t   start_child(enum dhcpleased_process, char *, int, int, int);
+
+void    main_dispatch_frontend(int, short, void *);
+void    main_dispatch_engine(int, short, void *);
+void    open_bpfsock(uint32_t);
+void    configure_interface(struct imsg_configure_interface *);
+void    deconfigure_interface(struct imsg_configure_interface *);
+void    propose_rdns(struct imsg_propose_rdns *);
+void    configure_gateway(struct imsg_configure_interface *, uint8_t);
+int     open_lease_file(int);
+
+static int     main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *);
+int            main_imsg_compose_frontend(int, int, void *, uint16_t);
+int            main_imsg_compose_engine(int, int, void *, uint16_t);
+
+static struct imsgev   *iev_frontend;
+static struct imsgev   *iev_engine;
+
+pid_t                   frontend_pid;
+pid_t                   engine_pid;
+
+int                     routesock, ioctl_sock, rtm_seq = 0;
+
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+       /*
+        * Normal signal handler rules don't apply because libevent
+        * decouples for us.
+        */
+
+       switch (sig) {
+       case SIGTERM:
+       case SIGINT:
+               main_shutdown();
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+__dead void
+usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr, "usage: %s [-dv] [-s socket]\n",
+           __progname);
+       exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+       struct event             ev_sigint, ev_sigterm;
+       int                      ch;
+       int                      debug = 0, engine_flag = 0, frontend_flag = 0;
+       int                      verbose = 0;
+       char                    *saved_argv0;
+       int                      pipe_main2frontend[2];
+       int                      pipe_main2engine[2];
+       int                      frontend_routesock, rtfilter;
+       int                      rtable_any = RTABLE_ANY;
+       char                    *csock = DHCPLEASED_SOCKET;
+#ifndef SMALL
+       int                      control_fd;
+#endif /* SMALL */
+
+       log_init(1, LOG_DAEMON);        /* Log to stderr until daemonized. */
+       log_setverbose(1);
+
+       saved_argv0 = argv[0];
+       if (saved_argv0 == NULL)
+               saved_argv0 = "dhcpleased";
+
+       while ((ch = getopt(argc, argv, "dEFs:v")) != -1) {
+               switch (ch) {
+               case 'd':
+                       debug = 1;
+                       break;
+               case 'E':
+                       engine_flag = 1;
+                       break;
+               case 'F':
+                       frontend_flag = 1;
+                       break;
+               case 's':
+                       csock = optarg;
+                       break;
+               case 'v':
+                       verbose++;
+                       break;
+               default:
+                       usage();
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       if (argc > 0 || (engine_flag && frontend_flag))
+               usage();
+
+       if (engine_flag)
+               engine(debug, verbose);
+       else if (frontend_flag)
+               frontend(debug, verbose);
+
+       /* Check for root privileges. */
+       if (geteuid())
+               errx(1, "need root privileges");
+
+       /* Check for assigned daemon user */
+       if (getpwnam(DHCPLEASED_USER) == NULL)
+               errx(1, "unknown user %s", DHCPLEASED_USER);
+
+       log_init(debug, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       if (!debug)
+               daemon(0, 0);
+
+       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           PF_UNSPEC, pipe_main2frontend) == -1)
+               fatal("main2frontend socketpair");
+       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           PF_UNSPEC, pipe_main2engine) == -1)
+               fatal("main2engine socketpair");
+
+       /* Start children. */
+       engine_pid = start_child(PROC_ENGINE, saved_argv0, pipe_main2engine[1],
+           debug, verbose);
+       frontend_pid = start_child(PROC_FRONTEND, saved_argv0,
+           pipe_main2frontend[1], debug, verbose);
+
+       log_procinit("main");
+
+       if ((routesock = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC |
+           SOCK_NONBLOCK, AF_INET)) == -1)
+               fatal("route socket");
+       shutdown(SHUT_RD, routesock);
+
+       event_init();
+
+       /* Setup signal handler. */
+       signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, SIG_IGN);
+
+       /* Setup pipes to children. */
+
+       if ((iev_frontend = malloc(sizeof(struct imsgev))) == NULL ||
+           (iev_engine = malloc(sizeof(struct imsgev))) == NULL)
+               fatal(NULL);
+       imsg_init(&iev_frontend->ibuf, pipe_main2frontend[0]);
+       iev_frontend->handler = main_dispatch_frontend;
+       imsg_init(&iev_engine->ibuf, pipe_main2engine[0]);
+       iev_engine->handler = main_dispatch_engine;
+
+       /* Setup event handlers for pipes to engine & frontend. */
+       iev_frontend->events = EV_READ;
+       event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
+           iev_frontend->events, iev_frontend->handler, iev_frontend);
+       event_add(&iev_frontend->ev, NULL);
+
+       iev_engine->events = EV_READ;
+       event_set(&iev_engine->ev, iev_engine->ibuf.fd, iev_engine->events,
+           iev_engine->handler, iev_engine);
+       event_add(&iev_engine->ev, NULL);
+
+       if (main_imsg_send_ipc_sockets(&iev_frontend->ibuf, &iev_engine->ibuf))
+               fatal("could not establish imsg links");
+
+       if ((ioctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1)
+               fatal("socket");
+
+       if ((frontend_routesock = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC,
+           AF_INET)) == -1)
+               fatal("route socket");
+
+       rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_NEWADDR) |
+           ROUTE_FILTER(RTM_PROPOSAL);
+       if (setsockopt(frontend_routesock, AF_ROUTE, ROUTE_MSGFILTER,
+           &rtfilter, sizeof(rtfilter)) == -1)
+               fatal("setsockopt(ROUTE_MSGFILTER)");
+       if (setsockopt(frontend_routesock, AF_ROUTE, ROUTE_TABLEFILTER,
+           &rtable_any, sizeof(rtable_any)) == -1)
+               fatal("setsockopt(ROUTE_TABLEFILTER)");
+
+#ifndef SMALL
+       if ((control_fd = control_init(csock)) == -1)
+               fatalx("control socket setup failed");
+#endif /* SMALL */
+
+       if (unveil("/dev/bpf", "rw") == -1)
+               fatal("unveil");
+
+       if (unveil(LEASE_PATH, "rwc") == -1)
+               fatal("unveil");
+
+       if (unveil(NULL, NULL) == -1)
+               fatal("unveil");
+#if notyet
+       if (pledge("stdio inet rpath wpath sendfd wroute bpf", NULL) == -1)
+               fatal("pledge");
+#endif
+       main_imsg_compose_frontend(IMSG_ROUTESOCK, frontend_routesock, NULL, 0);
+
+#ifndef SMALL
+       main_imsg_compose_frontend(IMSG_CONTROLFD, control_fd, NULL, 0);
+#endif /* SMALL */
+
+       main_imsg_compose_frontend(IMSG_STARTUP, -1, NULL, 0);
+
+       event_dispatch();
+
+       main_shutdown();
+       return (0);
+}
+
+__dead void
+main_shutdown(void)
+{
+       pid_t    pid;
+       int      status;
+
+       /* Close pipes. */
+       msgbuf_clear(&iev_frontend->ibuf.w);
+       close(iev_frontend->ibuf.fd);
+       msgbuf_clear(&iev_engine->ibuf.w);
+       close(iev_engine->ibuf.fd);
+
+       log_debug("waiting for children to terminate");
+       do {
+               pid = wait(&status);
+               if (pid == -1) {
+                       if (errno != EINTR && errno != ECHILD)
+                               fatal("wait");
+               } else if (WIFSIGNALED(status))
+                       log_warnx("%s terminated; signal %d",
+                           (pid == engine_pid) ? "engine" :
+                           "frontend", WTERMSIG(status));
+       } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+       free(iev_frontend);
+       free(iev_engine);
+
+       log_info("terminating");
+       exit(0);
+}
+
+static pid_t
+start_child(enum dhcpleased_process p, char *argv0, int fd, int debug, int
+    verbose)
+{
+       char    *argv[7];
+       int      argc = 0;
+       pid_t    pid;
+
+       switch (pid = fork()) {
+       case -1:
+               fatal("cannot fork");
+       case 0:
+               break;
+       default:
+               close(fd);
+               return (pid);
+       }
+
+       if (fd != 3) {
+               if (dup2(fd, 3) == -1)
+                       fatal("cannot setup imsg fd");
+       } else if (fcntl(fd, F_SETFD, 0) == -1)
+               fatal("cannot setup imsg fd");
+
+       argv[argc++] = argv0;
+       switch (p) {
+       case PROC_MAIN:
+               fatalx("Can not start main process");
+       case PROC_ENGINE:
+               argv[argc++] = "-E";
+               break;
+       case PROC_FRONTEND:
+               argv[argc++] = "-F";
+               break;
+       }
+       if (debug)
+               argv[argc++] = "-d";
+       if (verbose)
+               argv[argc++] = "-v";
+       if (verbose > 1)
+               argv[argc++] = "-v";
+       argv[argc++] = NULL;
+
+       execvp(argv0, argv);
+       fatal("execvp");
+}
+
+void
+main_dispatch_frontend(int fd, short event, void *bula)
+{
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf;
+       struct imsg              imsg;
+       struct imsg_ifinfo       imsg_ifinfo;
+       ssize_t                  n;
+       int                      shut = 0;
+       uint32_t                 if_index;
+#ifndef        SMALL
+       int                      verbose;
+#endif /* SMALL */
+
+       ibuf = &iev->ibuf;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("imsg_get");
+               if (n == 0)     /* No more messages. */
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_OPEN_BPFSOCK:
+                       log_debug("IMSG_OPEN_BPFSOCK");
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_OPEN_BPFSOCK wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       open_bpfsock(if_index);
+                        break;
+#ifndef        SMALL
+               case IMSG_CTL_LOG_VERBOSE:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
+                               fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_setverbose(verbose);
+                       break;
+#endif /* SMALL */
+               case IMSG_UPDATE_IF:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo))
+                               fatalx("%s: IMSG_UPDATE_IF wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
+                       main_imsg_compose_engine(IMSG_UPDATE_IF,
+                           open_lease_file(imsg_ifinfo.if_index), &imsg_ifinfo,
+                           sizeof(imsg_ifinfo));
+                       break;
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* This pipe is dead. Remove its event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+void
+main_dispatch_engine(int fd, short event, void *bula)
+{
+       struct imsgev                   *iev = bula;
+       struct imsgbuf                  *ibuf;
+       struct imsg                      imsg;
+       ssize_t                          n;
+       int                              shut = 0;
+
+       ibuf = &iev->ibuf;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("imsg_get");
+               if (n == 0)     /* No more messages. */
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_CONFIGURE_INTERFACE: {
+                       struct imsg_configure_interface imsg_interface;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_interface))
+                               fatalx("%s: IMSG_CONFIGURE_INTERFACE wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_interface, imsg.data,
+                           sizeof(imsg_interface));
+                       configure_interface(&imsg_interface);
+                       break;
+               }
+               case IMSG_DECONFIGURE_INTERFACE: {
+                       struct imsg_configure_interface imsg_interface;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_interface))
+                               fatalx("%s: IMSG_CONFIGURE_INTERFACE wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_interface, imsg.data,
+                           sizeof(imsg_interface));
+                       deconfigure_interface(&imsg_interface);
+                       main_imsg_compose_frontend(IMSG_CLOSE_UDPSOCK, -1,
+                           &imsg_interface.if_index,
+                           sizeof(imsg_interface.if_index));
+                       break;
+               }
+               case IMSG_PROPOSE_RDNS: {
+                       struct imsg_propose_rdns         rdns;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(rdns))
+                               fatalx("%s: IMSG_PROPOSE_RDNS wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&rdns, imsg.data, sizeof(rdns));
+                       if ((2 + rdns.rdns_count * sizeof(struct in_addr)) >
+                           sizeof(struct sockaddr_rtdns))
+                               fatalx("%s: rdns_count too big: %d", __func__,
+                                   rdns.rdns_count);
+                       propose_rdns(&rdns);
+                       break;
+               }
+               case IMSG_WITHDRAW_RDNS: {
+                       struct imsg_propose_rdns         rdns;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(rdns))
+                               fatalx("%s: IMSG_PROPOSE_RDNS wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&rdns, imsg.data, sizeof(rdns));
+                       if (rdns.rdns_count != 0)
+                               fatalx("%s: expected rdns_count == 0: %d",
+                                   __func__, rdns.rdns_count);
+                       propose_rdns(&rdns);
+                       break;
+               }
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* This pipe is dead. Remove its event handler. */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+int
+main_imsg_compose_frontend(int type, int fd, void *data, uint16_t datalen)
+{
+       if (iev_frontend)
+               return (imsg_compose_event(iev_frontend, type, 0, 0, fd, data,
+                   datalen));
+       else
+               return (-1);
+}
+
+int
+main_imsg_compose_engine(int type, int fd, void *data, uint16_t datalen)
+{
+       if (iev_engine)
+               return(imsg_compose_event(iev_engine, type, 0, 0, fd, data,
+                   datalen));
+       else
+               return (-1);
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+       iev->events = EV_READ;
+       if (iev->ibuf.w.queued)
+               iev->events |= EV_WRITE;
+
+       event_del(&iev->ev);
+       event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+       event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+    pid_t pid, int fd, void *data, uint16_t datalen)
+{
+       int     ret;
+
+       if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data,
+           datalen)) != -1)
+               imsg_event_add(iev);
+
+       return (ret);
+}
+
+static int
+main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf,
+    struct imsgbuf *engine_buf)
+{
+       int pipe_frontend2engine[2];
+
+       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           PF_UNSPEC, pipe_frontend2engine) == -1)
+               return (-1);
+
+       if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC, 0, 0,
+           pipe_frontend2engine[0], NULL, 0) == -1)
+               return (-1);
+       imsg_flush(frontend_buf);
+       if (imsg_compose(engine_buf, IMSG_SOCKET_IPC, 0, 0,
+           pipe_frontend2engine[1], NULL, 0) == -1)
+               return (-1);
+       imsg_flush(engine_buf);
+       return (0);
+}
+
+void
+configure_interface(struct imsg_configure_interface *imsg)
+{
+       struct ifaliasreq        ifaliasreq;
+       struct ifaddrs          *ifap, *ifa;
+       struct sockaddr_in      *req_sin_addr, *req_sin_mask;
+       int                      found = 0, udpsock, opt = 1, len, fd = -1;
+       char                    *if_name;
+       char                     ntop_buf[INET_ADDRSTRLEN];
+       char                     lease_buf[LEASE_SIZE];
+       char                     lease_file_buf[sizeof(LEASE_PATH) +
+           IF_NAMESIZE];
+       char                     tmpl[] = LEASE_PATH"XXXXXXXXXX";
+
+       log_debug("%s", __func__);
+
+       memset(&ifaliasreq, 0, sizeof(ifaliasreq));
+
+       if_name = if_indextoname(imsg->if_index, ifaliasreq.ifra_name);
+       if (if_name == NULL) {
+               log_warnx("%s: cannot find interface %d", __func__,
+                   imsg->if_index);
+               return;
+       }
+
+       if (getifaddrs(&ifap) != 0)
+               fatal("getifaddrs");
+
+       req_sin_addr = (struct sockaddr_in *)&ifaliasreq.ifra_addr;
+       req_sin_addr->sin_family = AF_INET;
+       req_sin_addr->sin_len = sizeof(*req_sin_addr);
+
+       for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+               struct in_addr   addr, mask;
+
+               if (strcmp(if_name, ifa->ifa_name) != 0)
+                       continue;
+               if (ifa->ifa_addr == NULL)
+                       continue;
+               if (ifa->ifa_addr->sa_family != AF_INET)
+                       continue;
+
+               addr.s_addr = ((struct sockaddr_in *)ifa->ifa_addr)
+                   ->sin_addr.s_addr;
+               mask.s_addr = ((struct sockaddr_in *)ifa->ifa_netmask)
+                   ->sin_addr.s_addr;
+
+               if (imsg->addr.s_addr == addr.s_addr) {
+                       if (imsg->mask.s_addr == mask.s_addr)
+                               found = 1;
+                       else {
+                               req_sin_addr->sin_addr.s_addr = addr.s_addr;
+                               if (ioctl(ioctl_sock, SIOCDIFADDR, &ifaliasreq)
+                                   == -1) {
+                                       if (errno != EADDRNOTAVAIL)
+                                               log_warn("SIOCDIFADDR");
+                               }
+                       }
+                       break;
+               }
+       }
+
+       req_sin_addr->sin_addr.s_addr = imsg->addr.s_addr;
+       if (!found) {
+               req_sin_mask = (struct sockaddr_in *)&ifaliasreq.ifra_mask;
+               req_sin_mask->sin_family = AF_INET;
+               req_sin_mask->sin_len = sizeof(*req_sin_mask);
+               req_sin_mask->sin_addr.s_addr = imsg->mask.s_addr;
+               if (ioctl(ioctl_sock, SIOCAIFADDR, &ifaliasreq) == -1)
+                       fatal("SIOCAIFADDR");
+
+               /* XXX check weird shit in dhclient/kroute.c set_routes() */
+               if (imsg->router.s_addr != INADDR_ANY)
+                       configure_gateway(imsg, RTM_ADD);
+       }
+       req_sin_addr->sin_port = ntohs(CLIENT_PORT);
+       if ((udpsock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
+               log_warn("socket");
+               return;
+       }
+       if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR, &opt,
+           sizeof(opt)) == -1)
+               log_warn("setting SO_REUSEADDR on socket");
+
+       if (setsockopt(udpsock, SOL_SOCKET, SO_RTABLE, &imsg->rdomain,
+           sizeof(imsg->rdomain)) == -1) {
+               /* we might race against removal of the rdomain */
+               log_warn("setsockopt SO_RTABLE");
+               close(udpsock);
+               return;
+       }
+
+       if (bind(udpsock, (struct sockaddr *)req_sin_addr,
+           sizeof(*req_sin_addr)) == -1) {
+               close(udpsock);
+               return;
+       }
+
+       shutdown(udpsock, SHUT_RD);
+       log_debug("%s: udpsock: %d", __func__, udpsock);
+       main_imsg_compose_frontend(IMSG_UDPSOCK, udpsock,
+           &imsg->if_index, sizeof(imsg->if_index));
+
+       if (inet_ntop(AF_INET, &imsg->addr, ntop_buf, sizeof(ntop_buf)) ==
+           NULL) {
+               log_warn("%s: inet_ntop", __func__);
+               return;
+       }
+
+       len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s",
+           LEASE_PATH, if_name);
+       if ( len == -1 || (size_t) len >= sizeof(lease_file_buf)) {
+               log_warnx("%s: failed to encode lease path for %s", __func__,
+                   if_name);
+               return;
+       }
+
+       len = snprintf(lease_buf, sizeof(lease_buf), "%s%s\n", LEASE_PREFIX,
+           ntop_buf);
+       if ( len == -1 || (size_t) len >= sizeof(lease_buf)) {
+               log_warnx("%s: failed to encode lease for %s", __func__,
+                   ntop_buf);
+               return;
+       }
+
+       if ((fd = mkstemp(tmpl)) == -1) {
+               log_warn("%s: mkstemp", __func__);
+               return;
+       }
+
+       if (write(fd, lease_buf, len) < len)
+               goto err;
+
+       if (fchmod(fd, 0644) == -1)
+               goto err;
+
+       if (close(fd) == -1)
+               goto err;
+       fd = -1;
+
+       if (rename(tmpl, lease_file_buf) == -1)
+               goto err;
+       return;
+ err:
+       log_warn("%s", __func__);
+       if (fd != -1)
+               close(fd);
+       unlink(tmpl);
+}
+
+void
+deconfigure_interface(struct imsg_configure_interface *imsg)
+{
+       struct ifaliasreq        ifaliasreq;
+       struct sockaddr_in      *req_sin_addr;
+
+       log_debug("%s", __func__);
+
+       memset(&ifaliasreq, 0, sizeof(ifaliasreq));
+
+       if (if_indextoname(imsg->if_index, ifaliasreq.ifra_name) == NULL) {
+               log_warnx("%s: cannot find interface %d", __func__,
+                   imsg->if_index);
+               return;
+       }
+
+       req_sin_addr = (struct sockaddr_in *)&ifaliasreq.ifra_addr;
+       req_sin_addr->sin_family = AF_INET;
+       req_sin_addr->sin_len = sizeof(*req_sin_addr);
+
+       req_sin_addr->sin_addr.s_addr = imsg->addr.s_addr;
+       if (ioctl(ioctl_sock, SIOCDIFADDR, &ifaliasreq) == -1) {
+               if (errno != EADDRNOTAVAIL)
+                       log_warn("SIOCDIFADDR");
+       }
+}
+
+#define        ROUNDUP(a)      \
+    (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a))
+void
+configure_gateway(struct imsg_configure_interface *imsg, uint8_t rtm_type)
+{
+       struct rt_msghdr                 rtm;
+       struct sockaddr_rtlabel          rl;
+       struct sockaddr_in               dst, gw, mask;
+       struct iovec                     iov[10];
+       long                             pad = 0;
+       int                              iovcnt = 0, padlen;
+
+       memset(&rtm, 0, sizeof(rtm));
+
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_type = rtm_type;
+       rtm.rtm_msglen = sizeof(rtm);
+       rtm.rtm_tableid = imsg->rdomain;
+       rtm.rtm_index = imsg->if_index;
+       rtm.rtm_seq = ++rtm_seq;
+       rtm.rtm_priority = RTP_NONE;
+       rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_LABEL;
+       rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+
+       iov[iovcnt].iov_base = &rtm;
+       iov[iovcnt++].iov_len = sizeof(rtm);
+
+       memset(&dst, 0, sizeof(dst));
+       dst.sin_family = AF_INET;
+       dst.sin_len = sizeof(struct sockaddr_in);
+
+       iov[iovcnt].iov_base = &dst;
+       iov[iovcnt++].iov_len = sizeof(dst);
+       rtm.rtm_msglen += sizeof(dst);
+       padlen = ROUNDUP(sizeof(dst)) - sizeof(dst);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+               rtm.rtm_msglen += padlen;
+       }
+
+       memset(&gw, 0, sizeof(gw));
+       memcpy(&gw.sin_addr, &imsg->router, sizeof(gw.sin_addr));
+       gw.sin_family = AF_INET;
+       gw.sin_len = sizeof(struct sockaddr_in);
+       iov[iovcnt].iov_base = &gw;
+       iov[iovcnt++].iov_len = sizeof(gw);
+       rtm.rtm_msglen += sizeof(gw);
+       padlen = ROUNDUP(sizeof(gw)) - sizeof(gw);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+               rtm.rtm_msglen += padlen;
+       }
+
+       memset(&mask, 0, sizeof(mask));
+       mask.sin_family = AF_INET;
+       mask.sin_len = sizeof(struct sockaddr_in);
+       iov[iovcnt].iov_base = &mask;
+       iov[iovcnt++].iov_len = sizeof(mask);
+       rtm.rtm_msglen += sizeof(mask);
+       padlen = ROUNDUP(sizeof(mask)) - sizeof(mask);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+               rtm.rtm_msglen += padlen;
+       }
+
+       memset(&rl, 0, sizeof(rl));
+       rl.sr_len = sizeof(rl);
+       rl.sr_family = AF_UNSPEC;
+       (void)snprintf(rl.sr_label, sizeof(rl.sr_label), "%s",
+           DHCPLEASED_RTA_LABEL);
+       iov[iovcnt].iov_base = &rl;
+       iov[iovcnt++].iov_len = sizeof(rl);
+       rtm.rtm_msglen += sizeof(rl);
+       padlen = ROUNDUP(sizeof(rl)) - sizeof(rl);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+               rtm.rtm_msglen += padlen;
+       }
+
+       if (writev(routesock, iov, iovcnt) == -1)
+               log_warn("failed to send route message");
+}
+
+#ifndef        SMALL
+const char*
+sin_to_str(struct sockaddr_in *sin)
+{
+       static char hbuf[NI_MAXHOST];
+       int error;
+
+       error = getnameinfo((struct sockaddr *)sin, sin->sin_len, hbuf,
+           sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV);
+       if (error) {
+               log_warnx("%s", gai_strerror(error));
+               strlcpy(hbuf, "unknown", sizeof(hbuf));
+       }
+       return hbuf;
+}
+#endif /* SMALL */
+
+void
+open_bpfsock(uint32_t if_index)
+{
+       int              bpfsock;
+       char             ifname[IF_NAMESIZE];
+
+       log_debug("%s: %d", __func__, if_index);
+
+       if (if_indextoname(if_index, ifname) == 0) {
+               log_warnx("%s: cannot find interface %d", __func__, if_index);
+               return;
+       }
+
+       if ((bpfsock = get_bpf_sock(ifname)) == -1)
+               return;
+
+       main_imsg_compose_frontend(IMSG_BPFSOCK, bpfsock, &if_index,
+           sizeof(if_index));
+}
+
+void
+propose_rdns(struct imsg_propose_rdns *rdns)
+{
+       struct rt_msghdr                 rtm;
+       struct sockaddr_rtdns            rtdns;
+       struct iovec                     iov[3];
+       long                             pad = 0;
+       int                              iovcnt = 0, padlen;
+
+       memset(&rtm, 0, sizeof(rtm));
+
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_type = RTM_PROPOSAL;
+       rtm.rtm_msglen = sizeof(rtm);
+       rtm.rtm_tableid = rdns->rdomain;
+       rtm.rtm_index = rdns->if_index;
+       rtm.rtm_seq = ++rtm_seq;
+       rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT;
+       rtm.rtm_addrs = RTA_DNS;
+       rtm.rtm_flags = RTF_UP;
+
+       iov[iovcnt].iov_base = &rtm;
+       iov[iovcnt++].iov_len = sizeof(rtm);
+
+       memset(&rtdns, 0, sizeof(rtdns));
+       rtdns.sr_family = AF_INET;
+       rtdns.sr_len = 2 + rdns->rdns_count * sizeof(struct in_addr);
+       memcpy(rtdns.sr_dns, rdns->rdns, sizeof(rtdns.sr_dns));
+
+       iov[iovcnt].iov_base = &rtdns;
+       iov[iovcnt++].iov_len = sizeof(rtdns);
+       rtm.rtm_msglen += sizeof(rtdns);
+       padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+               rtm.rtm_msglen += padlen;
+       }
+
+       if (writev(routesock, iov, iovcnt) == -1)
+               log_warn("failed to send route message");
+}
+
+int
+open_lease_file(int if_index)
+{
+       int      len;
+       char     if_name[IF_NAMESIZE];
+       char     lease_file_buf[sizeof(LEASE_PATH) + IF_NAMESIZE];
+
+       if (if_indextoname(if_index, if_name) == 0) {
+               log_warnx("%s: cannot find interface %d", __func__, if_index);
+               return -1;
+       }
+
+       len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s",
+           LEASE_PATH, if_name);
+       if ( len == -1 || (size_t) len >= sizeof(lease_file_buf)) {
+               log_warnx("%s: failed to encode lease path for %s", __func__,
+                   if_name);
+               return -1;
+       }
+
+       return (open(lease_file_buf, O_RDONLY));
+}
diff --git a/sbin/dhcpleased/dhcpleased.h b/sbin/dhcpleased/dhcpleased.h
new file mode 100644 (file)
index 0000000..ff17968
--- /dev/null
@@ -0,0 +1,262 @@
+/*     $OpenBSD: dhcpleased.h,v 1.1 2021/02/26 16:16:37 florian Exp $  */
+
+/*
+ * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define        DHCPLEASED_SOCKET       "/dev/dhcpleased.sock"
+#define        DHCPLEASED_USER         "_dhcp"
+#define        DHCPLEASED_RTA_LABEL    "dhcpleased"
+#define        SERVER_PORT             67
+#define        CLIENT_PORT             68
+#define        LEASE_PATH              "/var/db/dhcpleased/"
+#define        LEASE_PREFIX            "version: 1\nip: "
+#define        LEASE_SIZE              sizeof(LEASE_PREFIX"xxx.xxx.xxx.xxx\n")
+/* MAXDNAME from arpa/namesr.h */
+#define        DHCPLEASED_MAX_DNSSL    1025
+#define        MAX_RDNS_COUNT          8 /* max nameserver in a RTM_PROPOSAL */
+
+#define        DHCP_COOKIE             {99, 130, 83, 99}
+
+/* Possible values for hardware type (htype) field. */
+#define        HTYPE_ETHER             1
+#define        HTYPE_IPSEC_TUNNEL      31
+
+/* DHCP op code */
+#define        DHCP_BOOTREQUEST                1
+#define        DHCP_BOOTREPLY                  2
+
+/* DHCP Option codes: */
+#define        DHO_PAD                         0
+#define        DHO_SUBNET_MASK                 1
+#define        DHO_TIME_OFFSET                 2
+#define        DHO_ROUTERS                     3
+#define        DHO_TIME_SERVERS                4
+#define        DHO_NAME_SERVERS                5
+#define        DHO_DOMAIN_NAME_SERVERS         6
+#define        DHO_LOG_SERVERS                 7
+#define        DHO_COOKIE_SERVERS              8
+#define        DHO_LPR_SERVERS                 9
+#define        DHO_IMPRESS_SERVERS             10
+#define        DHO_RESOURCE_LOCATION_SERVERS   11
+#define        DHO_HOST_NAME                   12
+#define        DHO_BOOT_SIZE                   13
+#define        DHO_MERIT_DUMP                  14
+#define        DHO_DOMAIN_NAME                 15
+#define        DHO_SWAP_SERVER                 16
+#define        DHO_ROOT_PATH                   17
+#define        DHO_EXTENSIONS_PATH             18
+#define        DHO_IP_FORWARDING               19
+#define        DHO_NON_LOCAL_SOURCE_ROUTING    20
+#define        DHO_POLICY_FILTER               21
+#define        DHO_MAX_DGRAM_REASSEMBLY        22
+#define        DHO_DEFAULT_IP_TTL              23
+#define        DHO_PATH_MTU_AGING_TIMEOUT      24
+#define        DHO_PATH_MTU_PLATEAU_TABLE      25
+#define        DHO_INTERFACE_MTU               26
+#define        DHO_ALL_SUBNETS_LOCAL           27
+#define        DHO_BROADCAST_ADDRESS           28
+#define        DHO_PERFORM_MASK_DISCOVERY      29
+#define        DHO_MASK_SUPPLIER               30
+#define        DHO_ROUTER_DISCOVERY            31
+#define        DHO_ROUTER_SOLICITATION_ADDRESS 32
+#define        DHO_STATIC_ROUTES               33
+#define        DHO_TRAILER_ENCAPSULATION       34
+#define        DHO_ARP_CACHE_TIMEOUT           35
+#define        DHO_IEEE802_3_ENCAPSULATION     36
+#define        DHO_DEFAULT_TCP_TTL             37
+#define        DHO_TCP_KEEPALIVE_INTERVAL      38
+#define        DHO_TCP_KEEPALIVE_GARBAGE       39
+#define        DHO_NIS_DOMAIN                  40
+#define        DHO_NIS_SERVERS                 41
+#define        DHO_NTP_SERVERS                 42
+#define        DHO_VENDOR_ENCAPSULATED_OPTIONS 43
+#define        DHO_NETBIOS_NAME_SERVERS        44
+#define        DHO_NETBIOS_DD_SERVER           45
+#define        DHO_NETBIOS_NODE_TYPE           46
+#define        DHO_NETBIOS_SCOPE               47
+#define        DHO_FONT_SERVERS                48
+#define        DHO_X_DISPLAY_MANAGER           49
+#define        DHO_DHCP_REQUESTED_ADDRESS      50
+#define        DHO_DHCP_LEASE_TIME             51
+#define        DHO_DHCP_OPTION_OVERLOAD        52
+#define        DHO_DHCP_MESSAGE_TYPE           53
+#define        DHO_DHCP_SERVER_IDENTIFIER      54
+#define        DHO_DHCP_PARAMETER_REQUEST_LIST 55
+#define        DHO_DHCP_MESSAGE                56
+#define        DHO_DHCP_MAX_MESSAGE_SIZE       57
+#define        DHO_DHCP_RENEWAL_TIME           58
+#define        DHO_DHCP_REBINDING_TIME         59
+#define        DHO_DHCP_CLASS_IDENTIFIER       60
+#define        DHO_DHCP_CLIENT_IDENTIFIER      61
+#define        DHO_NISPLUS_DOMAIN              64
+#define        DHO_NISPLUS_SERVERS             65
+#define        DHO_TFTP_SERVER                 66
+#define        DHO_BOOTFILE_NAME               67
+#define        DHO_MOBILE_IP_HOME_AGENT        68
+#define        DHO_SMTP_SERVER                 69
+#define        DHO_POP_SERVER                  70
+#define        DHO_NNTP_SERVER                 71
+#define        DHO_WWW_SERVER                  72
+#define        DHO_FINGER_SERVER               73
+#define        DHO_IRC_SERVER                  74
+#define        DHO_STREETTALK_SERVER           75
+#define        DHO_STREETTALK_DIRECTORY_ASSISTANCE_SERVER      76
+#define        DHO_DHCP_USER_CLASS_ID          77
+#define        DHO_RELAY_AGENT_INFORMATION     82
+#define        DHO_NDS_SERVERS                 85
+#define        DHO_NDS_TREE_NAME               86
+#define        DHO_NDS_CONTEXT                 87
+#define        DHO_DOMAIN_SEARCH               119
+#define        DHO_CLASSLESS_STATIC_ROUTES     121
+#define        DHO_TFTP_CONFIG_FILE            144
+#define        DHO_VOIP_CONFIGURATION_SERVER   150
+#define        DHO_CLASSLESS_MS_STATIC_ROUTES  249
+#define        DHO_AUTOPROXY_SCRIPT            252
+#define        DHO_END                         255
+#define        DHO_COUNT                       256     /* # of DHCP options */
+
+/* DHCP message types. */
+#define        DHCPDISCOVER    1
+#define        DHCPOFFER       2
+#define        DHCPREQUEST     3
+#define        DHCPDECLINE     4
+#define        DHCPACK         5
+#define        DHCPNAK         6
+#define        DHCPRELEASE     7
+#define        DHCPINFORM      8
+
+#define        IMSG_DATA_SIZE(imsg)    ((imsg).hdr.len - IMSG_HEADER_SIZE)
+#define        DHCP_SNAME_LEN          64
+#define        DHCP_FILE_LEN           128
+
+struct dhcp_hdr {
+       uint8_t         op;     /* Message opcode/type */
+       uint8_t         htype;  /* Hardware addr type (see net/if_types.h) */
+       uint8_t         hlen;   /* Hardware addr length */
+       uint8_t         hops;   /* Number of relay agent hops from client */
+       uint32_t        xid;    /* Transaction ID */
+       uint16_t        secs;   /* Seconds since client started looking */
+       uint16_t        flags;  /* Flag bits */
+       struct in_addr  ciaddr; /* Client IP address (if already in use) */
+       struct in_addr  yiaddr; /* Client IP address */
+       struct in_addr  siaddr; /* IP address of next server to talk to */
+       struct in_addr  giaddr; /* DHCP relay agent IP address */
+       uint8_t         chaddr[16];             /* Client hardware address */
+       char            sname[DHCP_SNAME_LEN];  /* Server name */
+       char            file[DHCP_FILE_LEN];    /* Boot filename */
+};
+
+struct imsgev {
+       struct imsgbuf   ibuf;
+       void            (*handler)(int, short, void *);
+       struct event     ev;
+       short            events;
+};
+
+enum imsg_type {
+       IMSG_NONE,
+#ifndef        SMALL
+       IMSG_CTL_LOG_VERBOSE,
+       IMSG_CTL_SHOW_INTERFACE_INFO,
+       IMSG_CTL_SEND_REQUEST,
+       IMSG_CTL_END,
+#endif /* SMALL */
+       IMSG_SEND_DISCOVER,
+       IMSG_SEND_REQUEST,
+       IMSG_SOCKET_IPC,
+       IMSG_OPEN_BPFSOCK,
+       IMSG_BPFSOCK,
+       IMSG_UDPSOCK,
+       IMSG_CLOSE_UDPSOCK,
+       IMSG_ROUTESOCK,
+       IMSG_CONTROLFD,
+       IMSG_STARTUP,
+       IMSG_UPDATE_IF,
+       IMSG_REMOVE_IF,
+       IMSG_DHCP,
+       IMSG_CONFIGURE_INTERFACE,
+       IMSG_DECONFIGURE_INTERFACE,
+       IMSG_PROPOSE_RDNS,
+       IMSG_WITHDRAW_RDNS,
+       IMSG_REPROPOSE_RDNS,
+};
+
+#ifndef        SMALL
+struct ctl_engine_info {
+       uint32_t                if_index;
+       int                     running;
+       int                     link_state;
+       char                    state[sizeof("IF_INIT_REBOOT")];
+       struct timespec         request_time;
+       struct in_addr          server_identifier;
+       struct in_addr          dhcp_server; /* for unicast */
+       struct in_addr          requested_ip;
+       struct in_addr          mask;
+       struct in_addr          router;
+       struct in_addr          nameservers[MAX_RDNS_COUNT];
+       uint32_t                lease_time;
+       uint32_t                renewal_time;
+       uint32_t                rebinding_time;
+};
+
+#endif /* SMALL */
+
+struct imsg_ifinfo {
+       uint32_t                if_index;
+       int                     rdomain;
+       int                     running;
+       int                     link_state;
+       struct ether_addr       hw_address;
+};
+
+struct imsg_propose_rdns {
+       uint32_t                if_index;
+       int                     rdomain;
+       int                     rdns_count;
+       struct in_addr          rdns[MAX_RDNS_COUNT];
+};
+
+struct imsg_dhcp {
+       uint32_t                if_index;
+       ssize_t                 len;
+       uint8_t                 packet[1500];
+};
+
+
+struct imsg_req_discover {
+       uint32_t                if_index;
+       uint32_t                xid;
+};
+
+struct imsg_req_request {
+       uint32_t                if_index;
+       uint32_t                xid;
+       struct in_addr          requested_ip;
+       struct in_addr          server_identifier;
+       struct in_addr          dhcp_server;
+};
+
+/* dhcpleased.c */
+void           imsg_event_add(struct imsgev *);
+int            imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
+                   int, void *, uint16_t);
+#ifndef        SMALL
+const char     *sin_to_str(struct sockaddr_in *);
+#else
+#define        sin_to_str(x...)        ""
+#endif /* SMALL */
diff --git a/sbin/dhcpleased/engine.c b/sbin/dhcpleased/engine.c
new file mode 100644 (file)
index 0000000..dace5fc
--- /dev/null
@@ -0,0 +1,1327 @@
+/*     $OpenBSD: engine.c,v 1.1 2021/02/26 16:16:37 florian Exp $      */
+
+/*
+ * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "checksum.h"
+#include "log.h"
+#include "dhcpleased.h"
+#include "engine.h"
+
+#define        MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))
+
+enum if_state {
+       IF_DOWN,
+       IF_INIT,
+       /* IF_SELECTING, */
+       IF_REQUESTING,
+       IF_BOUND,
+       IF_RENEWING,
+       IF_REBINDING,
+       /* IF_INIT_REBOOT, */
+       IF_REBOOTING,
+};
+
+const char* if_state_name[] = {
+       "Down",
+       "Init",
+       /* "Selecting", */
+       "Requesting",
+       "Bound",
+       "Renewing",
+       "Rebinding",
+       /* "Init-Reboot", */
+       "Rebooting",
+};
+
+struct dhcpleased_iface {
+       LIST_ENTRY(dhcpleased_iface)     entries;
+       enum if_state                    state;
+       struct event                     timer;
+       struct timeval                   timo;
+       uint32_t                         if_index;
+       int                              rdomain;
+       int                              running;
+       struct ether_addr                hw_address;
+       int                              link_state;
+       uint32_t                         cur_mtu;
+       uint32_t                         xid;
+       struct timespec                  request_time;
+       struct in_addr                   server_identifier;
+       struct in_addr                   dhcp_server; /* for unicast */
+       struct in_addr                   requested_ip;
+       struct in_addr                   mask;
+       struct in_addr                   router;
+       struct in_addr                   nameservers[MAX_RDNS_COUNT];
+       uint32_t                         lease_time;
+       uint32_t                         renewal_time;
+       uint32_t                         rebinding_time;
+};
+
+LIST_HEAD(, dhcpleased_iface) dhcpleased_interfaces;
+
+__dead void             engine_shutdown(void);
+void                    engine_sig_handler(int sig, short, void *);
+void                    engine_dispatch_frontend(int, short, void *);
+void                    engine_dispatch_main(int, short, void *);
+#ifndef        SMALL
+void                    send_interface_info(struct dhcpleased_iface *, pid_t);
+void                    engine_showinfo_ctl(struct imsg *, uint32_t);
+#endif /* SMALL */
+void                    engine_update_iface(struct imsg_ifinfo *, int);
+struct dhcpleased_iface        *get_dhcpleased_iface_by_id(uint32_t);
+void                    remove_dhcpleased_iface(uint32_t);
+void                    parse_dhcp(struct dhcpleased_iface *,
+                            struct imsg_dhcp *);
+void                    state_transition(struct dhcpleased_iface *, enum
+                            if_state);
+void                    iface_timeout(int, short, void *);
+void                    request_dhcp_discover(struct dhcpleased_iface *);
+void                    request_dhcp_request(struct dhcpleased_iface *);
+void                    send_configure_interface(struct dhcpleased_iface *);
+void                    send_rdns_proposal(struct dhcpleased_iface *);
+void                    send_deconfigure_interface(struct dhcpleased_iface *);
+void                    send_rdns_withdraw(struct dhcpleased_iface *);
+void                    parse_lease(int, struct dhcpleased_iface *);
+int                     engine_imsg_compose_main(int, pid_t, void *, uint16_t);
+
+static struct imsgev   *iev_frontend;
+static struct imsgev   *iev_main;
+int64_t                         proposal_id;
+
+void
+engine_sig_handler(int sig, short event, void *arg)
+{
+       /*
+        * Normal signal handler rules don't apply because libevent
+        * decouples for us.
+        */
+
+       switch (sig) {
+       case SIGINT:
+       case SIGTERM:
+               engine_shutdown();
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+void
+engine(int debug, int verbose)
+{
+       struct event             ev_sigint, ev_sigterm;
+       struct passwd           *pw;
+
+       log_init(debug, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       if ((pw = getpwnam(DHCPLEASED_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chroot(pw->pw_dir) == -1)
+               fatal("chroot");
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       setproctitle("%s", "engine");
+       log_procinit("engine");
+
+       if (setgroups(1, &pw->pw_gid) ||
+           setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+           setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+               fatal("can't drop privileges");
+
+       if (pledge("stdio recvfd", NULL) == -1)
+               fatal("pledge");
+
+       event_init();
+
+       /* Setup signal handler(s). */
+       signal_set(&ev_sigint, SIGINT, engine_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, engine_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, SIG_IGN);
+
+       /* Setup pipe and event handler to the main process. */
+       if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+               fatal(NULL);
+
+       imsg_init(&iev_main->ibuf, 3);
+       iev_main->handler = engine_dispatch_main;
+
+       /* Setup event handlers. */
+       iev_main->events = EV_READ;
+       event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+           iev_main->handler, iev_main);
+       event_add(&iev_main->ev, NULL);
+
+       LIST_INIT(&dhcpleased_interfaces);
+
+       event_dispatch();
+
+       engine_shutdown();
+}
+
+__dead void
+engine_shutdown(void)
+{
+       /* Close pipes. */
+       msgbuf_clear(&iev_frontend->ibuf.w);
+       close(iev_frontend->ibuf.fd);
+       msgbuf_clear(&iev_main->ibuf.w);
+       close(iev_main->ibuf.fd);
+
+       free(iev_frontend);
+       free(iev_main);
+
+       log_info("engine exiting");
+       exit(0);
+}
+
+int
+engine_imsg_compose_frontend(int type, pid_t pid, void *data,
+    uint16_t datalen)
+{
+       return (imsg_compose_event(iev_frontend, type, 0, pid, -1,
+           data, datalen));
+}
+
+int
+engine_imsg_compose_main(int type, pid_t pid, void *data,
+    uint16_t datalen)
+{
+       return (imsg_compose_event(iev_main, type, 0, pid, -1,
+           data, datalen));
+}
+
+void
+engine_dispatch_frontend(int fd, short event, void *bula)
+{
+       struct imsgev                   *iev = bula;
+       struct imsgbuf                  *ibuf = &iev->ibuf;
+       struct imsg                      imsg;
+       struct dhcpleased_iface         *iface;
+       ssize_t                          n;
+       int                              shut = 0;
+#ifndef        SMALL
+       int                              verbose;
+#endif /* SMALL */
+       uint32_t                         if_index;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("%s: imsg_get error", __func__);
+               if (n == 0)     /* No more messages. */
+                       break;
+
+               switch (imsg.hdr.type) {
+#ifndef        SMALL
+               case IMSG_CTL_LOG_VERBOSE:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
+                               fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_setverbose(verbose);
+                       break;
+               case IMSG_CTL_SHOW_INTERFACE_INFO:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_CTL_SHOW_INTERFACE_INFO wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       engine_showinfo_ctl(&imsg, if_index);
+                       break;
+               case IMSG_CTL_SEND_REQUEST:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_CTL_SEND_DISCOVER wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       iface = get_dhcpleased_iface_by_id(if_index);
+                       if (iface != NULL) {
+                               switch (iface->state) {
+                               case IF_DOWN:
+                                       break;
+                               case IF_INIT:
+                               case IF_REQUESTING:
+                               case IF_RENEWING:
+                               case IF_REBINDING:
+                               case IF_REBOOTING:
+                                       state_transition(iface, iface->state);
+                                       break;
+                               case IF_BOUND:
+                                       state_transition(iface, IF_RENEWING);
+                                       break;
+                               }
+                       }
+                       break;
+#endif /* SMALL */
+               case IMSG_REMOVE_IF:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_REMOVE_IF wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       remove_dhcpleased_iface(if_index);
+                       break;
+               case IMSG_DHCP: {
+                       struct imsg_dhcp        imsg_dhcp;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_dhcp))
+                               fatalx("%s: IMSG_DHCP wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_dhcp, imsg.data, sizeof(imsg_dhcp));
+                       iface = get_dhcpleased_iface_by_id(imsg_dhcp.if_index);
+                       if (iface != NULL)
+                               parse_dhcp(iface, &imsg_dhcp);
+                       break;
+               }
+               case IMSG_REPROPOSE_RDNS:
+                       LIST_FOREACH (iface, &dhcpleased_interfaces, entries)
+                               send_rdns_proposal(iface);
+                       break;
+               default:
+                       log_debug("%s: unexpected imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* This pipe is dead. Remove its event handler. */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+void
+engine_dispatch_main(int fd, short event, void *bula)
+{
+       struct imsg              imsg;
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct imsg_ifinfo       imsg_ifinfo;
+       ssize_t                  n;
+       int                      shut = 0;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("%s: imsg_get error", __func__);
+               if (n == 0)     /* No more messages. */
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_SOCKET_IPC:
+                       /*
+                        * Setup pipe and event handler to the frontend
+                        * process.
+                        */
+                       if (iev_frontend)
+                               fatalx("%s: received unexpected imsg fd "
+                                   "to engine", __func__);
+
+                       if ((fd = imsg.fd) == -1)
+                               fatalx("%s: expected to receive imsg fd to "
+                                  "engine but didn't receive any", __func__);
+
+                       iev_frontend = malloc(sizeof(struct imsgev));
+                       if (iev_frontend == NULL)
+                               fatal(NULL);
+
+                       imsg_init(&iev_frontend->ibuf, fd);
+                       iev_frontend->handler = engine_dispatch_frontend;
+                       iev_frontend->events = EV_READ;
+
+                       event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
+                       iev_frontend->events, iev_frontend->handler,
+                           iev_frontend);
+                       event_add(&iev_frontend->ev, NULL);
+                       break;
+               case IMSG_UPDATE_IF:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo))
+                               fatalx("%s: IMSG_UPDATE_IF wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
+                       engine_update_iface(&imsg_ifinfo, imsg.fd);
+                       break;
+               default:
+                       log_debug("%s: unexpected imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* This pipe is dead. Remove its event handler. */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+#ifndef        SMALL
+void
+send_interface_info(struct dhcpleased_iface *iface, pid_t pid)
+{
+       struct ctl_engine_info   cei;
+
+       memset(&cei, 0, sizeof(cei));
+       cei.if_index = iface->if_index;
+       cei.running = iface->running;
+       cei.link_state = iface->link_state;
+       strlcpy(cei.state, if_state_name[iface->state], sizeof(cei.state));
+       memcpy(&cei.request_time, &iface->request_time,
+           sizeof(cei.request_time));
+       cei.server_identifier.s_addr = iface->server_identifier.s_addr;
+       cei.dhcp_server.s_addr = iface->dhcp_server.s_addr;
+       cei.requested_ip.s_addr = iface->requested_ip.s_addr;
+       cei.mask.s_addr = iface->mask.s_addr;
+       cei.router.s_addr = iface->router.s_addr;
+       memcpy(cei.nameservers, iface->nameservers, sizeof(cei.nameservers));
+       cei.lease_time = iface->lease_time;
+       cei.renewal_time = iface->renewal_time;
+       cei.rebinding_time = iface->rebinding_time;
+       engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei,
+           sizeof(cei));
+}
+
+void
+engine_showinfo_ctl(struct imsg *imsg, uint32_t if_index)
+{
+       struct dhcpleased_iface                 *iface;
+
+       switch (imsg->hdr.type) {
+       case IMSG_CTL_SHOW_INTERFACE_INFO:
+               if (if_index == 0) {
+                       LIST_FOREACH (iface, &dhcpleased_interfaces, entries)
+                               send_interface_info(iface, imsg->hdr.pid);
+               } else {
+                       if ((iface = get_dhcpleased_iface_by_id(if_index)) !=
+                           NULL)
+                               send_interface_info(iface, imsg->hdr.pid);
+               }
+               engine_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL,
+                   0);
+               break;
+       default:
+               log_debug("%s: error handling imsg", __func__);
+               break;
+       }
+}
+#endif /* SMALL */
+void
+engine_update_iface(struct imsg_ifinfo *imsg_ifinfo, int fd)
+{
+       struct dhcpleased_iface *iface;
+       int                      need_refresh = 0;
+
+       iface = get_dhcpleased_iface_by_id(imsg_ifinfo->if_index);
+
+       if (iface == NULL) {
+               if ((iface = calloc(1, sizeof(*iface))) == NULL)
+                       fatal("calloc");
+               iface->state = IF_DOWN;
+               iface->timo.tv_usec = arc4random_uniform(1000000);
+               evtimer_set(&iface->timer, iface_timeout, iface);
+               iface->if_index = imsg_ifinfo->if_index;
+               iface->rdomain = imsg_ifinfo->rdomain;
+               iface->running = imsg_ifinfo->running;
+               iface->link_state = imsg_ifinfo->link_state;
+               iface->requested_ip.s_addr = INADDR_ANY;
+               memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
+                   sizeof(struct ether_addr));
+               LIST_INSERT_HEAD(&dhcpleased_interfaces, iface, entries);
+
+               if (fd != -1)
+                       parse_lease(fd, iface);
+               need_refresh = 1;
+       } else {
+               if (fd != -1)
+                       close(fd);
+
+               if (memcmp(&iface->hw_address, &imsg_ifinfo->hw_address,
+                   sizeof(struct ether_addr)) != 0) {
+                       memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
+                           sizeof(struct ether_addr));
+                       need_refresh = 1;
+               }
+               if (imsg_ifinfo->rdomain != iface->rdomain) {
+                       iface->rdomain = imsg_ifinfo->rdomain;
+                       need_refresh = 1;
+               }
+               if (imsg_ifinfo->running != iface->running) {
+                       iface->running = imsg_ifinfo->running;
+                       need_refresh = 1;
+               }
+
+               if (imsg_ifinfo->link_state != iface->link_state) {
+                       iface->link_state = imsg_ifinfo->link_state;
+                       need_refresh = 1;
+               }
+       }
+
+       if (!need_refresh)
+               return;
+
+       if (iface->running && LINK_STATE_IS_UP(iface->link_state)) {
+               if (iface->requested_ip.s_addr == INADDR_ANY)
+                       state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_REBOOTING);
+       } else
+               state_transition(iface, IF_DOWN);
+}
+struct dhcpleased_iface*
+get_dhcpleased_iface_by_id(uint32_t if_index)
+{
+       struct dhcpleased_iface *iface;
+       LIST_FOREACH (iface, &dhcpleased_interfaces, entries) {
+               if (iface->if_index == if_index)
+                       return (iface);
+       }
+
+       return (NULL);
+}
+
+void
+remove_dhcpleased_iface(uint32_t if_index)
+{
+       struct dhcpleased_iface *iface;
+
+       iface = get_dhcpleased_iface_by_id(if_index);
+
+       if (iface == NULL)
+               return;
+
+       send_deconfigure_interface(iface);
+       send_rdns_withdraw(iface);
+       LIST_REMOVE(iface, entries);
+       evtimer_del(&iface->timer);
+       free(iface);
+}
+
+void
+parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
+{
+       static uint8_t           cookie[] = DHCP_COOKIE;
+       static struct ether_addr bcast_mac;
+       struct ether_header     *eh;
+       struct ether_addr        ether_src, ether_dst;
+       struct ip               *ip;
+       struct udphdr           *udp;
+       struct dhcp_hdr         *dhcp_hdr;
+       struct in_addr           server_identifier, subnet_mask, router;
+       struct in_addr           nameservers[MAX_RDNS_COUNT];
+       size_t                   rem, i;
+       uint32_t                 sum, usum, lease_time = 0, renewal_time = 0;
+       uint32_t                 rebinding_time = 0;
+       uint8_t                 *p, dho = DHO_PAD, dho_len;
+       uint8_t                  dhcp_message_type = 0;
+       char                     from[sizeof("xx:xx:xx:xx:xx:xx")];
+       char                     to[sizeof("xx:xx:xx:xx:xx:xx")];
+       char                     hbuf_src[INET_ADDRSTRLEN];
+       char                     hbuf_dst[INET_ADDRSTRLEN];
+       char                     hbuf[INET_ADDRSTRLEN];
+       char                     vis_buf[4 * 255 + 1];
+
+       if (bcast_mac.ether_addr_octet[0] == 0)
+               memset(bcast_mac.ether_addr_octet, 0xff, ETHER_ADDR_LEN);
+
+       memset(hbuf_src, 0, sizeof(hbuf_src));
+       memset(hbuf_dst, 0, sizeof(hbuf_dst));
+
+       p = dhcp->packet;
+       rem = dhcp->len;
+
+       if (rem < sizeof(*eh)) {
+               log_warnx("%s: message too short", __func__);
+               return;
+       }
+       eh = (struct ether_header *)p;
+       memcpy(ether_src.ether_addr_octet, eh->ether_shost,
+           sizeof(ether_src.ether_addr_octet));
+       strlcpy(from, ether_ntoa(&ether_src), sizeof(from));
+       memcpy(ether_dst.ether_addr_octet, eh->ether_dhost,
+           sizeof(ether_dst.ether_addr_octet));
+       strlcpy(to, ether_ntoa(&ether_dst), sizeof(to));
+       p += sizeof(*eh);
+       rem -= sizeof(*eh);
+
+       if (memcmp(&ether_dst, &iface->hw_address, sizeof(ether_dst)) != 0 &&
+           memcmp(&ether_dst, &bcast_mac, sizeof(ether_dst)) != 0)
+               return ; /* silently ignore packet not for us */
+
+       if (rem < sizeof(*ip))
+               goto too_short;
+
+       log_debug("%s, from: %s, to: %s", __func__, from, to);
+
+       ip = (struct ip *)p;
+
+       if (rem < (size_t)ip->ip_hl << 2)
+               goto too_short;
+
+       if (wrapsum(checksum((uint8_t *)ip, ip->ip_hl << 2, 0)) != 0) {
+               log_warnx("%s: bad IP checksum", __func__);
+               return;
+       }
+       if (rem < ntohs(ip->ip_len))
+               goto too_short;
+
+       p += ip->ip_hl << 2;
+       rem -= ip->ip_hl << 2;
+
+       if (inet_ntop(AF_INET, &ip->ip_src, hbuf_src, sizeof(hbuf_src)) == NULL)
+               hbuf_src[0] = '\0';
+       if (inet_ntop(AF_INET, &ip->ip_dst, hbuf_dst, sizeof(hbuf_dst)) == NULL)
+               hbuf_src[0] = '\0';
+
+       if (rem < sizeof(*udp))
+               goto too_short;
+
+       udp = (struct udphdr *)p;
+       if (rem < ntohs(udp->uh_ulen))
+               goto too_short;
+
+       if (rem > ntohs(udp->uh_ulen)) {
+               log_debug("%s: accepting packet with %lu bytes of data after "
+                   "udp payload", __func__, rem - ntohs(udp->uh_ulen));
+               rem = ntohs(udp->uh_ulen);
+       }
+
+       p += sizeof(*udp);
+       rem -= sizeof(*udp);
+
+       usum = udp->uh_sum;
+       udp->uh_sum = 0;
+
+       sum = wrapsum(checksum((uint8_t *)udp, sizeof(*udp), checksum(p, rem,
+           checksum((uint8_t *)&ip->ip_src, 2 * sizeof(ip->ip_src),
+           IPPROTO_UDP + ntohs(udp->uh_ulen)))));
+
+       if (usum != 0 && usum != sum) {
+               log_warnx("%s: bad UDP checksum", __func__);
+               return;
+       }
+
+       log_debug("%s: %s:%d -> %s:%d", __func__,
+           hbuf_src, ntohs(udp->uh_sport),
+           hbuf_dst, ntohs(udp->uh_dport));
+
+       if (ntohs(udp->uh_sport) != SERVER_PORT ||
+           ntohs(udp->uh_dport) != CLIENT_PORT) {
+               log_warnx("%s: invalid ports used %s:%d -> %s:%d", __func__,
+                   hbuf_src, ntohs(udp->uh_sport),
+                   hbuf_dst, ntohs(udp->uh_dport));
+               return;
+       }
+       if (rem < sizeof(*dhcp_hdr))
+               goto too_short;
+
+       dhcp_hdr = (struct dhcp_hdr *)p;
+       p += sizeof(*dhcp_hdr);
+       rem -= sizeof(*dhcp_hdr);
+
+       dhcp_hdr->sname[DHCP_SNAME_LEN -1 ] = '\0'; /* ensure it's a string */
+       dhcp_hdr->file[DHCP_FILE_LEN -1 ] = '\0'; /* ensure it's a string */
+       log_debug("dhcp_hdr op: %s (%d)", dhcp_hdr->op == DHCP_BOOTREQUEST ?
+           "Boot Request" : dhcp_hdr->op == DHCP_BOOTREPLY ? "Boot Reply" :
+           "Unknown", dhcp_hdr->op);
+       log_debug("dhcp_hdr htype: %s (%d)", dhcp_hdr->htype == 1 ? "Ethernet":
+           "Unknown", dhcp_hdr->htype);
+       log_debug("dhcp_hdr hlen: %d", dhcp_hdr->hlen);
+       log_debug("dhcp_hdr hops: %d", dhcp_hdr->hops);
+       log_debug("dhcp_hdr xid: 0x%x", dhcp_hdr->xid);
+       log_debug("dhcp_hdr secs: %u", dhcp_hdr->secs);
+       log_debug("dhcp_hdr flags: 0x%x", dhcp_hdr->flags);
+       log_debug("dhcp_hdr ciaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->ciaddr,
+           hbuf, sizeof(hbuf)));
+       log_debug("dhcp_hdr yiaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->yiaddr,
+           hbuf, sizeof(hbuf)));
+       log_debug("dhcp_hdr siaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->siaddr,
+           hbuf, sizeof(hbuf)));
+       log_debug("dhcp_hdr giaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->giaddr,
+           hbuf, sizeof(hbuf)));
+       log_debug("dhcp_hdr chaddr: %02x:%02x:%02x:%02x:%02x:%02x "
+           "(%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x)",
+           dhcp_hdr->chaddr[0], dhcp_hdr->chaddr[1], dhcp_hdr->chaddr[2],
+           dhcp_hdr->chaddr[3], dhcp_hdr->chaddr[4], dhcp_hdr->chaddr[5],
+           dhcp_hdr->chaddr[6], dhcp_hdr->chaddr[7], dhcp_hdr->chaddr[8],
+           dhcp_hdr->chaddr[9], dhcp_hdr->chaddr[10], dhcp_hdr->chaddr[11],
+           dhcp_hdr->chaddr[12], dhcp_hdr->chaddr[13], dhcp_hdr->chaddr[14],
+           dhcp_hdr->chaddr[15]);
+       /* ignore sname and file, if we ever print it use strvis(3) */
+
+       if (dhcp_hdr->op != DHCP_BOOTREPLY) {
+               log_warnx("%s: ignorning non-reply packet", __func__);
+               return;
+       }
+
+       if (dhcp_hdr->xid != iface->xid)
+               return; /* silently ignore wrong xid */
+
+       if (rem < sizeof(cookie))
+               goto too_short;
+
+       if (memcmp(p, cookie, sizeof(cookie)) != 0) {
+               log_warnx("%s: no dhcp cookie in packet from %s", __func__,
+                   from);
+               return;
+       }
+       p += sizeof(cookie);
+       rem -= sizeof(cookie);
+
+       memset(&server_identifier, 0, sizeof(server_identifier));
+       memset(&subnet_mask, 0, sizeof(subnet_mask));
+       memset(&router, 0, sizeof(router));
+       memset(&nameservers, 0, sizeof(nameservers));
+
+       while (rem > 0 && dho != DHO_END) {
+               dho = *p;
+               p += 1;
+               rem -= 1;
+               /* only DHO_END and DHO_PAD are 1 byte long without length */
+               if (dho == DHO_PAD || dho == DHO_END)
+                       dho_len = 0;
+               else {
+                       if (rem == 0)
+                               goto too_short; /* missing option length */
+                       dho_len = *p;
+                       p += 1;
+                       rem -= 1;
+                       if (rem < dho_len)
+                               goto too_short;
+               }
+
+               switch(dho) {
+               case DHO_PAD:
+                       log_debug("DHO_PAD");
+                       break;
+               case DHO_END:
+                       log_debug("DHO_END");
+                       break;
+               case DHO_DHCP_MESSAGE_TYPE:
+                       if (dho_len != 1)
+                               goto wrong_length;
+                       dhcp_message_type = *p;
+                       switch (dhcp_message_type) {
+                       case DHCPDISCOVER:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPDISCOVER");
+                               break;
+                       case DHCPOFFER:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPOFFER");
+                               break;
+                       case DHCPREQUEST:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPREQUEST");
+                               break;
+                       case DHCPDECLINE:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPDECLINE");
+                               break;
+                       case DHCPACK:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPACK");
+                               break;
+                       case DHCPNAK:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPNAK");
+                               break;
+                       case DHCPRELEASE:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPRELEASE");
+                               break;
+                       case DHCPINFORM:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPINFORM");
+                               break;
+                       default:
+                               log_debug("DHO_DHCP_MESSAGE_TYPE: Unknown");
+                               break;
+                       }
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DHCP_SERVER_IDENTIFIER:
+                       if (dho_len != sizeof(server_identifier))
+                               goto wrong_length;
+                       memcpy(&server_identifier, p,
+                           sizeof(server_identifier));
+                       log_debug("DHO_DHCP_SERVER_IDENTIFIER: %s",
+                           inet_ntop(AF_INET, &server_identifier,
+                           hbuf, sizeof(hbuf)));
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DHCP_LEASE_TIME:
+                       if (dho_len != sizeof(lease_time))
+                               goto wrong_length;
+                       memcpy(&lease_time, p, sizeof(lease_time));
+                       lease_time = ntohl(lease_time);
+                       log_debug("DHO_DHCP_LEASE_TIME %us", lease_time);
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_SUBNET_MASK:
+                       if (dho_len != sizeof(subnet_mask))
+                               goto wrong_length;
+                       memcpy(&subnet_mask, p, sizeof(subnet_mask));
+                       log_debug("DHO_SUBNET_MASK: %s", inet_ntop(AF_INET,
+                           &subnet_mask, hbuf, sizeof(hbuf)));
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_ROUTERS:
+                       if (dho_len < sizeof(router))
+                               goto wrong_length;
+                       if (dho_len % sizeof(router) != 0)
+                               goto wrong_length;
+                       /* we only use one router */
+                       memcpy(&router, p, sizeof(router));
+                       log_debug("DHO_ROUTER: %s (1/%lu)", inet_ntop(AF_INET,
+                           &router, hbuf, sizeof(hbuf)), dho_len /
+                           sizeof(router));
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DOMAIN_NAME_SERVERS:
+                       if (dho_len < sizeof(nameservers[0]))
+                               goto wrong_length;
+                       if (dho_len % sizeof(nameservers[0]) != 0)
+                               goto wrong_length;
+                       /* we limit ourself to 8 nameservers for proposals */
+                       memcpy(&nameservers, p, MINIMUM(sizeof(nameservers),
+                           dho_len));
+                       for (i = 0; i < MINIMUM(sizeof(nameservers), dho_len /
+                           sizeof(nameservers[0]));
+                            i++) {
+                               log_debug("DHO_DOMAIN_NAME_SERVERS: %s "
+                                   "(%lu/%lu)", inet_ntop(AF_INET,
+                                   &nameservers[i], hbuf, sizeof(hbuf)), i + 1,
+                                   dho_len / sizeof(nameservers[0]));
+                       }
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DOMAIN_NAME:
+                       if ( dho_len < 1)
+                               goto wrong_length;
+                       strvisx(vis_buf, p, dho_len, VIS_SAFE);
+                       log_debug("DHO_DOMAIN_NAME: %s", vis_buf);
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DHCP_RENEWAL_TIME:
+                       if (dho_len != sizeof(renewal_time))
+                               goto wrong_length;
+                       memcpy(&renewal_time, p, sizeof(renewal_time));
+                       renewal_time = ntohl(renewal_time);
+                       log_debug("DHO_DHCP_RENEWAL_TIME %us", renewal_time);
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DHCP_REBINDING_TIME:
+                       if (dho_len != sizeof(rebinding_time))
+                               goto wrong_length;
+                       memcpy(&rebinding_time, p, sizeof(rebinding_time));
+                       rebinding_time = ntohl(rebinding_time);
+                       log_debug("DHO_DHCP_REBINDING_TIME %us",
+                           rebinding_time);
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               case DHO_DHCP_CLIENT_IDENTIFIER:
+                       /* the server is supposed to echo this back to us */
+                       if (dho_len != 1 + sizeof(iface->hw_address))
+                               goto wrong_length;
+                       if (*p != HTYPE_ETHER) {
+                               log_warn("DHO_DHCP_CLIENT_IDENTIFIER: wrong "
+                                   "type");
+                               return;
+                       }
+                       if (memcmp(p + 1, &iface->hw_address,
+                           sizeof(iface->hw_address)) != 0) {
+                               log_warn("wrong DHO_DHCP_CLIENT_IDENTIFIER");
+                               return;
+                       }
+                       p += dho_len;
+                       rem -= dho_len;
+                       break;
+               default:
+                       log_debug("DHO_%u, len: %u", dho, dho_len);
+                       p += dho_len;
+                       rem -= dho_len;
+               }
+
+       }
+       while(rem != 0) {
+               if (*p != DHO_PAD)
+                       break;
+               p++;
+               rem--;
+       }
+       if (rem != 0)
+               log_warnx("%s: %lu bytes garbage data from %s", __func__, rem,
+                   from);
+
+       switch (dhcp_message_type) {
+       case DHCPOFFER:
+               if (iface->state != IF_INIT) {
+                       log_debug("ignoring unexpected DHCPOFFER");
+                       return;
+               }
+               if (server_identifier.s_addr == INADDR_ANY &&
+                   dhcp_hdr->yiaddr.s_addr == INADDR_ANY) {
+                       log_warnx("%s: did not receive server identifier or "
+                           "offered IP address", __func__);
+                       return;
+               }
+               iface->server_identifier.s_addr = server_identifier.s_addr;
+               iface->requested_ip.s_addr = dhcp_hdr->yiaddr.s_addr;
+               state_transition(iface, IF_REQUESTING);
+               break;
+       case DHCPACK:
+               switch (iface->state) {
+               case IF_REQUESTING:
+               case IF_RENEWING:
+               case IF_REBINDING:
+               case IF_REBOOTING:
+                       break;
+               default:
+                       log_debug("ignoring unexpected DHCPACK");
+                       return;
+               }
+               if (server_identifier.s_addr == INADDR_ANY &&
+                   dhcp_hdr->yiaddr.s_addr == INADDR_ANY) {
+                       log_warnx("%s: did not receive server identifier or "
+                           "offered IP address", __func__);
+                       return;
+               }
+               if (lease_time == 0) {
+                       log_warnx("%s no lease time from %s", __func__, from);
+                       return;
+               }
+               if (subnet_mask.s_addr == INADDR_ANY) {
+                       log_warnx("%s: no subnetmask received from %s",
+                           __func__, from);
+                       return;
+               }
+
+               /* RFC 2131 4.4.5 */
+               if(renewal_time == 0)
+                       renewal_time = lease_time / 2;
+               if (rebinding_time == 0)
+                       rebinding_time = (lease_time * 7) / 8;
+
+               if (renewal_time >= rebinding_time) {
+                       log_warnx("%s: renewal_time >= rebinding_time "
+                           "(%u >= %u) from %s", __func__, renewal_time,
+                           rebinding_time, from);
+                       return;
+               }
+               if (rebinding_time >= lease_time) {
+                       log_warnx("%s: rebinding_time >= lease_time"
+                           "(%u >= %u) from %s", __func__, rebinding_time,
+                           lease_time, from);
+                       return;
+               }
+               clock_gettime(CLOCK_MONOTONIC, &iface->request_time);
+               iface->server_identifier.s_addr = server_identifier.s_addr;
+               iface->requested_ip.s_addr = dhcp_hdr->yiaddr.s_addr;
+               iface->mask.s_addr = subnet_mask.s_addr;
+               iface->router.s_addr = router.s_addr;
+               iface->lease_time = lease_time;
+               iface->renewal_time = renewal_time;
+               iface->rebinding_time = rebinding_time;
+               memcpy(iface->nameservers, nameservers,
+                   sizeof(iface->nameservers));
+               state_transition(iface, IF_BOUND);
+               break;
+       case DHCPNAK:
+               switch (iface->state) {
+               case IF_REQUESTING:
+               case IF_RENEWING:
+               case IF_REBINDING:
+               case IF_REBOOTING:
+                       break;
+               default:
+                       log_debug("ignoring unexpected DHCPNAK");
+                       return;
+               }
+
+               /* we have been told that our IP is inapropriate, delete now */
+               send_deconfigure_interface(iface);
+               send_rdns_withdraw(iface);
+               iface->server_identifier.s_addr = INADDR_ANY;
+               iface->requested_ip.s_addr = INADDR_ANY;
+               memset(iface->nameservers, 0, sizeof(iface->nameservers));
+               state_transition(iface, IF_INIT);
+               break;
+       default:
+               log_warnx("%s: unimplemented message type %d", __func__,
+                   dhcp_message_type);
+               break;
+       }
+       return;
+ too_short:
+       log_warnx("%s: message from %s too short", __func__, from);
+       return;
+ wrong_length:
+       log_warnx("%s: received option %d with wrong length: %d", __func__,
+           dho, dho_len);
+       return;
+}
+
+/* XXX check valid transitions */
+void
+state_transition(struct dhcpleased_iface *iface, enum if_state new_state)
+{
+       enum if_state    old_state = iface->state;
+       struct timespec  now, res;
+
+       iface->state = new_state;
+       switch (new_state) {
+       case IF_DOWN:
+               if (iface->requested_ip.s_addr == INADDR_ANY) {
+                       /* nothing to do until iface comes up */
+                       iface->timo.tv_sec = -1;
+                       break;
+               }
+               if (old_state == IF_DOWN) {
+                       send_deconfigure_interface(iface);
+                       iface->server_identifier.s_addr = INADDR_ANY;
+                       iface->dhcp_server.s_addr = INADDR_ANY;
+                       iface->requested_ip.s_addr = INADDR_ANY;
+                       iface->mask.s_addr = INADDR_ANY;
+                       iface->router.s_addr = INADDR_ANY;
+
+                       /* nothing more to do until iface comes back */
+                       iface->timo.tv_sec = -1;
+               } else {
+                       send_rdns_withdraw(iface);
+                       memset(iface->nameservers, 0,
+                           sizeof(iface->nameservers));
+                       clock_gettime(CLOCK_MONOTONIC, &now);
+                       timespecsub(&now, &iface->request_time, &res);
+                       iface->timo.tv_sec = iface->lease_time - res.tv_sec;
+                       if (iface->timo.tv_sec < 0)
+                               iface->timo.tv_sec = 0; /* deconfigure now */
+               }
+               break;
+       case IF_INIT:
+               if (old_state == IF_REBINDING) {
+                       /*
+                        * XXX move to function, also is this the only
+                        * transition that needs delete?
+                        */
+                       /* lease expired, delete IP */
+                       send_deconfigure_interface(iface);
+                       send_rdns_withdraw(iface);
+                       iface->requested_ip.s_addr = INADDR_ANY;
+                       memset(iface->nameservers, 0,
+                           sizeof(iface->nameservers));
+               }
+               if (old_state == IF_INIT) {
+                       iface->timo.tv_sec *= 2;
+                       if (iface->timo.tv_sec > 64)
+                               iface->timo.tv_sec = 64;
+               } else
+                       iface->timo.tv_sec = 1;
+               request_dhcp_discover(iface);
+               break;
+       case IF_REBOOTING:
+               if (old_state == IF_REBOOTING) {
+                       iface->timo.tv_sec *= 2;
+                       if (iface->timo.tv_sec > 64)
+                               iface->timo.tv_sec = 64;
+               } else {
+                       /* make sure we send broadcast */
+                       iface->dhcp_server.s_addr = INADDR_ANY;
+                       iface->timo.tv_sec = 1;
+               }
+               request_dhcp_request(iface);
+               break;
+       case IF_REQUESTING:
+               if (old_state == IF_REQUESTING) {
+                       iface->timo.tv_sec *= 2;
+                       if (iface->timo.tv_sec > 64)
+                               iface->timo.tv_sec = 64;
+               } else
+                       iface->timo.tv_sec = 1;
+               request_dhcp_request(iface);
+               break;
+       case IF_BOUND:
+               iface->timo.tv_sec = iface->renewal_time;
+               if (old_state == IF_REQUESTING || old_state == IF_REBOOTING) {
+                       send_configure_interface(iface);
+                       send_rdns_proposal(iface);
+               }
+               break;
+       case IF_RENEWING:
+               if (old_state == IF_BOUND) {
+                       iface->dhcp_server.s_addr =
+                           iface->server_identifier.s_addr;
+                       iface->server_identifier.s_addr = INADDR_ANY;
+
+                       iface->timo.tv_sec = (iface->rebinding_time -
+                           iface->renewal_time) / 2; /* RFC 2131 4.4.5 */
+               } else
+                       iface->timo.tv_sec /= 2;
+
+               if (iface->timo.tv_sec < 60)
+                       iface->timo.tv_sec = 60;
+               request_dhcp_request(iface);
+               break;
+       case IF_REBINDING:
+               if (old_state == IF_RENEWING) {
+                       iface->dhcp_server.s_addr = INADDR_ANY;
+                       iface->timo.tv_sec = (iface->lease_time -
+                           iface->renewal_time) / 2; /* RFC 2131 4.4.5 */
+               } else
+                       iface->timo.tv_sec /= 2;
+               request_dhcp_request(iface);
+               break;
+       default:
+               fatal("%s: unhandled state: %s", __func__,
+                   if_state_name[new_state]);
+       }
+       log_debug("%s %s -> %s, timo: %lld", __func__, if_state_name[old_state],
+           if_state_name[new_state], iface->timo.tv_sec);
+       if (iface->timo.tv_sec == -1) {
+               if (evtimer_pending(&iface->timer, NULL))
+                       evtimer_del(&iface->timer);
+       } else
+               evtimer_add(&iface->timer, &iface->timo);
+}
+
+void
+iface_timeout(int fd, short events, void *arg)
+{
+       struct dhcpleased_iface *iface = (struct dhcpleased_iface *)arg;
+       struct timespec          now, res;
+
+       log_debug("%s[%d]: %s", __func__, iface->if_index,
+           if_state_name[iface->state]);
+
+       switch (iface->state) {
+       case IF_DOWN:
+               state_transition(iface, IF_DOWN);
+               break;
+       case IF_INIT:
+               state_transition(iface, IF_INIT);
+               break;
+       case IF_REBOOTING:
+               if (iface->timo.tv_sec >= 64)
+                       state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_REBOOTING);
+               break;
+       case IF_REQUESTING:
+               if (iface->timo.tv_sec >= 64)
+                       state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_REQUESTING);
+               break;
+       case IF_BOUND:
+               state_transition(iface, IF_RENEWING);
+               break;
+       case IF_RENEWING:
+               clock_gettime(CLOCK_MONOTONIC, &now);
+               timespecsub(&now, &iface->request_time, &res);
+               log_debug("%s: res.tv_sec: %lld, rebinding_time: %u", __func__,
+                   res.tv_sec, iface->rebinding_time);
+               if (res.tv_sec > iface->rebinding_time)
+                       state_transition(iface, IF_REBINDING);
+               else
+                       state_transition(iface, IF_RENEWING);
+               break;
+       case IF_REBINDING:
+               clock_gettime(CLOCK_MONOTONIC, &now);
+               timespecsub(&now, &iface->request_time, &res);
+               log_debug("%s: res.tv_sec: %lld, lease_time: %u", __func__,
+                   res.tv_sec, iface->lease_time);
+               if (res.tv_sec > iface->lease_time)
+                       state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_REBINDING);
+               break;
+       default:
+               break;
+       }
+}
+
+void
+request_dhcp_discover(struct dhcpleased_iface *iface)
+{
+       struct imsg_req_discover         imsg_req_discover;
+
+       imsg_req_discover.if_index = iface->if_index;
+       imsg_req_discover.xid = iface->xid = arc4random();
+       engine_imsg_compose_frontend(IMSG_SEND_DISCOVER, 0, &imsg_req_discover,
+           sizeof(imsg_req_discover));
+}
+
+void
+request_dhcp_request(struct dhcpleased_iface *iface)
+{
+       struct imsg_req_request  imsg_req_request;
+
+       iface->xid = arc4random();
+       imsg_req_request.if_index = iface->if_index;
+       imsg_req_request.xid = iface->xid;
+       imsg_req_request.server_identifier.s_addr =
+           iface->server_identifier.s_addr;
+       imsg_req_request.requested_ip.s_addr = iface->requested_ip.s_addr;
+       imsg_req_request.dhcp_server.s_addr =  iface->dhcp_server.s_addr;
+       engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg_req_request,
+           sizeof(imsg_req_request));
+}
+
+void
+send_configure_interface(struct dhcpleased_iface *iface)
+{
+       struct imsg_configure_interface  imsg;
+
+       imsg.if_index = iface->if_index;
+       imsg.rdomain = iface->rdomain;
+       imsg.addr.s_addr = iface->requested_ip.s_addr;
+       imsg.mask.s_addr = iface->mask.s_addr;
+       imsg.router.s_addr = iface->router.s_addr;
+       engine_imsg_compose_main(IMSG_CONFIGURE_INTERFACE, 0, &imsg,
+           sizeof(imsg));
+}
+
+void
+send_deconfigure_interface(struct dhcpleased_iface *iface)
+{
+       struct imsg_configure_interface  imsg;
+
+       imsg.if_index = iface->if_index;
+       imsg.rdomain = iface->rdomain;
+       imsg.addr.s_addr = iface->requested_ip.s_addr;
+       imsg.mask.s_addr = iface->mask.s_addr;
+       imsg.router.s_addr = iface->router.s_addr;
+       engine_imsg_compose_main(IMSG_DECONFIGURE_INTERFACE, 0, &imsg,
+           sizeof(imsg));
+}
+
+void
+send_rdns_proposal(struct dhcpleased_iface *iface)
+{
+       struct imsg_propose_rdns         imsg;
+
+       memset(&imsg, 0, sizeof(imsg));
+
+       imsg.if_index = iface->if_index;
+       imsg.rdomain = iface->rdomain;
+       for (imsg.rdns_count = 0; imsg.rdns_count < MAX_RDNS_COUNT &&
+                iface->nameservers[imsg.rdns_count].s_addr != INADDR_ANY;
+            imsg.rdns_count++)
+               ;
+       memcpy(imsg.rdns, iface->nameservers, sizeof(imsg.rdns));
+       engine_imsg_compose_main(IMSG_PROPOSE_RDNS, 0, &imsg, sizeof(imsg));
+}
+
+void
+send_rdns_withdraw(struct dhcpleased_iface *iface)
+{
+       struct imsg_propose_rdns         imsg;
+
+       memset(&imsg, 0, sizeof(imsg));
+
+       imsg.if_index = iface->if_index;
+       imsg.rdomain = iface->rdomain;
+       engine_imsg_compose_main(IMSG_WITHDRAW_RDNS, 0, &imsg, sizeof(imsg));
+}
+
+void
+parse_lease(int fd, struct dhcpleased_iface *iface)
+{
+       ssize_t  len;
+       char     lease_buf[LEASE_SIZE], *p, *p1;
+
+       memset(lease_buf, 0, sizeof(lease_buf));
+
+       len = read(fd, lease_buf, sizeof(lease_buf) - 1);
+
+       if (len <= 0)
+               goto out;
+
+       if ((p = strstr(lease_buf, LEASE_PREFIX)) == NULL)
+               goto out;
+
+       p += sizeof(LEASE_PREFIX) - 1;
+       if ((p1 = strchr(p, '\n')) == NULL)
+               goto out;
+       *p1 = '\0';
+
+       if (inet_pton(AF_INET, p, &iface->requested_ip) != 1)
+               iface->requested_ip.s_addr = INADDR_ANY;
+ out:
+       close(fd);
+}
diff --git a/sbin/dhcpleased/engine.h b/sbin/dhcpleased/engine.h
new file mode 100644 (file)
index 0000000..317825f
--- /dev/null
@@ -0,0 +1,29 @@
+/*     $OpenBSD: engine.h,v 1.1 2021/02/26 16:16:37 florian Exp $      */
+
+/*
+ * Copyright (c) 2021 Florian Obser <florian@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct imsg_configure_interface {
+       uint32_t                 if_index;
+       struct in_addr           addr;
+       struct in_addr           mask;
+       struct in_addr           router;
+       int                      rdomain;
+
+};
+
+void            engine(int, int);
+int             engine_imsg_compose_frontend(int, pid_t, void *, uint16_t);
diff --git a/sbin/dhcpleased/frontend.c b/sbin/dhcpleased/frontend.c
new file mode 100644 (file)
index 0000000..7f964e0
--- /dev/null
@@ -0,0 +1,979 @@
+/*     $OpenBSD: frontend.c,v 1.1 2021/02/26 16:16:37 florian Exp $    */
+
+/*
+ * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <event.h>
+#include <ifaddrs.h>
+#include <imsg.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bpf.h"
+#include "log.h"
+#include "dhcpleased.h"
+#include "frontend.h"
+#include "control.h"
+#include "checksum.h"
+
+#define        ROUTE_SOCKET_BUF_SIZE   16384
+
+struct bpf_ev {
+       struct event             ev;
+       uint8_t                  buf[BPFLEN];
+};
+
+struct iface           {
+       LIST_ENTRY(iface)        entries;
+       struct bpf_ev            bpfev;
+       struct ether_addr        hw_address;
+       uint32_t                 if_index;
+       int                      rdomain;
+       int                      send_discover;
+       uint32_t                 xid;
+       struct in_addr           requested_ip;
+       struct in_addr           server_identifier;
+       struct in_addr           dhcp_server;
+       int                      udpsock;
+};
+
+__dead void     frontend_shutdown(void);
+void            frontend_sig_handler(int, short, void *);
+void            update_iface(uint32_t, char*);
+void            frontend_startup(void);
+void            route_receive(int, short, void *);
+void            handle_route_message(struct rt_msghdr *, struct sockaddr **);
+void            get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
+void            bpf_receive(int, short, void *);
+int             get_flags(char *);
+int             get_xflags(char *);
+int             get_ifrdomain(char *);
+struct iface   *get_iface_by_id(uint32_t);
+void            remove_iface(uint32_t);
+void            set_bpfsock(int, uint32_t);
+ssize_t                 build_packet(uint8_t, uint32_t, struct ether_addr *, struct
+                    in_addr *, struct in_addr *);
+void            send_discover(struct iface *);
+void            send_request(struct iface *);
+void            bpf_send_packet(struct iface *, uint8_t *, ssize_t);
+void            udp_send_packet(struct iface *, uint8_t *, ssize_t);
+
+LIST_HEAD(, iface)              interfaces;
+static struct imsgev           *iev_main;
+static struct imsgev           *iev_engine;
+struct event                    ev_route;
+int                             ioctlsock;
+
+uint8_t                                 dhcp_packet[1500];
+
+void
+frontend_sig_handler(int sig, short event, void *bula)
+{
+       /*
+        * Normal signal handler rules don't apply because libevent
+        * decouples for us.
+        */
+
+       switch (sig) {
+       case SIGINT:
+       case SIGTERM:
+               frontend_shutdown();
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+void
+frontend(int debug, int verbose)
+{
+       struct event             ev_sigint, ev_sigterm;
+       struct passwd           *pw;
+
+       log_init(debug, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       if ((pw = getpwnam(DHCPLEASED_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chroot(pw->pw_dir) == -1)
+               fatal("chroot");
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       setproctitle("%s", "frontend");
+       log_procinit("frontend");
+
+       if ((ioctlsock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1)
+               fatal("socket");
+
+       if (setgroups(1, &pw->pw_gid) ||
+           setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+           setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+               fatal("can't drop privileges");
+
+       if (pledge("stdio unix recvfd route", NULL) == -1)
+               fatal("pledge");
+       event_init();
+
+       /* Setup signal handler. */
+       signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, SIG_IGN);
+
+       /* Setup pipe and event handler to the parent process. */
+       if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+               fatal(NULL);
+       imsg_init(&iev_main->ibuf, 3);
+       iev_main->handler = frontend_dispatch_main;
+       iev_main->events = EV_READ;
+       event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+           iev_main->handler, iev_main);
+       event_add(&iev_main->ev, NULL);
+
+       LIST_INIT(&interfaces);
+       event_dispatch();
+
+       frontend_shutdown();
+}
+
+__dead void
+frontend_shutdown(void)
+{
+       /* Close pipes. */
+       msgbuf_write(&iev_engine->ibuf.w);
+       msgbuf_clear(&iev_engine->ibuf.w);
+       close(iev_engine->ibuf.fd);
+       msgbuf_write(&iev_main->ibuf.w);
+       msgbuf_clear(&iev_main->ibuf.w);
+       close(iev_main->ibuf.fd);
+
+       free(iev_engine);
+       free(iev_main);
+
+       log_info("frontend exiting");
+       exit(0);
+}
+
+int
+frontend_imsg_compose_main(int type, pid_t pid, void *data,
+    uint16_t datalen)
+{
+       return (imsg_compose_event(iev_main, type, 0, pid, -1, data,
+           datalen));
+}
+
+int
+frontend_imsg_compose_engine(int type, uint32_t peerid, pid_t pid,
+    void *data, uint16_t datalen)
+{
+       return (imsg_compose_event(iev_engine, type, peerid, pid, -1,
+           data, datalen));
+}
+
+void
+frontend_dispatch_main(int fd, short event, void *bula)
+{
+       struct imsg              imsg;
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct iface            *iface;
+       ssize_t                  n;
+       int                      shut = 0, bpfsock, if_index, udpsock;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("%s: imsg_get error", __func__);
+               if (n == 0)     /* No more messages. */
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_SOCKET_IPC:
+                       /*
+                        * Setup pipe and event handler to the engine
+                        * process.
+                        */
+                       if (iev_engine)
+                               fatalx("%s: received unexpected imsg fd "
+                                   "to frontend", __func__);
+
+                       if ((fd = imsg.fd) == -1)
+                               fatalx("%s: expected to receive imsg fd to "
+                                  "frontend but didn't receive any",
+                                  __func__);
+
+                       iev_engine = malloc(sizeof(struct imsgev));
+                       if (iev_engine == NULL)
+                               fatal(NULL);
+
+                       imsg_init(&iev_engine->ibuf, fd);
+                       iev_engine->handler = frontend_dispatch_engine;
+                       iev_engine->events = EV_READ;
+
+                       event_set(&iev_engine->ev, iev_engine->ibuf.fd,
+                       iev_engine->events, iev_engine->handler, iev_engine);
+                       event_add(&iev_engine->ev, NULL);
+                       break;
+               case IMSG_BPFSOCK:
+                       if ((bpfsock = imsg.fd) == -1)
+                               fatalx("%s: expected to receive imsg "
+                                   "bpf fd but didn't receive any",
+                                   __func__);
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_BPFSOCK wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       set_bpfsock(bpfsock, if_index);
+                       break;
+               case IMSG_UDPSOCK:
+                       if ((udpsock = imsg.fd) == -1)
+                               fatalx("%s: expected to receive imsg "
+                                   "udpsocket fd but didn't receive any",
+                                   __func__);
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_UDPSOCK wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       if ((iface = get_iface_by_id(if_index)) == NULL) {
+                               close(fd);
+                               break;
+                       }
+                       if (iface->udpsock != -1)
+                               fatalx("%s: received unexpected udpsocket",
+                                   __func__);
+                       iface->udpsock = udpsock;
+                       break;
+               case IMSG_CLOSE_UDPSOCK:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_UDPSOCK wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       if ((iface = get_iface_by_id(if_index)) != NULL &&
+                           iface->udpsock != -1) {
+                               close(iface->udpsock);
+                               iface->udpsock = -1;
+                       }
+                       break;
+               case IMSG_ROUTESOCK:
+                       if ((fd = imsg.fd) == -1)
+                               fatalx("%s: expected to receive imsg "
+                                   "routesocket fd but didn't receive any",
+                                   __func__);
+                       event_set(&ev_route, fd, EV_READ | EV_PERSIST,
+                           route_receive, NULL);
+                       break;
+               case IMSG_STARTUP:
+                       frontend_startup();
+                       break;
+#ifndef        SMALL
+               case IMSG_CONTROLFD:
+                       if ((fd = imsg.fd) == -1)
+                               fatalx("%s: expected to receive imsg "
+                                   "control fd but didn't receive any",
+                                   __func__);
+                       /* Listen on control socket. */
+                       control_listen(fd);
+                       break;
+               case IMSG_CTL_END:
+                       control_imsg_relay(&imsg);
+                       break;
+#endif /* SMALL */
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* This pipe is dead. Remove its event handler. */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+void
+frontend_dispatch_engine(int fd, short event, void *bula)
+{
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct imsg              imsg;
+       struct iface            *iface;
+       ssize_t                  n;
+       int                      shut = 0;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* Connection closed. */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("%s: imsg_get error", __func__);
+               if (n == 0)     /* No more messages. */
+                       break;
+
+               switch (imsg.hdr.type) {
+#ifndef        SMALL
+               case IMSG_CTL_END:
+               case IMSG_CTL_SHOW_INTERFACE_INFO:
+                       control_imsg_relay(&imsg);
+                       break;
+#endif /* SMALL */
+               case IMSG_SEND_DISCOVER: {
+                       struct imsg_req_discover         imsg_req_discover;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_discover))
+                               fatalx("%s: IMSG_SEND_DISCOVER wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_req_discover, imsg.data,
+                           sizeof(imsg_req_discover));
+                       iface = get_iface_by_id(imsg_req_discover.if_index);
+                       if (iface != NULL) {
+                               iface->xid = imsg_req_discover.xid;
+                               send_discover(iface);
+                       }
+                       break;
+               }
+               case IMSG_SEND_REQUEST: {
+                       struct imsg_req_request  imsg_req_request;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_request))
+                               fatalx("%s: IMSG_SEND_REQUEST wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_req_request, imsg.data,
+                           sizeof(imsg_req_request));
+                       iface = get_iface_by_id(imsg_req_request.if_index);
+                       if (iface != NULL) {
+                               iface->xid = imsg_req_request.xid;
+                               iface->requested_ip.s_addr =
+                                   imsg_req_request.requested_ip.s_addr;
+                               iface->server_identifier.s_addr =
+                                   imsg_req_request.server_identifier.s_addr;
+                               iface->dhcp_server.s_addr =
+                                   imsg_req_request.dhcp_server.s_addr;
+                               send_request(iface);
+                       }
+                       break;
+               }
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* This pipe is dead. Remove its event handler. */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+int
+get_flags(char *if_name)
+{
+       struct ifreq             ifr;
+
+       strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+       if (ioctl(ioctlsock, SIOCGIFFLAGS, (caddr_t)&ifr) == -1) {
+               log_warn("SIOCGIFFLAGS");
+               return -1;
+       }
+       return ifr.ifr_flags;
+}
+
+int
+get_xflags(char *if_name)
+{
+       struct ifreq             ifr;
+
+       strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+       if (ioctl(ioctlsock, SIOCGIFXFLAGS, (caddr_t)&ifr) == -1) {
+               log_warn("SIOCGIFXFLAGS");
+               return -1;
+       }
+       return ifr.ifr_flags;
+}
+
+int
+get_ifrdomain(char *if_name)
+{
+       struct ifreq             ifr;
+
+       strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+       if (ioctl(ioctlsock, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) {
+               log_warn("SIOCGIFRDOMAIN");
+               return -1;
+       }
+       return ifr.ifr_rdomainid;
+}
+
+void
+update_iface(uint32_t if_index, char* if_name)
+{
+       struct iface            *iface;
+       struct imsg_ifinfo       imsg_ifinfo;
+       struct ifaddrs          *ifap, *ifa;
+       struct sockaddr_dl      *sdl;
+       int                      flags, xflags, ifrdomain;
+
+       if ((flags = get_flags(if_name)) == -1 || (xflags =
+           get_xflags(if_name)) == -1)
+               return;
+
+       if (!(xflags & IFXF_AUTOCONF4))
+               return;
+
+       if((ifrdomain = get_ifrdomain(if_name)) == -1)
+               return;
+
+       iface = get_iface_by_id(if_index);
+
+       if (iface != NULL) {
+               if (iface->rdomain != ifrdomain) {
+                       iface->rdomain = ifrdomain;
+                       if (iface->udpsock != -1) {
+                               close(iface->udpsock);
+                               iface->udpsock = -1;
+                       }
+               }
+       } else {
+               if ((iface = calloc(1, sizeof(*iface))) == NULL)
+                       fatal("calloc");
+               iface->if_index = if_index;
+               iface->rdomain = ifrdomain;
+               iface->udpsock = -1;
+               LIST_INSERT_HEAD(&interfaces, iface, entries);
+               frontend_imsg_compose_main(IMSG_OPEN_BPFSOCK, 0,
+                   &if_index, sizeof(if_index));
+       }
+
+       memset(&imsg_ifinfo, 0, sizeof(imsg_ifinfo));
+
+       imsg_ifinfo.if_index = if_index;
+       imsg_ifinfo.rdomain = ifrdomain;
+
+       imsg_ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP |
+           IFF_RUNNING);
+
+
+       if (getifaddrs(&ifap) != 0)
+               fatal("getifaddrs");
+
+       for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+               if (strcmp(if_name, ifa->ifa_name) != 0)
+                       continue;
+               if (ifa->ifa_addr == NULL)
+                       continue;
+
+               switch(ifa->ifa_addr->sa_family) {
+               case AF_LINK:
+                       imsg_ifinfo.link_state =
+                           ((struct if_data *)ifa->ifa_data)->ifi_link_state;
+                       sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+                       if (sdl->sdl_type != IFT_ETHER ||
+                           sdl->sdl_alen != ETHER_ADDR_LEN)
+                               continue;
+                       memcpy(iface->hw_address.ether_addr_octet,
+                           LLADDR(sdl), ETHER_ADDR_LEN);
+                       goto out;
+               default:
+                       break;
+               }
+       }
+ out:
+       freeifaddrs(ifap);
+
+       memcpy(&imsg_ifinfo.hw_address, &iface->hw_address,
+           sizeof(imsg_ifinfo.hw_address));
+
+       frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &imsg_ifinfo,
+           sizeof(imsg_ifinfo));
+}
+
+void
+frontend_startup(void)
+{
+       struct if_nameindex     *ifnidxp, *ifnidx;
+
+       if (!event_initialized(&ev_route))
+               fatalx("%s: did not receive a route socket from the main "
+                   "process", __func__);
+
+       event_add(&ev_route, NULL);
+
+       if ((ifnidxp = if_nameindex()) == NULL)
+               fatalx("if_nameindex");
+
+       for(ifnidx = ifnidxp; ifnidx->if_index !=0 && ifnidx->if_name != NULL;
+           ifnidx++)
+               update_iface(ifnidx->if_index, ifnidx->if_name);
+
+       if_freenameindex(ifnidxp);
+}
+
+void
+route_receive(int fd, short events, void *arg)
+{
+       static uint8_t                   *buf;
+
+       struct rt_msghdr                *rtm;
+       struct sockaddr                 *sa, *rti_info[RTAX_MAX];
+       ssize_t                          n;
+
+       if (buf == NULL) {
+               buf = malloc(ROUTE_SOCKET_BUF_SIZE);
+               if (buf == NULL)
+                       fatal("malloc");
+       }
+       rtm = (struct rt_msghdr *)buf;
+       if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
+               if (errno == EAGAIN || errno == EINTR)
+                       return;
+               log_warn("dispatch_rtmsg: read error");
+               return;
+       }
+
+       if (n == 0)
+               fatal("routing socket closed");
+
+       if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
+               log_warnx("partial rtm of %zd in buffer", n);
+               return;
+       }
+
+       if (rtm->rtm_version != RTM_VERSION)
+               return;
+
+       sa = (struct sockaddr *)(buf + rtm->rtm_hdrlen);
+       get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+       handle_route_message(rtm, rti_info);
+}
+
+void
+handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
+{
+       struct if_msghdr                *ifm;
+       int                              xflags, if_index;
+       char                             ifnamebuf[IFNAMSIZ];
+       char                            *if_name;
+
+       switch (rtm->rtm_type) {
+       case RTM_IFINFO:
+               ifm = (struct if_msghdr *)rtm;
+               if_name = if_indextoname(ifm->ifm_index, ifnamebuf);
+               if (if_name == NULL) {
+                       log_debug("RTM_IFINFO: lost if %d", ifm->ifm_index);
+                       if_index = ifm->ifm_index;
+                       frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
+                           &if_index, sizeof(if_index));
+                       remove_iface(if_index);
+               } else {
+                       xflags = get_xflags(if_name);
+                       if (xflags == -1 || !(xflags & IFXF_AUTOCONF4)) {
+                               log_debug("RTM_IFINFO: %s(%d) no(longer) "
+                                  "autoconf4", if_name, ifm->ifm_index);
+                               if_index = ifm->ifm_index;
+                               frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0,
+                                   0, &if_index, sizeof(if_index));
+                       } else {
+                               update_iface(ifm->ifm_index, if_name);
+                       }
+               }
+               break;
+       case RTM_NEWADDR:
+               ifm = (struct if_msghdr *)rtm;
+               if_name = if_indextoname(ifm->ifm_index, ifnamebuf);
+               log_debug("RTM_NEWADDR: %s[%u]", if_name, ifm->ifm_index);
+               update_iface(ifm->ifm_index, if_name);
+               break;
+       case RTM_PROPOSAL:
+               if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
+                       log_debug("RTP_PROPOSAL_SOLICIT");
+                       frontend_imsg_compose_engine(IMSG_REPROPOSE_RDNS,
+                           0, 0, NULL, 0);
+               }
+               break;
+       default:
+               log_debug("unexpected RTM: %d", rtm->rtm_type);
+               break;
+       }
+
+}
+
+#define ROUNDUP(a) \
+       ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+
+void
+get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
+{
+       int     i;
+
+       for (i = 0; i < RTAX_MAX; i++) {
+               if (addrs & (1 << i)) {
+                       rti_info[i] = sa;
+                       sa = (struct sockaddr *)((char *)(sa) +
+                           ROUNDUP(sa->sa_len));
+               } else
+                       rti_info[i] = NULL;
+       }
+}
+
+void
+bpf_receive(int fd, short events, void *arg)
+{
+       struct bpf_hdr          *hdr;
+       struct imsg_dhcp         imsg_dhcp;
+       struct iface            *iface;
+       ssize_t                  len, rem;
+       uint8_t                 *p;
+
+       iface = (struct iface *)arg;
+
+       log_debug("%s: fd: %d", __func__, fd);
+       if ((len = read(fd, iface->bpfev.buf, BPFLEN)) == -1) {
+               log_warn("read");
+               return;
+       }
+       /* XXX len = 0 */
+       log_debug("%s: %ld", __func__, len);
+
+       memset(&imsg_dhcp, 0, sizeof(imsg_dhcp));
+       imsg_dhcp.if_index = iface->if_index;
+
+       rem = len;
+       p = iface->bpfev.buf;
+
+       while (rem > 0) {
+               if ((size_t)rem < sizeof(*hdr)) {
+                       log_warnx("packet too short");
+                       return;
+               }
+               hdr = (struct bpf_hdr *)p;
+               if (hdr->bh_caplen != hdr->bh_datalen) {
+                       log_warnx("skipping truncated packet");
+                       goto cont;
+               }
+               if (rem < hdr->bh_hdrlen + hdr->bh_caplen)
+                       /* we are done */
+                       break;
+               if (hdr->bh_caplen > sizeof(imsg_dhcp.packet)) {
+                       log_warn("packet too big");
+                       goto cont;
+               }
+               memcpy(&imsg_dhcp.packet, p + hdr->bh_hdrlen, hdr->bh_caplen);
+               imsg_dhcp.len = hdr->bh_caplen;
+               frontend_imsg_compose_engine(IMSG_DHCP, 0, 0, &imsg_dhcp,
+                   sizeof(imsg_dhcp));
+ cont:
+               p += BPF_WORDALIGN(hdr->bh_hdrlen + hdr->bh_caplen);
+               rem -= BPF_WORDALIGN(hdr->bh_hdrlen + hdr->bh_caplen);
+
+       }
+}
+
+ssize_t
+build_packet(uint8_t message_type, uint32_t xid, struct ether_addr *hw_address,
+    struct in_addr *requested_ip, struct in_addr *server_identifier)
+{
+       static uint8_t   dhcp_cookie[] = DHCP_COOKIE;
+       static uint8_t   dhcp_message_type[] = {DHO_DHCP_MESSAGE_TYPE, 1,
+               DHCPDISCOVER};
+       static uint8_t   dhcp_hostname[255] = {DHO_HOST_NAME, 0 /*, ... */};
+       static uint8_t   dhcp_client_id[] = {DHO_DHCP_CLIENT_IDENTIFIER, 7,
+               HTYPE_ETHER, 0, 0, 0, 0, 0, 0};
+       static uint8_t   dhcp_req_list[] = {DHO_DHCP_PARAMETER_REQUEST_LIST,
+               8, DHO_SUBNET_MASK, DHO_ROUTERS, DHO_DOMAIN_NAME_SERVERS,
+               DHO_HOST_NAME, DHO_DOMAIN_NAME, DHO_BROADCAST_ADDRESS,
+               DHO_DOMAIN_SEARCH, DHO_CLASSLESS_STATIC_ROUTES};
+       static uint8_t   dhcp_requested_address[] = {DHO_DHCP_REQUESTED_ADDRESS,
+               4, 0, 0, 0, 0};
+       static uint8_t   dhcp_server_identifier[] = {DHO_DHCP_SERVER_IDENTIFIER,
+               4, 0, 0, 0, 0};
+       struct dhcp_hdr *hdr;
+       uint8_t         *p;
+       char            *c;
+
+       memset(dhcp_packet, 0, sizeof(dhcp_packet));
+       dhcp_message_type[2] = message_type;
+       p = dhcp_packet;
+       hdr = (struct dhcp_hdr *)p;
+       hdr->op = DHCP_BOOTREQUEST;
+       hdr->htype = HTYPE_ETHER;
+       hdr->hlen = 6;
+       hdr->hops = 0;
+       hdr->xid = xid;
+       hdr->secs = 0;
+       memcpy(hdr->chaddr, hw_address, sizeof(*hw_address));
+       p += sizeof(struct dhcp_hdr);
+       memcpy(p, dhcp_cookie, sizeof(dhcp_cookie));
+       p += sizeof(dhcp_cookie);
+       memcpy(p, dhcp_message_type, sizeof(dhcp_message_type));
+       p += sizeof(dhcp_message_type);
+       if (gethostname(dhcp_hostname + 2, sizeof(dhcp_hostname) - 2) == 0) {
+               if ((c = strchr(dhcp_hostname + 2, '.')) != NULL)
+                       *c = '\0';
+               dhcp_hostname[1] = strlen(dhcp_hostname + 2);
+               memcpy(p, dhcp_hostname, dhcp_hostname[1] + 2);
+               p += dhcp_hostname[1] + 2;
+       }
+       memcpy(dhcp_client_id + 3, hw_address, sizeof(*hw_address));
+       memcpy(p, dhcp_client_id, sizeof(dhcp_client_id));
+       p += sizeof(dhcp_client_id);
+       memcpy(p, dhcp_req_list, sizeof(dhcp_req_list));
+       p += sizeof(dhcp_req_list);
+
+       if (message_type == DHCPREQUEST) {
+               memcpy(dhcp_requested_address + 2, requested_ip,
+                   sizeof(*requested_ip));
+               memcpy(p, dhcp_requested_address,
+                   sizeof(dhcp_requested_address));
+               p += sizeof(dhcp_requested_address);
+
+               if (server_identifier->s_addr != INADDR_ANY) {
+                       memcpy(dhcp_server_identifier + 2, server_identifier,
+                           sizeof(*server_identifier));
+                       memcpy(p, dhcp_server_identifier,
+                           sizeof(dhcp_server_identifier));
+                       p += sizeof(dhcp_server_identifier);
+               }
+       }
+
+       *p = DHO_END;
+       p += 1;
+
+       return (p - dhcp_packet);
+}
+
+void
+send_discover(struct iface *iface)
+{
+       ssize_t  pkt_len;
+
+       if (!event_initialized(&iface->bpfev.ev)) {
+               iface->send_discover = 1;
+               return;
+       }
+       iface->send_discover = 0;
+       log_debug("%s", __func__);
+       pkt_len = build_packet(DHCPDISCOVER, iface->xid, &iface->hw_address,
+           &iface->requested_ip, NULL);
+       log_debug("%s, pkt_len: %ld", __func__, pkt_len);
+       bpf_send_packet(iface, dhcp_packet, pkt_len);
+}
+
+void
+send_request(struct iface *iface)
+{
+       ssize_t  pkt_len;
+
+       pkt_len = build_packet(DHCPREQUEST, iface->xid, &iface->hw_address,
+           &iface->requested_ip, &iface->server_identifier);
+       log_debug("%s, pkt_len: %ld", __func__, pkt_len);
+       if (iface->dhcp_server.s_addr != INADDR_ANY)
+               udp_send_packet(iface, dhcp_packet, pkt_len);
+       else
+               bpf_send_packet(iface, dhcp_packet, pkt_len);
+}
+
+void
+udp_send_packet(struct iface *iface, uint8_t *packet, ssize_t len)
+{
+       struct sockaddr_in      to;
+
+       log_debug("%s", __func__);
+       memset(&to, 0, sizeof(to));
+       to.sin_family = AF_INET;
+       to.sin_len = sizeof(to);
+       to.sin_addr.s_addr = iface->dhcp_server.s_addr;
+       to.sin_port = ntohs(SERVER_PORT);
+
+       if (sendto(iface->udpsock, packet, len, 0, (struct sockaddr *)&to,
+           sizeof(to)) == -1)
+               log_warn("sendto");
+}
+void
+bpf_send_packet(struct iface *iface, uint8_t *packet, ssize_t len)
+{
+       struct iovec             iov[4];
+       struct ether_header      eh;
+       struct ip                ip;
+       struct udphdr            udp;
+       ssize_t                  total, result;
+       int                      iovcnt = 0, i;
+
+       memset(eh.ether_dhost, 0xff, sizeof(eh.ether_dhost));
+       memcpy(eh.ether_shost, &iface->hw_address, sizeof(eh.ether_dhost));
+       eh.ether_type = htons(ETHERTYPE_IP);
+       iov[0].iov_base = &eh;
+       iov[0].iov_len = sizeof(eh);
+       iovcnt++;
+
+       ip.ip_v = 4;
+       ip.ip_hl = 5;
+       ip.ip_tos = IPTOS_LOWDELAY;
+       ip.ip_len = htons(sizeof(ip) + sizeof(udp) + len);
+       ip.ip_id = 0;
+       ip.ip_off = 0;
+       ip.ip_ttl = 128;
+       ip.ip_p = IPPROTO_UDP;
+       ip.ip_sum = 0;
+       ip.ip_src.s_addr = 0;
+       ip.ip_dst.s_addr = INADDR_BROADCAST;
+       ip.ip_sum = wrapsum(checksum((unsigned char *)&ip, sizeof(ip), 0));
+       iov[iovcnt].iov_base = &ip;
+       iov[iovcnt].iov_len = sizeof(ip);
+       iovcnt++;
+
+       udp.uh_sport = htons(CLIENT_PORT);
+       udp.uh_dport = htons(SERVER_PORT);
+       udp.uh_ulen = htons(sizeof(udp) + len);
+       udp.uh_sum = 0;
+       udp.uh_sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
+           checksum((unsigned char *)packet, len,
+           checksum((unsigned char *)&ip.ip_src,
+           2 * sizeof(ip.ip_src),
+           IPPROTO_UDP + (uint32_t)ntohs(udp.uh_ulen)))));
+       iov[iovcnt].iov_base = &udp;
+       iov[iovcnt].iov_len = sizeof(udp);
+       iovcnt++;
+
+       iov[iovcnt].iov_base = packet;
+       iov[iovcnt].iov_len = len;
+       iovcnt++;
+
+       total = 0;
+       for (i = 0; i < iovcnt; i++)
+               total += iov[i].iov_len;
+
+       result = writev(EVENT_FD(&iface->bpfev.ev), iov, iovcnt);
+       if (result == -1)
+               log_warn("%s: writev", __func__);
+       else if (result < total) {
+               log_warnx("%s, writev: %zd of %zd bytes", __func__, result,
+                   total);
+       }
+}
+
+struct iface*
+get_iface_by_id(uint32_t if_index)
+{
+       struct iface    *iface;
+
+       LIST_FOREACH (iface, &interfaces, entries) {
+               if (iface->if_index == if_index)
+                       return (iface);
+       }
+
+       return (NULL);
+}
+
+void
+remove_iface(uint32_t if_index)
+{
+       struct iface    *iface;
+
+       iface = get_iface_by_id(if_index);
+
+       if (iface == NULL)
+               return;
+
+       LIST_REMOVE(iface, entries);
+       event_del(&iface->bpfev.ev);
+       close(EVENT_FD(&iface->bpfev.ev));
+       if (iface->udpsock != -1)
+               close(iface->udpsock);
+       free(iface);
+}
+
+void
+set_bpfsock(int bpfsock, uint32_t if_index)
+{
+       struct iface    *iface;
+
+       log_debug("%s: %d fd: %d", __func__, if_index, bpfsock);
+
+       if ((iface = get_iface_by_id(if_index)) == NULL) {
+               /*
+                * The interface disappeared while we were waiting for the
+                * parent process to open the raw socket.
+                */
+               close(bpfsock);
+       } else {
+               event_set(&iface->bpfev.ev, bpfsock, EV_READ |
+                   EV_PERSIST, bpf_receive, iface);
+               event_add(&iface->bpfev.ev, NULL);
+               if (iface->send_discover)
+                       send_discover(iface);
+       }
+}
diff --git a/sbin/dhcpleased/frontend.h b/sbin/dhcpleased/frontend.h
new file mode 100644 (file)
index 0000000..7a875d3
--- /dev/null
@@ -0,0 +1,24 @@
+/*     $OpenBSD: frontend.h,v 1.1 2021/02/26 16:16:37 florian Exp $    */
+
+/*
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+void            frontend(int, int);
+void            frontend_dispatch_main(int, short, void *);
+void            frontend_dispatch_engine(int, short, void *);
+int             frontend_imsg_compose_main(int, pid_t, void *, uint16_t);
+int             frontend_imsg_compose_engine(int, uint32_t, pid_t, void *,
+                    uint16_t);
diff --git a/sbin/dhcpleased/log.c b/sbin/dhcpleased/log.c
new file mode 100644 (file)
index 0000000..3f4a4da
--- /dev/null
@@ -0,0 +1,199 @@
+/*     $OpenBSD: log.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int              debug;
+static int              verbose;
+static const char      *log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+       extern char     *__progname;
+
+       debug = n_debug;
+       verbose = n_debug;
+       log_procinit(__progname);
+
+       if (!debug)
+               openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+       tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+       if (procname != NULL)
+               log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+       verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+       return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vlog(pri, fmt, ap);
+       va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+       char    *nfmt;
+       int      saved_errno = errno;
+
+       if (debug) {
+               /* best effort in out of mem situations */
+               if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+                       vfprintf(stderr, fmt, ap);
+                       fprintf(stderr, "\n");
+               } else {
+                       vfprintf(stderr, nfmt, ap);
+                       free(nfmt);
+               }
+               fflush(stderr);
+       } else
+               vsyslog(pri, fmt, ap);
+
+       errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+       char            *nfmt;
+       va_list          ap;
+       int              saved_errno = errno;
+
+       /* best effort to even work in out of memory situations */
+       if (emsg == NULL)
+               logit(LOG_ERR, "%s", strerror(saved_errno));
+       else {
+               va_start(ap, emsg);
+
+               if (asprintf(&nfmt, "%s: %s", emsg,
+                   strerror(saved_errno)) == -1) {
+                       /* we tried it... */
+                       vlog(LOG_ERR, emsg, ap);
+                       logit(LOG_ERR, "%s", strerror(saved_errno));
+               } else {
+                       vlog(LOG_ERR, nfmt, ap);
+                       free(nfmt);
+               }
+               va_end(ap);
+       }
+
+       errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_ERR, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_INFO, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+       va_list  ap;
+
+       if (verbose) {
+               va_start(ap, emsg);
+               vlog(LOG_DEBUG, emsg, ap);
+               va_end(ap);
+       }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+       static char     s[BUFSIZ];
+       const char      *sep;
+
+       if (emsg != NULL) {
+               (void)vsnprintf(s, sizeof(s), emsg, ap);
+               sep = ": ";
+       } else {
+               s[0] = '\0';
+               sep = "";
+       }
+       if (code)
+               logit(LOG_CRIT, "fatal in %s: %s%s%s",
+                   log_procname, s, sep, strerror(code));
+       else
+               logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+       va_list ap;
+
+       va_start(ap, emsg);
+       vfatalc(errno, emsg, ap);
+       va_end(ap);
+       exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+       va_list ap;
+
+       va_start(ap, emsg);
+       vfatalc(0, emsg, ap);
+       va_end(ap);
+       exit(1);
+}
diff --git a/sbin/dhcpleased/log.h b/sbin/dhcpleased/log.h
new file mode 100644 (file)
index 0000000..90b094e
--- /dev/null
@@ -0,0 +1,60 @@
+/*     $OpenBSD: log.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+#include <sys/cdefs.h>
+
+#ifndef        SMALL
+void   log_init(int, int);
+void   log_procinit(const char *);
+void   log_setverbose(int);
+int    log_getverbose(void);
+void   log_warn(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_warnx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_info(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_debug(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   logit(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+void   vlog(int, const char *, va_list)
+           __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+#else
+#define log_init(x...)         do {} while(0)
+#define log_procinit(x...)     do {} while(0)
+#define log_setverbose(x...)   do {} while(0)
+#define log_warn(x...)         do {} while(0)
+#define log_warnx(x...)                do {} while(0)
+#define log_info(x...)         do {} while(0)
+#define log_debug(x...)                do {} while(0)
+#define logit(x...)            do {} while(0)
+#define vlog(x...)             do {} while(0)
+#define fatal(x...)            exit(1)
+#define fatalx(x...)           exit(1)
+#endif /* SMALL */
+
+#endif /* LOG_H */
diff --git a/usr.sbin/dhcpleasectl/Makefile b/usr.sbin/dhcpleasectl/Makefile
new file mode 100644 (file)
index 0000000..af564dd
--- /dev/null
@@ -0,0 +1,17 @@
+#      $OpenBSD: Makefile,v 1.1 2021/02/26 16:16:37 florian Exp $
+
+PROG=  dhcpleasectl
+SRCS=  dhcpleasectl.c parser.c
+
+MAN=   dhcpleasectl.8
+
+CFLAGS+= -Wall
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare
+CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../../sbin/dhcpleased
+LDADD= -lutil
+DPADD= ${LIBUTIL}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/dhcpleasectl/dhcpleasectl.8 b/usr.sbin/dhcpleasectl/dhcpleasectl.8
new file mode 100644 (file)
index 0000000..b68d592
--- /dev/null
@@ -0,0 +1,80 @@
+.\"    $OpenBSD: dhcpleasectl.8,v 1.1 2021/02/26 16:16:37 florian Exp $
+.\"
+.\" Copyright (c) 2021 Florian Obser <florian@openbsd.org>
+.\" Copyright (c) 2016 Kenneth R Westerback <kwesterback@gmail.com>
+.\" Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: February 26 2021 $
+.Dt DHCPLEASECTL 8
+.Os
+.Sh NAME
+.Nm dhcpleasectl
+.Nd control the dhcpleased daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl s Ar socket
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+program controls the
+.Xr dhcpleased 8
+daemon.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl s Ar socket
+Use
+.Ar socket
+instead of the default
+.Pa /dev/dhcpleased.sock
+to communicate with
+.Xr dhcpleased 8 .
+.El
+.Pp
+The following commands are available:
+.Bl -tag -width Ds
+.It Cm log brief
+Disable verbose debug logging.
+.It Cm log verbose
+Enable verbose debug logging.
+.It Cm send request Ar interfacename
+Send a DHCP request on interface
+.Ar interfacename
+to force a renew of the lease.
+.It Cm show interface Op Ar interfacename
+Display status about network interfaces.
+If
+.Ar interfacename
+is specified, only information relative to
+.Ar interfacename
+is shown.
+Otherwise information on all interfaces is shown.
+.El
+.Sh FILES
+.Bl -tag -width "/dev/dhcpleased.sockXX" -compact
+.It Pa /dev/dhcpleased.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr dhcpleased 8 .
+.El
+.Sh SEE ALSO
+.Xr dhcpleased 8
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 6.9 .
diff --git a/usr.sbin/dhcpleasectl/dhcpleasectl.c b/usr.sbin/dhcpleasectl/dhcpleasectl.c
new file mode 100644 (file)
index 0000000..e05b70f
--- /dev/null
@@ -0,0 +1,258 @@
+/*     $OpenBSD: dhcpleasectl.c,v 1.1 2021/02/26 16:16:37 florian Exp $        */
+
+/*
+ * Copyright (c) 2021 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet6/nd6.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dhcpleased.h"
+#include "frontend.h"
+#include "parser.h"
+
+__dead void     usage(void);
+int             show_interface_msg(struct imsg *);
+
+struct imsgbuf *ibuf;
+
+__dead void
+usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n",
+           __progname);
+       exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+       struct sockaddr_un       sun;
+       struct parse_result     *res;
+       struct imsg              imsg;
+       int                      ctl_sock;
+       int                      done = 0;
+       int                      n, verbose = 0;
+       int                      ch;
+       char                    *sockname;
+
+       sockname = DHCPLEASED_SOCKET;
+       while ((ch = getopt(argc, argv, "s:")) != -1) {
+               switch (ch) {
+               case 's':
+                       sockname = optarg;
+                       break;
+               default:
+                       usage();
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (pledge("stdio unix", NULL) == -1)
+               err(1, "pledge");
+
+       /* Parse command line. */
+       if ((res = parse(argc, argv)) == NULL)
+               exit(1);
+
+       /* Connect to control socket. */
+       if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+               err(1, "socket");
+
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+       strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path));
+
+       if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+               err(1, "connect: %s", sockname);
+
+       if (pledge("stdio", NULL) == -1)
+               err(1, "pledge");
+
+       if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
+               err(1, NULL);
+       imsg_init(ibuf, ctl_sock);
+       done = 0;
+
+       /* Process user request. */
+       switch (res->action) {
+       case LOG_VERBOSE:
+               verbose = 1;
+               /* FALLTHROUGH */
+       case LOG_BRIEF:
+               imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1,
+                   &verbose, sizeof(verbose));
+               printf("logging request sent.\n");
+               done = 1;
+               break;
+       case SHOW_INTERFACE:
+               imsg_compose(ibuf, IMSG_CTL_SHOW_INTERFACE_INFO, 0, 0, -1,
+                   &res->if_index, sizeof(res->if_index));
+               break;
+       case SEND_REQUEST:
+               imsg_compose(ibuf, IMSG_CTL_SEND_REQUEST, 0, 0, -1,
+                   &res->if_index, sizeof(res->if_index));
+               done = 1;
+               break;
+       default:
+               usage();
+       }
+
+       while (ibuf->w.queued)
+               if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+                       err(1, "write error");
+
+       while (!done) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       errx(1, "imsg_read error");
+               if (n == 0)
+                       errx(1, "pipe closed");
+
+               while (!done) {
+                       if ((n = imsg_get(ibuf, &imsg)) == -1)
+                               errx(1, "imsg_get error");
+                       if (n == 0)
+                               break;
+
+                       switch (res->action) {
+                       case SHOW_INTERFACE:
+                               done = show_interface_msg(&imsg);
+                               break;
+                       default:
+                               break;
+                       }
+
+                       imsg_free(&imsg);
+               }
+       }
+       close(ctl_sock);
+       free(ibuf);
+
+       return (0);
+}
+
+int
+show_interface_msg(struct imsg *imsg)
+{
+       static int               if_count = 0;
+       struct ctl_engine_info  *cei;
+       struct timespec          now, diff;
+       int                      i, h, m, s;
+       char                     buf[IF_NAMESIZE], *bufp;
+       char                     ipbuf[INET_ADDRSTRLEN];
+       char                     maskbuf[INET_ADDRSTRLEN];
+       char                     routerbuf[INET_ADDRSTRLEN];
+
+       switch (imsg->hdr.type) {
+       case IMSG_CTL_SHOW_INTERFACE_INFO:
+               cei = imsg->data;
+
+               if (if_count++ > 0)
+                       printf("\n");
+
+               bufp = if_indextoname(cei->if_index, buf);
+               printf("%s [%s]:\n", bufp != NULL ? bufp : "unknown",
+                   cei->state);
+               memset(ipbuf, 0, sizeof(ipbuf));
+               if (cei->server_identifier.s_addr != INADDR_ANY) {
+                       if (inet_ntop(AF_INET, &cei->server_identifier, ipbuf,
+                           sizeof(ipbuf)) == NULL)
+                               ipbuf[0] = '\0';
+               } else if (cei->dhcp_server.s_addr != INADDR_ANY) {
+                       if (inet_ntop(AF_INET, &cei->dhcp_server, ipbuf,
+                           sizeof(ipbuf)) == NULL)
+                               ipbuf[0] = '\0';
+               }
+               if (ipbuf[0] != '\0')
+                       printf("\tserver: %s\n", ipbuf);
+               if (cei->requested_ip.s_addr != INADDR_ANY) {
+                       clock_gettime(CLOCK_MONOTONIC, &now);
+                       timespecsub(&now, &cei->request_time, &diff);
+                       memset(ipbuf, 0, sizeof(ipbuf));
+                       memset(maskbuf, 0, sizeof(maskbuf));
+                       memset(routerbuf, 0, sizeof(routerbuf));
+                       if (inet_ntop(AF_INET, &cei->requested_ip, ipbuf,
+                           sizeof(ipbuf)) == NULL)
+                               ipbuf[0] = '\0';
+                       if (inet_ntop(AF_INET, &cei->mask, maskbuf,
+                           sizeof(maskbuf)) == NULL)
+                               maskbuf[0] = '\0';
+                       if (inet_ntop(AF_INET, &cei->router, routerbuf,
+                           sizeof(routerbuf)) == NULL)
+                               routerbuf[0] = '\0';
+                       printf("\t    IP: %s/%s\n", ipbuf, maskbuf);
+                       if (cei->router.s_addr != INADDR_ANY)
+                               printf("\trouter: %s\n", routerbuf);
+                       if (cei->nameservers[0].s_addr != INADDR_ANY) {
+                               printf("\t   DNS:");
+                               for (i = 0; i < MAX_RDNS_COUNT &&
+                                   cei->nameservers[i].s_addr != INADDR_ANY;
+                                   i++) {
+                                       if (inet_ntop(AF_INET,
+                                           &cei->nameservers[i], ipbuf,
+                                           sizeof(ipbuf)) == NULL)
+                                               continue;
+                                       printf(" %s", ipbuf);
+                               }
+                               printf("\n");
+                       }
+                       s = cei->lease_time - diff.tv_sec;
+                       if (s < 0)
+                               s = 0;
+                       h = s / 3600; s -= h * 3600;
+                       m = s / 60; s -= m * 60;
+                       if (h > 0)
+                               printf("\t lease: %dh%dm%ds\n", h, m ,s);
+                       else if (m > 0)
+                               printf("\t lease: %dm%ds\n", m ,s);
+                       else
+                               printf("\t lease: %ds\n", s);
+               }
+               break;
+       case IMSG_CTL_END:
+               return (1);
+       default:
+               break;
+       }
+
+       return (0);
+}
diff --git a/usr.sbin/dhcpleasectl/parser.c b/usr.sbin/dhcpleasectl/parser.c
new file mode 100644 (file)
index 0000000..eb9a0d8
--- /dev/null
@@ -0,0 +1,207 @@
+/*     $OpenBSD: parser.c,v 1.1 2021/02/26 16:16:37 florian Exp $      */
+
+/*
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dhcpleased.h"
+#include "parser.h"
+
+enum token_type {
+       NOTOKEN,
+       ENDTOKEN,
+       INTERFACENAME,
+       KEYWORD
+};
+
+struct token {
+       enum token_type          type;
+       const char              *keyword;
+       int                      value;
+       const struct token      *next;
+};
+
+static const struct token t_main[];
+static const struct token t_log[];
+static const struct token t_show[];
+static const struct token t_show_interface[];
+static const struct token t_send[];
+static const struct token t_send_request[];
+
+static const struct token t_main[] = {
+       {KEYWORD,       "show",         SHOW,           t_show},
+       {KEYWORD,       "log",          NONE,           t_log},
+       {KEYWORD,       "send",         NONE,           t_send},
+       {ENDTOKEN,      "",             NONE,           NULL}
+};
+
+static const struct token t_log[] = {
+       {KEYWORD,       "verbose",      LOG_VERBOSE,    NULL},
+       {KEYWORD,       "brief",        LOG_BRIEF,      NULL},
+       {ENDTOKEN,      "",             NONE,           NULL}
+};
+
+static const struct token t_show[] = {
+       {KEYWORD,       "interface",    SHOW_INTERFACE,  t_show_interface},
+       {ENDTOKEN,      "",             NONE,           NULL}
+};
+
+static const struct token t_send[] = {
+       {KEYWORD,       "request",      SEND_REQUEST,   t_send_request},
+       {ENDTOKEN,      "",             NONE,           NULL}
+};
+static const struct token t_send_request[] = {
+       {INTERFACENAME, "",             SEND_REQUEST,   NULL},
+       {ENDTOKEN,      "",             NONE,           NULL}
+};
+
+static const struct token t_show_interface[] = {
+       {NOTOKEN,       "",             NONE,           NULL},
+       {INTERFACENAME, "",             SHOW_INTERFACE, NULL},
+       {ENDTOKEN,      "",             NONE,           NULL}
+};
+
+static const struct token *match_token(const char *, const struct token *,
+    struct parse_result *);
+static void show_valid_args(const struct token *);
+
+struct parse_result *
+parse(int argc, char *argv[])
+{
+       static struct parse_result      res;
+       const struct token      *table = t_main;
+       const struct token      *match;
+
+       memset(&res, 0, sizeof(res));
+
+       while (argc >= 0) {
+               if ((match = match_token(argv[0], table, &res)) == NULL) {
+                       fprintf(stderr, "valid commands/args:\n");
+                       show_valid_args(table);
+                       return (NULL);
+               }
+
+               argc--;
+               argv++;
+
+               if (match->type == NOTOKEN || match->next == NULL)
+                       break;
+
+               table = match->next;
+       }
+
+       if (argc > 0) {
+               fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+               return (NULL);
+       }
+
+       return (&res);
+}
+
+static const struct token *
+match_token(const char *word, const struct token *table,
+    struct parse_result *res)
+{
+       u_int                    i, match;
+       const struct token      *t = NULL;
+
+       match = 0;
+
+       for (i = 0; table[i].type != ENDTOKEN; i++) {
+               switch (table[i].type) {
+               case NOTOKEN:
+                       if (word == NULL || strlen(word) == 0) {
+                               match++;
+                               t = &table[i];
+                       }
+                       break;
+               case INTERFACENAME:
+                       if (!match && word != NULL && strlen(word) > 0) {
+                               if ((res->if_index = if_nametoindex(word)) == 0)
+                                       errx(1, "unknown interface");
+                               match++;
+                               t = &table[i];
+                               if (t->value)
+                                       res->action = t->value;
+                       }
+                       break;
+               case KEYWORD:
+                       if (word != NULL && strncmp(word, table[i].keyword,
+                           strlen(word)) == 0) {
+                               match++;
+                               t = &table[i];
+                               if (t->value)
+                                       res->action = t->value;
+                       }
+                       break;
+               case ENDTOKEN:
+                       break;
+               }
+       }
+
+       if (match != 1) {
+               if (word == NULL)
+                       fprintf(stderr, "missing argument:\n");
+               else if (match > 1)
+                       fprintf(stderr, "ambiguous argument: %s\n", word);
+               else if (match < 1)
+                       fprintf(stderr, "unknown argument: %s\n", word);
+               return (NULL);
+       }
+
+       return (t);
+}
+
+static void
+show_valid_args(const struct token *table)
+{
+       int     i;
+
+       for (i = 0; table[i].type != ENDTOKEN; i++) {
+               switch (table[i].type) {
+               case NOTOKEN:
+                       fprintf(stderr, "  <cr>\n");
+                       break;
+               case INTERFACENAME:
+                       fprintf(stderr, " <interface>\n");
+                       break;
+               case KEYWORD:
+                       fprintf(stderr, "  %s\n", table[i].keyword);
+                       break;
+               case ENDTOKEN:
+                       break;
+               }
+       }
+}
diff --git a/usr.sbin/dhcpleasectl/parser.h b/usr.sbin/dhcpleasectl/parser.h
new file mode 100644 (file)
index 0000000..dad595b
--- /dev/null
@@ -0,0 +1,34 @@
+/*     $OpenBSD: parser.h,v 1.1 2021/02/26 16:16:37 florian Exp $      */
+
+/*
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+enum actions {
+       NONE,
+       LOG_VERBOSE,
+       LOG_BRIEF,
+       SHOW,
+       SHOW_INTERFACE,
+       SEND_REQUEST
+};
+
+struct parse_result {
+       enum actions    action;
+       uint32_t        if_index;
+};
+
+struct parse_result    *parse(int, char *[]);