Import dhcp6leased(8)
authorflorian <florian@openbsd.org>
Sun, 2 Jun 2024 12:28:05 +0000 (12:28 +0000)
committerflorian <florian@openbsd.org>
Sun, 2 Jun 2024 12:28:05 +0000 (12:28 +0000)
dhcp6leased is a daemon to manage IPv6 prefix delegations. It requests
a prefix from an upstream DHCPv6 server and configures downstream
network interfaces. rad(8) can be used to advertise available prefixes
to clients.

It's a transmogrified dhcpleased(8), so it's a bit rough around the
edges. But it can already request and renew prefixes and configure
interfaces. It's time to hack on it in-tree.

OK deraadt

15 files changed:
sbin/dhcp6leased/Makefile [new file with mode: 0644]
sbin/dhcp6leased/control.c [new file with mode: 0644]
sbin/dhcp6leased/control.h [new file with mode: 0644]
sbin/dhcp6leased/dhcp6leased.8 [new file with mode: 0644]
sbin/dhcp6leased/dhcp6leased.c [new file with mode: 0644]
sbin/dhcp6leased/dhcp6leased.conf.5 [new file with mode: 0644]
sbin/dhcp6leased/dhcp6leased.h [new file with mode: 0644]
sbin/dhcp6leased/engine.c [new file with mode: 0644]
sbin/dhcp6leased/engine.h [new file with mode: 0644]
sbin/dhcp6leased/frontend.c [new file with mode: 0644]
sbin/dhcp6leased/frontend.h [new file with mode: 0644]
sbin/dhcp6leased/log.c [new file with mode: 0644]
sbin/dhcp6leased/log.h [new file with mode: 0644]
sbin/dhcp6leased/parse.y [new file with mode: 0644]
sbin/dhcp6leased/printconf.c [new file with mode: 0644]

diff --git a/sbin/dhcp6leased/Makefile b/sbin/dhcp6leased/Makefile
new file mode 100644 (file)
index 0000000..4574a9a
--- /dev/null
@@ -0,0 +1,23 @@
+#      $OpenBSD: Makefile,v 1.1 2024/06/02 12:28:05 florian Exp $
+
+PROG=  dhcp6leased
+SRCS=  control.c dhcp6leased.c engine.c frontend.c log.c
+SRCS+= parse.y printconf.c
+
+MAN=   dhcp6leased.8 dhcp6leased.conf.5
+
+#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>
+
+# Don't compile dhcp6leased as static binary by default
+LDSTATIC=
diff --git a/sbin/dhcp6leased/control.c b/sbin/dhcp6leased/control.c
new file mode 100644 (file)
index 0000000..9ded313
--- /dev/null
@@ -0,0 +1,307 @@
+/*     $OpenBSD: control.c,v 1.1 2024/06/02 12:28:05 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "dhcp6leased.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_RELOAD:
+                       frontend_imsg_compose_main(imsg.hdr.type, 0, NULL, 0);
+                       break;
+               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_REQUEST_REBOOT, 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/dhcp6leased/control.h b/sbin/dhcp6leased/control.h
new file mode 100644 (file)
index 0000000..b1fdef8
--- /dev/null
@@ -0,0 +1,23 @@
+/*     $OpenBSD: control.h,v 1.1 2024/06/02 12:28:05 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.
+ */
+
+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 *);
diff --git a/sbin/dhcp6leased/dhcp6leased.8 b/sbin/dhcp6leased/dhcp6leased.8
new file mode 100644 (file)
index 0000000..762fa43
--- /dev/null
@@ -0,0 +1,102 @@
+.\"    $OpenBSD: dhcp6leased.8,v 1.1 2024/06/02 12:28:05 florian Exp $
+.\"
+.\" Copyright (c) 2024 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: June 2 2024 $
+.Dt DHCP6LEASED 8
+.Os
+.Sh NAME
+.Nm dhcp6leased
+.Nd Dynamic Host Configuration Protocol (DHCPv6) client daemon for IPv6 prefix delegation
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl f Ar file
+.Op Fl s Ar socket
+.Sh DESCRIPTION
+.Nm
+is an IPv6 dynamic host configuration protocol (DHCPv6) daemon for clients.
+It requests IPv6 prefix delegations from DHCPv6 servers for assignment
+to downstream interfaces.
+.Pp
+A running
+.Nm
+can be controlled with the
+.Xr dhcp6leasectl 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 f Ar file
+Specify an alternative configuration file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.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 "/var/db/dhcp6leased/<if>" -compact
+.It Pa /dev/dhcp6leased.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr dhcp6leasectl 8 .
+.It Pa /etc/dhcp6leased.conf
+Default
+.Nm
+configuration file.
+.It Pa /var/db/dhcp6leased/ Ns Aq Ar if
+Interface specific lease files.
+.El
+.Sh SEE ALSO
+.Xr dhcp6leased.conf 5 ,
+.Xr dhcp6leasectl 8 ,
+.Xr ifconfig 8
+.Sh STANDARDS
+.Rs
+.%A T. Mrugalski
+.%A M. Siodelski
+.%A B. Volz
+.%A A. Yourtchenko
+.%A M. Richardson
+.%A S. Jiang
+.%A T. Lemon
+.%A T. Winters
+.%D November 2018
+.%R RFC 8415
+.%T Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 7.6 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Florian Obser Aq Mt florian@openbsd.org .
diff --git a/sbin/dhcp6leased/dhcp6leased.c b/sbin/dhcp6leased/dhcp6leased.c
new file mode 100644 (file)
index 0000000..0165e35
--- /dev/null
@@ -0,0 +1,985 @@
+/*     $OpenBSD: dhcp6leased.c,v 1.1 2024/06/02 12:28:05 florian Exp $ */
+
+/*
+ * Copyright (c) 2017, 2021, 2024 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 <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet6/in6_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 <uuid.h>
+
+#include "log.h"
+#include "dhcp6leased.h"
+#include "frontend.h"
+#include "engine.h"
+#include "control.h"
+
+enum dhcp6leased_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 dhcp6leased_process, char *, int, int, int);
+
+void    main_dispatch_frontend(int, short, void *);
+void    main_dispatch_engine(int, short, void *);
+void    open_udpsock(uint32_t);
+void    configure_address(struct imsg_configure_address *);
+void    deconfigure_address(struct imsg_configure_address *);
+void    read_lease_file(struct imsg_ifinfo *);
+uint8_t        *get_uuid(void);
+
+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);
+int     main_imsg_send_config(struct dhcp6leased_conf *);
+int     main_reload(void);
+
+static struct imsgev   *iev_frontend;
+static struct imsgev   *iev_engine;
+
+struct dhcp6leased_conf        *main_conf;
+const char              default_conffile[] = _PATH_CONF_FILE;
+const char             *conffile = default_conffile;
+pid_t                   frontend_pid;
+pid_t                   engine_pid;
+
+int                     routesock, ioctl_sock, rtm_seq, no_lease_files;
+
+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();
+       case SIGHUP:
+               if (main_reload() == -1)
+                       log_warnx("configuration reload failed");
+               else
+                       log_debug("configuration reloaded");
+               break;
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+__dead void
+usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n",
+           __progname);
+       exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+       struct event     ev_sigint, ev_sigterm, ev_sighup;
+       int              ch;
+       int              debug = 0, engine_flag = 0, frontend_flag = 0;
+       int              verbose = 0, no_action = 0;
+       char            *saved_argv0;
+       int              pipe_main2frontend[2];
+       int              pipe_main2engine[2];
+       int              frontend_routesock, rtfilter, lockfd;
+       int              rtable_any = RTABLE_ANY;
+       char            *csock = _PATH_CTRL_SOCKET;
+       int              control_fd;
+       uint8_t         *uuid;
+
+       log_init(1, LOG_DAEMON);        /* Log to stderr until daemonized. */
+       log_setverbose(1);
+
+       saved_argv0 = argv[0];
+       if (saved_argv0 == NULL)
+               saved_argv0 = "dhcp6leased";
+
+       while ((ch = getopt(argc, argv, "dEFf:ns:v")) != -1) {
+               switch (ch) {
+               case 'd':
+                       debug = 1;
+                       break;
+               case 'E':
+                       engine_flag = 1;
+                       break;
+               case 'F':
+                       frontend_flag = 1;
+                       break;
+               case 'f':
+                       conffile = optarg;
+                       break;
+               case 'n':
+                       no_action = 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);
+
+       /* parse config file */
+       if ((main_conf = parse_config(conffile)) == NULL)
+               exit(1);
+
+       if (no_action) {
+               if (verbose)
+                       print_config(main_conf, verbose);
+               else
+                       fprintf(stderr, "configuration OK\n");
+               exit(0);
+       }
+
+       /* Check for root privileges. */
+       if (geteuid())
+               errx(1, "need root privileges");
+
+       lockfd = open(_PATH_LOCKFILE, O_CREAT|O_RDWR|O_EXLOCK|O_NONBLOCK, 0600);
+       if (lockfd == -1) {
+               if (errno == EAGAIN)
+                       errx(1, "already running");
+               err(1, "%s", _PATH_LOCKFILE);
+       }
+
+       /* Check for assigned daemon user */
+       if (getpwnam(DHCP6LEASED_USER) == NULL)
+               errx(1, "unknown user %s", DHCP6LEASED_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(routesock, SHUT_RD);
+
+       event_init();
+
+       /* Setup signal handler. */
+       signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+       signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal_add(&ev_sighup, NULL);
+       signal(SIGPIPE, 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_INET6, 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_IFANNOUNCE);
+       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)");
+
+       uuid = get_uuid();
+
+       if ((control_fd = control_init(csock)) == -1)
+               warnx("control socket setup failed");
+
+       if (unveil(conffile, "r") == -1)
+               fatal("unveil %s", conffile);
+
+       if (unveil(_PATH_LEASE, "rwc") == -1) {
+               no_lease_files = 1;
+               log_warn("disabling lease files, unveil " _PATH_LEASE);
+       }
+
+       if (unveil(NULL, NULL) == -1)
+               fatal("unveil");
+
+       if (pledge("stdio inet rpath wpath sendfd wroute", NULL) == -1)
+               fatal("pledge");
+
+       main_imsg_compose_frontend(IMSG_ROUTESOCK, frontend_routesock, NULL, 0);
+
+       main_imsg_compose_frontend(IMSG_UUID, -1, uuid, UUID_SIZE);
+       main_imsg_compose_engine(IMSG_UUID, -1, uuid, UUID_SIZE);
+
+       if (control_fd != -1)
+               main_imsg_compose_frontend(IMSG_CONTROLFD, control_fd, NULL, 0);
+       main_imsg_send_config(main_conf);
+
+       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);
+
+       config_clear(main_conf);
+
+       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 dhcp6leased_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;
+       int                      verbose;
+
+       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_UDPSOCK:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
+                               fatalx("%s: IMSG_OPEN_UDPSOCK wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       memcpy(&if_index, imsg.data, sizeof(if_index));
+                       open_udpsock(if_index);
+                       break;
+               case IMSG_CTL_RELOAD:
+                       if (main_reload() == -1)
+                               log_warnx("configuration reload failed");
+                       else
+                               log_warnx("configuration reloaded");
+                       break;
+               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_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));
+                       read_lease_file(&imsg_ifinfo);
+                       main_imsg_compose_engine(IMSG_UPDATE_IF, -1,
+                           &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_ADDRESS: {
+                       struct imsg_configure_address imsg_configure_address;
+                       if (IMSG_DATA_SIZE(imsg) !=
+                           sizeof(imsg_configure_address))
+                               fatalx("%s: IMSG_CONFIGURE_ADDRESS wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_configure_address, imsg.data,
+                           sizeof(imsg_configure_address));
+                       configure_address(&imsg_configure_address);
+                       break;
+               }
+               case IMSG_DECONFIGURE_ADDRESS: {
+                       struct imsg_configure_address imsg_configure_address;
+                       if (IMSG_DATA_SIZE(imsg) !=
+                           sizeof(imsg_configure_address))
+                               fatalx("%s: IMSG_CONFIGURE_ADDRESS wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_configure_address, imsg.data,
+                           sizeof(imsg_configure_address));
+                       deconfigure_address(&imsg_configure_address);
+                       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);
+}
+
+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);
+}
+
+int
+main_reload(void)
+{
+       struct dhcp6leased_conf *xconf;
+
+       if ((xconf = parse_config(conffile)) == NULL)
+               return (-1);
+
+       if (main_imsg_send_config(xconf) == -1)
+               return (-1);
+
+       merge_config(main_conf, xconf);
+
+       return (0);
+}
+
+int
+main_imsg_send_config(struct dhcp6leased_conf *xconf)
+{
+       struct iface_conf       *iface_conf;
+       struct iface_ia_conf    *ia_conf;
+       struct iface_pd_conf    *pd_conf;
+
+       main_imsg_compose_frontend(IMSG_RECONF_CONF, -1, NULL, 0);
+       main_imsg_compose_engine(IMSG_RECONF_CONF, -1, NULL, 0);
+
+       /* Send the interface list to the frontend & engine. */
+       SIMPLEQ_FOREACH(iface_conf, &xconf->iface_list, entry) {
+               main_imsg_compose_frontend(IMSG_RECONF_IFACE, -1, iface_conf,
+                   sizeof(*iface_conf));
+               main_imsg_compose_engine(IMSG_RECONF_IFACE, -1, iface_conf,
+                   sizeof(*iface_conf));
+               SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list,
+                   entry) {
+                       main_imsg_compose_frontend(IMSG_RECONF_IFACE_IA, -1,
+                           ia_conf, sizeof(*ia_conf));
+                       main_imsg_compose_engine(IMSG_RECONF_IFACE_IA, -1,
+                           ia_conf, sizeof(*ia_conf));
+                       SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list,
+                           entry) {
+                               main_imsg_compose_frontend(IMSG_RECONF_IFACE_PD,
+                                   -1, pd_conf, sizeof(*pd_conf));
+                               main_imsg_compose_engine(IMSG_RECONF_IFACE_PD,
+                                   -1, pd_conf, sizeof(*pd_conf));
+                       }
+                       main_imsg_compose_frontend(IMSG_RECONF_IFACE_IA_END,
+                           -1, NULL, 0);
+                       main_imsg_compose_engine(IMSG_RECONF_IFACE_IA_END,
+                           -1, NULL, 0);
+               }
+               main_imsg_compose_frontend(IMSG_RECONF_IFACE_END, -1, NULL, 0);
+               main_imsg_compose_engine(IMSG_RECONF_IFACE_END, -1, NULL, 0);
+
+       }
+
+       /* Config is now complete. */
+       main_imsg_compose_frontend(IMSG_RECONF_END, -1, NULL, 0);
+       main_imsg_compose_engine(IMSG_RECONF_END, -1, NULL, 0);
+
+       return (0);
+}
+
+void
+configure_address(struct imsg_configure_address *address)
+{
+       struct in6_aliasreq      in6_addreq;
+       time_t                   t;
+       char                    *if_name;
+
+       memset(&in6_addreq, 0, sizeof(in6_addreq));
+
+       if_name = if_indextoname(address->if_index, in6_addreq.ifra_name);
+       if (if_name == NULL) {
+               log_warnx("%s: cannot find interface %d", __func__,
+                   address->if_index);
+               return;
+       }
+
+       memcpy(&in6_addreq.ifra_addr, &address->addr,
+           sizeof(in6_addreq.ifra_addr));
+       memcpy(&in6_addreq.ifra_prefixmask.sin6_addr, &address->mask,
+           sizeof(in6_addreq.ifra_prefixmask.sin6_addr));
+       in6_addreq.ifra_prefixmask.sin6_family = AF_INET6;
+       in6_addreq.ifra_prefixmask.sin6_len =
+           sizeof(in6_addreq.ifra_prefixmask);
+
+       t = time(NULL);
+
+       in6_addreq.ifra_lifetime.ia6t_expire = t + address->vltime;
+       in6_addreq.ifra_lifetime.ia6t_vltime = address->vltime;
+
+       in6_addreq.ifra_lifetime.ia6t_preferred = t + address->pltime;
+       in6_addreq.ifra_lifetime.ia6t_pltime = address->pltime;
+
+       log_debug("%s: %s", __func__, if_name);
+
+       if (ioctl(ioctl_sock, SIOCAIFADDR_IN6, &in6_addreq) == -1)
+               log_warn("SIOCAIFADDR_IN6");
+}
+
+void
+deconfigure_address(struct imsg_configure_address *imsg)
+{
+       fatalx("%s: not implemented", __func__); /* XXX */
+}
+
+const char*
+sin6_to_str(struct sockaddr_in6 *sin6)
+{
+       static char hbuf[NI_MAXHOST];
+       int error;
+
+       error = getnameinfo((struct sockaddr *)sin6, sin6->sin6_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;
+}
+
+void
+open_udpsock(uint32_t if_index)
+{
+       struct ifaddrs          *ifap, *ifa;
+       struct sockaddr_in6     *sin6;
+       int                      udpsock = -1, rdomain, opt = 1;
+       char                     if_name[IF_NAMESIZE];
+
+       if (if_indextoname(if_index, if_name) == NULL) {
+               log_warnx("%s: cannot find interface %d", __func__, if_index);
+               return;
+       }
+
+       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: {
+                               struct if_data          *if_data;
+
+                               if_data = (struct if_data *)ifa->ifa_data;
+                               rdomain = if_data->ifi_rdomain;
+                               break;
+                       }
+                       case AF_INET6: {
+                               struct sockaddr_in6 *s6;
+                               s6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+#ifdef __KAME__
+                               if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr) &&
+                                   s6->sin6_scope_id == 0) {
+                                       s6->sin6_scope_id =
+                                           ntohs(*(u_int16_t *)
+                                               &s6->sin6_addr.s6_addr[2]);
+                                       s6->sin6_addr.s6_addr[2] =
+                                           s6->sin6_addr.s6_addr[3] = 0;
+                               }
+#endif
+                               if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr))
+                                       sin6 = s6;
+                               break;
+                       }
+                       default:
+                               break;
+
+                       }
+       }
+       sin6->sin6_port = htons(CLIENT_PORT);
+       log_debug("%s: %s rdomain: %d", __func__, sin6_to_str(sin6),
+           rdomain);
+
+       if ((udpsock = socket(AF_INET6, 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, &rdomain,
+           sizeof(rdomain)) == -1) {
+               /* we might race against removal of the rdomain */
+               log_warn("setsockopt SO_RTABLE");
+               close(udpsock);
+               return;
+       }
+
+       if (bind(udpsock, (struct sockaddr *)sin6, sizeof(*sin6)) == -1) {
+               close(udpsock);
+               return;
+       }
+
+       main_imsg_compose_frontend(IMSG_UDPSOCK, udpsock, &if_index,
+           sizeof(if_index));
+}
+
+void
+read_lease_file(struct imsg_ifinfo *imsg_ifinfo)
+{
+       int      len, fd;
+       char     if_name[IF_NAMESIZE];
+       char     lease_file_buf[sizeof(_PATH_LEASE) + IF_NAMESIZE];
+
+       if (no_lease_files)
+               return;
+
+       memset(imsg_ifinfo->lease, 0, sizeof(imsg_ifinfo->lease));
+
+       if (if_indextoname(imsg_ifinfo->if_index, if_name) == NULL) {
+               log_warnx("%s: cannot find interface %d", __func__,
+                   imsg_ifinfo->if_index);
+               return;
+       }
+
+       len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s",
+           _PATH_LEASE, 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;
+       }
+
+       if ((fd = open(lease_file_buf, O_RDONLY)) == -1)
+               return;
+
+       /* no need for error handling, we'll just do a DHCP discover */
+       read(fd, imsg_ifinfo->lease, sizeof(imsg_ifinfo->lease) - 1);
+       close(fd);
+}
+
+void
+merge_config(struct dhcp6leased_conf *conf, struct dhcp6leased_conf *xconf)
+{
+       struct iface_conf       *iface_conf;
+       struct iface_ia_conf    *ia_conf;
+       struct iface_pd_conf    *pd_conf;
+
+       /* Remove & discard existing interfaces. */
+       while ((iface_conf = SIMPLEQ_FIRST(&conf->iface_list)) != NULL) {
+               SIMPLEQ_REMOVE_HEAD(&conf->iface_list, entry);
+               while((ia_conf =
+                   SIMPLEQ_FIRST(&iface_conf->iface_ia_list)) != NULL) {
+                       SIMPLEQ_REMOVE_HEAD(&iface_conf->iface_ia_list,
+                           entry);
+                       while((pd_conf =
+                           SIMPLEQ_FIRST(&ia_conf->iface_pd_list)) != NULL) {
+                               SIMPLEQ_REMOVE_HEAD(&ia_conf->iface_pd_list,
+                                   entry);
+                               free(pd_conf);
+                       }
+                       free(ia_conf);
+               }
+               free(iface_conf);
+       }
+
+       /* Add new interfaces. */
+       SIMPLEQ_CONCAT(&conf->iface_list, &xconf->iface_list);
+
+       free(xconf);
+}
+
+struct dhcp6leased_conf *
+config_new_empty(void)
+{
+       struct dhcp6leased_conf *xconf;
+
+       xconf = calloc(1, sizeof(*xconf));
+       if (xconf == NULL)
+               fatal(NULL);
+
+       SIMPLEQ_INIT(&xconf->iface_list);
+
+       return (xconf);
+}
+
+void
+config_clear(struct dhcp6leased_conf *conf)
+{
+       struct dhcp6leased_conf *xconf;
+
+       /* Merge current config with an empty config. */
+       xconf = config_new_empty();
+       merge_config(conf, xconf);
+
+       free(conf);
+}
+
+uint8_t*
+get_uuid(void) {
+       static uint8_t   uuid_buf[UUID_SIZE];
+       uuid_t           uuid;
+       uint32_t         status;
+       int              fd, len;
+       char            *str;
+       char             strbuf[UUID_STR_SIZE];
+       char             tmpl[] = _PATH_UUID"XXXXXXXXXX";
+
+       if ((fd = open(_PATH_UUID, O_RDONLY)) == -1) {
+ gen:
+               uuid_create(&uuid, NULL);
+               uuid_to_string(&uuid, &str, &status);
+               if(status != uuid_s_ok)
+                       fatalx("failed to generate uuid string representation");
+
+               len = snprintf(strbuf, sizeof(strbuf), "%s\n", str);
+               if (len < 0 || (size_t)len >= sizeof(strbuf))
+                       fatalx("uuid string too long");
+               free(str);
+
+               if ((fd = mkstemp(tmpl)) == -1) {
+                       log_warn("mkstemp");
+                       goto err;
+               }
+               if (write(fd, strbuf, len) < len) {
+                       log_warn("write");
+                       goto err;
+               }
+               if (fchmod(fd, 0644) == -1) {
+                       log_warn("fchmod");
+                       goto err;
+               }
+
+               if (close(fd) == -1) {
+                       log_warn("fchmod");
+                       goto err;
+               }
+               fd = -1;
+               if (rename(tmpl, _PATH_UUID) == -1) {
+                       log_warn("rename");
+                       goto err;
+               }
+       } else {
+               read(fd, strbuf, sizeof(strbuf));
+               close(fd);
+               strbuf[sizeof(strbuf) - 2] = '\0';
+
+               uuid_from_string(strbuf, &uuid, &status);
+
+               if(status != uuid_s_ok) {
+                       log_warnx("failed to convert string to uuid: %s - %d",
+                           strbuf, status);
+                       goto gen;
+               }
+       }
+       uuid_enc_be(uuid_buf, &uuid);
+       return (uuid_buf);
+
+ err:
+       if (fd != -1)
+               close(fd);
+       unlink(tmpl);
+       fatal("Could not read or create UUID");
+}
diff --git a/sbin/dhcp6leased/dhcp6leased.conf.5 b/sbin/dhcp6leased/dhcp6leased.conf.5
new file mode 100644 (file)
index 0000000..fd5029c
--- /dev/null
@@ -0,0 +1,119 @@
+.\"    $OpenBSD: dhcp6leased.conf.5,v 1.1 2024/06/02 12:28:05 florian Exp $
+.\"
+.\" Copyright (c) 2018, 2021, 2024 Florian Obser <florian@openbsd.org>
+.\" Copyright (c) 2005 Esben Norby <norby@openbsd.org>
+.\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>
+.\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+.\" Copyright (c) 2002 Daniel Hartmeier <dhartmei@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: June 2 2024 $
+.Dt DHCP6LEASED.CONF 5
+.Os
+.Sh NAME
+.Nm dhcp6leased.conf
+.Nd DHCPv6 client configuration file
+.Sh DESCRIPTION
+The
+.Xr dhcp6leased 8
+daemon is a dynamic host configuration protocol client daemon for IPv6 prefix
+delegation.
+.Pp
+The
+.Nm
+config file is divided into the following main sections:
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Prefix delegation
+.Xr dhcp6leased 8
+requests prefix delegation from a DHCPv6 server and assigns prefixes
+to interfaces.
+This section defines on which interfaces prefix delegation should be
+requested and to which interfaces prefixes should be assigned.
+.El
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, digit, or underscore,
+and may contain any of those characters.
+Macro names may not be reserved words (for example,
+.Ic interface ) .
+Macros are not expanded inside quotes.
+.Sh PREFIX DELEGATION
+A list of interfaces on which to request prefix delegation:
+.Bd -unfilled -offset indent
+.Ic request prefix delegation on Ar name Ic for Ar { name/prefix Oo Ar name/prefix ... Oc }
+.Ed
+.Pp
+This requests a prefix delegation on
+.Ar name
+upstream network interface for the list of
+.Ar name/prefix
+network interfaces.
+If
+.Ar prefix
+is omitted a default of /64 is used.
+.Pp
+.Xr dhcp6leased 8
+will calculate the prefix length needed to cover all interfaces in the list.
+When a prefix is delegated by a DHCPv6 server,
+.Xr dhcp6leased 8
+splits the prefix into smaller prefixes and assigns them to the interfaces
+in the order they are listed.
+This might create unassigned gaps in the delegated prefix.
+.Pp
+For example if a /64 and /60 prefix are to be assigned to network interfaces,
+.Xr dhcp6leased 8
+requests a /59 prefix.
+The prefix is then split into two /60 prefixes and the first /64 out of the
+first /60 prefix is assigned to the first interface.
+The sencond /60 prefix from the delegated /59 is assigned to the
+second interface.
+This leaves 15 unused /64 prefixes in the first /60.
+.Pp
+Care should be taken to avoid renumbering of existing interfaces
+when new interfaces are added or existing interfaces are removed.
+New interfaces can be added to the end of the list or in places
+where unassigned gaps were present.
+.Pp
+The special name
+.Cm reserve
+can be used to reserve space in the delegated prefix for later use or
+when an interface is removed.
+.Pp
+Running
+.Xr dhcp6leased 8
+in configtest mode with a verbosity of two or more will print the
+configuration file with comments indicated how prefixes would be
+assigned to network interfaces.
+This can be used to check that existing interface are not renumbered.
+.Pp
+More than one prefix can be requested from a DHCPv6 server, however most ISP
+DHCPv6 servers will only delegate a single prefix.
+Therefore it is better to let
+.Xr dhcp6leased 8
+request a single larger prefix and split it up.
+.Xr dhcp6leased 8
+has a compile time limit on how many prefix requests per interface it can
+handle.
+.Sh FILES
+.Bl -tag -width /etc/dhcp6leased.conf -compact
+.It Pa /etc/dhcp6leased.conf
+.Xr dhcp6leased 8
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr dhcp6leasectl 8 ,
+.Xr dhcp6leased 8
diff --git a/sbin/dhcp6leased/dhcp6leased.h b/sbin/dhcp6leased/dhcp6leased.h
new file mode 100644 (file)
index 0000000..e58cbe2
--- /dev/null
@@ -0,0 +1,274 @@
+/*     $OpenBSD: dhcp6leased.h,v 1.1 2024/06/02 12:28:05 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        _PATH_LOCKFILE          "/dev/dhcp6leased.lock"
+#define        _PATH_CONF_FILE         "/etc/dhcp6leased.conf"
+#define        _PATH_CTRL_SOCKET       "/dev/dhcp6leased.sock"
+#define        DHCP6LEASED_USER        "_dhcp6leased"
+#define        DHCP6LEASED_RTA_LABEL   "dhcp6leased"
+#define        CLIENT_PORT             546
+#define        SERVER_PORT             547
+#define        _PATH_LEASE             "/var/db/dhcp6leased/"
+#define        _PATH_UUID              _PATH_LEASE"uuid"
+#define        UUID_SIZE               16
+#define UUID_STR_SIZE          sizeof("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\n")
+#define        DUID_UUID_TYPE          4
+#define        XID_SIZE                3
+#define        SERVERID_SIZE           130 /* 2 octet type, max 128 octets data */
+#define        MAX_IA                  32
+#define        LEASE_VERSION           "version: 2"
+#define        LEASE_IP_PREFIX         "ip: "
+#define        LEASE_NEXTSERVER_PREFIX "next-server: "
+#define        LEASE_BOOTFILE_PREFIX   "filename: "
+#define        LEASE_HOSTNAME_PREFIX   "host-name: "
+#define        LEASE_DOMAIN_PREFIX     "domain-name: "
+#define        LEASE_SIZE              4096
+/* MAXDNAME from arpa/namesr.h */
+#define        DHCP6LEASED_MAX_DNSSL   1025
+#define        MAX_RDNS_COUNT          8 /* max nameserver in a RTM_PROPOSAL */
+
+/* A 1500 bytes packet can hold less than 300 classless static routes */
+#define        MAX_DHCP_ROUTES         256
+
+#define        OPENBSD_ENTERPRISENO    30155
+
+/* DHCP message types. */
+#define        DHCPSOLICIT             1
+#define        DHCPADVERTISE           2
+#define        DHCPREQUEST             3
+#define        DHCPCONFIRM             4
+#define        DHCPRENEW               5
+#define        DHCPREBIND              6
+#define        DHCPREPLY               7
+#define        DHCPRELEASE             8
+#define        DHCPDECLINE             9
+#define        DHCPRECONFIGURE         10
+#define        DHCPINFORMATIONREQUEST  11
+#define        DHCPRELAYFORW           12
+#define        DHCPRELAYREPL           13
+
+/* DHCP options */
+#define        DHO_CLIENTID            1
+#define        DHO_SERVERID            2
+#define        DHO_ORO                 6
+#define        DHO_ELAPSED_TIME        8
+#define        DHO_STATUS_CODE         13
+#define        DHO_RAPID_COMMIT        14
+#define        DHO_VENDOR_CLASS        16
+#define        DHO_IA_PD               25
+#define        DHO_IA_PREFIX           26
+#define        DHO_SOL_MAX_RT          82
+#define        DHO_INF_MAX_RT          83
+
+/* Status Code Option status codes */
+#define        DHCP_STATUS_SUCCESS             0
+#define        DHCP_STATUS_UNSPECFAIL          1
+#define        DHCP_STATUS_NOADDRSAVAIL        2
+#define        DHCP_STATUS_NOBINDING           3
+#define        DHCP_STATUS_NOTONLINK           4
+#define        DHCP_STATUS_USEMULTICAST        5
+#define        DHCP_STATUS_NOPREFIXAVAIL       6
+
+/* Ignore parts of DHCP lease */
+#define        IGN_ROUTES      1
+#define        IGN_DNS         2
+
+#define        MAX_SERVERS     16      /* max servers that can be ignored per if */
+
+#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         msg_type;       /* Message opcode/type */
+       uint8_t         xid[XID_SIZE];  /* Transaction ID */
+} __packed;
+
+struct dhcp_option_hdr {
+       uint16_t        code;
+       uint16_t        len;
+} __packed;
+
+struct dhcp_duid {
+       uint16_t        type;
+       uint8_t         uuid[UUID_SIZE];
+} __packed;
+
+struct dhcp_iapd {
+       uint32_t        iaid;
+       uint32_t        t1;
+       uint32_t        t2;
+} __packed;
+
+struct dhcp_vendor_class {
+       uint32_t        enterprise_number;
+       uint16_t        vendor_class_len;
+} __packed;
+
+struct dhcp_iaprefix {
+       uint32_t        pltime;
+       uint32_t        vltime;
+       uint8_t         prefix_len;
+       struct in6_addr prefix;
+} __packed;
+
+struct imsgev {
+       struct imsgbuf   ibuf;
+       void            (*handler)(int, short, void *);
+       struct event     ev;
+       short            events;
+};
+
+struct dhcp_route {
+       struct in_addr           dst;
+       struct in_addr           mask;
+       struct in_addr           gw;
+};
+
+enum imsg_type {
+       IMSG_NONE,
+       IMSG_CTL_LOG_VERBOSE,
+       IMSG_CTL_SHOW_INTERFACE_INFO,
+       IMSG_CTL_SEND_REQUEST,
+       IMSG_CTL_RELOAD,
+       IMSG_CTL_END,
+       IMSG_RECONF_CONF,
+       IMSG_RECONF_IFACE,
+       IMSG_RECONF_IFACE_IA,
+       IMSG_RECONF_IFACE_PD,
+       IMSG_RECONF_IFACE_IA_END,
+       IMSG_RECONF_IFACE_END,
+       IMSG_RECONF_END,
+       IMSG_SEND_DISCOVER,
+       IMSG_SEND_REQUEST,
+       IMSG_SOCKET_IPC,
+       IMSG_OPEN_UDPSOCK,
+       IMSG_UDPSOCK,
+       IMSG_ROUTESOCK,
+       IMSG_UUID,
+       IMSG_CONTROLFD,
+       IMSG_STARTUP,
+       IMSG_UPDATE_IF,
+       IMSG_REMOVE_IF,
+       IMSG_DHCP,
+       IMSG_CONFIGURE_ADDRESS,
+       IMSG_DECONFIGURE_ADDRESS,
+       IMSG_REQUEST_REBOOT,
+};
+
+struct ctl_engine_info {
+       uint32_t                if_index;
+       int                     running;
+       int                     link_state;
+       char                    state[sizeof("IF_INIT_REBOOT")];
+       struct timespec         request_time;
+       uint32_t                lease_time;
+       uint32_t                t1;
+       uint32_t                t2;
+};
+
+struct iface_pd_conf {
+       SIMPLEQ_ENTRY(iface_pd_conf)     entry;
+       char                             name[IF_NAMESIZE];
+       struct in6_addr                  prefix_mask;
+       int                              prefix_len;
+};
+
+struct iface_ia_conf {
+       SIMPLEQ_ENTRY(iface_ia_conf)                     entry;
+       SIMPLEQ_HEAD(iface_pd_conf_head, iface_pd_conf)  iface_pd_list;
+       int                                              id;
+       int                                              prefix_len;
+};
+
+struct iface_conf {
+       SIMPLEQ_ENTRY(iface_conf)                entry;
+       SIMPLEQ_HEAD(iface_ia_conf_head,
+           iface_ia_conf)                       iface_ia_list;
+       uint32_t                                 ia_count;
+       char                                     name[IF_NAMESIZE];
+};
+
+struct dhcp6leased_conf {
+       SIMPLEQ_HEAD(iface_conf_head, iface_conf)       iface_list;
+};
+
+struct imsg_ifinfo {
+       uint32_t                if_index;
+       int                     rdomain;
+       int                     running;
+       int                     link_state;
+       struct ether_addr       hw_address;
+       char                    lease[LEASE_SIZE];
+};
+
+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 prefix {
+       struct in6_addr  prefix;
+       int              prefix_len;
+       uint32_t         vltime;
+       uint32_t         pltime;
+};
+
+struct imsg_req_dhcp {
+       uint32_t                 if_index;
+       int                      elapsed_time;
+       uint8_t                  xid[XID_SIZE];
+       int                      serverid_len;
+       uint8_t                  serverid[SERVERID_SIZE];
+       struct prefix            pds[MAX_IA];
+};
+
+/* dhcp6leased.c */
+void                    imsg_event_add(struct imsgev *);
+int                     imsg_compose_event(struct imsgev *, uint16_t, uint32_t,
+                            pid_t, int, void *, uint16_t);
+void                    config_clear(struct dhcp6leased_conf *);
+struct dhcp6leased_conf        *config_new_empty(void);
+void                    merge_config(struct dhcp6leased_conf *, struct
+                            dhcp6leased_conf *);
+const char             *sin6_to_str(struct sockaddr_in6 *);
+
+/* engine.c */
+const char             *dhcp_message_type2str(uint8_t);
+
+/* frontend.c */
+struct iface_conf      *find_iface_conf(struct iface_conf_head *, char *);
+int                    *changed_ifaces(struct dhcp6leased_conf *, struct
+                            dhcp6leased_conf *);
+/* printconf.c */
+void   print_config(struct dhcp6leased_conf *, int);
+
+/* parse.y */
+struct dhcp6leased_conf        *parse_config(const char *);
+int                     cmdline_symset(char *);
+
diff --git a/sbin/dhcp6leased/engine.c b/sbin/dhcp6leased/engine.c
new file mode 100644 (file)
index 0000000..56fd120
--- /dev/null
@@ -0,0 +1,1464 @@
+/*     $OpenBSD: engine.c,v 1.1 2024/06/02 12:28:05 florian Exp $      */
+
+/*
+ * Copyright (c) 2017, 2021, 2024 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 <sys/mbuf.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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "log.h"
+#include "dhcp6leased.h"
+#include "engine.h"
+
+/*
+ * RFC 2131 4.1 p23 has "SHOULD be 4 seconds", we are a bit more aggressive,
+ * networks are faster these days.
+ */
+#define        START_EXP_BACKOFF        1
+#define        MAX_EXP_BACKOFF_SLOW    64 /* RFC 2131 4.1 p23 */
+#define        MAX_EXP_BACKOFF_FAST     2
+#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",
+       "IPv6 only",
+};
+
+struct dhcp6leased_iface {
+       LIST_ENTRY(dhcp6leased_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;
+       uint8_t                          xid[XID_SIZE];
+       int                              serverid_len;
+       uint8_t                          serverid[SERVERID_SIZE];
+       struct prefix                    pds[MAX_IA];
+       struct timespec                  request_time;
+       struct timespec                  elapsed_time_start;
+       uint32_t                         lease_time;
+       uint32_t                         t1;
+       uint32_t                         t2;
+};
+
+LIST_HEAD(, dhcp6leased_iface) dhcp6leased_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 *);
+void                    send_interface_info(struct dhcp6leased_iface *, pid_t);
+void                    engine_showinfo_ctl(struct imsg *, uint32_t);
+void                    engine_update_iface(struct imsg_ifinfo *);
+struct dhcp6leased_iface       *get_dhcp6leased_iface_by_id(uint32_t);
+void                    remove_dhcp6leased_iface(uint32_t);
+void                    parse_dhcp(struct dhcp6leased_iface *,
+                            struct imsg_dhcp *);
+void                    parse_ia_pd_options(uint8_t *, size_t, struct prefix *);
+void                    state_transition(struct dhcp6leased_iface *, enum
+                            if_state);
+void                    iface_timeout(int, short, void *);
+void                    request_dhcp_discover(struct dhcp6leased_iface *);
+void                    request_dhcp_request(struct dhcp6leased_iface *);
+void                    log_lease(struct dhcp6leased_iface *, int);
+void                    configure_interfaces(struct dhcp6leased_iface *);
+void                    send_configure_interface(struct iface_pd_conf *,
+                            struct prefix *);
+void                    send_deconfigure_interface(struct dhcp6leased_iface *);
+void                    parse_lease(struct dhcp6leased_iface *,
+                            struct imsg_ifinfo *);
+int                     engine_imsg_compose_main(int, pid_t, void *, uint16_t);
+const char             *dhcp_message_type2str(uint8_t);
+const char             *dhcp_option_type2str(uint16_t);
+const char             *dhcp_duid2str(int, uint8_t *);
+const char             *dhcp_status2str(uint8_t);
+void                    in6_prefixlen2mask(struct in6_addr *, int len);
+
+struct dhcp6leased_conf        *engine_conf;
+
+static struct imsgev   *iev_frontend;
+static struct imsgev   *iev_main;
+int64_t                         proposal_id;
+static struct dhcp_duid         duid;
+
+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;
+
+       engine_conf = config_new_empty();
+
+       log_init(debug, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       if ((pw = getpwnam(DHCP6LEASED_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       if (unveil("/", "") == -1)
+               fatal("unveil /");
+       if (unveil(NULL, NULL) == -1)
+               fatal("unveil");
+
+       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(&dhcp6leased_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 dhcp6leased_iface                *iface;
+       ssize_t                          n;
+       int                              shut = 0;
+       int                              verbose;
+       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) {
+               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_REQUEST_REBOOT:
+                       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_dhcp6leased_iface_by_id(if_index);
+                       if (iface != NULL) {
+                               switch (iface->state) {
+                               case IF_DOWN:
+                                       break;
+                               case IF_INIT:
+                               case IF_REQUESTING:
+                                       state_transition(iface, iface->state);
+                                       break;
+                               case IF_RENEWING:
+                               case IF_REBINDING:
+                               case IF_REBOOTING:
+                               case IF_BOUND:
+                                       state_transition(iface, IF_REBOOTING);
+                                       break;
+                               }
+                       }
+                       break;
+               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_dhcp6leased_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_dhcp6leased_iface_by_id(imsg_dhcp.if_index);
+                       if (iface != NULL)
+                               parse_dhcp(iface, &imsg_dhcp);
+                       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)
+{
+       static struct dhcp6leased_conf  *nconf;
+       static struct iface_conf        *iface_conf;
+       static struct iface_ia_conf     *iface_ia_conf;
+       struct iface_pd_conf            *iface_pd_conf;
+       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_get_fd(&imsg)) == -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);
+
+                       if (pledge("stdio", NULL) == -1)
+                               fatal("pledge");
+
+                       break;
+               case IMSG_UUID:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(duid.uuid))
+                               fatalx("%s: IMSG_UUID wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       duid.type = htons(DUID_UUID_TYPE);
+                       memcpy(duid.uuid, imsg.data, sizeof(duid.uuid));
+                       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));
+                       if (imsg_ifinfo.lease[LEASE_SIZE - 1] != '\0')
+                               fatalx("Invalid lease");
+                       engine_update_iface(&imsg_ifinfo);
+                       break;
+               case IMSG_RECONF_CONF:
+                       if (nconf != NULL)
+                               fatalx("%s: IMSG_RECONF_CONF already in "
+                                   "progress", __func__);
+                       if ((nconf = malloc(sizeof(struct dhcp6leased_conf))) ==
+                           NULL)
+                               fatal(NULL);
+                       SIMPLEQ_INIT(&nconf->iface_list);
+                       break;
+               case IMSG_RECONF_IFACE:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct
+                           iface_conf))
+                               fatalx("%s: IMSG_RECONF_IFACE wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       if ((iface_conf = malloc(sizeof(struct iface_conf)))
+                           == NULL)
+                               fatal(NULL);
+                       memcpy(iface_conf, imsg.data, sizeof(struct
+                           iface_conf));
+                       SIMPLEQ_INIT(&iface_conf->iface_ia_list);
+                       SIMPLEQ_INSERT_TAIL(&nconf->iface_list,
+                           iface_conf, entry);
+                       iface_conf->ia_count = 0;
+                       break;
+               case IMSG_RECONF_IFACE_IA:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct
+                           iface_ia_conf))
+                               fatalx("%s: IMSG_RECONF_IFACE_IA wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       if ((iface_ia_conf =
+                           malloc(sizeof(struct iface_ia_conf))) == NULL)
+                               fatal(NULL);
+                       memcpy(iface_ia_conf, imsg.data, sizeof(struct
+                           iface_ia_conf));
+                       SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list);
+                       SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list,
+                           iface_ia_conf, entry);
+                       iface_conf->ia_count++;
+                       if (iface_conf->ia_count > MAX_IA)
+                               fatalx("Too many prefix delegation requests.");
+                       break;
+               case IMSG_RECONF_IFACE_PD:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct
+                           iface_pd_conf))
+                               fatalx("%s: IMSG_RECONF_IFACE_PD wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       if ((iface_pd_conf =
+                           malloc(sizeof(struct iface_pd_conf))) == NULL)
+                               fatal(NULL);
+                       memcpy(iface_pd_conf, imsg.data, sizeof(struct
+                           iface_pd_conf));
+                       SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list,
+                           iface_pd_conf, entry);
+                       break;
+               case IMSG_RECONF_IFACE_IA_END:
+                       iface_ia_conf = NULL;
+                       break;
+               case IMSG_RECONF_IFACE_END:
+                       iface_conf = NULL;
+                       break;
+               case IMSG_RECONF_END: {
+                       struct dhcp6leased_iface        *iface;
+                       int                     *ifaces;
+                       int                      i, if_index;
+                       char                    *if_name;
+                       char                     ifnamebuf[IF_NAMESIZE];
+
+                       if (nconf == NULL)
+                               fatalx("%s: IMSG_RECONF_END without "
+                                   "IMSG_RECONF_CONF", __func__);
+                       ifaces = changed_ifaces(engine_conf, nconf);
+                       merge_config(engine_conf, nconf);
+                       nconf = NULL;
+                       for (i = 0; ifaces[i] != 0; i++) {
+                               if_index = ifaces[i];
+                               if_name = if_indextoname(if_index, ifnamebuf);
+                               iface = get_dhcp6leased_iface_by_id(if_index);
+                               if (if_name == NULL || iface == NULL)
+                                       continue;
+                               iface_conf = find_iface_conf(
+                                   &engine_conf->iface_list, if_name);
+                               if (iface_conf == NULL)
+                                       continue;
+                       }
+                       free(ifaces);
+                       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
+send_interface_info(struct dhcp6leased_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.lease_time = iface->lease_time;
+       cei.t1 = iface->t1;
+       cei.t2 = iface->t2;
+       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 dhcp6leased_iface                        *iface;
+
+       switch (imsg->hdr.type) {
+       case IMSG_CTL_SHOW_INTERFACE_INFO:
+               if ((iface = get_dhcp6leased_iface_by_id(if_index)) != NULL)
+                       send_interface_info(iface, imsg->hdr.pid);
+               else
+                       engine_imsg_compose_frontend(IMSG_CTL_END,
+                           imsg->hdr.pid, NULL, 0);
+               break;
+       default:
+               log_debug("%s: error handling imsg", __func__);
+               break;
+       }
+}
+
+void
+engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
+{
+       struct dhcp6leased_iface        *iface;
+       int                      need_refresh = 0;
+
+       iface = get_dhcp6leased_iface_by_id(imsg_ifinfo->if_index);
+
+       if (iface == NULL) {
+               if ((iface = calloc(1, sizeof(*iface))) == NULL)
+                       fatal("calloc");
+               iface->state = IF_DOWN;
+               arc4random_buf(iface->xid, sizeof(iface->xid));
+               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;
+               memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
+                   sizeof(struct ether_addr));
+               LIST_INSERT_HEAD(&dhcp6leased_interfaces, iface, entries);
+               need_refresh = 1;
+       } else {
+               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 0
+XXXX
+               if (iface->requested_ip.s_addr == INADDR_ANY)
+                       parse_lease(iface, imsg_ifinfo);
+
+               if (iface->requested_ip.s_addr == INADDR_ANY)
+                       state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_REBOOTING);
+#endif
+               state_transition(iface, IF_INIT);
+       } else
+               state_transition(iface, IF_DOWN);
+}
+struct dhcp6leased_iface*
+get_dhcp6leased_iface_by_id(uint32_t if_index)
+{
+       struct dhcp6leased_iface        *iface;
+       LIST_FOREACH (iface, &dhcp6leased_interfaces, entries) {
+               if (iface->if_index == if_index)
+                       return (iface);
+       }
+
+       return (NULL);
+}
+
+void
+remove_dhcp6leased_iface(uint32_t if_index)
+{
+       struct dhcp6leased_iface        *iface;
+
+       iface = get_dhcp6leased_iface_by_id(if_index);
+
+       if (iface == NULL)
+               return;
+
+       send_deconfigure_interface(iface);
+       LIST_REMOVE(iface, entries);
+       evtimer_del(&iface->timer);
+       free(iface);
+}
+
+void
+parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp)
+{
+       struct iface_conf       *iface_conf;
+       struct iface_ia_conf    *ia_conf;
+       struct dhcp_hdr          hdr;
+       struct dhcp_option_hdr   opt_hdr;
+       struct dhcp_iapd         iapd;
+       struct prefix            *pds = NULL;
+       size_t                   rem;
+       uint32_t                 t1, t2;
+       int                      serverid_len;
+       uint8_t                  serverid[SERVERID_SIZE];
+       uint8_t                 *p;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+       char                     ntopbuf[INET6_ADDRSTRLEN];
+
+       if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
+               log_debug("%s: unknown interface %d", __func__,
+                   iface->if_index);
+               goto out;
+       }
+       if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
+           == NULL) {
+               log_debug("%s: no interface configuration for %d", __func__,
+                   iface->if_index);
+               goto out;
+       }
+
+       log_debug("%s: %s ia_count: %d", __func__, if_name,
+           iface_conf->ia_count);
+
+       pds = calloc(iface_conf->ia_count, sizeof(struct prefix));
+       if (pds == NULL)
+               fatal("%s: calloc", __func__);
+
+       serverid_len = t1 = t2 = 0;
+
+       p = dhcp->packet;
+       rem = dhcp->len;
+
+       if (rem < sizeof(struct dhcp_hdr)) {
+               log_warnx("%s: message too short", __func__);
+               goto out;
+       }
+       memcpy(&hdr, p, sizeof(struct dhcp_hdr));
+       p += sizeof(struct dhcp_hdr);
+       rem -= sizeof(struct dhcp_hdr);
+
+       if (log_getverbose() > 1)
+               log_debug("%s: %s, xid: 0x%02x%02x%02x", __func__,
+                   dhcp_message_type2str(hdr.msg_type), hdr.xid[0], hdr.xid[1],
+                   hdr.xid[2]);
+
+       while(rem >= sizeof(struct dhcp_option_hdr)) {
+               memcpy(&opt_hdr, p, sizeof(struct dhcp_option_hdr));
+               opt_hdr.code = ntohs(opt_hdr.code);
+               opt_hdr.len = ntohs(opt_hdr.len);
+               p += sizeof(struct dhcp_option_hdr);
+               rem -= sizeof(struct dhcp_option_hdr);
+               if (log_getverbose() > 1)
+                       log_debug("%s: %s, len: %u", __func__,
+                           dhcp_option_type2str(opt_hdr.code), opt_hdr.len);
+
+               if (rem < opt_hdr.len) {
+                       log_warnx("%s: malformed packet, ignoring", __func__);
+                       goto out;
+               }
+
+               switch (opt_hdr.code) {
+               case DHO_CLIENTID:
+                       if (opt_hdr.len != sizeof(struct dhcp_duid) ||
+                           memcmp(&duid, p, sizeof(struct dhcp_duid)) != 0) {
+                               log_debug("%s: message not for us", __func__);
+                               goto out;
+                       }
+                       break;
+               case DHO_SERVERID:
+                       /*
+                        * RFC 8415, 11.1:
+                        * The length of the DUID (not including the type code)
+                        * is at least 1 octet and at most 128 octets.
+                        */
+                       if (opt_hdr.len < 2 + 1) {
+                               log_warnx("%s: SERVERID too short", __func__);
+                               goto out;
+                       }
+                       if (opt_hdr.len > SERVERID_SIZE) {
+                               log_warnx("%s: SERVERID too long", __func__);
+                               goto out;
+                       }
+                       log_debug("%s: SERVERID: %s", __func__,
+                           dhcp_duid2str(opt_hdr.len, p));
+                       if (serverid_len != 0) {
+                               log_warnx("%s: duplicate SERVERID option",
+                                   __func__);
+                               goto out;
+                       }
+                       serverid_len = opt_hdr.len;
+                       memcpy(serverid, p, serverid_len);
+                       break;
+               case DHO_IA_PD:
+                       if (opt_hdr.len < sizeof(struct dhcp_iapd)) {
+                               log_warnx("%s: IA_PD too short", __func__);
+                               goto out;
+                       }
+                       memcpy(&iapd, p, sizeof(struct dhcp_iapd));
+
+                       if (t1 == 0 || t1 > ntohl(iapd.t1))
+                               t1 = ntohl(iapd.t1);
+                       if (t2 == 0 || t2 > ntohl(iapd.t2))
+                               t2 = ntohl(iapd.t2);
+
+                       log_debug("%s: IA_PD, IAID: %08x, T1: %u, T2: %u",
+                           __func__, ntohl(iapd.iaid), ntohl(iapd.t1),
+                           ntohl(iapd.t2));
+                       if (ntohl(iapd.iaid) <= iface_conf->ia_count)
+                               parse_ia_pd_options(p +
+                                   sizeof(struct dhcp_iapd), opt_hdr.len -
+                                   sizeof(struct dhcp_iapd),
+                                   &pds[ntohl(iapd.iaid) -1]);
+                       break;
+               default:
+                       log_debug("unhandled option: %u", opt_hdr.code);
+                       break;
+               }
+
+               p += opt_hdr.len;
+               rem -= opt_hdr.len;
+       }
+
+       /* check that we got all the information we need */
+       if (serverid_len == 0) {
+               log_warnx("%s: Did not receive server identifier", __func__);
+               goto out;
+       }
+
+
+       SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
+               struct prefix   *pd = &pds[ia_conf->id - 1];
+
+               if(pd->prefix_len == 0) {
+                       log_warnx("%s: no IA for IAID %d found", __func__,
+                           ia_conf->id);
+                       goto out;
+               }
+               if(pd->prefix_len > ia_conf->prefix_len) {
+                       log_warnx("%s: prefix for IAID %d too small: %d > %d",
+                           __func__, ia_conf->id, pd->prefix_len,
+                           ia_conf->prefix_len);
+                       goto out;
+               }
+               log_debug("%s: pltime: %u, vltime: %u, prefix: %s/%u",
+                   __func__, pd->pltime, pd->vltime, inet_ntop(AF_INET6,
+                   &pd->prefix, ntopbuf, INET6_ADDRSTRLEN), pd->prefix_len);
+       }
+
+       switch (hdr.msg_type) {
+       case DHCPSOLICIT:
+       case DHCPREQUEST:
+       case DHCPCONFIRM:
+       case DHCPRENEW:
+       case DHCPREBIND:
+       case DHCPRELEASE:
+       case DHCPDECLINE:
+       case DHCPINFORMATIONREQUEST:
+               log_warnx("%s: Ignoring client-only message (%s) from server",
+                   __func__, dhcp_message_type2str(hdr.msg_type));
+               goto out;
+       case DHCPRELAYFORW:
+       case DHCPRELAYREPL:
+               log_warnx("%s: Ignoring relay-agent-only message (%s) from "
+                   "server", __func__, dhcp_message_type2str(hdr.msg_type));
+               goto out;
+       case DHCPADVERTISE:
+               if (iface->state != IF_INIT) {
+                       log_debug("%s: ignoring unexpected %s", __func__,
+                           dhcp_message_type2str(hdr.msg_type));
+                       goto out;
+               }
+               iface->serverid_len = serverid_len;
+               memcpy(iface->serverid, serverid, SERVERID_SIZE);
+               memset(iface->pds, 0, sizeof(iface->pds));
+               memcpy(iface->pds, pds,
+                   iface_conf->ia_count * sizeof(struct prefix));
+               state_transition(iface, IF_REQUESTING);
+               break;
+       case DHCPREPLY:
+               /* XXX rapid commit, rebinding, renewing */
+               if (iface->state != IF_REQUESTING) {
+                       log_debug("%s: ignoring unexpected %s", __func__,
+                           dhcp_message_type2str(hdr.msg_type));
+                       goto out;
+               }
+               iface->serverid_len = serverid_len;
+               memcpy(iface->serverid, serverid, SERVERID_SIZE);
+               memset(iface->pds, 0, sizeof(iface->pds));
+               memcpy(iface->pds, pds,
+                   iface_conf->ia_count * sizeof(struct prefix));
+               /* XXX handle t1 = 0 or t2 = 0 */
+               iface->t1 = t1;
+               iface->t2 = t2;
+               state_transition(iface, IF_BOUND);
+               break;
+       case DHCPRECONFIGURE:
+               log_warnx("%s: Ignoring %s from server",
+                   __func__, dhcp_message_type2str(hdr.msg_type));
+               goto out;
+       default:
+               fatalx("%s: %s unhandled",
+                   __func__, dhcp_message_type2str(hdr.msg_type));
+               break;
+       }
+ out:
+       free(pds);
+}
+
+void
+parse_ia_pd_options(uint8_t *p, size_t len, struct prefix *prefix)
+{
+       struct dhcp_option_hdr   opt_hdr;
+       struct dhcp_iaprefix     iaprefix;
+       struct in6_addr          mask;
+       int                      i;
+       uint16_t                 status_code;
+       char                     ntopbuf[INET6_ADDRSTRLEN], *visbuf;
+
+       while(len >= sizeof(struct dhcp_option_hdr)) {
+               memcpy(&opt_hdr, p, sizeof(struct dhcp_option_hdr));
+               opt_hdr.code = ntohs(opt_hdr.code);
+               opt_hdr.len = ntohs(opt_hdr.len);
+               p += sizeof(struct dhcp_option_hdr);
+               len -= sizeof(struct dhcp_option_hdr);
+               if (log_getverbose() > 1)
+                       log_debug("%s: %s, len: %u", __func__,
+                           dhcp_option_type2str(opt_hdr.code), opt_hdr.len);
+               if (len < opt_hdr.len) {
+                       log_warnx("%s: malformed packet, ignoring", __func__);
+                       return;
+               }
+
+               switch (opt_hdr.code) {
+               case DHO_IA_PREFIX:
+                       if (len < sizeof(struct dhcp_iaprefix)) {
+                               log_warnx("%s: malformed packet, ignoring",
+                                   __func__);
+                               return;
+                       }
+
+                       memcpy(&iaprefix, p, sizeof(struct dhcp_iaprefix));
+                       log_debug("%s: pltime: %u, vltime: %u, prefix: %s/%u",
+                           __func__, ntohl(iaprefix.pltime),
+                           ntohl(iaprefix.vltime), inet_ntop(AF_INET6,
+                           &iaprefix.prefix, ntopbuf, INET6_ADDRSTRLEN),
+                           iaprefix.prefix_len);
+                       if (ntohl(iaprefix.vltime) < ntohl(iaprefix.pltime)) {
+                               log_warnx("%s: vltime < pltime, ignoring IA_PD",
+                                   __func__);
+                               break;
+                       }
+
+                       prefix->prefix = iaprefix.prefix;
+                       prefix->prefix_len = iaprefix.prefix_len;
+                       prefix->vltime = ntohl(iaprefix.vltime);
+                       prefix->pltime = ntohl(iaprefix.pltime);
+
+                       /* make sure prefix is mask correctly */
+                       memset(&mask, 0, sizeof(mask));
+                       in6_prefixlen2mask(&mask, prefix->prefix_len);
+                       for (i = 0; i < 16; i++)
+                               prefix->prefix.s6_addr[i] &= mask.s6_addr[i];
+
+                       break;
+               case DHO_STATUS_CODE:
+                       /*
+                        * XXX handle STATUS_CODE if not success
+                        * STATUS_CODE can also appear in other parts of
+                        * the packet.
+                        */
+                       if (len < 2) {
+                               log_warnx("%s: malformed packet, ignoring",
+                                   __func__);
+                               return;
+                       }
+                       memcpy(&status_code, p, sizeof(uint16_t));
+                       status_code = ntohs(status_code);
+                       visbuf = calloc(4, len - 2);
+                       strvisx(visbuf, p + 2, len - 2, VIS_SAFE);
+                       log_debug("%s: %s - %s", __func__,
+                           dhcp_status2str(status_code), visbuf);
+                       break;
+               default:
+                       log_debug("unhandled option: %u", opt_hdr.code);
+               }
+               p += opt_hdr.len;
+               len -= opt_hdr.len;
+       }
+}
+
+/* XXX check valid transitions */
+void
+state_transition(struct dhcp6leased_iface *iface, enum if_state new_state)
+{
+       enum if_state    old_state = iface->state;
+       char             ifnamebuf[IF_NAMESIZE], *if_name;
+
+       iface->state = new_state;
+
+       switch (new_state) {
+       case IF_DOWN:
+#if 0
+XXXX
+               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) {
+                       /* nameservers already withdrawn when if went down */
+                       send_deconfigure_interface(iface);
+                       /* nothing more to do until iface comes back */
+                       iface->timo.tv_sec = -1;
+               } else {
+                       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 */
+               }
+#endif
+               /* nothing to do until iface comes up */
+               iface->timo.tv_sec = -1;
+               break;
+       case IF_INIT:
+               switch (old_state) {
+               case IF_INIT:
+                       if (iface->timo.tv_sec < MAX_EXP_BACKOFF_SLOW)
+                               iface->timo.tv_sec *= 2;
+                       break;
+               case IF_REQUESTING:
+               case IF_RENEWING:
+               case IF_REBINDING:
+               case IF_REBOOTING:
+                       /* lease expired, got DHCPNAK or timeout: delete IP */
+                       send_deconfigure_interface(iface);
+                       /* fall through */
+               case IF_DOWN:
+                       iface->timo.tv_sec = START_EXP_BACKOFF;
+                       clock_gettime(CLOCK_MONOTONIC,
+                           &iface->elapsed_time_start);
+                       break;
+               case IF_BOUND:
+                       fatal("invalid transition Bound -> Init");
+                       break;
+               }
+               request_dhcp_discover(iface);
+               break;
+       case IF_REBOOTING:
+               if (old_state == IF_REBOOTING)
+                       iface->timo.tv_sec *= 2;
+               else {
+                       iface->timo.tv_sec = START_EXP_BACKOFF;
+                       arc4random_buf(iface->xid, sizeof(iface->xid));
+               }
+               request_dhcp_request(iface);
+               break;
+       case IF_REQUESTING:
+               if (old_state == IF_REQUESTING)
+                       iface->timo.tv_sec *= 2;
+               else {
+                       iface->timo.tv_sec = START_EXP_BACKOFF;
+                       clock_gettime(CLOCK_MONOTONIC,
+                           &iface->elapsed_time_start);
+               }
+               request_dhcp_request(iface);
+               break;
+       case IF_BOUND:
+               iface->timo.tv_sec = iface->t1;
+               if (old_state == IF_REQUESTING || old_state == IF_REBOOTING) {
+                       configure_interfaces(iface);
+               }
+               break;
+       case IF_RENEWING:
+               if (old_state == IF_BOUND) {
+                       iface->timo.tv_sec = (iface->t2 -
+                           iface->t1) / 2; /* RFC 2131 4.4.5 */
+                       arc4random_buf(iface->xid, sizeof(iface->xid));
+               } 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->timo.tv_sec = (iface->lease_time -
+                           iface->t2) / 2; /* RFC 2131 4.4.5 */
+               } else
+                       iface->timo.tv_sec /= 2;
+               request_dhcp_request(iface);
+               break;
+       }
+
+       if_name = if_indextoname(iface->if_index, ifnamebuf);
+       log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ?
+           "?" : if_name, 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 dhcp6leased_iface        *iface = (struct dhcp6leased_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 >= MAX_EXP_BACKOFF_FAST)
+                       state_transition(iface, IF_INIT);
+               else
+                       state_transition(iface, IF_REBOOTING);
+               break;
+       case IF_REQUESTING:
+               if (iface->timo.tv_sec >= MAX_EXP_BACKOFF_SLOW)
+                       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, t2: %u", __func__,
+                   res.tv_sec, iface->t2);
+               if (res.tv_sec > iface->t2)
+                       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;
+       }
+}
+
+void
+request_dhcp_discover(struct dhcp6leased_iface *iface)
+{
+       struct imsg_req_dhcp     imsg;
+       struct timespec          now, res;
+
+       memset(&imsg, 0, sizeof(imsg));
+       imsg.if_index = iface->if_index;
+       memcpy(imsg.xid, iface->xid, sizeof(imsg.xid));
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       timespecsub(&now, &iface->elapsed_time_start, &res);
+       if (res.tv_sec * 100 > 0xffff)
+               imsg.elapsed_time = 0xffff;
+       else
+               imsg.elapsed_time = res.tv_sec * 100;
+       engine_imsg_compose_frontend(IMSG_SEND_DISCOVER, 0, &imsg, sizeof(imsg));
+}
+
+void
+request_dhcp_request(struct dhcp6leased_iface *iface)
+{
+       struct imsg_req_dhcp     imsg;
+       struct timespec          now, res;
+
+       memset(&imsg, 0, sizeof(imsg));
+       imsg.if_index = iface->if_index;
+       memcpy(imsg.xid, iface->xid, sizeof(imsg.xid));
+
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       timespecsub(&now, &iface->elapsed_time_start, &res);
+       if (res.tv_sec * 100 > 0xffff)
+               imsg.elapsed_time = 0xffff;
+       else
+               imsg.elapsed_time = res.tv_sec * 100;
+
+       switch (iface->state) {
+       case IF_DOWN:
+               fatalx("invalid state IF_DOWN in %s", __func__);
+               break;
+       case IF_INIT:
+               fatalx("invalid state IF_INIT in %s", __func__);
+               break;
+       case IF_BOUND:
+               fatalx("invalid state IF_BOUND in %s", __func__);
+               break;
+       case IF_REBOOTING:
+               fatalx("XXX state IF_REBOOTING in %s not IMPL", __func__);
+               break;
+       case IF_REQUESTING:
+               imsg.serverid_len = iface->serverid_len;
+               memcpy(imsg.serverid, iface->serverid, SERVERID_SIZE);
+               memcpy(imsg.pds, iface->pds, sizeof(iface->pds));
+               break;
+       case IF_RENEWING:
+               fatalx("XXX state IF_RENEWING in %s not IMPL", __func__);
+               break;
+       case IF_REBINDING:
+               fatalx("XXX state IF_REBINDING in %s not IMPL", __func__);
+               break;
+       }
+
+       engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg, sizeof(imsg));
+}
+
+void
+log_lease(struct dhcp6leased_iface *iface, int deconfigure)
+{
+       fatalx("%s: not implemented", __func__); /* XXX */
+}
+
+/* XXX we need to install a reject route for the delegated prefix */
+void
+configure_interfaces(struct dhcp6leased_iface *iface)
+{
+       struct iface_conf       *iface_conf;
+       struct iface_ia_conf    *ia_conf;
+       struct iface_pd_conf    *pd_conf;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+
+       if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) {
+               log_debug("%s: unknown interface %d", __func__,
+                   iface->if_index);
+               return;
+       }
+       if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name))
+           == NULL) {
+               log_debug("%s: no interface configuration for %d", __func__,
+                   iface->if_index);
+               return;
+       }
+
+       SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
+               struct prefix   *pd = &iface->pds[ia_conf->id - 1];
+
+               SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) {
+                       send_configure_interface(pd_conf, pd);
+               }
+       }
+}
+
+void
+send_configure_interface(struct iface_pd_conf *pd_conf, struct prefix *pd)
+{
+       struct imsg_configure_address    address;
+       uint32_t                         if_index;
+       int                              i;
+       char                             ntopbuf[INET6_ADDRSTRLEN];
+
+       if (strcmp(pd_conf->name, "reserve") == 0)
+               return;
+
+       if ((if_index = if_nametoindex(pd_conf->name)) == 0)
+               return;
+
+       memset(&address, 0, sizeof(address));
+
+       address.if_index = if_index;
+       address.addr.sin6_family = AF_INET6;
+       address.addr.sin6_len = sizeof(address.addr);
+       address.addr.sin6_addr = pd->prefix;
+
+       for (i = 0; i < 16; i++)
+               address.addr.sin6_addr.s6_addr[i] |=
+                   pd_conf->prefix_mask.s6_addr[i];
+
+       /* XXX make this configurable & use SOII */
+       address.addr.sin6_addr.s6_addr[15] |= 1;
+
+       in6_prefixlen2mask(&address.mask, pd_conf->prefix_len);
+
+       log_debug("%s: %s: %s/%d", __func__, pd_conf->name,
+           inet_ntop(AF_INET6, &address.addr.sin6_addr, ntopbuf,
+           INET6_ADDRSTRLEN), pd_conf->prefix_len);
+
+       address.vltime = pd->vltime;
+       address.pltime = pd->pltime;
+
+       engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address,
+           sizeof(address));
+}
+
+void
+send_deconfigure_interface(struct dhcp6leased_iface *iface)
+{
+       fatalx("%s: not implemented", __func__); /* XXX */
+}
+
+void
+parse_lease(struct dhcp6leased_iface *iface, struct imsg_ifinfo *imsg_ifinfo)
+{
+       fatalx("%s: not implemented", __func__); /* XXX */
+}
+
+const char *
+dhcp_message_type2str(uint8_t type)
+{
+       static char buf[sizeof("Unknown [255]")];
+
+       switch (type) {
+       case DHCPSOLICIT:
+               return "DHCPSOLICIT";
+       case DHCPADVERTISE:
+               return "DHCPADVERTISE";
+       case DHCPREQUEST:
+               return "DHCPREQUEST";
+       case DHCPCONFIRM:
+               return "DHCPCONFIRM";
+       case DHCPRENEW:
+               return "DHCPRENEW";
+       case DHCPREBIND:
+               return "DHCPREBIND";
+       case DHCPREPLY:
+               return "DHCPREPLY";
+       case DHCPRELEASE:
+               return "DHCPRELEASE";
+       case DHCPDECLINE:
+               return "DHCPDECLINE";
+       case DHCPRECONFIGURE:
+               return "DHCPRECONFIGURE";
+       case DHCPINFORMATIONREQUEST:
+               return "DHCPINFORMATIONREQUEST";
+       case DHCPRELAYFORW:
+               return "DHCPRELAYFORW";
+       case DHCPRELAYREPL:
+               return "DHCPRELAYREPL";
+       default:
+               snprintf(buf, sizeof(buf), "Unknown [%u]", type);
+               return buf;
+       }
+}
+
+const char *
+dhcp_option_type2str(uint16_t code)
+{
+       static char buf[sizeof("Unknown [65535]")];
+       switch (code) {
+       case DHO_CLIENTID:
+               return "DHO_CLIENTID";
+       case DHO_SERVERID:
+               return "DHO_SERVERID";
+       case DHO_ORO:
+               return "DHO_ORO";
+       case DHO_ELAPSED_TIME:
+               return "DHO_ELAPSED_TIME";
+       case DHO_STATUS_CODE:
+               return "DHO_STATUS_CODE";
+       case DHO_RAPID_COMMIT:
+               return "DHO_RAPID_COMMIT";
+       case DHO_VENDOR_CLASS:
+               return "DHO_VENDOR_CLASS";
+       case DHO_IA_PD:
+               return "DHO_IA_PD";
+       case DHO_IA_PREFIX:
+               return "DHO_IA_PREFIX";
+       case DHO_SOL_MAX_RT:
+               return "DHO_SOL_MAX_RT";
+       case DHO_INF_MAX_RT:
+               return "DHO_INF_MAX_RT";
+       default:
+               snprintf(buf, sizeof(buf), "Unknown [%u]", code);
+               return buf;
+       }
+}
+
+const char*
+dhcp_duid2str(int len, uint8_t *p)
+{
+       static char      buf[2 * 130];
+       int              i, rem;
+       char            *pbuf;
+
+       if (len > 130)
+               return "invalid";
+
+       pbuf = buf;
+       rem = sizeof(buf);
+       for (i = 0; i < len && rem > 0; i++, pbuf += 2, rem -=2)
+               snprintf(pbuf, rem, "%02x", p[i]);
+
+       return buf;
+}
+
+const char*
+dhcp_status2str(uint8_t status)
+{
+       static char buf[sizeof("Unknown [255]")];
+
+       switch (status) {
+       case DHCP_STATUS_SUCCESS:
+               return "Success";
+       case DHCP_STATUS_UNSPECFAIL:
+               return "UnspecFail";
+       case DHCP_STATUS_NOADDRSAVAIL:
+               return "NoAddrsAvail";
+       case DHCP_STATUS_NOBINDING:
+               return "NoBinding";
+       case DHCP_STATUS_NOTONLINK:
+               return "NotOnLink";
+       case DHCP_STATUS_USEMULTICAST:
+               return "UseMulticast";
+       case DHCP_STATUS_NOPREFIXAVAIL:
+               return "NoPrefixAvail";
+       default:
+               snprintf(buf, sizeof(buf), "Unknown [%u]", status);
+               return buf;
+    }
+}
+
+/* from sys/netinet6/in6.c */
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * 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 project 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 PROJECT 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 PROJECT 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.
+ */
+void
+in6_prefixlen2mask(struct in6_addr *maskp, int len)
+{
+       u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
+       int bytelen, bitlen, i;
+
+       if (0 > len || len > 128)
+               fatalx("%s: invalid prefix length(%d)\n", __func__, len);
+
+       bzero(maskp, sizeof(*maskp));
+       bytelen = len / 8;
+       bitlen = len % 8;
+       for (i = 0; i < bytelen; i++)
+               maskp->s6_addr[i] = 0xff;
+       /* len == 128 is ok because bitlen == 0 then */
+       if (bitlen)
+               maskp->s6_addr[bytelen] = maskarray[bitlen - 1];
+}
diff --git a/sbin/dhcp6leased/engine.h b/sbin/dhcp6leased/engine.h
new file mode 100644 (file)
index 0000000..61c0fd1
--- /dev/null
@@ -0,0 +1,28 @@
+/*     $OpenBSD: engine.h,v 1.1 2024/06/02 12:28:05 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_address {
+       uint32_t                 if_index;
+       struct sockaddr_in6      addr;
+       struct in6_addr          mask;
+       uint32_t                 vltime;
+       uint32_t                 pltime;
+};
+
+void            engine(int, int);
+int             engine_imsg_compose_frontend(int, pid_t, void *, uint16_t);
diff --git a/sbin/dhcp6leased/frontend.c b/sbin/dhcp6leased/frontend.c
new file mode 100644 (file)
index 0000000..5e2e54c
--- /dev/null
@@ -0,0 +1,1103 @@
+/*     $OpenBSD: frontend.c,v 1.1 2024/06/02 12:28:05 florian Exp $    */
+
+/*
+ * Copyright (c) 2017, 2021, 2024 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 <sys/utsname.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include <arpa/inet.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 "log.h"
+#include "dhcp6leased.h"
+#include "frontend.h"
+#include "control.h"
+
+#define        ALL_DHCP_RELAY_AGENTS_AND_SERVERS       "ff02::1:2"
+#define        ROUTE_SOCKET_BUF_SIZE                   16384
+
+struct iface {
+       LIST_ENTRY(iface)        entries;
+       struct event             udpev;
+       struct imsg_ifinfo       ifinfo;
+       int                      send_solicit;
+       int                      elapsed_time;
+       uint8_t                  xid[XID_SIZE];
+       int                      serverid_len;
+       uint8_t                  serverid[SERVERID_SIZE];
+       struct prefix            pds[MAX_IA];
+};
+
+__dead void     frontend_shutdown(void);
+void            frontend_sig_handler(int, short, void *);
+void            rtsock_update_iface(struct if_msghdr *, struct sockaddr_dl *);
+void            frontend_startup(void);
+void            update_iface(uint32_t);
+void            route_receive(int, short, void *);
+void            handle_route_message(struct rt_msghdr *, struct sockaddr **);
+void            get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
+void            udp_receive(int, short, void *);
+int             get_flags(char *);
+struct iface   *get_iface_by_id(uint32_t);
+struct iface   *get_iface_by_name(const char *);
+void            remove_iface(uint32_t);
+void            set_udpsock(int, uint32_t);
+void            iface_data_from_imsg(struct iface*, struct imsg_req_dhcp *);
+ssize_t                 build_packet(uint8_t, struct iface *, char *);
+void            send_packet(uint8_t, struct iface *);
+void            udp_send_packet(struct iface *, uint8_t *, ssize_t);
+int             iface_conf_cmp(struct iface_conf *, struct iface_conf *);
+
+LIST_HEAD(, iface)              interfaces;
+struct dhcp6leased_conf                *frontend_conf;
+static struct imsgev           *iev_main;
+static struct imsgev           *iev_engine;
+struct event                    ev_route;
+int                             ioctlsock;
+
+uint8_t                                 dhcp_packet[1500];
+static struct dhcp_duid                 duid;
+char                           *vendor_class_data;
+int                             vendor_class_len;
+
+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;
+       struct utsname           utsname;
+
+       frontend_conf = config_new_empty();
+
+       log_init(debug, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       if ((pw = getpwnam(DHCP6LEASED_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       if (unveil("/", "") == -1)
+               fatal("unveil /");
+       if (unveil(NULL, NULL) == -1)
+               fatal("unveil");
+
+       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");
+
+       if (uname(&utsname) == -1)
+               fatal("uname");
+       vendor_class_len = asprintf(&vendor_class_data, "%s %s %s",
+           utsname.sysname, utsname.release, utsname.machine);
+       if (vendor_class_len == -1)
+               fatal("Cannot generate vendor-class-data");
+
+       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);
+
+       config_clear(frontend_conf);
+
+       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)
+{
+       static struct dhcp6leased_conf  *nconf;
+       static struct iface_conf        *iface_conf;
+       static struct iface_ia_conf     *iface_ia_conf;
+       struct iface_pd_conf            *iface_pd_conf;
+       struct imsg                      imsg;
+       struct imsgev                   *iev = bula;
+       struct imsgbuf                  *ibuf = &iev->ibuf;
+       ssize_t                          n;
+       int                              shut = 0, udpsock, 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) {
+               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_get_fd(&imsg)) == -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_UDPSOCK:
+                       if ((udpsock = imsg_get_fd(&imsg)) == -1)
+                               fatalx("%s: expected to receive imsg "
+                                   "udp 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));
+                       set_udpsock(udpsock, if_index);
+                       break;
+               case IMSG_ROUTESOCK:
+                       if ((fd = imsg_get_fd(&imsg)) == -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_UUID:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(duid.uuid))
+                               fatalx("%s: IMSG_UUID wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       duid.type = htons(DUID_UUID_TYPE);
+                       memcpy(duid.uuid, imsg.data, sizeof(duid.uuid));
+                       break;
+               case IMSG_STARTUP:
+                       frontend_startup();
+                       break;
+               case IMSG_RECONF_CONF:
+                       if (nconf != NULL)
+                               fatalx("%s: IMSG_RECONF_CONF already in "
+                                   "progress", __func__);
+                       if ((nconf = malloc(sizeof(struct dhcp6leased_conf))) ==
+                           NULL)
+                               fatal(NULL);
+                       SIMPLEQ_INIT(&nconf->iface_list);
+                       break;
+               case IMSG_RECONF_IFACE:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct
+                           iface_conf))
+                               fatalx("%s: IMSG_RECONF_IFACE wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       if ((iface_conf = malloc(sizeof(struct iface_conf)))
+                           == NULL)
+                               fatal(NULL);
+                       memcpy(iface_conf, imsg.data, sizeof(struct
+                           iface_conf));
+                       SIMPLEQ_INIT(&iface_conf->iface_ia_list);
+                       SIMPLEQ_INSERT_TAIL(&nconf->iface_list,
+                           iface_conf, entry);
+                       iface_conf->ia_count = 0;
+                       break;
+               case IMSG_RECONF_IFACE_IA:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct
+                           iface_ia_conf))
+                               fatalx("%s: IMSG_RECONF_IFACE_IA wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       if ((iface_ia_conf =
+                           malloc(sizeof(struct iface_ia_conf))) == NULL)
+                               fatal(NULL);
+                       memcpy(iface_ia_conf, imsg.data, sizeof(struct
+                           iface_ia_conf));
+                       SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list);
+                       SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list,
+                           iface_ia_conf, entry);
+                       iface_conf->ia_count++;
+                       if (iface_conf->ia_count > MAX_IA)
+                               fatalx("Too many prefix delegation requests.");
+                       break;
+               case IMSG_RECONF_IFACE_PD:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct
+                           iface_pd_conf))
+                               fatalx("%s: IMSG_RECONF_IFACE_PD wrong length: "
+                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
+                       if ((iface_pd_conf =
+                           malloc(sizeof(struct iface_pd_conf))) == NULL)
+                               fatal(NULL);
+                       memcpy(iface_pd_conf, imsg.data, sizeof(struct
+                           iface_pd_conf));
+                       SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list,
+                           iface_pd_conf, entry);
+                       break;
+               case IMSG_RECONF_IFACE_IA_END:
+                       iface_ia_conf = NULL;
+                       break;
+               case IMSG_RECONF_IFACE_END:
+                       iface_conf = NULL;
+                       break;
+               case IMSG_RECONF_END: {
+                       int      i;
+                       int     *ifaces;
+                       char     ifnamebuf[IF_NAMESIZE], *if_name;
+
+                       if (nconf == NULL)
+                               fatalx("%s: IMSG_RECONF_END without "
+                                   "IMSG_RECONF_CONF", __func__);
+
+                       ifaces = changed_ifaces(frontend_conf, nconf);
+                       merge_config(frontend_conf, nconf);
+                       nconf = NULL;
+                       for (i = 0; ifaces[i] != 0; i++) {
+                               if_index = ifaces[i];
+                               if_name = if_indextoname(if_index, ifnamebuf);
+                               log_debug("changed iface: %s[%d]", if_name !=
+                                   NULL ? if_name : "<unknown>", if_index);
+                               update_iface(if_index);
+                               frontend_imsg_compose_engine(
+                                   IMSG_REQUEST_REBOOT, 0, 0, &if_index,
+                                   sizeof(if_index));
+                       }
+                       free(ifaces);
+                       break;
+               }
+               case IMSG_CONTROLFD:
+                       if ((fd = imsg_get_fd(&imsg)) == -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;
+               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) {
+               case IMSG_CTL_END:
+               case IMSG_CTL_SHOW_INTERFACE_INFO:
+                       control_imsg_relay(&imsg);
+                       break;
+               case IMSG_SEND_DISCOVER: {
+                       struct imsg_req_dhcp     imsg_req_dhcp;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_dhcp))
+                               fatalx("%s: IMSG_SEND_DISCOVER wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_req_dhcp, imsg.data,
+                           sizeof(imsg_req_dhcp));
+
+                       iface = get_iface_by_id(imsg_req_dhcp.if_index);
+
+                       if (iface == NULL)
+                               break;
+
+                       iface_data_from_imsg(iface, &imsg_req_dhcp);
+                       send_packet(DHCPSOLICIT, iface);
+                       break;
+               }
+               case IMSG_SEND_REQUEST: {
+                       struct imsg_req_dhcp     imsg_req_dhcp;
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_dhcp))
+                               fatalx("%s: IMSG_SEND_REQUEST wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&imsg_req_dhcp, imsg.data,
+                           sizeof(imsg_req_dhcp));
+
+                       iface = get_iface_by_id(imsg_req_dhcp.if_index);
+
+                       if (iface == NULL)
+                               break;
+
+                       iface_data_from_imsg(iface, &imsg_req_dhcp);
+                       send_packet(DHCPREQUEST, 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;
+}
+
+void
+update_iface(uint32_t if_index)
+{
+       struct ifaddrs          *ifap, *ifa;
+       struct iface            *iface;
+       struct imsg_ifinfo       ifinfo;
+       int                      flags;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       if (getifaddrs(&ifap) != 0)
+               fatal("getifaddrs");
+
+       if ((if_name = if_indextoname(if_index, ifnamebuf)) == NULL)
+               return;
+
+       if ((flags = get_flags(if_name)) == -1)
+               return;
+
+       memset(&ifinfo, 0, sizeof(ifinfo));
+       ifinfo.if_index = if_index;
+       ifinfo.link_state = -1;
+       ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) ==
+           (IFF_UP | IFF_RUNNING);
+
+       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: {
+                       struct if_data          *if_data;
+                       struct sockaddr_dl      *sdl;
+
+                       sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+                       if ((sdl->sdl_type != IFT_ETHER &&
+                           sdl->sdl_type != IFT_CARP) ||
+                           sdl->sdl_alen != ETHER_ADDR_LEN)
+                               continue;
+                       memcpy(ifinfo.hw_address.ether_addr_octet,
+                           LLADDR(sdl), ETHER_ADDR_LEN);
+
+                       if_data = (struct if_data *)ifa->ifa_data;
+                       ifinfo.link_state = if_data->ifi_link_state;
+                       ifinfo.rdomain = if_data->ifi_rdomain;
+                       goto out;
+               }
+               default:
+                       break;
+               }
+       }
+ out:
+
+       iface = get_iface_by_id(if_index);
+       if (iface == NULL) {
+               if ((iface = calloc(1, sizeof(*iface))) == NULL)
+                       fatal("calloc");
+               //iface->send_solicit = 1;
+               memcpy(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo));
+               LIST_INSERT_HEAD(&interfaces, iface, entries);
+               frontend_imsg_compose_main(IMSG_OPEN_UDPSOCK, 0,
+                   &if_index, sizeof(if_index));
+       } else
+               /* XXX check rdomain changed ?*/
+               memcpy(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo));
+
+       frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &iface->ifinfo,
+           sizeof(iface->ifinfo));
+}
+
+void
+rtsock_update_iface(struct if_msghdr *ifm, struct sockaddr_dl *sdl)
+{
+#if 0
+XXX
+       struct iface            *iface;
+       struct imsg_ifinfo       ifinfo;
+       uint32_t                 if_index;
+       int                      flags;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       if_index = ifm->ifm_index;
+
+       flags = ifm->ifm_flags;
+
+       iface = get_iface_by_id(if_index);
+       if_name = if_indextoname(if_index, ifnamebuf);
+
+       if (if_name == NULL) {
+               if (iface != NULL) {
+                       log_debug("interface with idx %d removed", if_index);
+                       frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
+                           &if_index, sizeof(if_index));
+                       remove_iface(if_index);
+               }
+               return;
+       }
+
+       memset(&ifinfo, 0, sizeof(ifinfo));
+       ifinfo.if_index = if_index;
+       ifinfo.link_state = ifm->ifm_data.ifi_link_state;
+       ifinfo.rdomain = ifm->ifm_tableid;
+       ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) ==
+           (IFF_UP | IFF_RUNNING);
+
+       if (sdl != NULL && (sdl->sdl_type == IFT_ETHER ||
+           sdl->sdl_type == IFT_CARP) && sdl->sdl_alen == ETHER_ADDR_LEN)
+               memcpy(ifinfo.hw_address.ether_addr_octet, LLADDR(sdl),
+                   ETHER_ADDR_LEN);
+       else if (iface == NULL) {
+               log_warnx("Could not find AF_LINK address for %s.", if_name);
+               return;
+       }
+
+       if (iface == NULL) {
+               if ((iface = calloc(1, sizeof(*iface))) == NULL)
+                       fatal("calloc");
+               iface->udpsock = -1;
+               LIST_INSERT_HEAD(&interfaces, iface, entries);
+               frontend_imsg_compose_main(IMSG_OPEN_UDPSOCK, 0,
+                   &if_index, sizeof(if_index));
+       } else {
+               if (iface->ifinfo.rdomain != ifinfo.rdomain &&
+                   iface->udpsock != -1) {
+                       close(iface->udpsock);
+                       iface->udpsock = -1;
+               }
+       }
+
+       if (memcmp(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo)) != 0) {
+               memcpy(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo));
+               frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &iface->ifinfo,
+                   sizeof(iface->ifinfo));
+       }
+#endif
+}
+
+void
+frontend_startup(void)
+{
+       if (!event_initialized(&ev_route))
+               fatalx("%s: did not receive a route socket from the main "
+                   "process", __func__);
+
+       if (pledge("stdio unix recvfd", NULL) == -1)
+               fatal("pledge");
+       event_add(&ev_route, NULL);
+}
+
+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 sockaddr_dl              *sdl = NULL;
+       struct if_announcemsghdr        *ifan;
+       uint32_t                         if_index;
+
+       switch (rtm->rtm_type) {
+       case RTM_IFINFO:
+               if (rtm->rtm_addrs & RTA_IFP && rti_info[RTAX_IFP]->sa_family
+                   == AF_LINK)
+                       sdl = (struct sockaddr_dl *)rti_info[RTAX_IFP];
+               rtsock_update_iface((struct if_msghdr *)rtm, sdl);
+               break;
+       case RTM_IFANNOUNCE:
+               ifan = (struct if_announcemsghdr *)rtm;
+               if_index = ifan->ifan_index;
+                if (ifan->ifan_what == IFAN_DEPARTURE) {
+                       frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
+                           &if_index, sizeof(if_index));
+                       remove_iface(if_index);
+               }
+               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
+udp_receive(int fd, short events, void *arg)
+{
+       struct imsg_dhcp         imsg_dhcp;
+       struct iface            *iface;
+       ssize_t                  len;
+
+       iface = (struct iface *)arg;
+       memset(&imsg_dhcp, 0, sizeof(imsg_dhcp));
+
+       if ((len = read(fd, imsg_dhcp.packet, 1500)) == -1) {
+               log_warn("%s: read", __func__);
+               return;
+       }
+
+       if (len == 0)
+               fatal("%s len == 0", __func__);
+
+       imsg_dhcp.if_index = iface->ifinfo.if_index;
+       imsg_dhcp.len = len;
+       frontend_imsg_compose_engine(IMSG_DHCP, 0, 0, &imsg_dhcp,
+           sizeof(imsg_dhcp));
+}
+
+void
+iface_data_from_imsg(struct iface* iface, struct imsg_req_dhcp *imsg)
+{
+       memcpy(iface->xid, imsg->xid, sizeof(iface->xid));
+       iface->elapsed_time = imsg->elapsed_time;
+       iface->serverid_len = imsg->serverid_len;
+       memcpy(iface->serverid, imsg->serverid, SERVERID_SIZE);
+       memcpy(iface->pds, imsg->pds, sizeof(iface->pds));
+}
+
+ssize_t
+build_packet(uint8_t message_type, struct iface *iface, char *if_name)
+{
+       struct iface_conf               *iface_conf;
+       struct iface_ia_conf            *ia_conf;
+       struct dhcp_hdr                  hdr;
+       struct dhcp_option_hdr           opt_hdr;
+       struct dhcp_iapd                 iapd;
+       struct dhcp_iaprefix             iaprefix;
+       struct dhcp_vendor_class         vendor_class;
+       ssize_t                          len;
+       uint16_t                         request_option_code, elapsed_time;
+       uint8_t                         *p;
+
+       switch(message_type) {
+       case DHCPSOLICIT:
+       case DHCPREQUEST:
+               break;
+       default:
+               fatalx("%s: %s not implemented", __func__,
+                   dhcp_message_type2str(message_type));
+       }
+
+       iface_conf = find_iface_conf(&frontend_conf->iface_list, if_name);
+
+       memset(dhcp_packet, 0, sizeof(dhcp_packet));
+
+       p = dhcp_packet;
+       hdr.msg_type = message_type;
+       memcpy(hdr.xid, iface->xid, sizeof(hdr.xid));
+       memcpy(p, &hdr, sizeof(struct dhcp_hdr));
+       p += sizeof(struct dhcp_hdr);
+
+       opt_hdr.code = htons(DHO_CLIENTID);
+       opt_hdr.len = htons(sizeof(struct dhcp_duid));
+       memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+       p += sizeof(struct dhcp_option_hdr);
+       memcpy(p, &duid, sizeof(struct dhcp_duid));
+       p += sizeof(struct dhcp_duid);
+
+       if (message_type == DHCPREQUEST) {
+               opt_hdr.code = htons(DHO_SERVERID);
+               opt_hdr.len = htons(iface->serverid_len);
+               memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+               p += sizeof(struct dhcp_option_hdr);
+               memcpy(p, iface->serverid, iface->serverid_len);
+               p += iface->serverid_len;
+       }
+       SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) {
+               struct prefix *pd;
+
+               opt_hdr.code = htons(DHO_IA_PD);
+               opt_hdr.len = htons(sizeof(struct dhcp_iapd) +
+                   sizeof(struct dhcp_option_hdr) +
+                   sizeof(struct dhcp_iaprefix));
+               memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+               p += sizeof(struct dhcp_option_hdr);
+               iapd.iaid = htonl(ia_conf->id);
+               iapd.t1 = 0;
+               iapd.t2 = 0;
+               memcpy(p, &iapd, sizeof(struct dhcp_iapd));
+               p += sizeof(struct dhcp_iapd);
+
+               opt_hdr.code = htons(DHO_IA_PREFIX);
+               opt_hdr.len = htons(sizeof(struct dhcp_iaprefix));
+               memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+               p += sizeof(struct dhcp_option_hdr);
+
+               memset(&iaprefix, 0, sizeof(struct dhcp_iaprefix));
+
+               switch(message_type) {
+               case DHCPSOLICIT:
+                       iaprefix.prefix_len = ia_conf->prefix_len;
+                       break;
+               case DHCPREQUEST:
+                       pd = &iface->pds[ia_conf->id - 1];
+                       iaprefix.prefix_len = pd->prefix_len;
+                       memcpy(&iaprefix.prefix, &pd->prefix,
+                           sizeof(struct in6_addr));
+                       break;
+               default:
+                       fatalx("%s: %s not implemented", __func__,
+                           dhcp_message_type2str(message_type));
+               }
+               memcpy(p, &iaprefix, sizeof(struct dhcp_iaprefix));
+               p += sizeof(struct dhcp_iaprefix);
+       }
+
+       opt_hdr.code = htons(DHO_ORO);
+       opt_hdr.len = htons(2 * 2);
+       memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+       p += sizeof(struct dhcp_option_hdr);
+       request_option_code = htons(DHO_SOL_MAX_RT);
+       memcpy(p, &request_option_code, sizeof(uint16_t));
+       p += sizeof(uint16_t);
+       request_option_code = htons(DHO_INF_MAX_RT);
+       memcpy(p, &request_option_code, sizeof(uint16_t));
+       p += sizeof(uint16_t);
+
+       opt_hdr.code = htons(DHO_ELAPSED_TIME);
+       opt_hdr.len = htons(2);
+       memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+       p += sizeof(struct dhcp_option_hdr);
+       elapsed_time = htons(iface->elapsed_time);
+       memcpy(p, &elapsed_time, sizeof(uint16_t));
+       p += sizeof(uint16_t);
+
+#ifdef notyet
+       opt_hdr.code = htons(DHO_RAPID_COMMIT);
+       opt_hdr.len = htons(0);
+       memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+       p += sizeof(struct dhcp_option_hdr);
+#endif
+
+       opt_hdr.code = htons(DHO_VENDOR_CLASS);
+       opt_hdr.len = htons(sizeof(struct dhcp_vendor_class) +
+           vendor_class_len);
+       memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr));
+       p += sizeof(struct dhcp_option_hdr);
+       vendor_class.enterprise_number = htonl(OPENBSD_ENTERPRISENO);
+       vendor_class.vendor_class_len = htons(vendor_class_len);
+       memcpy(p, &vendor_class, sizeof(struct dhcp_vendor_class));
+       p += sizeof(struct dhcp_vendor_class);
+       /* Not a C-string, leave out \0 */
+       memcpy(p, vendor_class_data, vendor_class_len);
+       p += vendor_class_len;
+
+       len = p - dhcp_packet;
+       return (len);
+}
+
+void
+send_packet(uint8_t message_type, struct iface *iface)
+{
+       ssize_t                  pkt_len;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       if (!event_initialized(&iface->udpev)) {
+               iface->send_solicit = 1;
+               return;
+       }
+
+       iface->send_solicit = 0;
+
+       if ((if_name = if_indextoname(iface->ifinfo.if_index, ifnamebuf))
+           == NULL)
+               return; /* iface went away, nothing to do */
+
+       log_debug("%s on %s", message_type == DHCPSOLICIT ? "DHCPSOLICIT" :
+           "DHCPREQUEST", if_name);
+
+       pkt_len = build_packet(message_type, iface, if_name);
+       udp_send_packet(iface, dhcp_packet, pkt_len);
+}
+
+void
+udp_send_packet(struct iface *iface, uint8_t *packet, ssize_t len)
+{
+       struct sockaddr_in6     to;
+
+       memset(&to, 0, sizeof(to));
+       to.sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, ALL_DHCP_RELAY_AGENTS_AND_SERVERS,
+           &to.sin6_addr.s6_addr) != 1)
+               fatal("inet_pton");
+
+       to.sin6_port = ntohs(SERVER_PORT);
+       to.sin6_scope_id = iface->ifinfo.if_index;
+
+       if (sendto(EVENT_FD(&iface->udpev), packet, len, 0,
+           (struct sockaddr *)&to, sizeof(to)) == -1)
+               log_warn("sendto");
+}
+
+struct iface*
+get_iface_by_id(uint32_t if_index)
+{
+       struct iface    *iface;
+
+       LIST_FOREACH (iface, &interfaces, entries) {
+               if (iface->ifinfo.if_index == if_index)
+                       return (iface);
+       }
+
+       return (NULL);
+}
+
+struct iface*
+get_iface_by_name(const char *if_name)
+{
+       uint32_t ifidx = if_nametoindex(if_name);
+
+       if (ifidx == 0)
+               return (NULL);
+       return get_iface_by_id(ifidx);
+}
+
+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);
+       if (event_initialized(&iface->udpev)) {
+               event_del(&iface->udpev);
+               close(EVENT_FD(&iface->udpev));
+       }
+       free(iface);
+}
+
+void
+set_udpsock(int udpsock, uint32_t if_index)
+{
+       struct iface    *iface;
+
+       iface = get_iface_by_id(if_index);
+
+       if (iface == NULL) {
+               /*
+                * The interface disappeared while we were waiting for the
+                * parent process to open the udp socket.
+                */
+               close(udpsock);
+       } else if (event_initialized(&iface->udpev)) {
+               /*
+                * XXX
+                * The autoconf flag is flapping and we have multiple udp
+                * sockets in flight. We don't need this one because we already
+                * got one.
+                */
+               close(udpsock);
+       } else {
+               event_set(&iface->udpev, udpsock, EV_READ |
+                   EV_PERSIST, udp_receive, iface);
+               event_add(&iface->udpev, NULL);
+               if (iface->send_solicit)
+                       send_packet(DHCPSOLICIT, iface);
+       }
+}
+
+struct iface_conf*
+find_iface_conf(struct iface_conf_head *head, char *if_name)
+{
+       struct iface_conf       *iface_conf;
+
+       if (if_name == NULL)
+               return (NULL);
+
+       SIMPLEQ_FOREACH(iface_conf, head, entry) {
+               if (strcmp(iface_conf->name, if_name) == 0)
+                       return iface_conf;
+       }
+       return (NULL);
+}
+
+int*
+changed_ifaces(struct dhcp6leased_conf *oconf, struct dhcp6leased_conf *nconf)
+{
+       struct iface_conf       *iface_conf, *oiface_conf;
+       int                     *ret, if_index, count = 0, i = 0;
+
+       /*
+        * Worst case: All old interfaces replaced with new interfaces.
+        * This should still be a small number
+        */
+       SIMPLEQ_FOREACH(iface_conf, &oconf->iface_list, entry)
+           count++;
+       SIMPLEQ_FOREACH(iface_conf, &nconf->iface_list, entry)
+           count++;
+
+       ret = calloc(count + 1, sizeof(int));
+
+       SIMPLEQ_FOREACH(iface_conf, &nconf->iface_list, entry) {
+               if ((if_index = if_nametoindex(iface_conf->name)) == 0)
+                       continue;
+               oiface_conf = find_iface_conf(&oconf->iface_list,
+                   iface_conf->name);
+               if (oiface_conf == NULL) {
+                       /* new interface added to config */
+                       ret[i++] = if_index;
+               } else if (iface_conf_cmp(iface_conf, oiface_conf) != 0) {
+                       /* interface conf changed */
+                       ret[i++] = if_index;
+               }
+       }
+       SIMPLEQ_FOREACH(oiface_conf, &oconf->iface_list, entry) {
+               if ((if_index = if_nametoindex(oiface_conf->name)) == 0)
+                       continue;
+               if (find_iface_conf(&nconf->iface_list, oiface_conf->name) ==
+                   NULL) {
+                       /* interface removed from config */
+                       ret[i++] = if_index;
+               }
+       }
+       return ret;
+}
+
+int
+iface_conf_cmp(struct iface_conf *a, struct iface_conf *b)
+{
+       return 0;
+}
diff --git a/sbin/dhcp6leased/frontend.h b/sbin/dhcp6leased/frontend.h
new file mode 100644 (file)
index 0000000..7694907
--- /dev/null
@@ -0,0 +1,24 @@
+/*     $OpenBSD: frontend.h,v 1.1 2024/06/02 12:28:05 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/dhcp6leased/log.c b/sbin/dhcp6leased/log.c
new file mode 100644 (file)
index 0000000..8cbf416
--- /dev/null
@@ -0,0 +1,199 @@
+/*     $OpenBSD: log.c,v 1.1 2024/06/02 12:28:05 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/dhcp6leased/log.h b/sbin/dhcp6leased/log.h
new file mode 100644 (file)
index 0000000..18db5fd
--- /dev/null
@@ -0,0 +1,46 @@
+/*     $OpenBSD: log.h,v 1.1 2024/06/02 12:28:05 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 <stdlib.h>
+
+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)));
+
+#endif /* LOG_H */
diff --git a/sbin/dhcp6leased/parse.y b/sbin/dhcp6leased/parse.y
new file mode 100644 (file)
index 0000000..8fb7778
--- /dev/null
@@ -0,0 +1,917 @@
+/*     $OpenBSD: parse.y,v 1.1 2024/06/02 12:28:05 florian Exp $       */
+
+/*
+ * Copyright (c) 2018, 2024 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * 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/stat.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "log.h"
+#include "dhcp6leased.h"
+#include "frontend.h"
+
+TAILQ_HEAD(files, file)                 files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+       TAILQ_ENTRY(file)        entry;
+       FILE                    *stream;
+       char                    *name;
+       size_t                   ungetpos;
+       size_t                   ungetsize;
+       u_char                  *ungetbuf;
+       int                      eof_reached;
+       int                      lineno;
+       int                      errors;
+} *file, *topfile;
+struct file    *pushfile(const char *, int);
+int             popfile(void);
+int             check_file_secrecy(int, const char *);
+int             yyparse(void);
+int             yylex(void);
+int             yyerror(const char *, ...)
+    __attribute__((__format__ (printf, 1, 2)))
+    __attribute__((__nonnull__ (1)));
+int             kw_cmp(const void *, const void *);
+int             lookup(char *);
+int             igetc(void);
+int             lgetc(int);
+void            lungetc(int);
+int             findeol(void);
+
+TAILQ_HEAD(symhead, sym)        symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+       TAILQ_ENTRY(sym)         entry;
+       int                      used;
+       int                      persist;
+       char                    *nam;
+       char                    *val;
+};
+
+int     symset(const char *, const char *, int);
+char   *symget(const char *);
+
+static struct dhcp6leased_conf *conf;
+static int                      errors;
+
+static struct iface_conf       *iface_conf;
+static struct iface_ia_conf    *iface_ia_conf;
+
+struct iface_conf      *conf_get_iface(char *);
+struct iface_pd_conf   *conf_get_pd_iface(char *, int);
+void                    addressing_plan(struct iface_ia_conf *);
+int                     fls64(uint64_t);
+
+typedef struct {
+       union {
+               int64_t          number;
+               char            *string;
+       } v;
+       int lineno;
+} YYSTYPE;
+
+%}
+
+%token ERROR DELEGATION FOR ON PREFIX REQUEST
+
+%token <v.string>      STRING
+%token <v.number>      NUMBER
+%type  <v.string>      string
+
+%%
+
+grammar                : /* empty */
+               | grammar '\n'
+               | grammar varset '\n'
+               | grammar ia_pd '\n'
+               | grammar error '\n'            { file->errors++; }
+               ;
+
+string         : string STRING {
+                       if (asprintf(&$$, "%s %s", $1, $2) == -1) {
+                               free($1);
+                               free($2);
+                               yyerror("string: asprintf");
+                               YYERROR;
+                       }
+                       free($1);
+                       free($2);
+               }
+               | STRING
+               ;
+
+varset         : STRING '=' string             {
+                       char *s = $1;
+                       if (log_getverbose() == 1)
+                               printf("%s = \"%s\"\n", $1, $3);
+                       while (*s++) {
+                               if (isspace((unsigned char)*s)) {
+                                       yyerror("macro name cannot contain "
+                                           "whitespace");
+                                       free($1);
+                                       free($3);
+                                       YYERROR;
+                               }
+                       }
+                       if (symset($1, $3, 0) == -1)
+                               fatal("cannot store variable");
+                       free($1);
+                       free($3);
+               }
+               ;
+
+optnl          : '\n' optnl            /* zero or more newlines */
+               | /*empty*/
+               ;
+
+nl             : '\n' optnl            /* one or more newlines */
+               ;
+
+ia_pd          : REQUEST PREFIX DELEGATION ON STRING FOR {
+                       iface_conf = conf_get_iface($5);
+                       iface_ia_conf = calloc(1, sizeof(*iface_ia_conf));
+                       if (iface_ia_conf == NULL)
+                               err(1, "%s: calloc", __func__);
+                       iface_ia_conf->id = ++iface_conf->ia_count;
+                       if (iface_ia_conf->id > MAX_IA) {
+                               yyerror("Too many prefix delegation requests");
+                               YYERROR;
+                       }
+                       SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list);
+                       SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list,
+                           iface_ia_conf, entry);
+               } '{' iface_block '}' {
+                       iface_conf = NULL;
+                       iface_ia_conf = NULL;
+               }
+               ;
+
+iface_block    : optnl ifaceopts_l
+               | optnl
+               ;
+
+ifaceopts_l    : ifaceopts_l ifaceoptsl nl
+               | ifaceoptsl optnl
+               ;
+
+ifaceoptsl     : STRING {
+                       struct iface_pd_conf    *iface_pd_conf;
+                       int                      prefixlen;
+                       char                    *p;
+                       const char              *errstr;
+
+                       p = strchr($1, '/');
+                       if (p != NULL) {
+                               *p++ = '\0';
+                               prefixlen = strtonum(p, 0, 128, &errstr);
+                               if (errstr != NULL) {
+                                       yyerror("error parsing interface "
+                                           "\"%s/%s\"", $1, p);
+                                       free($1);
+                                       YYERROR;
+                               }
+                       } else
+                               prefixlen = 64;
+                       if ((iface_pd_conf = conf_get_pd_iface($1, prefixlen))
+                           == NULL) {
+                               yyerror("duplicate interface %s", $1);
+                               free($1);
+                               YYERROR;
+                       }
+               }
+               ;
+%%
+
+struct keywords {
+       const char      *k_name;
+       int              k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+       va_list          ap;
+       char            *msg;
+
+       file->errors++;
+       va_start(ap, fmt);
+       if (vasprintf(&msg, fmt, ap) == -1)
+               fatalx("yyerror vasprintf");
+       va_end(ap);
+       logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+       free(msg);
+       return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+       return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+       /* This has to be sorted always. */
+       static const struct keywords keywords[] = {
+               {"delegation",  DELEGATION},
+               {"for",         FOR},
+               {"on",          ON},
+               {"prefix",      PREFIX},
+               {"request",     REQUEST},
+       };
+       const struct keywords   *p;
+
+       p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+           sizeof(keywords[0]), kw_cmp);
+
+       if (p)
+               return (p->k_val);
+       else
+               return (STRING);
+}
+
+#define START_EXPAND   1
+#define DONE_EXPAND    2
+
+static int     expanding;
+
+int
+igetc(void)
+{
+       int     c;
+
+       while (1) {
+               if (file->ungetpos > 0)
+                       c = file->ungetbuf[--file->ungetpos];
+               else
+                       c = getc(file->stream);
+
+               if (c == START_EXPAND)
+                       expanding = 1;
+               else if (c == DONE_EXPAND)
+                       expanding = 0;
+               else
+                       break;
+       }
+       return (c);
+}
+
+int
+lgetc(int quotec)
+{
+       int             c, next;
+
+       if (quotec) {
+               if ((c = igetc()) == EOF) {
+                       yyerror("reached end of file while parsing "
+                           "quoted string");
+                       if (file == topfile || popfile() == EOF)
+                               return (EOF);
+                       return (quotec);
+               }
+               return (c);
+       }
+
+       while ((c = igetc()) == '\\') {
+               next = igetc();
+               if (next != '\n') {
+                       c = next;
+                       break;
+               }
+               yylval.lineno = file->lineno;
+               file->lineno++;
+       }
+
+       if (c == EOF) {
+               /*
+                * Fake EOL when hit EOF for the first time. This gets line
+                * count right if last line in included file is syntactically
+                * invalid and has no newline.
+                */
+               if (file->eof_reached == 0) {
+                       file->eof_reached = 1;
+                       return ('\n');
+               }
+               while (c == EOF) {
+                       if (file == topfile || popfile() == EOF)
+                               return (EOF);
+                       c = igetc();
+               }
+       }
+       return (c);
+}
+
+void
+lungetc(int c)
+{
+       if (c == EOF)
+               return;
+
+       if (file->ungetpos >= file->ungetsize) {
+               void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
+               if (p == NULL)
+                       err(1, "lungetc");
+               file->ungetbuf = p;
+               file->ungetsize *= 2;
+       }
+       file->ungetbuf[file->ungetpos++] = c;
+}
+
+int
+findeol(void)
+{
+       int     c;
+
+       /* Skip to either EOF or the first real EOL. */
+       while (1) {
+               c = lgetc(0);
+               if (c == '\n') {
+                       file->lineno++;
+                       break;
+               }
+               if (c == EOF)
+                       break;
+       }
+       return (ERROR);
+}
+
+int
+yylex(void)
+{
+       char     buf[8096];
+       char    *p, *val;
+       int      quotec, next, c;
+       int      token;
+
+top:
+       p = buf;
+       while ((c = lgetc(0)) == ' ' || c == '\t')
+               ; /* nothing */
+
+       yylval.lineno = file->lineno;
+       if (c == '#')
+               while ((c = lgetc(0)) != '\n' && c != EOF)
+                       ; /* nothing */
+       if (c == '$' && !expanding) {
+               while (1) {
+                       if ((c = lgetc(0)) == EOF)
+                               return (0);
+
+                       if (p + 1 >= buf + sizeof(buf) - 1) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+                       if (isalnum(c) || c == '_') {
+                               *p++ = c;
+                               continue;
+                       }
+                       *p = '\0';
+                       lungetc(c);
+                       break;
+               }
+               val = symget(buf);
+               if (val == NULL) {
+                       yyerror("macro '%s' not defined", buf);
+                       return (findeol());
+               }
+               p = val + strlen(val) - 1;
+               lungetc(DONE_EXPAND);
+               while (p >= val) {
+                       lungetc((unsigned char)*p);
+                       p--;
+               }
+               lungetc(START_EXPAND);
+               goto top;
+       }
+
+       switch (c) {
+       case '\'':
+       case '"':
+               quotec = c;
+               while (1) {
+                       if ((c = lgetc(quotec)) == EOF)
+                               return (0);
+                       if (c == '\n') {
+                               file->lineno++;
+                               continue;
+                       } else if (c == '\\') {
+                               if ((next = lgetc(quotec)) == EOF)
+                                       return (0);
+                               if (next == quotec || next == ' ' ||
+                                   next == '\t')
+                                       c = next;
+                               else if (next == '\n') {
+                                       file->lineno++;
+                                       continue;
+                               } else
+                                       lungetc(next);
+                       } else if (c == quotec) {
+                               *p = '\0';
+                               break;
+                       } else if (c == '\0') {
+                               yyerror("syntax error");
+                               return (findeol());
+                       }
+                       if (p + 1 >= buf + sizeof(buf) - 1) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+                       *p++ = c;
+               }
+               yylval.v.string = strdup(buf);
+               if (yylval.v.string == NULL)
+                       err(1, "yylex: strdup");
+               return (STRING);
+       }
+
+#define allowed_to_end_number(x) \
+       (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+       if (c == '-' || isdigit(c)) {
+               do {
+                       *p++ = c;
+                       if ((size_t)(p-buf) >= sizeof(buf)) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+               } while ((c = lgetc(0)) != EOF && isdigit(c));
+               lungetc(c);
+               if (p == buf + 1 && buf[0] == '-')
+                       goto nodigits;
+               if (c == EOF || allowed_to_end_number(c)) {
+                       const char *errstr = NULL;
+
+                       *p = '\0';
+                       yylval.v.number = strtonum(buf, LLONG_MIN,
+                           LLONG_MAX, &errstr);
+                       if (errstr) {
+                               yyerror("\"%s\" invalid number: %s",
+                                   buf, errstr);
+                               return (findeol());
+                       }
+                       return (NUMBER);
+               } else {
+nodigits:
+                       while (p > buf + 1)
+                               lungetc((unsigned char)*--p);
+                       c = (unsigned char)*--p;
+                       if (c == '-')
+                               return (c);
+               }
+       }
+
+#define allowed_in_string(x) \
+       (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+       x != '{' && x != '}' && \
+       x != '!' && x != '=' && x != '#' && \
+       x != ','))
+
+       if (isalnum(c) || c == ':' || c == '_') {
+               do {
+                       *p++ = c;
+                       if ((size_t)(p-buf) >= sizeof(buf)) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+               } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+               lungetc(c);
+               *p = '\0';
+               if ((token = lookup(buf)) == STRING)
+                       if ((yylval.v.string = strdup(buf)) == NULL)
+                               err(1, "yylex: strdup");
+               return (token);
+       }
+       if (c == '\n') {
+               yylval.lineno = file->lineno;
+               file->lineno++;
+       }
+       if (c == EOF)
+               return (0);
+       return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+       struct stat     st;
+
+       if (fstat(fd, &st)) {
+               log_warn("cannot stat %s", fname);
+               return (-1);
+       }
+       if (st.st_uid != 0 && st.st_uid != getuid()) {
+               log_warnx("%s: owner not root or current user", fname);
+               return (-1);
+       }
+       if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+               log_warnx("%s: group writable or world read/writable", fname);
+               return (-1);
+       }
+       return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+       struct file     *nfile;
+
+       if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+               log_warn("calloc");
+               return (NULL);
+       }
+       if ((nfile->name = strdup(name)) == NULL) {
+               log_warn("strdup");
+               free(nfile);
+               return (NULL);
+       }
+       if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       } else if (secret &&
+           check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+               fclose(nfile->stream);
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       }
+       nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
+       nfile->ungetsize = 16;
+       nfile->ungetbuf = malloc(nfile->ungetsize);
+       if (nfile->ungetbuf == NULL) {
+               log_warn("malloc");
+               fclose(nfile->stream);
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       }
+       TAILQ_INSERT_TAIL(&files, nfile, entry);
+       return (nfile);
+}
+
+int
+popfile(void)
+{
+       struct file     *prev;
+
+       if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+               prev->errors += file->errors;
+
+       TAILQ_REMOVE(&files, file, entry);
+       fclose(file->stream);
+       free(file->name);
+       free(file->ungetbuf);
+       free(file);
+       file = prev;
+       return (file ? 0 : EOF);
+}
+
+struct dhcp6leased_conf *
+parse_config(const char *filename)
+{
+       extern const char        default_conffile[];
+       struct sym              *sym, *next;
+       struct iface_conf       *iface;
+       struct iface_ia_conf    *ia_conf;
+
+       conf = config_new_empty();
+
+       file = pushfile(filename, 0);
+       if (file == NULL) {
+               free(conf);
+               return (NULL);
+       }
+       topfile = file;
+
+       yyparse();
+       errors = file->errors;
+       popfile();
+
+       /* Free macros and check which have not been used. */
+       TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
+               if ((log_getverbose() == 2) && !sym->used)
+                       fprintf(stderr, "warning: macro '%s' not used\n",
+                           sym->nam);
+               if (!sym->persist) {
+                       free(sym->nam);
+                       free(sym->val);
+                       TAILQ_REMOVE(&symhead, sym, entry);
+                       free(sym);
+               }
+       }
+
+       if (errors) {
+               config_clear(conf);
+               return (NULL);
+       }
+
+       SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
+               SIMPLEQ_FOREACH(ia_conf, &iface->iface_ia_list, entry) {
+                       addressing_plan(ia_conf);
+               }
+       }
+       return (conf);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+       struct sym      *sym;
+
+       TAILQ_FOREACH(sym, &symhead, entry) {
+               if (strcmp(nam, sym->nam) == 0)
+                       break;
+       }
+
+       if (sym != NULL) {
+               if (sym->persist == 1)
+                       return (0);
+               else {
+                       free(sym->nam);
+                       free(sym->val);
+                       TAILQ_REMOVE(&symhead, sym, entry);
+                       free(sym);
+               }
+       }
+       if ((sym = calloc(1, sizeof(*sym))) == NULL)
+               return (-1);
+
+       sym->nam = strdup(nam);
+       if (sym->nam == NULL) {
+               free(sym);
+               return (-1);
+       }
+       sym->val = strdup(val);
+       if (sym->val == NULL) {
+               free(sym->nam);
+               free(sym);
+               return (-1);
+       }
+       sym->used = 0;
+       sym->persist = persist;
+       TAILQ_INSERT_TAIL(&symhead, sym, entry);
+       return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+       char    *sym, *val;
+       int     ret;
+
+       if ((val = strrchr(s, '=')) == NULL)
+               return (-1);
+       sym = strndup(s, val - s);
+       if (sym == NULL)
+               errx(1, "%s: strndup", __func__);
+       ret = symset(sym, val + 1, 1);
+       free(sym);
+
+       return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+       struct sym      *sym;
+
+       TAILQ_FOREACH(sym, &symhead, entry) {
+               if (strcmp(nam, sym->nam) == 0) {
+                       sym->used = 1;
+                       return (sym->val);
+               }
+       }
+       return (NULL);
+}
+
+struct iface_conf *
+conf_get_iface(char *name)
+{
+       struct iface_conf       *iface;
+       size_t                   n;
+
+       SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
+               if (strcmp(name, iface->name) == 0)
+                       return (iface);
+       }
+
+       iface = calloc(1, sizeof(*iface));
+       if (iface == NULL)
+               errx(1, "%s: calloc", __func__);
+       n = strlcpy(iface->name, name, sizeof(iface->name));
+       if (n >= sizeof(iface->name))
+               errx(1, "%s: name too long", __func__);
+       SIMPLEQ_INIT(&iface->iface_ia_list);
+
+       SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry);
+
+       return (iface);
+}
+
+struct iface_pd_conf *
+conf_get_pd_iface(char *name, int prefixlen)
+{
+       struct iface_ia_conf    *iface_ia;
+       struct iface_pd_conf    *iface_pd;
+       size_t                   n;
+
+       if (strcmp(name, "reserve") != 0) {
+               SIMPLEQ_FOREACH(iface_ia, &iface_conf->iface_ia_list,
+                   entry) {
+                       SIMPLEQ_FOREACH(iface_pd, &iface_ia->iface_pd_list,
+                           entry) {
+                               if (strcmp(name, iface_pd->name) == 0)
+                                       return NULL;
+                       }
+               }
+       }
+
+       iface_pd = calloc(1, sizeof(*iface_pd));
+       if (iface_pd == NULL)
+               err(1, "%s: calloc", __func__);
+       n = strlcpy(iface_pd->name, name, sizeof(iface_pd->name));
+       if (n >= sizeof(iface_pd->name))
+               errx(1, "%s: name too long", __func__);
+       iface_pd->prefix_len = prefixlen;
+
+       SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list, iface_pd, entry);
+
+       return (iface_pd);
+}
+
+static inline uint64_t
+get_shift(int plen)
+{
+       if (plen > 64)
+               plen -= 64;
+
+       return 1ULL << (64 - plen);
+}
+
+void
+addressing_plan(struct iface_ia_conf *ia_conf)
+{
+       struct iface_pd_conf    *pd_conf;
+       uint64_t                *p, lo_counter, hi_counter, lo_shift, hi_shift;
+       int                      prev_plen;
+
+       lo_counter = hi_counter = 0;
+
+       SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) {
+               /* not the first prefix */
+               if (ia_conf->prefix_len != 0) {
+                       lo_shift = hi_shift = 0;
+                       if (prev_plen > pd_conf->prefix_len) {
+                               if (pd_conf->prefix_len > 64)
+                                       lo_shift =
+                                           get_shift(pd_conf->prefix_len);
+                               else
+                                       hi_shift =
+                                           get_shift(pd_conf->prefix_len);
+                       } else  {
+                               if (prev_plen > 64)
+                                       lo_shift = get_shift(prev_plen);
+                               else
+                                       hi_shift = get_shift(prev_plen);
+                       }
+
+                       if (lo_shift != 0) {
+                               if (lo_counter > UINT64_MAX - lo_shift) {
+                                       /* overflow */
+                                       hi_counter++;
+                                       lo_counter = 0;
+                               } else {
+                                       lo_counter += lo_shift;
+                                       /* remove all lower bits */
+                                       lo_counter &= ~(lo_shift - 1);
+                               }
+                       } else {
+                               hi_counter += hi_shift;
+                               /* remove all lower bits */
+                               hi_counter &= ~(hi_shift - 1);
+                               lo_counter = 0;
+                       }
+
+               } else
+                       ia_conf->prefix_len = pd_conf->prefix_len;
+
+               p = (uint64_t *)&pd_conf->prefix_mask.s6_addr;
+               *p |= htobe64(hi_counter);
+
+               p = (uint64_t *)&pd_conf->prefix_mask.s6_addr[8];
+               *p |= htobe64(lo_counter);
+
+               prev_plen = pd_conf->prefix_len;
+       }
+
+       if (hi_counter != 0)
+               ia_conf->prefix_len = 64 - fls64(hi_counter);
+       else if (lo_counter != 0)
+               ia_conf->prefix_len = 128 - fls64(lo_counter);
+}
+
+/* from NetBSD's sys/sys/bitops.h */
+/*-
+ * Copyright (c) 2007, 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas and Joerg Sonnenberger.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+int
+fls64(uint64_t _n)
+{
+       int _v;
+
+       if (!_n)
+               return 0;
+
+       _v = 64;
+       if ((_n & 0xFFFFFFFF00000000ULL) == 0) {
+               _n <<= 32;
+               _v -= 32;
+       }
+       if ((_n & 0xFFFF000000000000ULL) == 0) {
+               _n <<= 16;
+               _v -= 16;
+       }
+       if ((_n & 0xFF00000000000000ULL) == 0) {
+               _n <<= 8;
+               _v -= 8;
+       }
+       if ((_n & 0xF000000000000000ULL) == 0) {
+               _n <<= 4;
+               _v -= 4;
+       }
+       if ((_n & 0xC000000000000000ULL) == 0) {
+               _n <<= 2;
+               _v -= 2;
+       }
+       if ((_n & 0x8000000000000000ULL) == 0) {
+               //_n <<= 1;
+               _v -= 1;
+       }
+       return _v;
+}
diff --git a/sbin/dhcp6leased/printconf.c b/sbin/dhcp6leased/printconf.c
new file mode 100644 (file)
index 0000000..a38a652
--- /dev/null
@@ -0,0 +1,106 @@
+/*     $OpenBSD: printconf.c,v 1.1 2024/06/02 12:28:05 florian Exp $   */
+
+/*
+ * Copyright (c) 2024 Florian Obser <florian@openbsd.org>
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <event.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dhcp6leased.h"
+#include "log.h"
+
+void   print_iface_conf(struct iface_conf *, int);
+void   print_iface_ia_conf(struct iface_ia_conf *, int);
+void   print_iface_pd_conf(char *, struct iface_pd_conf *, int);
+
+void
+print_iface_pd_conf(char *indent, struct iface_pd_conf *pd_conf, int verbose)
+{
+       if (verbose > 1) {
+               struct in6_addr  ia6;
+               int              i;
+               char             ntopbuf[INET6_ADDRSTRLEN];
+
+               memset(&ia6, 0, sizeof(ia6));
+               inet_pton(AF_INET6, "2001:db8::", &ia6);
+
+               for (i = 0; i < 16; i++)
+                       ia6.s6_addr[i] |= pd_conf->prefix_mask.s6_addr[i];
+
+               inet_ntop(AF_INET6, &ia6, ntopbuf, INET6_ADDRSTRLEN);
+               printf("%s%s/%d\t# %s/%d\n", indent, pd_conf->name,
+                   pd_conf->prefix_len, ntopbuf, pd_conf->prefix_len);
+       } else
+               printf("%s%s/%d\n", indent, pd_conf->name, pd_conf->prefix_len);
+}
+
+void
+print_iface_ia_conf(struct iface_ia_conf *ia_conf, int verbose)
+{
+       struct iface_pd_conf    *pd_conf;
+
+       SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry)
+           print_iface_pd_conf("\t", pd_conf,
+               ia_conf->prefix_len >= 32 ? verbose : 1);
+}
+
+void
+print_iface_conf(struct iface_conf *iface, int verbose)
+{
+       struct iface_ia_conf    *ia_conf;
+       int                      first = 1;
+
+       SIMPLEQ_FOREACH(ia_conf, &iface->iface_ia_list, entry) {
+               if (!first)
+                       printf("\n");
+               first = 0;
+
+               if (verbose > 1) {
+                       printf("request prefix delegation on %s for {"
+                           "\t# prefix length = %d\n", iface->name,
+                           ia_conf->prefix_len);
+               } else {
+                       printf("request prefix delegation on %s for {\n",
+                           iface->name);
+               }
+               print_iface_ia_conf(ia_conf, verbose);
+               printf("}\n");
+       }
+}
+void
+print_config(struct dhcp6leased_conf *conf, int verbose)
+{
+       struct iface_conf       *iface;
+
+       SIMPLEQ_FOREACH(iface, &conf->iface_list, entry)
+               print_iface_conf(iface, verbose);
+}