--- /dev/null
+# $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=
--- /dev/null
+/* $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)));
+}
--- /dev/null
+/* $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 *);
--- /dev/null
+.\" $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 .
--- /dev/null
+/* $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");
+}
--- /dev/null
+.\" $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
--- /dev/null
+/* $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 *);
+
--- /dev/null
+/* $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];
+}
--- /dev/null
+/* $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);
--- /dev/null
+/* $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;
+}
--- /dev/null
+/* $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);
--- /dev/null
+/* $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);
+}
--- /dev/null
+/* $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 */
--- /dev/null
+/* $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;
+}
--- /dev/null
+/* $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);
+}