summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/rad/Makefile18
-rw-r--r--usr.sbin/rad/control.c307
-rw-r--r--usr.sbin/rad/control.h35
-rw-r--r--usr.sbin/rad/engine.c478
-rw-r--r--usr.sbin/rad/engine.h21
-rw-r--r--usr.sbin/rad/frontend.c936
-rw-r--r--usr.sbin/rad/frontend.h27
-rw-r--r--usr.sbin/rad/log.c199
-rw-r--r--usr.sbin/rad/log.h46
-rw-r--r--usr.sbin/rad/parse.y891
-rw-r--r--usr.sbin/rad/printconf.c102
-rw-r--r--usr.sbin/rad/rad.8147
-rw-r--r--usr.sbin/rad/rad.c783
-rw-r--r--usr.sbin/rad/rad.conf.5148
-rw-r--r--usr.sbin/rad/rad.h161
15 files changed, 4299 insertions, 0 deletions
diff --git a/usr.sbin/rad/Makefile b/usr.sbin/rad/Makefile
new file mode 100644
index 00000000000..bb1d1e0a3e2
--- /dev/null
+++ b/usr.sbin/rad/Makefile
@@ -0,0 +1,18 @@
+# $OpenBSD: Makefile,v 1.1 2018/07/10 16:39:54 florian Exp $
+
+PROG= rad
+SRCS= control.c engine.c frontend.c log.c rad.c parse.y printconf.c
+
+MAN= rad.8 rad.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>
diff --git a/usr.sbin/rad/control.c b/usr.sbin/rad/control.c
new file mode 100644
index 00000000000..1771ad3d4b9
--- /dev/null
+++ b/usr.sbin/rad/control.c
@@ -0,0 +1,307 @@
+/* $OpenBSD: control.c,v 1.1 2018/07/10 16:39:54 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 <netinet/in.h>
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <md5.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "rad.h"
+#include "control.h"
+#include "frontend.h"
+
+#define CONTROL_BACKLOG 5
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+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);
+ }
+
+ control_state.fd = fd;
+
+ return (0);
+}
+
+int
+control_listen(void)
+{
+
+ 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_cleanup(char *path)
+{
+ if (path == NULL)
+ return;
+ event_del(&control_state.ev);
+ event_del(&control_state.evt);
+ unlink(path);
+}
+
+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.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(verbose))
+ break;
+
+ /* Forward to all other processes. */
+ frontend_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid,
+ imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
+ frontend_imsg_compose_engine(imsg.hdr.type,
+ imsg.hdr.pid, imsg.data,
+ imsg.hdr.len - IMSG_HEADER_SIZE);
+
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ case IMSG_CTL_SHOW_MAIN_INFO:
+ c->iev.ibuf.pid = imsg.hdr.pid;
+ frontend_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid,
+ imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
+ break;
+ case IMSG_CTL_SHOW_FRONTEND_INFO:
+ frontend_showinfo_ctl(c);
+ imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SHOW_ENGINE_INFO:
+ c->iev.ibuf.pid = imsg.hdr.pid;
+ frontend_imsg_compose_engine(imsg.hdr.type,
+ imsg.hdr.pid,
+ imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
+ 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->hdr.len - IMSG_HEADER_SIZE));
+}
diff --git a/usr.sbin/rad/control.h b/usr.sbin/rad/control.h
new file mode 100644
index 00000000000..7ac69527f11
--- /dev/null
+++ b/usr.sbin/rad/control.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: control.h,v 1.1 2018/07/10 16:39:54 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.
+ */
+
+struct {
+ struct event ev;
+ struct event evt;
+ int fd;
+} control_state;
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ struct imsgev iev;
+};
+
+int control_init(char *);
+int control_listen(void);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+int control_imsg_relay(struct imsg *);
+void control_cleanup(char *);
diff --git a/usr.sbin/rad/engine.c b/usr.sbin/rad/engine.c
new file mode 100644
index 00000000000..3fd06d2c9f1
--- /dev/null
+++ b/usr.sbin/rad/engine.c
@@ -0,0 +1,478 @@
+/* $OpenBSD: engine.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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 <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/icmp6.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "rad.h"
+#include "engine.h"
+
+__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 engine_showinfo_ctl(struct imsg *);
+void parse_ra_rs(struct imsg_ra_rs *);
+void parse_ra(struct imsg_ra_rs *);
+void parse_rs(struct imsg_ra_rs *);
+
+struct rad_conf *engine_conf;
+struct imsgev *iev_frontend;
+struct imsgev *iev_main;
+struct sockaddr_in6 all_nodes;
+
+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(RAD_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ rad_process = PROC_ENGINE;
+ setproctitle("%s", log_procnames[rad_process]);
+ log_procinit(log_procnames[rad_process]);
+
+ 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);
+
+ all_nodes.sin6_len = sizeof(all_nodes);
+ all_nodes.sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, "ff02::1", &all_nodes.sin6_addr) != 1)
+ fatal("inet_pton");
+
+ 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);
+
+ config_clear(engine_conf);
+
+ 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));
+}
+
+void
+engine_dispatch_frontend(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ struct imsg_ra_rs ra_rs;
+ ssize_t n;
+ int shut = 0, 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("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_RA_RS:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(ra_rs))
+ fatal("%s: IMSG_RA_RS wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&ra_rs, imsg.data, sizeof(ra_rs));
+ parse_ra_rs(&ra_rs);
+ break;
+ case IMSG_CTL_LOG_VERBOSE:
+ /* Already checked by frontend. */
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ case IMSG_CTL_SHOW_ENGINE_INFO:
+ engine_showinfo_ctl(&imsg);
+ 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 rad_conf *nconf;
+ static struct ra_iface_conf *ra_iface_conf;
+ struct imsg imsg;
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf;
+ struct ra_prefix_conf *ra_prefix_conf;
+ 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("%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) {
+ log_warnx("%s: received unexpected imsg fd "
+ "to engine", __func__);
+ break;
+ }
+ if ((fd = imsg.fd) == -1) {
+ log_warnx("%s: expected to receive imsg fd to "
+ "engine but didn't receive any", __func__);
+ break;
+ }
+
+ iev_frontend = malloc(sizeof(struct imsgev));
+ if (iev_frontend == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_frontend->ibuf, fd);
+ iev_frontend->handler = engine_dispatch_frontend;
+ iev_frontend->events = EV_READ;
+
+ event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
+ iev_frontend->events, iev_frontend->handler,
+ iev_frontend);
+ event_add(&iev_frontend->ev, NULL);
+ break;
+ case IMSG_RECONF_CONF:
+ if ((nconf = malloc(sizeof(struct rad_conf))) == NULL)
+ fatal(NULL);
+ memcpy(nconf, imsg.data, sizeof(struct rad_conf));
+ SIMPLEQ_INIT(&nconf->ra_iface_list);
+ break;
+ case IMSG_RECONF_RA_IFACE:
+ if ((ra_iface_conf = malloc(sizeof(struct
+ ra_iface_conf))) == NULL)
+ fatal(NULL);
+ memcpy(ra_iface_conf, imsg.data,
+ sizeof(struct ra_iface_conf));
+ ra_iface_conf->autoprefix = NULL;
+ SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list);
+ SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list,
+ ra_iface_conf, entry);
+ break;
+ case IMSG_RECONF_RA_AUTOPREFIX:
+ if ((ra_iface_conf->autoprefix = malloc(sizeof(struct
+ ra_prefix_conf))) == NULL)
+ fatal(NULL);
+ memcpy(ra_iface_conf->autoprefix, imsg.data,
+ sizeof(struct ra_prefix_conf));
+ break;
+ case IMSG_RECONF_RA_PREFIX:
+ if ((ra_prefix_conf = malloc(sizeof(struct
+ ra_prefix_conf))) == NULL)
+ fatal(NULL);
+ memcpy(ra_prefix_conf, imsg.data, sizeof(struct
+ ra_prefix_conf));
+ SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list,
+ ra_prefix_conf, entry);
+ break;
+ case IMSG_RECONF_END:
+ merge_config(engine_conf, nconf);
+ nconf = NULL;
+ 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_showinfo_ctl(struct imsg *imsg)
+{
+ char filter[IF_NAMESIZE];
+ struct ctl_engine_info cei;
+ struct ra_iface_conf *ra_iface_conf;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SHOW_ENGINE_INFO:
+ memcpy(filter, imsg->data, sizeof(filter));
+ SIMPLEQ_FOREACH(ra_iface_conf, &engine_conf->ra_iface_list,
+ entry) {
+ if (filter[0] == '\0' || memcmp(filter,
+ ra_iface_conf->name, sizeof(filter)) == 0) {
+ memcpy(cei.name, ra_iface_conf->name,
+ sizeof(cei.name));
+
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_ENGINE_INFO, imsg->hdr.pid,
+ &cei, sizeof(cei));
+ }
+ }
+ engine_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL,
+ 0);
+ break;
+ default:
+ log_debug("%s: error handling imsg", __func__);
+ break;
+ }
+}
+
+void
+parse_ra_rs(struct imsg_ra_rs *ra_rs)
+{
+ char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ struct icmp6_hdr *hdr;
+
+ hdr = (struct icmp6_hdr *) ra_rs->packet;
+
+ switch (hdr->icmp6_type) {
+ case ND_ROUTER_ADVERT:
+ parse_ra(ra_rs);
+ break;
+ case ND_ROUTER_SOLICIT:
+ parse_rs(ra_rs);
+ break;
+ default:
+ log_warnx("unexpected icmp6_type: %d from %s on %s",
+ hdr->icmp6_type, inet_ntop(AF_INET6, &ra_rs->from.sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN), if_indextoname(ra_rs->if_index,
+ ifnamebuf));
+ break;
+ }
+}
+
+void
+parse_ra(struct imsg_ra_rs *ra)
+{
+ char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ log_debug("got RA from %s on %s",
+ inet_ntop(AF_INET6, &ra->from.sin6_addr, ntopbuf,
+ INET6_ADDRSTRLEN), if_indextoname(ra->if_index,
+ ifnamebuf));
+ /* XXX not yet */
+}
+
+void
+parse_rs(struct imsg_ra_rs *rs)
+{
+ struct nd_router_solicit *nd_rs;
+ struct imsg_send_ra send_ra;
+ ssize_t len;
+ int source_linkaddr = 0;
+ const char *hbuf;
+ char ifnamebuf[IFNAMSIZ];
+ uint8_t *p;
+
+ hbuf = sin6_to_str(&rs->from);
+
+ send_ra.if_index = rs->if_index;
+ memcpy(&send_ra.to, &all_nodes, sizeof(send_ra.to));
+
+ log_debug("got RS from %s on %s", hbuf, if_indextoname(rs->if_index,
+ ifnamebuf));
+
+ len = rs->len;
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&rs->from.sin6_addr)) {
+ log_warnx("RA from non link local address %s", hbuf);
+ return;
+ }
+
+ if ((size_t)len < sizeof(struct nd_router_solicit)) {
+ log_warnx("received too short message (%ld) from %s", len,
+ hbuf);
+ return;
+ }
+
+ p = rs->packet;
+ nd_rs = (struct nd_router_solicit *)p;
+ len -= sizeof(struct nd_router_solicit);
+ p += sizeof(struct nd_router_solicit);
+
+ if (nd_rs->nd_rs_code != 0) {
+ log_warnx("invalid ICMPv6 code (%d) from %s", nd_rs->nd_rs_code,
+ hbuf);
+ return;
+ }
+ while ((size_t)len >= sizeof(struct nd_opt_hdr)) {
+ struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p;
+
+ len -= sizeof(struct nd_opt_hdr);
+ p += sizeof(struct nd_opt_hdr);
+
+ if (nd_opt_hdr->nd_opt_len * 8 - 2 > len) {
+ log_warnx("invalid option len: %u > %ld",
+ nd_opt_hdr->nd_opt_len, len);
+ return;
+ }
+ switch (nd_opt_hdr->nd_opt_type) {
+ case ND_OPT_SOURCE_LINKADDR:
+ log_debug("got RS with source linkaddr option");
+ memcpy(&send_ra.to, &rs->from, sizeof(send_ra.to));
+ break;
+ default:
+ log_debug("\t\tUNKNOWN: %d", nd_opt_hdr->nd_opt_type);
+ break;
+ }
+ len -= nd_opt_hdr->nd_opt_len * 8 - 2;
+ p += nd_opt_hdr->nd_opt_len * 8 - 2;
+ }
+ engine_imsg_compose_frontend(IMSG_SEND_RA, 0, &send_ra,
+ sizeof(send_ra));
+}
diff --git a/usr.sbin/rad/engine.h b/usr.sbin/rad/engine.h
new file mode 100644
index 00000000000..68cb2b9587d
--- /dev/null
+++ b/usr.sbin/rad/engine.h
@@ -0,0 +1,21 @@
+/* $OpenBSD: engine.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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.
+ */
+
+void engine(int, int);
+int engine_imsg_compose_frontend(int, pid_t, void *, uint16_t);
diff --git a/usr.sbin/rad/frontend.c b/usr.sbin/rad/frontend.c
new file mode 100644
index 00000000000..5a33378b194
--- /dev/null
+++ b/usr.sbin/rad/frontend.c
@@ -0,0 +1,936 @@
+/* $OpenBSD: frontend.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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.
+ */
+
+/*
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet6/nd6.h>
+#include <netinet6/in6_var.h>
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet/icmp6.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 "rad.h"
+#include "frontend.h"
+#include "control.h"
+
+struct icmp6_ev {
+ struct event ev;
+ uint8_t answer[1500];
+ struct msghdr rcvmhdr;
+ struct iovec rcviov[1];
+ struct sockaddr_in6 from;
+} icmp6ev;
+
+struct ra_iface {
+ TAILQ_ENTRY(ra_iface) entry;
+ struct ra_prefix_conf_head prefixes;
+ char name[IF_NAMESIZE];
+ uint32_t if_index;
+ int removed;
+ int prefix_count;
+ size_t datalen;
+ uint8_t data[1500];
+};
+
+TAILQ_HEAD(, ra_iface) ra_interfaces;
+
+__dead void frontend_shutdown(void);
+void frontend_sig_handler(int, short, void *);
+void frontend_startup(void);
+void icmp6_receive(int, short, void *);
+void join_all_routers_mcast_group(struct ra_iface *);
+void leave_all_routers_mcast_group(struct ra_iface *);
+void merge_ra_interfaces(void);
+struct ra_iface *find_ra_iface_by_id(uint32_t);
+struct ra_iface *find_ra_iface_by_name(char *);
+struct ra_iface_conf *find_ra_iface_conf(struct ra_iface_conf_head *,
+ char *);
+struct ra_prefix_conf *find_ra_prefix_conf(struct ra_prefix_conf_head*,
+ struct in6_addr *, int);
+void add_new_prefix_to_ra_iface(struct ra_iface *r,
+ struct in6_addr *, int, struct ra_prefix_conf *);
+void free_ra_iface(struct ra_iface *);
+int in6_mask2prefixlen(struct in6_addr *);
+void get_interface_prefixes(struct ra_iface *,
+ struct ra_prefix_conf *);
+void build_package(struct ra_iface *);
+void ra_output(struct ra_iface *, struct sockaddr_in6 *);
+
+struct rad_conf *frontend_conf;
+struct imsgev *iev_main;
+struct imsgev *iev_engine;
+int icmp6sock = -1, ioctlsock = -1;
+struct ipv6_mreq all_routers;
+struct sockaddr_in6 all_nodes;
+struct msghdr sndmhdr;
+struct iovec sndiov[2];
+
+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, char *sockname)
+{
+ struct event ev_sigint, ev_sigterm;
+ struct passwd *pw;
+ size_t rcvcmsglen, sndcmsgbuflen;
+ uint8_t *rcvcmsgbuf;
+ uint8_t *sndcmsgbuf = NULL;
+
+ frontend_conf = config_new_empty();
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ /* XXX pass in from main */
+ /* Create rad control socket outside chroot. */
+ if (control_init(sockname) == -1)
+ fatalx("control socket setup failed");
+
+ if ((pw = getpwnam(RAD_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ rad_process = PROC_FRONTEND;
+ setproctitle("%s", log_procnames[rad_process]);
+ log_procinit(log_procnames[rad_process]);
+
+ 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");
+
+ /* XXX pass in from main */
+ if ((ioctlsock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0)
+ fatal("socket");
+
+ if (pledge("stdio inet unix recvfd route mcast", NULL) == -1)
+ fatal("pledge");
+
+ event_init();
+
+ /* Setup signal handler. */
+ signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ /* Setup pipe and event handler to the parent process. */
+ if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+ fatal(NULL);
+ imsg_init(&iev_main->ibuf, 3);
+ iev_main->handler = frontend_dispatch_main;
+ iev_main->events = EV_READ;
+ event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+ iev_main->handler, iev_main);
+ event_add(&iev_main->ev, NULL);
+
+ rcvcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ if((rcvcmsgbuf = malloc(rcvcmsglen)) == NULL)
+ fatal("malloc");
+
+ icmp6ev.rcviov[0].iov_base = (caddr_t)icmp6ev.answer;
+ icmp6ev.rcviov[0].iov_len = sizeof(icmp6ev.answer);
+ icmp6ev.rcvmhdr.msg_name = (caddr_t)&icmp6ev.from;
+ icmp6ev.rcvmhdr.msg_namelen = sizeof(icmp6ev.from);
+ icmp6ev.rcvmhdr.msg_iov = icmp6ev.rcviov;
+ icmp6ev.rcvmhdr.msg_iovlen = 1;
+ icmp6ev.rcvmhdr.msg_control = (caddr_t) rcvcmsgbuf;
+ icmp6ev.rcvmhdr.msg_controllen = rcvcmsglen;
+
+ if (inet_pton(AF_INET6, "ff02::2",
+ &all_routers.ipv6mr_multiaddr.s6_addr) == -1)
+ fatal("inet_pton");
+
+ all_nodes.sin6_len = sizeof(all_nodes);
+ all_nodes.sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, "ff02::1", &all_nodes.sin6_addr) != 1)
+ fatal("inet_pton");
+
+ sndcmsgbuflen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ if ((sndcmsgbuf = malloc(sndcmsgbuflen)) == NULL)
+ fatal("%s", __func__);
+
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iov = sndiov;
+ sndmhdr.msg_iovlen = 1;
+ sndmhdr.msg_control = sndcmsgbuf;
+ sndmhdr.msg_controllen = sndcmsgbuflen;
+
+ TAILQ_INIT(&ra_interfaces);
+
+ /* Listen on control socket. */
+ TAILQ_INIT(&ctl_conns);
+ control_listen();
+
+ 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, pid_t pid, void *data, uint16_t datalen)
+{
+ return (imsg_compose_event(iev_engine, type, 0, pid, -1, data,
+ datalen));
+}
+
+void
+frontend_dispatch_main(int fd, short event, void *bula)
+{
+ static struct rad_conf *nconf;
+ static struct ra_iface_conf *ra_iface_conf;
+ struct imsg imsg;
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct ra_prefix_conf *ra_prefix_conf;
+ int n, 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 engine
+ * process.
+ */
+ if (iev_engine) {
+ log_warnx("%s: received unexpected imsg fd "
+ "to frontend", __func__);
+ break;
+ }
+ if ((fd = imsg.fd) == -1) {
+ log_warnx("%s: expected to receive imsg fd to "
+ "frontend but didn't receive any",
+ __func__);
+ break;
+ }
+
+ 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_RECONF_CONF:
+ if ((nconf = malloc(sizeof(struct rad_conf))) ==
+ NULL)
+ fatal(NULL);
+ memcpy(nconf, imsg.data, sizeof(struct rad_conf));
+ SIMPLEQ_INIT(&nconf->ra_iface_list);
+ break;
+ case IMSG_RECONF_RA_IFACE:
+ if ((ra_iface_conf = malloc(sizeof(struct
+ ra_iface_conf))) == NULL)
+ fatal(NULL);
+ memcpy(ra_iface_conf, imsg.data, sizeof(struct
+ ra_iface_conf));
+ ra_iface_conf->autoprefix = NULL;
+ SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list);
+ SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list,
+ ra_iface_conf, entry);
+ break;
+ case IMSG_RECONF_RA_AUTOPREFIX:
+ if ((ra_iface_conf->autoprefix = malloc(sizeof(struct
+ ra_prefix_conf))) == NULL)
+ fatal(NULL);
+ memcpy(ra_iface_conf->autoprefix, imsg.data,
+ sizeof(struct ra_prefix_conf));
+ break;
+ case IMSG_RECONF_RA_PREFIX:
+ if ((ra_prefix_conf = malloc(sizeof(struct
+ ra_prefix_conf))) == NULL)
+ fatal(NULL);
+ memcpy(ra_prefix_conf, imsg.data,
+ sizeof(struct ra_prefix_conf));
+ SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list,
+ ra_prefix_conf, entry);
+ break;
+ case IMSG_RECONF_END:
+ merge_config(frontend_conf, nconf);
+ merge_ra_interfaces();
+ nconf = NULL;
+ break;
+ case IMSG_ICMP6SOCK:
+ if ((icmp6sock = imsg.fd) == -1)
+ fatalx("%s: expected to receive imsg "
+ "ICMPv6 fd but didn't receive any",
+ __func__);
+ event_set(&icmp6ev.ev, icmp6sock, EV_READ | EV_PERSIST,
+ icmp6_receive, NULL);
+ case IMSG_STARTUP:
+ if (pledge("stdio inet unix route mcast", NULL) == -1)
+ fatal("pledge");
+ frontend_startup();
+ break;
+ case IMSG_CTL_END:
+ case IMSG_CTL_SHOW_MAIN_INFO:
+ 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 imsg_send_ra send_ra;
+ struct ra_iface *ra_iface;
+ int n, 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_ENGINE_INFO:
+ control_imsg_relay(&imsg);
+ break;
+ case IMSG_SEND_RA:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(send_ra))
+ fatal("%s: IMSG_SEND_RA wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&send_ra, imsg.data, sizeof(send_ra));
+ ra_iface = find_ra_iface_by_id(send_ra.if_index);
+ if (ra_iface)
+ ra_output(ra_iface, &send_ra.to);
+ 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_showinfo_ctl(struct ctl_conn *c)
+{
+ static struct ctl_frontend_info cfi;
+
+ imsg_compose_event(&c->iev, IMSG_CTL_SHOW_FRONTEND_INFO, 0, 0, -1,
+ &cfi, sizeof(struct ctl_frontend_info));
+}
+
+void
+frontend_startup(void)
+{
+#if 0
+ if (!event_initialized(&ev_route))
+ fatalx("%s: did not receive a route socket from the main "
+ "process", __func__);
+
+ event_add(&ev_route, NULL);
+#endif
+
+ if (!event_initialized(&icmp6ev.ev))
+ fatalx("%s: did not receive a icmp6 socket fd from the main "
+ "process", __func__);
+
+ event_add(&icmp6ev.ev, NULL);
+
+ frontend_imsg_compose_main(IMSG_STARTUP_DONE, 0, NULL, 0);
+}
+
+
+void
+icmp6_receive(int fd, short events, void *arg)
+{
+ struct imsg_ra_rs ra_rs;
+ struct in6_pktinfo *pi = NULL;
+ struct cmsghdr *cm;
+ ssize_t len;
+ int if_index = 0, *hlimp = NULL;
+ char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+
+ if ((len = recvmsg(fd, &icmp6ev.rcvmhdr, 0)) < 0) {
+ log_warn("recvmsg");
+ return;
+ }
+
+ /* extract optional information via Advanced API */
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&icmp6ev.rcvmhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&icmp6ev.rcvmhdr, cm)) {
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
+ pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
+ if_index = pi->ipi6_ifindex;
+ }
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_HOPLIMIT &&
+ cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ hlimp = (int *)CMSG_DATA(cm);
+ }
+
+ if (if_index == 0) {
+ log_warnx("failed to get receiving interface");
+ return;
+ }
+
+ if (hlimp == NULL) {
+ log_warnx("failed to get receiving hop limit");
+ return;
+ }
+
+ if (*hlimp != 255) {
+ log_warnx("invalid RA or RS with hop limit of %d from %s on %s",
+ *hlimp, inet_ntop(AF_INET6, &icmp6ev.from.sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
+ ifnamebuf));
+ return;
+ }
+
+ log_warnx("RA or RS with hop limit of %d from %s on %s",
+ *hlimp, inet_ntop(AF_INET6, &icmp6ev.from.sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
+ ifnamebuf));
+
+ if ((size_t)len > sizeof(ra_rs.packet)) {
+ log_warnx("invalid RA or RS with size %ld from %s on %s",
+ len, inet_ntop(AF_INET6, &icmp6ev.from.sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
+ ifnamebuf));
+ return;
+ }
+
+ ra_rs.if_index = if_index;
+ memcpy(&ra_rs.from, &icmp6ev.from, sizeof(ra_rs.from));
+ ra_rs.len = len;
+ memcpy(ra_rs.packet, icmp6ev.answer, len);
+
+ frontend_imsg_compose_engine(IMSG_RA_RS, 0, &ra_rs, sizeof(ra_rs));
+}
+
+void
+join_all_routers_mcast_group(struct ra_iface *ra_iface)
+{
+ log_debug("joining multicast group on %s", ra_iface->name);
+ all_routers.ipv6mr_interface = ra_iface->if_index;
+ if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &all_routers, sizeof(all_routers)) == -1)
+ fatal("IPV6_JOIN_GROUP(%s)", ra_iface->name);
+}
+
+void
+leave_all_routers_mcast_group(struct ra_iface *ra_iface)
+{
+ log_debug("leaving multicast group on %s", ra_iface->name);
+ all_routers.ipv6mr_interface = ra_iface->if_index;
+ if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
+ &all_routers, sizeof(all_routers)) == -1)
+ fatal("IPV6_LEAVE_GROUP(%s)", ra_iface->name);
+}
+
+struct ra_iface*
+find_ra_iface_by_id(uint32_t if_index) {
+ struct ra_iface *ra_iface;
+
+ TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) {
+ if (ra_iface->if_index == if_index)
+ return ra_iface;
+ }
+ return (NULL);
+}
+
+struct ra_iface*
+find_ra_iface_by_name(char *if_name)
+{
+ struct ra_iface *ra_iface;
+
+ TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) {
+ if (strcmp(ra_iface->name, if_name) == 0)
+ return ra_iface;
+ }
+ return (NULL);
+}
+
+struct ra_iface_conf*
+find_ra_iface_conf(struct ra_iface_conf_head *head, char *if_name)
+{
+ struct ra_iface_conf *ra_iface_conf;
+
+ SIMPLEQ_FOREACH(ra_iface_conf, head, entry) {
+ if (strcmp(ra_iface_conf->name, if_name) == 0)
+ return ra_iface_conf;
+ }
+ return (NULL);
+}
+
+void
+merge_ra_interfaces(void)
+{
+ struct ra_iface_conf *ra_iface_conf;
+ struct ra_prefix_conf *ra_prefix_conf;
+ struct ra_iface *ra_iface, *tmp;
+ uint32_t if_index;
+
+ TAILQ_FOREACH(ra_iface, &ra_interfaces, entry)
+ ra_iface->removed = 1;
+
+ SIMPLEQ_FOREACH(ra_iface_conf, &frontend_conf->ra_iface_list, entry) {
+ ra_iface = find_ra_iface_by_name(ra_iface_conf->name);
+ if (ra_iface == NULL) {
+ log_debug("new interface %s", ra_iface_conf->name);
+ if ((if_index = if_nametoindex(ra_iface_conf->name))
+ == 0)
+ continue;
+ log_debug("adding interface %s", ra_iface_conf->name);
+ if ((ra_iface = calloc(1, sizeof(*ra_iface))) == NULL)
+ fatal("%s", __func__);
+
+ (void) strlcpy(ra_iface->name, ra_iface_conf->name,
+ sizeof(ra_iface->name));
+ ra_iface->if_index = if_index;
+ SIMPLEQ_INIT(&ra_iface->prefixes);
+ TAILQ_INSERT_TAIL(&ra_interfaces, ra_iface, entry);
+ join_all_routers_mcast_group(ra_iface);
+ } else {
+ log_debug("keeping interface %s", ra_iface_conf->name);
+ ra_iface->removed = 0;
+ }
+ }
+
+ TAILQ_FOREACH_SAFE(ra_iface, &ra_interfaces, entry, tmp) {
+ if (ra_iface->removed) {
+ TAILQ_REMOVE(&ra_interfaces, ra_iface, entry);
+ free_ra_iface(ra_iface);
+ }
+ }
+
+ TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) {
+ while ((ra_prefix_conf = SIMPLEQ_FIRST(&ra_iface->prefixes))
+ != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&ra_iface->prefixes,
+ entry);
+ free(ra_prefix_conf);
+ }
+ ra_iface->prefix_count = 0;
+
+ ra_iface_conf = find_ra_iface_conf(
+ &frontend_conf->ra_iface_list, ra_iface->name);
+
+ if (ra_iface_conf->autoprefix)
+ get_interface_prefixes(ra_iface,
+ ra_iface_conf->autoprefix);
+
+ log_debug("add static prefixes for %s", ra_iface->name);
+
+ SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface_conf->ra_prefix_list,
+ entry) {
+ add_new_prefix_to_ra_iface(ra_iface,
+ &ra_prefix_conf->prefix,
+ ra_prefix_conf->prefixlen, ra_prefix_conf);
+ }
+ build_package(ra_iface);
+ }
+}
+
+void
+free_ra_iface(struct ra_iface *ra_iface)
+{
+ struct ra_prefix_conf *prefix;
+
+ leave_all_routers_mcast_group(ra_iface);
+
+ while ((prefix = SIMPLEQ_FIRST(&ra_iface->prefixes)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&ra_iface->prefixes, entry);
+ free(prefix);
+ }
+
+ free(ra_iface);
+}
+
+/* from kame via ifconfig, where it's called prefix() */
+int
+in6_mask2prefixlen(struct in6_addr *in6)
+{
+ u_char *nam = (u_char *)in6;
+ int byte, bit, plen = 0, size = sizeof(struct in6_addr);
+
+ for (byte = 0; byte < size; byte++, plen += 8)
+ if (nam[byte] != 0xff)
+ break;
+ if (byte == size)
+ return (plen);
+ for (bit = 7; bit != 0; bit--, plen++)
+ if (!(nam[byte] & (1 << bit)))
+ break;
+ for (; bit != 0; bit--)
+ if (nam[byte] & (1 << bit))
+ return (0);
+ byte++;
+ for (; byte < size; byte++)
+ if (nam[byte])
+ return (0);
+ return (plen);
+}
+
+void
+get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf
+ *autoprefix)
+{
+ struct in6_ifreq ifr6;
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in6 *sin6;
+ int prefixlen;
+
+ log_debug("%s: %s", __func__, ra_iface->name);
+
+ if (getifaddrs(&ifap) != 0)
+ fatal("getifaddrs");
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if (strcmp(ra_iface->name, ifa->ifa_name) != 0)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ continue;
+
+ memset(&ifr6, 0, sizeof(ifr6));
+ (void) strlcpy(ifr6.ifr_name, ra_iface->name,
+ sizeof(ifr6.ifr_name));
+ memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
+
+ if (ioctl(ioctlsock, SIOCGIFNETMASK_IN6, (caddr_t)&ifr6) < 0)
+ fatal("SIOCGIFNETMASK_IN6");
+
+ prefixlen = in6_mask2prefixlen(&((struct sockaddr_in6 *)
+ &ifr6.ifr_addr)->sin6_addr);
+
+ if (prefixlen == 128)
+ continue;
+
+ mask_prefix(&sin6->sin6_addr, prefixlen);
+
+ add_new_prefix_to_ra_iface(ra_iface, &sin6->sin6_addr,
+ prefixlen, autoprefix);
+
+ }
+}
+
+struct ra_prefix_conf*
+find_ra_prefix_conf(struct ra_prefix_conf_head* head, struct in6_addr *prefix,
+ int prefixlen)
+{
+ struct ra_prefix_conf *ra_prefix_conf;
+
+ SIMPLEQ_FOREACH(ra_prefix_conf, head, entry) {
+ if (ra_prefix_conf->prefixlen == prefixlen &&
+ memcmp(&ra_prefix_conf->prefix, prefix, sizeof(*prefix)) ==
+ 0)
+ return (ra_prefix_conf);
+ }
+ return (NULL);
+}
+
+void
+add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr,
+ int prefixlen, struct ra_prefix_conf *ra_prefix_conf)
+{
+ struct ra_prefix_conf *new_ra_prefix_conf;
+
+ if (find_ra_prefix_conf(&ra_iface->prefixes, addr, prefixlen)) {
+ log_debug("ignoring duplicate %s/%d prefix",
+ in6_to_str(addr), prefixlen);
+ return;
+ }
+
+ log_debug("adding %s/%d prefix", in6_to_str(addr), prefixlen);
+
+ if ((new_ra_prefix_conf = calloc(1, sizeof(*ra_prefix_conf))) == NULL)
+ fatal("%s", __func__);
+ new_ra_prefix_conf->prefix = *addr;
+ new_ra_prefix_conf->prefixlen = prefixlen;
+ new_ra_prefix_conf->vltime = ra_prefix_conf->vltime;
+ new_ra_prefix_conf->pltime = ra_prefix_conf->pltime;
+ new_ra_prefix_conf->aflag = ra_prefix_conf->aflag;
+ new_ra_prefix_conf->lflag = ra_prefix_conf->lflag;
+ SIMPLEQ_INSERT_TAIL(&ra_iface->prefixes, new_ra_prefix_conf, entry);
+ ra_iface->prefix_count++;
+}
+
+void
+build_package(struct ra_iface *ra_iface)
+{
+ struct nd_router_advert *ra;
+ struct nd_opt_prefix_info *ndopt_pi;
+ struct ra_iface_conf *ra_iface_conf;
+ struct ra_options_conf *ra_options_conf;
+ struct ra_prefix_conf *ra_prefix_conf;
+ uint8_t *buf;
+
+ ra_iface_conf = find_ra_iface_conf(&frontend_conf->ra_iface_list,
+ ra_iface->name);
+ ra_options_conf = &ra_iface_conf->ra_options;
+
+ ra_iface->datalen = sizeof(*ra);
+ ra_iface->datalen += sizeof(*ndopt_pi) * ra_iface->prefix_count;
+
+ if (ra_iface->datalen > sizeof(ra_iface->data))
+ fatal("%s: packet too big", __func__); /* XXX send multiple */
+
+ buf = ra_iface->data;
+
+ ra = (struct nd_router_advert *)buf;
+
+ memset(ra, 0, sizeof(*ra));
+
+ ra->nd_ra_type = ND_ROUTER_ADVERT;
+ ra->nd_ra_curhoplimit = ra_options_conf->cur_hl;
+ if (ra_options_conf->m_flag)
+ ra->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
+ if (ra_options_conf->o_flag)
+ ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;
+ if (ra_options_conf->dfr)
+ ra->nd_ra_router_lifetime =
+ htons(ra_options_conf->router_lifetime);
+ ra->nd_ra_reachable = htonl(ra_options_conf->reachable_time);
+ ra->nd_ra_retransmit = htonl(ra_options_conf->retrans_timer);
+ buf += sizeof(*ra);
+
+ SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface->prefixes, entry) {
+ ndopt_pi = (struct nd_opt_prefix_info *)buf;
+ memset(ndopt_pi, 0, sizeof(*ndopt_pi));
+ ndopt_pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
+ ndopt_pi->nd_opt_pi_len = 4;
+ ndopt_pi->nd_opt_pi_prefix_len = ra_prefix_conf->prefixlen;
+ if (ra_prefix_conf->lflag)
+ ndopt_pi->nd_opt_pi_flags_reserved |=
+ ND_OPT_PI_FLAG_ONLINK;
+ if (ra_prefix_conf->aflag)
+ ndopt_pi->nd_opt_pi_flags_reserved |=
+ ND_OPT_PI_FLAG_AUTO;
+ ndopt_pi->nd_opt_pi_valid_time = htonl(ra_prefix_conf->vltime);
+ ndopt_pi->nd_opt_pi_preferred_time =
+ htonl(ra_prefix_conf->pltime);
+ ndopt_pi->nd_opt_pi_prefix = ra_prefix_conf->prefix;
+
+ buf += sizeof(*ndopt_pi);
+ }
+}
+
+void
+ra_output(struct ra_iface *ra_iface, struct sockaddr_in6 *to)
+{
+
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi;
+ ssize_t len;
+ int hoplimit = 255;
+
+ sndmhdr.msg_name = to;
+ sndmhdr.msg_iov[0].iov_base = ra_iface->data;
+ sndmhdr.msg_iov[0].iov_len = ra_iface->datalen;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ /* specify the outgoing interface */
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ pi = (struct in6_pktinfo *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr));
+ pi->ipi6_ifindex = ra_iface->if_index;
+
+ /* specify the hop limit of the packet */
+ cm = CMSG_NXTHDR(&sndmhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
+
+ log_debug("send RA on %s", ra_iface->name);
+
+ len = sendmsg(icmp6sock, &sndmhdr, 0);
+ if (len < 0)
+ log_warn("sendmsg on %s", ra_iface->name);
+
+}
diff --git a/usr.sbin/rad/frontend.h b/usr.sbin/rad/frontend.h
new file mode 100644
index 00000000000..ac368cb7e33
--- /dev/null
+++ b/usr.sbin/rad/frontend.h
@@ -0,0 +1,27 @@
+/* $OpenBSD: frontend.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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.
+ */
+
+TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns;
+
+void frontend(int, int, char *);
+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, pid_t, void *, uint16_t);
+void frontend_showinfo_ctl(struct ctl_conn *);
diff --git a/usr.sbin/rad/log.c b/usr.sbin/rad/log.c
new file mode 100644
index 00000000000..50b0a0e95ac
--- /dev/null
+++ b/usr.sbin/rad/log.c
@@ -0,0 +1,199 @@
+/* $OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int debug;
+static int verbose;
+static const char *log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(__progname);
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+ tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+ if (procname != NULL)
+ log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+ verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+ return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+ int saved_errno = errno;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+
+ errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+ int saved_errno = errno;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg,
+ strerror(saved_errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_ERR, emsg, ap);
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ } else {
+ vlog(LOG_ERR, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+
+ errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_ERR, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+ static char s[BUFSIZ];
+ const char *sep;
+
+ if (emsg != NULL) {
+ (void)vsnprintf(s, sizeof(s), emsg, ap);
+ sep = ": ";
+ } else {
+ s[0] = '\0';
+ sep = "";
+ }
+ if (code)
+ logit(LOG_CRIT, "fatal in %s: %s%s%s",
+ log_procname, s, sep, strerror(code));
+ else
+ logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(errno, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(0, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
diff --git a/usr.sbin/rad/log.h b/usr.sbin/rad/log.h
new file mode 100644
index 00000000000..317b3314f7a
--- /dev/null
+++ b/usr.sbin/rad/log.h
@@ -0,0 +1,46 @@
+/* $OpenBSD: log.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+#include <sys/cdefs.h>
+
+void log_init(int, int);
+void log_procinit(const char *);
+void log_setverbose(int);
+int log_getverbose(void);
+void log_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
diff --git a/usr.sbin/rad/parse.y b/usr.sbin/rad/parse.y
new file mode 100644
index 00000000000..ab533c8be09
--- /dev/null
+++ b/usr.sbin/rad/parse.y
@@ -0,0 +1,891 @@
+/* $OpenBSD: parse.y,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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 <netinet/in.h>
+#include <net/if.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <ifaddrs.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "rad.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 *);
+
+void clear_config(struct rad_conf *xconf);
+
+static struct rad_conf *conf;
+static struct ra_options_conf *ra_options;
+static int errors;
+
+static struct ra_iface_conf *ra_iface_conf;
+static struct ra_prefix_conf *ra_prefix_conf;
+
+struct ra_prefix_conf *conf_get_ra_prefix(struct in6_addr*, int);
+struct ra_iface_conf *conf_get_ra_iface(char *);
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token RA_IFACE YES NO INCLUDE ERROR
+%token DEFAULT ROUTER HOP LIMIT MANAGED ADDRESS
+%token CONFIGURATION OTHER LIFETIME REACHABLE TIME RETRANS TIMER
+%token AUTO PREFIX VALID PREFERRED LIFETIME ONLINK AUTONOMOUS
+%token ADDRESS_CONFIGURATION
+
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> yesno
+%type <v.string> string
+
+%%
+
+grammar : /* empty */
+ | grammar include '\n'
+ | grammar '\n'
+ | grammar { ra_options = &conf->ra_options; } conf_main '\n'
+ | grammar varset '\n'
+ | grammar ra_iface '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 1)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+string : string STRING {
+ if (asprintf(&$$, "%s %s", $1, $2) == -1) {
+ free($1);
+ free($2);
+ yyerror("string: asprintf");
+ YYERROR;
+ }
+ free($1);
+ free($2);
+ }
+ | STRING
+ ;
+
+yesno : YES { $$ = 1; }
+ | NO { $$ = 0; }
+ ;
+
+varset : STRING '=' string {
+ char *s = $1;
+ if (cmd_opts & OPT_VERBOSE)
+ printf("%s = \"%s\"\n", $1, $3);
+ while (*s++) {
+ if (isspace((unsigned char)*s)) {
+ yyerror("macro name cannot contain "
+ "whitespace");
+ YYERROR;
+ }
+ }
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+conf_main : ra_opt_block {
+ ra_options = &conf->ra_options;
+ }
+ ;
+
+ra_opt_block : DEFAULT ROUTER yesno {
+ ra_options->dfr = $3;
+ }
+ | HOP LIMIT NUMBER {
+ ra_options->cur_hl = $3;
+ }
+ | MANAGED ADDRESS CONFIGURATION yesno {
+ ra_options->m_flag = $4;
+ }
+ | OTHER CONFIGURATION yesno {
+ ra_options->o_flag = $3;
+ }
+ | ROUTER LIFETIME NUMBER {
+ ra_options->router_lifetime = $3;
+ }
+ | REACHABLE TIME NUMBER {
+ ra_options->reachable_time = $3;
+ }
+ | RETRANS TIMER NUMBER {
+ ra_options->retrans_timer = $3;
+ }
+ ;
+
+optnl : '\n' optnl /* zero or more newlines */
+ | /*empty*/
+ ;
+
+nl : '\n' optnl /* one or more newlines */
+ ;
+
+ra_iface : RA_IFACE STRING {
+ ra_iface_conf = conf_get_ra_iface($2);
+ /* set auto prefix defaults */
+ ra_iface_conf->autoprefix = conf_get_ra_prefix(NULL, 0);
+ ra_options = &ra_iface_conf->ra_options;
+ } ra_iface_block {
+ ra_iface_conf = NULL;
+ }
+ ;
+
+ra_iface_block : '{' optnl ra_ifaceopts_l '}'
+ | '{' optnl '}'
+ | /* empty */
+ ;
+
+ra_ifaceopts_l : ra_ifaceopts_l ra_ifaceoptsl nl
+ | ra_ifaceoptsl optnl
+ ;
+
+ra_ifaceoptsl : NO AUTO PREFIX {
+ free(ra_iface_conf->autoprefix);
+ ra_iface_conf->autoprefix = NULL;
+ }
+ | AUTO PREFIX {
+ if (ra_iface_conf->autoprefix == NULL)
+ ra_iface_conf->autoprefix =
+ ra_prefix_conf = conf_get_ra_prefix(NULL, 0);
+ } ra_prefix_block {
+ ra_prefix_conf = NULL;
+ }
+ | PREFIX STRING {
+ struct in6_addr addr;
+ int prefixlen;
+
+ memset(&addr, 0, sizeof(addr));
+ prefixlen = inet_net_pton(AF_INET6, $2, &addr,
+ sizeof(addr));
+ if (prefixlen == -1) {
+ yyerror("error parsing prefix");
+ free($2);
+ YYERROR;
+ }
+ mask_prefix(&addr, prefixlen);
+ ra_prefix_conf = conf_get_ra_prefix(&addr, prefixlen);
+ } ra_prefix_block {
+ ra_prefix_conf = NULL;
+ }
+ | ra_opt_block
+ ;
+
+ra_prefix_block : '{' optnl ra_prefixopts_l '}'
+ | '{' optnl '}'
+ | /* empty */
+ ;
+
+ra_prefixopts_l : ra_prefixopts_l ra_prefixoptsl nl
+ | ra_prefixoptsl optnl
+ ;
+
+ra_prefixoptsl : VALID LIFETIME NUMBER {
+ ra_prefix_conf->vltime = $3;
+ }
+ | PREFERRED LIFETIME NUMBER {
+ ra_prefix_conf->pltime = $3;
+ }
+ | ONLINK yesno {
+ ra_prefix_conf->lflag = $2;
+ }
+ | AUTONOMOUS ADDRESS_CONFIGURATION yesno {
+ ra_prefix_conf->aflag = $3;
+ }
+ ;
+
+%%
+
+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[] = {
+ {"address", ADDRESS},
+ {"address-configuration", ADDRESS_CONFIGURATION},
+ {"auto", AUTO},
+ {"autonomous", AUTONOMOUS},
+ {"configuration", CONFIGURATION},
+ {"default", DEFAULT},
+ {"hop", HOP},
+ {"include", INCLUDE},
+ {"interface", RA_IFACE},
+ {"lifetime", LIFETIME},
+ {"limit", LIMIT},
+ {"managed", MANAGED},
+ {"no", NO},
+ {"on-link", ONLINK},
+ {"other", OTHER},
+ {"preferred", PREFERRED},
+ {"prefix", PREFIX},
+ {"reachable", REACHABLE},
+ {"retrans", RETRANS},
+ {"router", ROUTER},
+ {"time", TIME},
+ {"timer", TIMER},
+ {"valid", VALID},
+ {"yes", YES},
+ };
+ 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)
+{
+ unsigned char buf[8096];
+ unsigned 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(*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 || c == ' ' || c == '\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 ((unsigned)(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(*--p);
+ c = *--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 ((unsigned)(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) {
+ log_warn("%s", nfile->name);
+ 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 rad_conf *
+parse_config(char *filename)
+{
+ struct sym *sym, *next;
+
+ conf = config_new_empty();
+ ra_options = NULL;
+
+ file = pushfile(filename, !(cmd_opts & OPT_NOACTION));
+ 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 ((cmd_opts & OPT_VERBOSE2) && !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) {
+ clear_config(conf);
+ return (NULL);
+ }
+
+ 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;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ strlcpy(sym, s, len);
+
+ 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 ra_prefix_conf *
+conf_get_ra_prefix(struct in6_addr *addr, int prefixlen)
+{
+ struct ra_prefix_conf *prefix;
+
+ if (addr == NULL) {
+ if (ra_iface_conf->autoprefix != NULL)
+ return (ra_iface_conf->autoprefix);
+ } else {
+ SIMPLEQ_FOREACH(prefix, &ra_iface_conf->ra_prefix_list, entry) {
+ if (prefix->prefixlen == prefixlen && memcmp(addr,
+ &prefix->prefix, sizeof(*addr)) == 0)
+ return (prefix);
+ }
+ }
+
+ prefix = calloc(1, sizeof(*prefix));
+ if (prefix == NULL)
+ errx(1, "%s: calloc", __func__);
+ prefix->prefixlen = prefixlen;
+ prefix->vltime = 2592000; /* 30 days */
+ prefix->pltime = 604800; /* 7 days */
+ prefix->lflag = 1;
+ prefix->aflag = 1;
+
+ if (addr == NULL)
+ ra_iface_conf->autoprefix = prefix;
+ else {
+ prefix->prefix = *addr;
+ SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list, prefix,
+ entry);
+ }
+
+ return (prefix);
+}
+
+struct ra_iface_conf *
+conf_get_ra_iface(char *name)
+{
+ struct ra_iface_conf *iface;
+ size_t n;
+
+ SIMPLEQ_FOREACH(iface, &conf->ra_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__);
+
+ /* Inherit attributes set in global section. */
+ iface->ra_options = conf->ra_options;
+
+ SIMPLEQ_INIT(&iface->ra_prefix_list);
+
+ SIMPLEQ_INSERT_TAIL(&conf->ra_iface_list, iface, entry);
+
+ return (iface);
+}
+
+void
+clear_config(struct rad_conf *xconf)
+{
+ struct ra_iface_conf *iface;
+
+ while((iface = SIMPLEQ_FIRST(&xconf->ra_iface_list)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&xconf->ra_iface_list, entry);
+ free(iface);
+ }
+
+ free(xconf);
+}
diff --git a/usr.sbin/rad/printconf.c b/usr.sbin/rad/printconf.c
new file mode 100644
index 00000000000..57ccb2ccaf5
--- /dev/null
+++ b/usr.sbin/rad/printconf.c
@@ -0,0 +1,102 @@
+/* $OpenBSD: printconf.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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 <netinet/in.h>
+#include <net/if.h>
+
+#include <arpa/inet.h>
+
+#include <event.h>
+#include <imsg.h>
+#include <stdio.h>
+
+#include "rad.h"
+
+const char* yesno(int);
+void print_ra_options(const char*, const struct ra_options_conf*);
+void print_prefix_options(const char*, const struct ra_prefix_conf*);
+
+const char*
+yesno(int flag)
+{
+ return flag ? "yes" : "no";
+}
+
+void
+print_ra_options(const char *indent, const struct ra_options_conf *ra_options)
+{
+ printf("%sdefault router %s\n", indent, yesno(ra_options->dfr));
+ printf("%shop limit %d\n", indent, ra_options->cur_hl);
+ printf("%smanaged address configuration %s\n", indent,
+ yesno(ra_options->m_flag));
+ printf("%sother configuration %s\n", indent, yesno(ra_options->o_flag));
+ printf("%srouter lifetime %d\n", indent, ra_options->router_lifetime);
+ printf("%sreachable time %u\n", indent, ra_options->reachable_time);
+ printf("%sretrans timer %u\n", indent, ra_options->retrans_timer);
+}
+
+void
+print_prefix_options(const char *indent, const struct ra_prefix_conf
+ *ra_prefix_conf)
+{
+ printf("%svalid lifetime %u\n", indent, ra_prefix_conf->vltime);
+ printf("%spreferred lifetime %u\n", indent, ra_prefix_conf->pltime);
+ printf("%son-link %s\n", indent, yesno(ra_prefix_conf->lflag));
+ printf("%sautonomous address-configuration %s\n", indent,
+ yesno(ra_prefix_conf->aflag));
+}
+
+void
+print_config(struct rad_conf *conf)
+{
+ struct ra_iface_conf *iface;
+ struct ra_prefix_conf *prefix;
+ char buf[INET6_ADDRSTRLEN], *bufp;
+
+ print_ra_options("", &conf->ra_options);
+ printf("\n");
+
+ SIMPLEQ_FOREACH(iface, &conf->ra_iface_list, entry) {
+ printf("interface %s {\n", iface->name);
+
+ print_ra_options("\t", &iface->ra_options);
+
+ if (iface->autoprefix) {
+ printf("\tauto prefix {\n");
+ print_prefix_options("\t\t", iface->autoprefix);
+ printf("\t}\n");
+ } else
+ printf("\tno auto prefix\n");
+
+ SIMPLEQ_FOREACH(prefix, &iface->ra_prefix_list, entry) {
+ bufp = inet_net_ntop(AF_INET6, &prefix->prefix,
+ prefix->prefixlen, buf, sizeof(buf));
+ printf("\tprefix %s {\n", bufp);
+ print_prefix_options("\t\t", prefix);
+ printf("\t}\n");
+ }
+
+ printf("}\n");
+ }
+}
diff --git a/usr.sbin/rad/rad.8 b/usr.sbin/rad/rad.8
new file mode 100644
index 00000000000..c1017ae062d
--- /dev/null
+++ b/usr.sbin/rad/rad.8
@@ -0,0 +1,147 @@
+.\" $OpenBSD: rad.8,v 1.1 2018/07/10 16:39:54 florian Exp $
+.\"
+.\" Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+.\" Copyright (c) 2016 Kenneth R Westerback <kwesterback@gmail.com>
+.\"
+.\" 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: July 10 2018 $
+.Dt RAD 8
+.Os
+.Sh NAME
+.Nm rad
+.Nd router advertisement daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl f Ar file
+.Op Fl s Ar socket
+.Sh DESCRIPTION
+.Nm
+is an IPv6 router advertisement daemon.
+It periodically sends IPv6 router advertisement messages with prefix
+and default router informations.
+Clients like
+.Xr slaacd 8
+use these to configure IPv6 addresses on network interfaces and set default
+routes.
+Additionally it listens for IPv6 router solicitation messages and responds
+with router advertisements.
+.Pp
+.Nm
+is usually started at boot time, and can be enabled by
+setting the following in
+.Pa /etc/rc.conf.local :
+.Pp
+.Dl rad_flags=\&"\&"
+.Pp
+See
+.Xr rc 8
+and
+.Xr rc.conf 8
+for more information on the boot process
+and enabling daemons.
+.Pp
+A running
+.Nm
+can be controlled with the
+.Xr ractl 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.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/rad.sockXX" -compact
+.It Pa /etc/rad.conf
+Default
+.Nm
+configuration file.
+.It Pa /var/run/rad.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr ractl 8 .
+.El
+.Sh SEE ALSO
+.Xr rad.conf 5 ,
+.Xr ractl 8
+.Sh STANDARDS
+.Rs
+.%A R. Draves
+.%A D. Thaler
+.%D November 2005
+.%R RFC 4191
+.%T Default Router Preferences and More-Specific Routes
+.Re
+.Pp
+.Rs
+.%A R. Hinden
+.%A S. Deering
+.%D February 2006
+.%R RFC 4291
+.%T IP Version 6 Addressing Architecture
+.Re
+.Pp
+.Rs
+.%A T. Narten
+.%A E. Nordmark
+.%A W. Simpson
+.%A H. Soliman
+.%D September 2007
+.%R RFC 4861
+.%T Neighbor Discovery for IP version 6 (IPv6)
+.Re
+.Pp
+.Rs
+.%A A. Yourtchenko
+.%A L. Colitti
+.%D February 2016
+.%R RFC 7772
+.%T Reducing Energy Consumption of Router Advertisements
+.Re
+.Pp
+.Rs
+.%A J. Jeong
+.%A S. Park
+.%A L. Beloeil
+.%A S. Madanapalli
+.%D March 2017
+.%R RFC 8106
+.%T IPv6 Router Advertisement Options for DNS Configuration
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 6.4 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Florian Obser Aq Mt florian@openbsd.org .
diff --git a/usr.sbin/rad/rad.c b/usr.sbin/rad/rad.c
new file mode 100644
index 00000000000..ae3a4238817
--- /dev/null
+++ b/usr.sbin/rad/rad.c
@@ -0,0 +1,783 @@
+/* $OpenBSD: rad.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet6/in6_var.h>
+#include <netinet/icmp6.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "rad.h"
+#include "frontend.h"
+#include "engine.h"
+#include "control.h"
+
+const char* imsg_type_name[] = {
+ "IMSG_NONE",
+ "IMSG_CTL_LOG_VERBOSE",
+ "IMSG_CTL_RELOAD",
+ "IMSG_CTL_SHOW_ENGINE_INFO",
+ "IMSG_CTL_SHOW_FRONTEND_INFO",
+ "IMSG_CTL_SHOW_MAIN_INFO",
+ "IMSG_CTL_END",
+ "IMSG_RECONF_CONF",
+ "IMSG_RECONF_RA_IFACE",
+ "IMSG_RECONF_RA_AUTOPREFIX",
+ "IMSG_RECONF_RA_PREFIX",
+ "IMSG_RECONF_END",
+ "IMSG_ICMP6SOCK",
+ "IMSG_STARTUP",
+ "IMSG_STARTUP_DONE",
+ "IMSG_SOCKET_IPC",
+};
+
+__dead void usage(void);
+__dead void main_shutdown(void);
+
+void main_sig_handler(int, short, void *);
+
+static pid_t start_child(int, char *, int, int, int, char *);
+
+void main_dispatch_frontend(int, short, void *);
+void main_dispatch_engine(int, short, void *);
+
+static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *);
+static int main_imsg_send_config(struct rad_conf *);
+
+int main_reload(void);
+int main_sendboth(enum imsg_type, void *, uint16_t);
+void main_showinfo_ctl(struct imsg *);
+
+void free_ra_iface_conf(struct ra_iface_conf *);
+void in6_prefixlen2mask(struct in6_addr *, int len);
+
+struct rad_conf *main_conf;
+struct imsgev *iev_frontend;
+struct imsgev *iev_engine;
+char *conffile;
+char *csock;
+
+pid_t frontend_pid;
+pid_t engine_pid;
+
+uint32_t cmd_opts;
+
+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;
+ struct icmp6_filter filt;
+ int ch;
+ int debug = 0, engine_flag = 0, frontend_flag = 0;
+ char *saved_argv0;
+ int pipe_main2frontend[2];
+ int pipe_main2engine[2];
+ int icmp6sock, on = 1;
+
+ conffile = CONF_FILE;
+ csock = RAD_SOCKET;
+
+ log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
+ log_setverbose(1);
+
+ saved_argv0 = argv[0];
+ if (saved_argv0 == NULL)
+ saved_argv0 = "rad";
+
+ 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':
+ cmd_opts |= OPT_NOACTION;
+ break;
+ case 's':
+ csock = optarg;
+ break;
+ case 'v':
+ if (cmd_opts & OPT_VERBOSE)
+ cmd_opts |= OPT_VERBOSE2;
+ cmd_opts |= OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc > 0 || (engine_flag && frontend_flag))
+ usage();
+
+ if (engine_flag)
+ engine(debug, cmd_opts & OPT_VERBOSE);
+ else if (frontend_flag)
+ frontend(debug, cmd_opts & OPT_VERBOSE, csock);
+
+ /* parse config file */
+ if ((main_conf = parse_config(conffile)) == NULL) {
+ exit(1);
+ }
+
+ if (cmd_opts & OPT_NOACTION) {
+ if (cmd_opts & OPT_VERBOSE)
+ print_config(main_conf);
+ else
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ /* Check for root privileges. */
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ /* Check for assigned daemon user */
+ if (getpwnam(RAD_USER) == NULL)
+ errx(1, "unknown user %s", RAD_USER);
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(cmd_opts & OPT_VERBOSE);
+
+ if (!debug)
+ daemon(1, 0);
+
+ log_info("startup");
+
+ 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, cmd_opts & OPT_VERBOSE, NULL);
+ frontend_pid = start_child(PROC_FRONTEND, saved_argv0,
+ pipe_main2frontend[1], debug, cmd_opts & OPT_VERBOSE, csock);
+
+ rad_process = PROC_MAIN;
+ log_procinit(log_procnames[rad_process]);
+
+ 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 ((icmp6sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC,
+ IPPROTO_ICMPV6)) < 0)
+ fatal("ICMPv6 socket");
+
+ if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0)
+ fatal("IPV6_RECVPKTINFO");
+
+ if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on,
+ sizeof(on)) < 0)
+ fatal("IPV6_RECVHOPLIMIT");
+
+ /* only router advertisements and solicitations */
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filt);
+ if (setsockopt(icmp6sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) == -1)
+ fatal("ICMP6_FILTER");
+
+ main_imsg_compose_frontend_fd(IMSG_ICMP6SOCK, 0, icmp6sock);
+
+ main_imsg_send_config(main_conf);
+
+ if (pledge("stdio rpath cpath sendfd", NULL) == -1)
+ fatal("pledge");
+
+ 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);
+
+ control_cleanup(csock);
+
+ log_info("terminating");
+ exit(0);
+}
+
+static pid_t
+start_child(int p, char *argv0, int fd, int debug, int verbose, char *sockname)
+{
+ 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 (dup2(fd, 3) == -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 (sockname) {
+ argv[argc++] = "-s";
+ argv[argc++] = sockname;
+ }
+ 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;
+ ssize_t n;
+ int shut = 0, 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_STARTUP_DONE:
+ if (pledge("stdio rpath cpath", NULL) == -1)
+ fatal("pledge");
+ 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:
+ /* Already checked by frontend. */
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ case IMSG_CTL_SHOW_MAIN_INFO:
+ main_showinfo_ctl(&imsg);
+ break;
+ default:
+ log_debug("%s: error handling imsg %s", __func__,
+ imsg_type_name[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) {
+ 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_imsg_compose_frontend(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ if (iev_frontend)
+ imsg_compose_event(iev_frontend, type, 0, pid, -1, data,
+ datalen);
+}
+
+void
+main_imsg_compose_frontend_fd(int type, pid_t pid, int fd)
+{
+ if (iev_frontend)
+ imsg_compose_event(iev_frontend, type, 0, pid, fd, NULL, 0);
+}
+
+
+void
+main_imsg_compose_engine(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ if (iev_engine)
+ imsg_compose_event(iev_engine, type, 0, pid, -1, data,
+ datalen);
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+ pid_t pid, int fd, void *data, uint16_t datalen)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data,
+ datalen)) != -1)
+ imsg_event_add(iev);
+
+ return (ret);
+}
+
+static int
+main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf,
+ struct imsgbuf *engine_buf)
+{
+ int pipe_frontend2engine[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_frontend2engine) == -1)
+ return (-1);
+
+ if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC, 0, 0,
+ pipe_frontend2engine[0], NULL, 0) == -1)
+ return (-1);
+ if (imsg_compose(engine_buf, IMSG_SOCKET_IPC, 0, 0,
+ pipe_frontend2engine[1], NULL, 0) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+main_reload(void)
+{
+ struct rad_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 rad_conf *xconf)
+{
+ struct ra_iface_conf *ra_iface_conf;
+ struct ra_prefix_conf *ra_prefix_conf;
+
+ /* Send fixed part of config to children. */
+ if (main_sendboth(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1)
+ return (-1);
+
+ /* Send the interface list to children. */
+ SIMPLEQ_FOREACH(ra_iface_conf, &xconf->ra_iface_list, entry) {
+ if (main_sendboth(IMSG_RECONF_RA_IFACE, ra_iface_conf,
+ sizeof(*ra_iface_conf)) == -1)
+ return (-1);
+ if (ra_iface_conf->autoprefix) {
+ if (main_sendboth(IMSG_RECONF_RA_AUTOPREFIX,
+ ra_iface_conf->autoprefix,
+ sizeof(*ra_iface_conf->autoprefix)) == -1)
+ return (-1);
+ }
+ SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface_conf->ra_prefix_list,
+ entry) {
+ if (main_sendboth(IMSG_RECONF_RA_PREFIX,
+ ra_prefix_conf, sizeof(*ra_prefix_conf)) == -1)
+ return (-1);
+ }
+ }
+
+ /* Tell children the revised config is now complete. */
+ if (main_sendboth(IMSG_RECONF_END, NULL, 0) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+main_sendboth(enum imsg_type type, void *buf, uint16_t len)
+{
+ if (imsg_compose_event(iev_frontend, type, 0, 0, -1, buf, len) == -1)
+ return (-1);
+ if (imsg_compose_event(iev_engine, type, 0, 0, -1, buf, len) == -1)
+ return (-1);
+ return (0);
+}
+
+void
+main_showinfo_ctl(struct imsg *imsg)
+{
+ struct ctl_main_info cmi;
+ size_t n;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SHOW_MAIN_INFO:
+ memset(cmi.text, 0, sizeof(cmi.text));
+ n = strlcpy(cmi.text, "I'm a little teapot.",
+ sizeof(cmi.text));
+ if (n >= sizeof(cmi.text))
+ log_debug("%s: I was cut off!", __func__);
+ main_imsg_compose_frontend(IMSG_CTL_SHOW_MAIN_INFO,
+ imsg->hdr.pid, &cmi, sizeof(cmi));
+ memset(cmi.text, 0, sizeof(cmi.text));
+ n = strlcpy(cmi.text, "Full of sencha.",
+ sizeof(cmi.text));
+ if (n >= sizeof(cmi.text))
+ log_debug("%s: I was cut off!", __func__);
+ main_imsg_compose_frontend(IMSG_CTL_SHOW_MAIN_INFO,
+ imsg->hdr.pid, &cmi, sizeof(cmi));
+ main_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL,
+ 0);
+ break;
+ default:
+ log_debug("%s: error handling imsg", __func__);
+ break;
+ }
+}
+
+void
+free_ra_iface_conf(struct ra_iface_conf *ra_iface_conf)
+{
+ struct ra_prefix_conf *prefix;
+
+ if (!ra_iface_conf)
+ return;
+
+ free(ra_iface_conf->autoprefix);
+
+ while ((prefix = SIMPLEQ_FIRST(&ra_iface_conf->ra_prefix_list))
+ != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&ra_iface_conf->ra_prefix_list, entry);
+ free(prefix);
+ }
+
+ free(ra_iface_conf);
+}
+
+void
+merge_config(struct rad_conf *conf, struct rad_conf *xconf)
+{
+ struct ra_iface_conf *ra_iface_conf;
+
+ conf->ra_options = xconf->ra_options;
+
+ /* Remove & discard existing interfaces. */
+ while ((ra_iface_conf = SIMPLEQ_FIRST(&conf->ra_iface_list)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&conf->ra_iface_list, entry);
+ free_ra_iface_conf(ra_iface_conf);
+ }
+
+ /* Add new interfaces. */
+ while ((ra_iface_conf = SIMPLEQ_FIRST(&xconf->ra_iface_list)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&xconf->ra_iface_list, entry);
+ SIMPLEQ_INSERT_TAIL(&conf->ra_iface_list, ra_iface_conf, entry);
+ }
+
+ free(xconf);
+}
+
+struct rad_conf *
+config_new_empty(void)
+{
+ struct rad_conf *xconf;
+
+ xconf = calloc(1, sizeof(*xconf));
+ if (xconf == NULL)
+ fatal(NULL);
+
+ SIMPLEQ_INIT(&xconf->ra_iface_list);
+
+ xconf->ra_options.dfr = 1;
+ xconf->ra_options.cur_hl = 0;
+ xconf->ra_options.m_flag = 0;
+ xconf->ra_options.o_flag = 0;
+ xconf->ra_options.router_lifetime = 1800;
+ xconf->ra_options.reachable_time = 0;
+ xconf->ra_options.retrans_timer = 0;
+
+ return (xconf);
+}
+
+void
+config_clear(struct rad_conf *conf)
+{
+ struct rad_conf *xconf;
+
+ /* Merge current config with an empty config. */
+ xconf = config_new_empty();
+ merge_config(conf, xconf);
+
+ free(conf);
+}
+
+void mask_prefix(struct in6_addr* in6, int len)
+{
+ uint8_t bitmask[8] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+ int i, skip;
+
+ if (len < 0 || len > 128)
+ fatalx("invalid prefix length: %d", len);
+
+ skip = len / 8;
+
+ in6->s6_addr[skip] &= bitmask[len % 8];
+
+ for (i = skip + 1; i < 16; i++)
+ in6->s6_addr[i] = 0;
+}
+
+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;
+}
+
+const char*
+in6_to_str(struct in6_addr *in6)
+{
+
+ struct sockaddr_in6 sin6;
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_len = sizeof(sin6);
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = *in6;
+
+ return (sin6_to_str(&sin6));
+}
diff --git a/usr.sbin/rad/rad.conf.5 b/usr.sbin/rad/rad.conf.5
new file mode 100644
index 00000000000..4cd90836e94
--- /dev/null
+++ b/usr.sbin/rad/rad.conf.5
@@ -0,0 +1,148 @@
+.\" $OpenBSD: rad.conf.5,v 1.1 2018/07/10 16:39:54 florian Exp $
+.\"
+.\" Copyright (c) 2018 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: July 10 2018 $
+.Dt RAD.CONF 5
+.Os
+.Sh NAME
+.Nm rad.conf
+.Nd router advertisement daemon configuration file
+.Sh DESCRIPTION
+The
+.Xr rad 8
+daemon is an IPv6 router advertisement daemon.
+.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 Global Configuration
+Global settings for
+.Xr rad 8 .
+These are used as default values for
+.Ic interface
+definitions and can be overwritten in an
+.Ic interface
+block.
+.It Sy Interfaces
+.Xr rad 8
+sends IPv6 router advertisement messages.
+This section defines on which interfaces to advertise prefix information
+and their associated parameters.
+.El
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword.
+.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 GLOBAL CONFIGURATION
+The global configuration section sets defaults for router advertisement
+messages.
+These can be overwritten in interface blocks.
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic default router Pq Ic yes Ns | Ns Ic no
+act as a default router or not.
+Default is yes.
+.It Ic hop limit Ar hops
+specifies the diameter of the internet.
+Default is 0 meaning unspecified by this router.
+.It managed address configuration Pq Ic yes Ns | Ns Ic no
+if set to yes indicates that stateless address configuration prefixes are
+not available and hosts should consult DHCPv6.
+Default is no.
+.It other configuration Pq Ic yes Ns | Ns Ic no
+if set to yes hosts should consult DHCPv6 for additional configuration
+like NTP servers or DNS resolvers.
+.It Ic router lifetime Ar seconds
+number of seconds this router is a valid default router after receiving
+a router advertisement message.
+Default 1800 seconds.
+.\" .It Ic reachable time Ar number
+.\" XXX
+.\" .It Ic retrans timer Ar number
+.\" XXX
+.El
+.Sh INTERFACES
+A list of interfaces to send advertisments on.
+.Bl -tag -width interface
+.It Ic interface Ar name Op { prefix list }
+.El
+.Pp
+Options set in the global section can be overwritten inside an interface
+block.
+In addition an interface block can contain a list of prefixes:
+.Bl -tag -width prefix
+.It Oo Ic no Oc Ic auto prefix Op { prefix options }
+.It Ic prefix Ar prefix Op { prefix options }
+.El
+.Pp
+The default is to discover prefixes to announce by inspecting the IPv6
+addresses configured on an interface.
+This can be disabled with
+.Ic no auto prefix .
+.Pp
+.Ic prefix
+options are as follows:
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic autonomous address-configuration Pq Ic yes Ns | Ns Ic no
+this prefix can be used to generate IPv6 addresses.
+The default is yes.
+.It Ic on-link Pq Ic yes Ns | Ns Ic no
+this prefix shall be considered on-link.
+The default is yes.
+.It Ic preferred lifetime Ar seconds
+preferred lifetime (pltime) in seconds for addresses generated from this
+prefix.
+The default is 604800.
+.It Ic valid lifetime Ar seconds
+valid lifetime (vltime) in seconds for addresses generated from this
+prefix.
+The default is 2592000.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/rad.conf" -compact
+.It Pa /etc/rad.conf
+.Xr rad 8
+configuration file
+.El
+.Sh EXAMPLES
+.Bd -literal -offset indent
+interface ix1
+.Ed
+.Sh SEE ALSO
+.Xr ractl 8 ,
+.Xr rad 8 ,
+.Xr rc.conf.local 8
+.Sh HISTORY
+The
+.Nm
+file format first appeared in
+.Ox 6.4 .
diff --git a/usr.sbin/rad/rad.h b/usr.sbin/rad/rad.h
new file mode 100644
index 00000000000..d670d1d18ea
--- /dev/null
+++ b/usr.sbin/rad/rad.h
@@ -0,0 +1,161 @@
+/* $OpenBSD: rad.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 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 CONF_FILE "/etc/rad.conf"
+#define RAD_SOCKET "/var/run/rad.sock"
+#define RAD_USER "_rtadvd" /* XXX */
+
+#define OPT_VERBOSE 0x00000001
+#define OPT_VERBOSE2 0x00000002
+#define OPT_NOACTION 0x00000004
+
+#define RAD_MAXTEXT 256
+
+enum {
+ PROC_MAIN,
+ PROC_ENGINE,
+ PROC_FRONTEND
+} rad_process;
+
+static const char * const log_procnames[] = {
+ "main",
+ "engine",
+ "frontend",
+};
+
+struct imsgev {
+ struct imsgbuf ibuf;
+ void (*handler)(int, short, void *);
+ struct event ev;
+ short events;
+};
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_LOG_VERBOSE,
+ IMSG_CTL_RELOAD,
+ IMSG_CTL_SHOW_ENGINE_INFO,
+ IMSG_CTL_SHOW_FRONTEND_INFO,
+ IMSG_CTL_SHOW_MAIN_INFO,
+ IMSG_CTL_END,
+ IMSG_RECONF_CONF,
+ IMSG_RECONF_RA_IFACE,
+ IMSG_RECONF_RA_AUTOPREFIX,
+ IMSG_RECONF_RA_PREFIX,
+ IMSG_RECONF_END,
+ IMSG_ICMP6SOCK,
+ IMSG_STARTUP,
+ IMSG_STARTUP_DONE,
+ IMSG_RA_RS,
+ IMSG_SEND_RA,
+ IMSG_SOCKET_IPC
+};
+
+extern const char* imsg_type_name[];
+
+/* RFC 4861 Section 4.2 */
+struct ra_options_conf {
+ int dfr; /* is default router? */
+ int cur_hl; /* current hop limit */
+ int m_flag; /* managed address conf flag */
+ int o_flag; /* other conf flag */
+ int router_lifetime; /* default router lifetime */
+ uint32_t reachable_time;
+ uint32_t retrans_timer;
+};
+
+/* RFC 4861 Section 4.6.2 */
+struct ra_prefix_conf {
+ SIMPLEQ_ENTRY(ra_prefix_conf) entry;
+ struct in6_addr prefix; /* prefix */
+ int prefixlen; /* prefix length */
+ uint32_t vltime; /* valid lifetime */
+ uint32_t pltime; /* prefered lifetime */
+ int lflag; /* on-link flag*/
+ int aflag; /* autonom. addr flag */
+};
+
+struct ra_iface_conf {
+ SIMPLEQ_ENTRY(ra_iface_conf) entry;
+ struct ra_options_conf ra_options;
+ struct ra_prefix_conf *autoprefix;
+ SIMPLEQ_HEAD(ra_prefix_conf_head,
+ ra_prefix_conf) ra_prefix_list;
+ char name[IF_NAMESIZE];
+};
+
+struct rad_conf {
+ struct ra_options_conf ra_options;
+ SIMPLEQ_HEAD(ra_iface_conf_head, ra_iface_conf) ra_iface_list;
+};
+
+struct ctl_frontend_info {
+ int yesno;
+ int integer;
+ char global_text[RAD_MAXTEXT];
+};
+
+struct ctl_engine_info {
+ char name[IF_NAMESIZE];
+ int yesno;
+ int integer;
+};
+
+struct ctl_main_info {
+ char text[RAD_MAXTEXT];
+};
+
+struct imsg_ra_rs {
+ uint32_t if_index;
+ struct sockaddr_in6 from;
+ ssize_t len;
+ uint8_t packet[1500];
+};
+
+struct imsg_send_ra {
+ uint32_t if_index;
+ struct sockaddr_in6 to;
+};
+
+extern uint32_t cmd_opts;
+
+/* rad.c */
+void main_imsg_compose_frontend(int, pid_t, void *, uint16_t);
+void main_imsg_compose_frontend_fd(int, pid_t, int);
+
+void main_imsg_compose_engine(int, pid_t, void *, uint16_t);
+void merge_config(struct rad_conf *, struct rad_conf *);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
+ int, void *, uint16_t);
+
+struct rad_conf *config_new_empty(void);
+void config_clear(struct rad_conf *);
+
+void mask_prefix(struct in6_addr*, int len);
+const char *sin6_to_str(struct sockaddr_in6 *);
+const char *in6_to_str(struct in6_addr *);
+
+/* printconf.c */
+void print_config(struct rad_conf *);
+
+/* parse.y */
+struct rad_conf *parse_config(char *);
+int cmdline_symset(char *);