summaryrefslogtreecommitdiff
path: root/sbin
diff options
context:
space:
mode:
authorFlorian Obser <florian@cvs.openbsd.org>2017-06-03 10:00:30 +0000
committerFlorian Obser <florian@cvs.openbsd.org>2017-06-03 10:00:30 +0000
commitf5d3e9ec6a694be409202febd15b21d110c9ccd2 (patch)
tree6da146b33744ff127b42eec00f54d21a3352e87f /sbin
parentd2e739346f9e461b7a5afbecfc9a5fb2644db1ab (diff)
Move slaacd to /sbin
jca points out that all the other interface configuration tools live there (like ifconfig or dhclient). Furthermore it starts so early in the boot process that /usr might not be mounted yet if it's a nfs filesystem. sthen and deraadt agree
Diffstat (limited to 'sbin')
-rw-r--r--sbin/Makefile4
-rw-r--r--sbin/slaacd/Makefile21
-rw-r--r--sbin/slaacd/control.c301
-rw-r--r--sbin/slaacd/control.h35
-rw-r--r--sbin/slaacd/engine.c2177
-rw-r--r--sbin/slaacd/engine.h47
-rw-r--r--sbin/slaacd/frontend.c826
-rw-r--r--sbin/slaacd/frontend.h26
-rw-r--r--sbin/slaacd/log.c199
-rw-r--r--sbin/slaacd/log.h46
-rw-r--r--sbin/slaacd/slaacd.8116
-rw-r--r--sbin/slaacd/slaacd.c768
-rw-r--r--sbin/slaacd/slaacd.h188
13 files changed, 4752 insertions, 2 deletions
diff --git a/sbin/Makefile b/sbin/Makefile
index 8100ac541f1..f93db6aa573 100644
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.105 2016/09/17 18:37:01 florian Exp $
+# $OpenBSD: Makefile,v 1.106 2017/06/03 10:00:29 florian Exp $
SUBDIR= atactl badsect bioctl clri dhclient \
disklabel dmesg dump dumpfs fdisk fsck fsck_ext2fs fsck_ffs \
@@ -9,7 +9,7 @@ SUBDIR= atactl badsect bioctl clri dhclient \
mount_vnd mountd ncheck_ffs newfs newfs_ext2fs newfs_msdos \
nfsd nologin pdisk pfctl pflogd ping quotacheck \
reboot restore route savecore scan_ffs \
- scsi shutdown swapctl sysctl ttyflags tunefs \
+ scsi slaacd shutdown swapctl sysctl ttyflags tunefs \
umount wsconsctl
.include <bsd.subdir.mk>
diff --git a/sbin/slaacd/Makefile b/sbin/slaacd/Makefile
new file mode 100644
index 00000000000..989f432c621
--- /dev/null
+++ b/sbin/slaacd/Makefile
@@ -0,0 +1,21 @@
+# $OpenBSD: Makefile,v 1.1 2017/06/03 10:00:29 florian Exp $
+
+PROG= slaacd
+SRCS= control.c engine.c frontend.c log.c slaacd.c
+
+MAN= slaacd.8
+
+#DEBUG= -g -DDEBUG=3 -O0
+
+CFLAGS+= -DSKIP_PROPOSAL
+
+CFLAGS+= -Wall -I${.CURDIR}
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare
+YFLAGS=
+LDADD+= -levent -lutil
+DPADD+= ${LIBEVENT} ${LIBUTIL}
+
+.include <bsd.prog.mk>
diff --git a/sbin/slaacd/control.c b/sbin/slaacd/control.c
new file mode 100644
index 00000000000..76b0f3b15ea
--- /dev/null
+++ b/sbin/slaacd/control.c
@@ -0,0 +1,301 @@
+/* $OpenBSD: control.c,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <md5.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "slaacd.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_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, 0,
+ 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_INTERFACE_INFO:
+ c->iev.ibuf.pid = imsg.hdr.pid;
+ frontend_imsg_compose_engine(imsg.hdr.type, 0,
+ imsg.hdr.pid, imsg.data, imsg.hdr.len -
+ IMSG_HEADER_SIZE);
+ break;
+ case IMSG_CTL_SEND_SOLICITATION:
+ c->iev.ibuf.pid = imsg.hdr.pid;
+ frontend_imsg_compose_engine(imsg.hdr.type, 0,
+ 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/sbin/slaacd/control.h b/sbin/slaacd/control.h
new file mode 100644
index 00000000000..49d6bd8105b
--- /dev/null
+++ b/sbin/slaacd/control.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: control.h,v 1.1 2017/06/03 10:00:29 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/sbin/slaacd/engine.c b/sbin/slaacd/engine.c
new file mode 100644
index 00000000000..3231599e2d8
--- /dev/null
+++ b/sbin/slaacd/engine.c
@@ -0,0 +1,2177 @@
+/* $OpenBSD: engine.c,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+/*
+ * 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/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet6/nd6.h>
+#include <netinet/icmp6.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "slaacd.h"
+#include "engine.h"
+
+#define MAX_RTR_SOLICITATION_DELAY 1
+#define MAX_RTR_SOLICITATION_DELAY_USEC MAX_RTR_SOLICITATION_DELAY * 1000000
+#define RTR_SOLICITATION_INTERVAL 4
+#define MAX_RTR_SOLICITATIONS 3
+
+enum if_state {
+ IF_DOWN,
+ IF_DELAY,
+ IF_PROBE,
+ IF_IDLE,
+};
+
+const char* if_state_name[] = {
+ "IF_DOWN",
+ "IF_DELAY",
+ "IF_PROBE",
+ "IF_IDLE",
+};
+
+enum proposal_state {
+ PROPOSAL_NOT_CONFIGURED,
+ PROPOSAL_SENT,
+ PROPOSAL_CONFIGURED,
+ PROPOSAL_NEARLY_EXPIRED,
+ PROPOSAL_WITHDRAWN,
+};
+
+const char* proposal_state_name[] = {
+ "NOT_CONFIGURED",
+ "SENT",
+ "CONFIGURED",
+ "NEARLY_EXPIRED",
+ "WITHDRAWN",
+};
+
+const char* rpref_name[] = {
+ "Low",
+ "Medium",
+ "High",
+};
+
+struct radv_prefix {
+ LIST_ENTRY(radv_prefix) entries;
+ struct in6_addr prefix;
+ uint8_t prefix_len; /*XXX int */
+ int onlink;
+ int autonomous;
+ uint32_t vltime;
+ uint32_t pltime;
+};
+
+struct radv_rdns {
+ LIST_ENTRY(radv_rdns) entries;
+ struct in6_addr rdns;
+};
+
+struct radv_dnssl {
+ LIST_ENTRY(radv_dnssl) entries;
+ char dnssl[SLAACD_MAX_DNSSL];
+};
+
+struct radv {
+ LIST_ENTRY(radv) entries;
+ struct sockaddr_in6 from;
+ struct timespec when;
+ struct timespec uptime;
+ struct event timer;
+ uint32_t min_lifetime;
+ uint8_t curhoplimit;
+ int managed;
+ int other;
+ enum rpref rpref;
+ uint16_t router_lifetime; /* in seconds */
+ uint32_t reachable_time; /* in milliseconds */
+ uint32_t retrans_time; /* in milliseconds */
+ LIST_HEAD(, radv_prefix) prefixes;
+ uint32_t rdns_lifetime;
+ LIST_HEAD(, radv_rdns) rdns_servers;
+ uint32_t dnssl_lifetime;
+ LIST_HEAD(, radv_dnssl) dnssls;
+};
+
+struct address_proposal {
+ LIST_ENTRY(address_proposal) entries;
+ struct event timer;
+ int64_t id;
+ enum proposal_state state;
+ int next_timeout;
+ int timeout_count;
+ struct timespec when;
+ struct timespec uptime;
+ uint32_t if_index;
+ struct ether_addr hw_address;
+ struct sockaddr_in6 addr;
+ struct in6_addr mask;
+ struct in6_addr prefix;
+ int privacy;
+ uint8_t prefix_len;
+ uint32_t vltime;
+ uint32_t pltime;
+};
+
+struct dfr_proposal {
+ LIST_ENTRY(dfr_proposal) entries;
+ struct event timer;
+ int64_t id;
+ enum proposal_state state;
+ int next_timeout;
+ int timeout_count;
+ struct timespec when;
+ struct timespec uptime;
+ uint32_t if_index;
+ struct sockaddr_in6 addr;
+ uint32_t router_lifetime;
+ enum rpref rpref;
+};
+
+struct slaacd_iface {
+ LIST_ENTRY(slaacd_iface) entries;
+ enum if_state state;
+ struct event timer;
+ int probes;
+ uint32_t if_index;
+ int running;
+ int autoconfprivacy;
+ struct ether_addr hw_address;
+ struct sockaddr_in6 ll_address;
+ LIST_HEAD(, radv) radvs;
+ LIST_HEAD(, address_proposal) addr_proposals;
+ LIST_HEAD(, dfr_proposal) dfr_proposals;
+};
+
+LIST_HEAD(, slaacd_iface) slaacd_interfaces;
+
+__dead void engine_shutdown(void);
+void engine_sig_handler(int sig, short, void *);
+void engine_dispatch_frontend(int, short, void *);
+void engine_dispatch_main(int, short, void *);
+void send_interface_info(struct slaacd_iface *, pid_t);
+void engine_showinfo_ctl(struct imsg *, uint32_t);
+struct slaacd_iface *get_slaacd_iface_by_id(uint32_t);
+void remove_slaacd_iface(uint32_t);
+void free_ra(struct radv *);
+void parse_ra(struct slaacd_iface *, struct imsg_ra *);
+void gen_addr(struct slaacd_iface *, struct radv_prefix *,
+ struct address_proposal *, int);
+void gen_address_proposal(struct slaacd_iface *, struct
+ radv *, struct radv_prefix *, int);
+void configure_address(struct address_proposal *);
+void in6_prefixlen2mask(struct in6_addr *, int len);
+void gen_dfr_proposal(struct slaacd_iface *, struct
+ radv *);
+void configure_dfr(struct dfr_proposal *);
+void free_dfr_proposal(struct dfr_proposal *);
+void withdraw_dfr(struct dfr_proposal *);
+void debug_log_ra(struct imsg_ra *);
+char *parse_dnssl(char *, int);
+void update_iface_ra(struct slaacd_iface *, struct radv *);
+void send_proposal(struct imsg_proposal *);
+void start_probe(struct slaacd_iface *);
+void address_proposal_timeout(int, short, void *);
+void dfr_proposal_timeout(int, short, void *);
+void iface_timeout(int, short, void *);
+struct radv *find_ra(struct slaacd_iface *, struct sockaddr_in6 *);
+struct address_proposal *find_address_proposal_by_id(struct slaacd_iface *,
+ int64_t);
+struct address_proposal *find_address_proposal_by_addr(struct slaacd_iface *,
+ struct sockaddr_in6 *);
+struct dfr_proposal *find_dfr_proposal_by_id(struct slaacd_iface *,
+ int64_t);
+void find_prefix(struct slaacd_iface *, struct
+ address_proposal *, struct radv **, struct
+ radv_prefix **);
+int engine_imsg_compose_main(int, pid_t, void *, uint16_t);
+uint32_t real_lifetime(struct timespec *, uint32_t);
+
+struct imsgev *iev_frontend;
+struct imsgev *iev_main;
+int64_t proposal_id;
+
+void
+engine_sig_handler(int sig, short event, void *arg)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ engine_shutdown();
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+void
+engine(int debug, int verbose)
+{
+ struct event ev_sigint, ev_sigterm;
+ struct passwd *pw;
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ if ((pw = getpwnam(SLAACD_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ slaacd_process = PROC_ENGINE;
+ setproctitle("%s", log_procnames[slaacd_process]);
+ log_procinit(log_procnames[slaacd_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);
+
+ LIST_INIT(&slaacd_interfaces);
+
+ event_dispatch();
+
+ engine_shutdown();
+}
+
+__dead void
+engine_shutdown(void)
+{
+ /* Close pipes. */
+ msgbuf_clear(&iev_frontend->ibuf.w);
+ close(iev_frontend->ibuf.fd);
+ msgbuf_clear(&iev_main->ibuf.w);
+ close(iev_main->ibuf.fd);
+
+ free(iev_frontend);
+ free(iev_main);
+
+ log_info("engine exiting");
+ exit(0);
+}
+
+int
+engine_imsg_compose_frontend(int type, pid_t pid, void *data,
+ uint16_t datalen)
+{
+ return (imsg_compose_event(iev_frontend, type, 0, pid, -1,
+ data, datalen));
+}
+
+int
+engine_imsg_compose_main(int type, pid_t pid, void *data,
+ uint16_t datalen)
+{
+ return (imsg_compose_event(iev_main, type, 0, pid, -1,
+ data, datalen));
+}
+
+void
+engine_dispatch_frontend(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ struct slaacd_iface *iface;
+ struct imsg_ra ra;
+ struct imsg_ifinfo imsg_ifinfo;
+ struct imsg_proposal_ack proposal_ack;
+ struct address_proposal *addr_proposal = NULL;
+ struct dfr_proposal *dfr_proposal = NULL;
+ struct imsg_del_addr del_addr;
+ ssize_t n;
+ int shut = 0, verbose;
+ uint32_t if_index;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_LOG_VERBOSE:
+ /* Already checked by frontend. */
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ case IMSG_CTL_SHOW_INTERFACE_INFO:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(if_index))
+ fatal("%s: IMSG_CTL_SHOW_INTERFACE_INFO wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&if_index, imsg.data, sizeof(if_index));
+ engine_showinfo_ctl(&imsg, if_index);
+ break;
+ case IMSG_UPDATE_IF:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(imsg_ifinfo))
+ fatal("%s: IMSG_UPDATE_IF wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
+
+ iface = get_slaacd_iface_by_id(imsg_ifinfo.if_index);
+ if (iface == NULL) {
+ if ((iface = calloc(1, sizeof(*iface))) == NULL)
+ fatal("calloc");
+ evtimer_set(&iface->timer, iface_timeout,
+ iface);
+ iface->if_index = imsg_ifinfo.if_index;
+ iface->running = imsg_ifinfo.running;
+ if (iface->running)
+ start_probe(iface);
+ else
+ iface->state = IF_DOWN;
+ iface->autoconfprivacy =
+ imsg_ifinfo.autoconfprivacy;
+ memcpy(&iface->hw_address,
+ &imsg_ifinfo.hw_address,
+ sizeof(struct ether_addr));
+ memcpy(&iface->ll_address,
+ &imsg_ifinfo.ll_address,
+ sizeof(struct sockaddr_in6));
+ LIST_INIT(&iface->radvs);
+ LIST_INSERT_HEAD(&slaacd_interfaces,
+ iface, entries);
+ LIST_INIT(&iface->addr_proposals);
+ LIST_INIT(&iface->dfr_proposals);
+ } else {
+ int need_refresh = 0;
+
+ if (iface->autoconfprivacy !=
+ imsg_ifinfo.autoconfprivacy) {
+ iface->autoconfprivacy =
+ imsg_ifinfo.autoconfprivacy;
+ need_refresh = 1;
+ }
+ if (memcmp(&iface->hw_address,
+ &imsg_ifinfo.hw_address,
+ sizeof(struct ether_addr)) != 0) {
+ memcpy(&iface->hw_address,
+ &imsg_ifinfo.hw_address,
+ sizeof(struct ether_addr));
+ need_refresh = 1;
+ }
+
+ if (iface->state != IF_DOWN &&
+ imsg_ifinfo.running && need_refresh)
+ start_probe(iface);
+
+ iface->running = imsg_ifinfo.running;
+ if (!iface->running) {
+ iface->state = IF_DOWN;
+ if (evtimer_pending(&iface->timer,
+ NULL))
+ evtimer_del(&iface->timer);
+ }
+
+ memcpy(&iface->ll_address,
+ &imsg_ifinfo.ll_address,
+ sizeof(struct sockaddr_in6));
+ }
+ break;
+ case IMSG_REMOVE_IF:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(if_index))
+ fatal("%s: IMSG_REMOVE_IF wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&if_index, imsg.data, sizeof(if_index));
+ remove_slaacd_iface(if_index);
+ break;
+ case IMSG_RA:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(ra))
+ fatal("%s: IMSG_RA wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&ra, imsg.data, sizeof(ra));
+ iface = get_slaacd_iface_by_id(ra.if_index);
+ if (iface != NULL)
+ parse_ra(iface, &ra);
+ break;
+ case IMSG_CTL_SEND_SOLICITATION:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(if_index))
+ fatal("%s: IMSG_CTL_SEND_SOLICITATION wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&if_index, imsg.data, sizeof(if_index));
+ iface = get_slaacd_iface_by_id(if_index);
+ if (iface == NULL)
+ log_warn("requested to send solicitation on "
+ "non-autoconf interface: %u", if_index);
+ else
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SEND_SOLICITATION, imsg.hdr.pid,
+ &iface->if_index, sizeof(iface->if_index));
+ break;
+ case IMSG_PROPOSAL_ACK:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(proposal_ack))
+ fatal("%s: IMSG_PROPOSAL_ACK wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&proposal_ack, imsg.data, sizeof(proposal_ack));
+ log_debug("%s: IMSG_PROPOSAL_ACK: %lld - %d", __func__,
+ proposal_ack.id, proposal_ack.pid);
+ if (proposal_ack.pid != getpid()) {
+ log_debug("IMSG_PROPOSAL_ACK: wrong pid, "
+ "ignoring");
+ break;
+ }
+
+ iface = get_slaacd_iface_by_id(proposal_ack.if_index);
+ if (iface == NULL) {
+ log_debug("IMSG_PROPOSAL_ACK: unknown interface"
+ ", ignoring");
+ break;
+ }
+
+ addr_proposal = find_address_proposal_by_id(iface,
+ proposal_ack.id);
+ if (addr_proposal == NULL) {
+ dfr_proposal = find_dfr_proposal_by_id(iface,
+ proposal_ack.id);
+ if (dfr_proposal == NULL) {
+ log_debug("IMSG_PROPOSAL_ACK: cannot "
+ "find proposal, ignoring");
+ break;
+ }
+ }
+ if (addr_proposal != NULL)
+ configure_address(addr_proposal);
+ else if (dfr_proposal != NULL)
+ configure_dfr(dfr_proposal);
+
+ break;
+ case IMSG_DEL_ADDRESS:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(del_addr))
+ fatal("%s: IMSG_DEL_ADDRESS wrong length: %d",
+ __func__, imsg.hdr.len);
+ memcpy(&del_addr, imsg.data, sizeof(del_addr));
+ iface = get_slaacd_iface_by_id(del_addr.if_index);
+ if (iface == NULL) {
+ log_debug("IMSG_DEL_ADDRESS: unknown interface"
+ ", ignoring");
+ break;
+ }
+
+ addr_proposal = find_address_proposal_by_addr(iface,
+ &del_addr.addr);
+
+ if (addr_proposal) {
+ /* XXX should we inform netcfgd? */
+ LIST_REMOVE(addr_proposal, entries);
+ evtimer_del(&addr_proposal->timer);
+ free(addr_proposal);
+ }
+
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+engine_dispatch_main(int fd, short event, void *bula)
+{
+ struct imsg imsg;
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ ssize_t n;
+ int shut = 0;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_SOCKET_IPC:
+ /*
+ * Setup pipe and event handler to the frontend
+ * process.
+ */
+ if (iev_frontend) {
+ 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);
+
+ if (pledge("stdio", NULL) == -1)
+ fatal("pledge");
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+send_interface_info(struct slaacd_iface *iface, pid_t pid)
+{
+ struct ctl_engine_info cei;
+ struct ctl_engine_info_ra cei_ra;
+ struct ctl_engine_info_ra_prefix cei_ra_prefix;
+ struct ctl_engine_info_ra_rdns cei_ra_rdns;
+ struct ctl_engine_info_ra_dnssl cei_ra_dnssl;
+ struct ctl_engine_info_address_proposal cei_addr_proposal;
+ struct ctl_engine_info_dfr_proposal cei_dfr_proposal;
+ struct radv *ra;
+ struct radv_prefix *prefix;
+ struct radv_rdns *rdns;
+ struct radv_dnssl *dnssl;
+ struct address_proposal *addr_proposal;
+ struct dfr_proposal *dfr_proposal;
+
+ memset(&cei, 0, sizeof(cei));
+ cei.if_index = iface->if_index;
+ cei.running = iface->running;
+ cei.autoconfprivacy = iface->autoconfprivacy;
+ memcpy(&cei.hw_address, &iface->hw_address, sizeof(struct ether_addr));
+ memcpy(&cei.ll_address, &iface->ll_address,
+ sizeof(struct sockaddr_in6));
+ engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei,
+ sizeof(cei));
+ LIST_FOREACH(ra, &iface->radvs, entries) {
+ memset(&cei_ra, 0, sizeof(cei_ra));
+ memcpy(&cei_ra.from, &ra->from, sizeof(cei_ra.from));
+ memcpy(&cei_ra.when, &ra->when, sizeof(cei_ra.when));
+ memcpy(&cei_ra.uptime, &ra->uptime, sizeof(cei_ra.uptime));
+ cei_ra.curhoplimit = ra->curhoplimit;
+ cei_ra.managed = ra->managed;
+ cei_ra.other = ra->other;
+ if (strlcpy(cei_ra.rpref, rpref_name[ra->rpref], sizeof(
+ cei_ra.rpref)) >= sizeof(cei_ra.rpref))
+ log_warn("truncated router preference");
+ cei_ra.router_lifetime = ra->router_lifetime;
+ cei_ra.reachable_time = ra->reachable_time;
+ cei_ra.retrans_time = ra->retrans_time;
+ engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO_RA,
+ pid, &cei_ra, sizeof(cei_ra));
+
+ LIST_FOREACH(prefix, &ra->prefixes, entries) {
+ memset(&cei_ra_prefix, 0, sizeof(cei_ra_prefix));
+
+ cei_ra_prefix.prefix = prefix->prefix;
+ cei_ra_prefix.prefix_len = prefix->prefix_len;
+ cei_ra_prefix.onlink = prefix->onlink;
+ cei_ra_prefix.autonomous = prefix->autonomous;
+ cei_ra_prefix.vltime = prefix->vltime;
+ cei_ra_prefix.pltime = prefix->pltime;
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX, pid,
+ &cei_ra_prefix, sizeof(cei_ra_prefix));
+ }
+
+ LIST_FOREACH(rdns, &ra->rdns_servers, entries) {
+ memset(&cei_ra_rdns, 0, sizeof(cei_ra_rdns));
+ memcpy(&cei_ra_rdns.rdns, &rdns->rdns,
+ sizeof(cei_ra_rdns.rdns));
+ cei_ra_rdns.lifetime = ra->rdns_lifetime;
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS, pid,
+ &cei_ra_rdns, sizeof(cei_ra_rdns));
+ }
+
+ LIST_FOREACH(dnssl, &ra->dnssls, entries) {
+ memset(&cei_ra_dnssl, 0, sizeof(cei_ra_dnssl));
+ memcpy(&cei_ra_dnssl.dnssl, &dnssl->dnssl,
+ sizeof(cei_ra_dnssl.dnssl));
+ cei_ra_dnssl.lifetime = ra->dnssl_lifetime;
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA_DNSSL, pid,
+ &cei_ra_dnssl, sizeof(cei_ra_dnssl));
+ }
+ }
+
+ if (!LIST_EMPTY(&iface->addr_proposals))
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS, pid, NULL, 0);
+
+ LIST_FOREACH(addr_proposal, &iface->addr_proposals, entries) {
+ memset(&cei_addr_proposal, 0, sizeof(cei_addr_proposal));
+ cei_addr_proposal.id = addr_proposal->id;
+ if(strlcpy(cei_addr_proposal.state,
+ proposal_state_name[addr_proposal->state],
+ sizeof(cei_addr_proposal.state)) >=
+ sizeof(cei_addr_proposal.state))
+ log_warn("truncated state name");
+ cei_addr_proposal.next_timeout = addr_proposal->next_timeout;
+ cei_addr_proposal.timeout_count = addr_proposal->timeout_count;
+ cei_addr_proposal.when = addr_proposal->when;
+ cei_addr_proposal.uptime = addr_proposal->uptime;
+ memcpy(&cei_addr_proposal.addr, &addr_proposal->addr, sizeof(
+ cei_addr_proposal.addr));
+ memcpy(&cei_addr_proposal.prefix, &addr_proposal->prefix,
+ sizeof(cei_addr_proposal.prefix));
+ cei_addr_proposal.prefix_len = addr_proposal->prefix_len;
+ cei_addr_proposal.privacy = addr_proposal->privacy;
+ cei_addr_proposal.vltime = addr_proposal->vltime;
+ cei_addr_proposal.pltime = addr_proposal->pltime;
+
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL, pid,
+ &cei_addr_proposal, sizeof(cei_addr_proposal));
+ }
+
+ if (!LIST_EMPTY(&iface->dfr_proposals))
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS, pid, NULL, 0);
+
+ LIST_FOREACH(dfr_proposal, &iface->dfr_proposals, entries) {
+ memset(&cei_dfr_proposal, 0, sizeof(cei_dfr_proposal));
+ cei_dfr_proposal.id = dfr_proposal->id;
+ if(strlcpy(cei_dfr_proposal.state,
+ proposal_state_name[dfr_proposal->state],
+ sizeof(cei_dfr_proposal.state)) >=
+ sizeof(cei_dfr_proposal.state))
+ log_warn("truncated state name");
+ cei_dfr_proposal.next_timeout = dfr_proposal->next_timeout;
+ cei_dfr_proposal.timeout_count = dfr_proposal->timeout_count;
+ cei_dfr_proposal.when = dfr_proposal->when;
+ cei_dfr_proposal.uptime = dfr_proposal->uptime;
+ memcpy(&cei_dfr_proposal.addr, &dfr_proposal->addr, sizeof(
+ cei_dfr_proposal.addr));
+ cei_dfr_proposal.router_lifetime =
+ dfr_proposal->router_lifetime;
+ if(strlcpy(cei_dfr_proposal.rpref,
+ rpref_name[dfr_proposal->rpref],
+ sizeof(cei_dfr_proposal.rpref)) >=
+ sizeof(cei_dfr_proposal.rpref))
+ log_warn("truncated router preference");
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL, pid,
+ &cei_dfr_proposal, sizeof(cei_dfr_proposal));
+ }
+}
+
+void
+engine_showinfo_ctl(struct imsg *imsg, uint32_t if_index)
+{
+ struct slaacd_iface *iface;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SHOW_INTERFACE_INFO:
+ if (if_index == 0) {
+ LIST_FOREACH (iface, &slaacd_interfaces, entries)
+ send_interface_info(iface, imsg->hdr.pid);
+ } else {
+ if ((iface = get_slaacd_iface_by_id(if_index)) != NULL)
+ send_interface_info(iface, imsg->hdr.pid);
+ }
+ engine_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL,
+ 0);
+ break;
+ default:
+ log_debug("%s: error handling imsg", __func__);
+ break;
+ }
+}
+
+struct slaacd_iface*
+get_slaacd_iface_by_id(uint32_t if_index)
+{
+ struct slaacd_iface *iface;
+ LIST_FOREACH (iface, &slaacd_interfaces, entries) {
+ if (iface->if_index == if_index)
+ return (iface);
+ }
+
+ return (NULL);
+}
+
+void
+remove_slaacd_iface(uint32_t if_index)
+{
+ struct slaacd_iface *iface, *tiface;
+ struct radv *ra;
+ struct address_proposal *addr_proposal;
+ struct dfr_proposal *dfr_proposal;
+
+ LIST_FOREACH_SAFE (iface, &slaacd_interfaces, entries, tiface) {
+ if (iface->if_index == if_index) {
+ LIST_REMOVE(iface, entries);
+ while(!LIST_EMPTY(&iface->radvs)) {
+ ra = LIST_FIRST(&iface->radvs);
+ LIST_REMOVE(ra, entries);
+ free_ra(ra);
+ }
+ /* XXX inform netcfgd? */
+ while(!LIST_EMPTY(&iface->addr_proposals)) {
+ addr_proposal =
+ LIST_FIRST(&iface->addr_proposals);
+ LIST_REMOVE(addr_proposal, entries);
+ free(addr_proposal);
+ }
+ while(!LIST_EMPTY(&iface->dfr_proposals)) {
+ dfr_proposal =
+ LIST_FIRST(&iface->dfr_proposals);
+ free_dfr_proposal(dfr_proposal);
+ }
+ free(iface);
+ break;
+ }
+ }
+}
+
+void
+free_ra(struct radv *ra)
+{
+ struct radv_prefix *prefix;
+ struct radv_rdns *rdns;
+ struct radv_dnssl *dnssl;
+
+ if (ra == NULL)
+ return;
+
+ evtimer_del(&ra->timer);
+
+ while (!LIST_EMPTY(&ra->prefixes)) {
+ prefix = LIST_FIRST(&ra->prefixes);
+ LIST_REMOVE(prefix, entries);
+ free(prefix);
+ }
+
+ while (!LIST_EMPTY(&ra->rdns_servers)) {
+ rdns = LIST_FIRST(&ra->rdns_servers);
+ LIST_REMOVE(rdns, entries);
+ free(rdns);
+ }
+
+ while (!LIST_EMPTY(&ra->dnssls)) {
+ dnssl = LIST_FIRST(&ra->dnssls);
+ LIST_REMOVE(dnssl, entries);
+ free(dnssl);
+ }
+
+ free(ra);
+}
+
+void
+parse_ra(struct slaacd_iface *iface, struct imsg_ra *ra)
+{
+ struct nd_router_advert *nd_ra;
+ struct radv *radv;
+ struct radv_prefix *prefix;
+ struct radv_rdns *rdns;
+ struct radv_dnssl *ra_dnssl;
+ ssize_t len = ra->len;
+ char hbuf[NI_MAXHOST];
+ uint8_t *p;
+
+#if 0
+ debug_log_ra(ra);
+#endif
+
+ if (getnameinfo((struct sockaddr *)&ra->from, ra->from.sin6_len, hbuf,
+ sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get router IP");
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&ra->from.sin6_addr)) {
+ log_warn("RA from non link local address %s", hbuf);
+ return;
+ }
+
+ if ((size_t)len < sizeof(struct nd_router_advert)) {
+ log_warn("received too short message (%ld) from %s", len, hbuf);
+ return;
+ }
+
+ if ((radv = calloc(1, sizeof(*radv))) == NULL)
+ fatal("calloc");
+
+ LIST_INIT(&radv->prefixes);
+ LIST_INIT(&radv->rdns_servers);
+ LIST_INIT(&radv->dnssls);
+
+ radv->min_lifetime = UINT32_MAX;
+
+ p = ra->packet;
+ nd_ra = (struct nd_router_advert *)p;
+ len -= sizeof(struct nd_router_advert);
+ p += sizeof(struct nd_router_advert);
+
+ log_debug("ICMPv6 type(%d), code(%d) from %s of length %ld",
+ nd_ra->nd_ra_type, nd_ra->nd_ra_code, hbuf, len);
+
+ if (nd_ra->nd_ra_type != ND_ROUTER_ADVERT) {
+ log_warnx("invalid ICMPv6 type (%d) from %s", nd_ra->nd_ra_type,
+ hbuf);
+ goto err;
+ }
+
+ if (nd_ra->nd_ra_code != 0) {
+ log_warn("invalid ICMPv6 code (%d) from %s", nd_ra->nd_ra_code,
+ hbuf);
+ goto err;
+ }
+
+ memcpy(&radv->from, &ra->from, sizeof(ra->from));
+
+ if (clock_gettime(CLOCK_REALTIME, &radv->when))
+ fatal("clock_gettime");
+ if (clock_gettime(CLOCK_MONOTONIC, &radv->uptime))
+ fatal("clock_gettime");
+
+ radv->curhoplimit = nd_ra->nd_ra_curhoplimit;
+ radv->managed = nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED;
+ radv->other = nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER;
+
+ switch (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_RTPREF_MASK) {
+ case ND_RA_FLAG_RTPREF_HIGH:
+ radv->rpref=HIGH;
+ break;
+ case ND_RA_FLAG_RTPREF_LOW:
+ radv->rpref=LOW;
+ break;
+ case ND_RA_FLAG_RTPREF_MEDIUM:
+ /* fallthrough */
+ default:
+ radv->rpref=MEDIUM;
+ break;
+ }
+ radv->router_lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
+ if (radv->router_lifetime != 0)
+ radv->min_lifetime = radv->router_lifetime;
+ radv->reachable_time = ntohl(nd_ra->nd_ra_reachable);
+ radv->retrans_time = ntohl(nd_ra->nd_ra_retransmit);
+
+ while ((size_t)len >= sizeof(struct nd_opt_hdr)) {
+ struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p;
+ struct nd_opt_prefix_info *prf;
+ struct nd_opt_rdnss *rdnss;
+ struct nd_opt_dnssl *dnssl;
+ struct in6_addr *in6;
+ int i;
+ char *nssl;
+
+ 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);
+ goto err;
+ }
+
+ switch (nd_opt_hdr->nd_opt_type) {
+ case ND_OPT_PREFIX_INFORMATION:
+ if (nd_opt_hdr->nd_opt_len != 4) {
+ log_warn("invalid ND_OPT_PREFIX_INFORMATION: "
+ "len != 4");
+ goto err;
+ }
+
+ if ((prefix = calloc(1, sizeof(*prefix))) == NULL)
+ fatal("calloc");
+
+ prf = (struct nd_opt_prefix_info*) nd_opt_hdr;
+ prefix->prefix = prf->nd_opt_pi_prefix;
+ prefix->prefix_len = prf->nd_opt_pi_prefix_len;
+ prefix->onlink = prf->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_ONLINK;
+ prefix->autonomous = prf->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_AUTO;
+ prefix->vltime = ntohl(prf->nd_opt_pi_valid_time);
+ prefix->pltime = ntohl(prf->nd_opt_pi_preferred_time);
+ if (radv->min_lifetime > prefix->pltime)
+ radv->min_lifetime = prefix->pltime;
+
+ LIST_INSERT_HEAD(&radv->prefixes, prefix, entries);
+
+ break;
+
+ case ND_OPT_RDNSS:
+ if (nd_opt_hdr->nd_opt_len < 3) {
+ log_warnx("invalid ND_OPT_RDNSS: len < 24");
+ goto err;
+ }
+
+ if ((nd_opt_hdr->nd_opt_len - 1) % 2 != 0) {
+ log_warnx("invalid ND_OPT_RDNSS: length with"
+ "out header is not multiply of 16: %d",
+ (nd_opt_hdr->nd_opt_len - 1) * 8);
+ goto err;
+ }
+
+ rdnss = (struct nd_opt_rdnss*) nd_opt_hdr;
+
+ radv->rdns_lifetime = ntohl(
+ rdnss->nd_opt_rdnss_lifetime);
+ if (radv->min_lifetime > radv->rdns_lifetime)
+ radv->min_lifetime = radv->rdns_lifetime;
+
+ in6 = (struct in6_addr*) (p + 6);
+ for (i=0; i < (nd_opt_hdr->nd_opt_len - 1)/2; i++,
+ in6++) {
+ if((rdns = calloc(1, sizeof(*rdns))) == NULL)
+ fatal("calloc");
+ memcpy(&rdns->rdns, in6, sizeof(rdns->rdns));
+ LIST_INSERT_HEAD(&radv->rdns_servers, rdns,
+ entries);
+ }
+ break;
+ case ND_OPT_DNSSL:
+ if (nd_opt_hdr->nd_opt_len < 2) {
+ log_warnx("invalid ND_OPT_DNSSL: len < 16");
+ goto err;
+ }
+
+ dnssl = (struct nd_opt_dnssl*) nd_opt_hdr;
+
+ if ((nssl = parse_dnssl(p + 6,
+ (nd_opt_hdr->nd_opt_len - 1) * 8)) == NULL)
+ goto err; /* error logging in parse_dnssl */
+
+ if((ra_dnssl = calloc(1, sizeof(*ra_dnssl))) == NULL)
+ fatal("calloc");
+
+ radv->dnssl_lifetime = ntohl(
+ dnssl->nd_opt_dnssl_lifetime);
+ if (radv->min_lifetime > radv->dnssl_lifetime)
+ radv->min_lifetime = radv->dnssl_lifetime;
+
+ if (strlcpy(ra_dnssl->dnssl, nssl,
+ sizeof(ra_dnssl->dnssl)) >=
+ sizeof(ra_dnssl->dnssl)) {
+ log_warn("dnssl too long");
+ goto err;
+ }
+ free(nssl);
+
+ LIST_INSERT_HEAD(&radv->dnssls, ra_dnssl, entries);
+
+ break;
+ case ND_OPT_REDIRECTED_HEADER:
+ case ND_OPT_SOURCE_LINKADDR:
+ case ND_OPT_TARGET_LINKADDR:
+ case ND_OPT_MTU:
+ case ND_OPT_ROUTE_INFO:
+#if 0
+ log_debug("\tOption: %u (len: %u) not implemented",
+ nd_opt_hdr->nd_opt_type, nd_opt_hdr->nd_opt_len *
+ 8);
+#endif
+ 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;
+ }
+ update_iface_ra(iface, radv);
+ iface->state = IF_IDLE;
+ return;
+
+err:
+ free_ra(radv);
+}
+
+void
+gen_addr(struct slaacd_iface *iface, struct radv_prefix *prefix, struct
+ address_proposal *addr_proposal, int privacy)
+{
+ struct in6_addr priv_in6;
+
+ /* from in6_ifadd() in nd6_rtr.c */
+ /* XXX from in6.h, guarded by #ifdef _KERNEL XXX nonstandard */
+#define s6_addr32 __u6_addr.__u6_addr32
+
+ /* XXX from in6_ifattach.c */
+#define EUI64_GBIT 0x01
+#define EUI64_UBIT 0x02
+
+ if (privacy) {
+ arc4random_buf(&priv_in6.s6_addr32[2], 8);
+ priv_in6.s6_addr[8] &= ~EUI64_GBIT; /* g bit to "individual" */
+ priv_in6.s6_addr[8] |= EUI64_UBIT; /* u bit to "local" */
+ /* convert EUI64 into IPv6 interface identifier */
+ priv_in6.s6_addr[8] ^= EUI64_UBIT;
+ }
+
+ in6_prefixlen2mask(&addr_proposal->mask, addr_proposal->prefix_len);
+
+ memset(&addr_proposal->addr, 0, sizeof(addr_proposal->addr));
+
+ addr_proposal->addr.sin6_family = AF_INET6;
+ addr_proposal->addr.sin6_len = sizeof(addr_proposal->addr);
+
+ memcpy(&addr_proposal->addr.sin6_addr, &prefix->prefix,
+ sizeof(addr_proposal->addr.sin6_addr));
+
+ addr_proposal->addr.sin6_addr.s6_addr32[0] &=
+ addr_proposal->mask.s6_addr32[0];
+ addr_proposal->addr.sin6_addr.s6_addr32[1] &=
+ addr_proposal->mask.s6_addr32[1];
+ addr_proposal->addr.sin6_addr.s6_addr32[2] &=
+ addr_proposal->mask.s6_addr32[2];
+ addr_proposal->addr.sin6_addr.s6_addr32[3] &=
+ addr_proposal->mask.s6_addr32[3];
+
+ if (privacy) {
+ addr_proposal->addr.sin6_addr.s6_addr32[0] |=
+ (priv_in6.s6_addr32[0] & ~addr_proposal->mask.s6_addr32[0]);
+ addr_proposal->addr.sin6_addr.s6_addr32[1] |=
+ (priv_in6.s6_addr32[1] & ~addr_proposal->mask.s6_addr32[1]);
+ addr_proposal->addr.sin6_addr.s6_addr32[2] |=
+ (priv_in6.s6_addr32[2] & ~addr_proposal->mask.s6_addr32[2]);
+ addr_proposal->addr.sin6_addr.s6_addr32[3] |=
+ (priv_in6.s6_addr32[3] & ~addr_proposal->mask.s6_addr32[3]);
+ } else {
+ addr_proposal->addr.sin6_addr.s6_addr32[0] |=
+ (iface->ll_address.sin6_addr.s6_addr32[0] &
+ ~addr_proposal->mask.s6_addr32[0]);
+ addr_proposal->addr.sin6_addr.s6_addr32[1] |=
+ (iface->ll_address.sin6_addr.s6_addr32[1] &
+ ~addr_proposal->mask.s6_addr32[1]);
+ addr_proposal->addr.sin6_addr.s6_addr32[2] |=
+ (iface->ll_address.sin6_addr.s6_addr32[2] &
+ ~addr_proposal->mask.s6_addr32[2]);
+ addr_proposal->addr.sin6_addr.s6_addr32[3] |=
+ (iface->ll_address.sin6_addr.s6_addr32[3] &
+ ~addr_proposal->mask.s6_addr32[3]);
+ }
+
+#undef s6_addr32
+}
+
+/* from sys/netinet6/in6.c */
+void
+in6_prefixlen2mask(struct in6_addr *maskp, int len)
+{
+ u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
+ int bytelen, bitlen, i;
+
+ if (0 > len || len > 128)
+ fatal("%s: invalid prefix length(%d)\n", __func__, len);
+
+ bzero(maskp, sizeof(*maskp));
+ bytelen = len / 8;
+ bitlen = len % 8;
+ for (i = 0; i < bytelen; i++)
+ maskp->s6_addr[i] = 0xff;
+ /* len == 128 is ok because bitlen == 0 then */
+ if (bitlen)
+ maskp->s6_addr[bytelen] = maskarray[bitlen - 1];
+}
+
+void
+debug_log_ra(struct imsg_ra *ra)
+{
+ struct nd_router_advert *nd_ra;
+ ssize_t len = ra->len;
+ char hbuf[NI_MAXHOST], ntopbuf[INET6_ADDRSTRLEN];
+ uint8_t *p;
+
+ if (getnameinfo((struct sockaddr *)&ra->from, ra->from.sin6_len, hbuf,
+ sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get router IP");
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&ra->from.sin6_addr)) {
+ log_warn("RA from non link local address %s", hbuf);
+ return;
+ }
+
+ if ((size_t)len < sizeof(struct nd_router_advert)) {
+ log_warn("received too short message (%ld) from %s", len, hbuf);
+ return;
+ }
+
+ p = ra->packet;
+ nd_ra = (struct nd_router_advert *)p;
+ len -= sizeof(struct nd_router_advert);
+ p += sizeof(struct nd_router_advert);
+
+ log_debug("ICMPv6 type(%d), code(%d) from %s of length %ld",
+ nd_ra->nd_ra_type, nd_ra->nd_ra_code, hbuf, len);
+
+ if (nd_ra->nd_ra_type != ND_ROUTER_ADVERT) {
+ log_warnx("invalid ICMPv6 type (%d) from %s", nd_ra->nd_ra_type,
+ hbuf);
+ return;
+ }
+
+ if (nd_ra->nd_ra_code != 0) {
+ log_warn("invalid ICMPv6 code (%d) from %s", nd_ra->nd_ra_code,
+ hbuf);
+ return;
+ }
+
+ log_debug("---");
+ log_debug("RA from %s", hbuf);
+ log_debug("\tCur Hop Limit: %u", nd_ra->nd_ra_curhoplimit);
+ log_debug("\tManaged address configuration: %d",
+ (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ? 1 : 0);
+ log_debug("\tOther configuration: %d",
+ (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) ? 1 : 0);
+ switch (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_RTPREF_MASK) {
+ case ND_RA_FLAG_RTPREF_HIGH:
+ log_debug("\tRouter Preference: high");
+ break;
+ case ND_RA_FLAG_RTPREF_MEDIUM:
+ log_debug("\tRouter Preference: medium");
+ break;
+ case ND_RA_FLAG_RTPREF_LOW:
+ log_debug("\tRouter Preference: low");
+ break;
+ case ND_RA_FLAG_RTPREF_RSV:
+ log_debug("\tRouter Preference: reserved");
+ break;
+ }
+ log_debug("\tRouter Lifetime: %hds",
+ ntohs(nd_ra->nd_ra_router_lifetime));
+ log_debug("\tReachable Time: %ums", ntohl(nd_ra->nd_ra_reachable));
+ log_debug("\tRetrans Timer: %ums", ntohl(nd_ra->nd_ra_retransmit));
+
+ while ((size_t)len >= sizeof(struct nd_opt_hdr)) {
+ struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p;
+ struct nd_opt_mtu *mtu;
+ struct nd_opt_prefix_info *prf;
+ struct nd_opt_rdnss *rdnss;
+ struct nd_opt_dnssl *dnssl;
+ struct in6_addr *in6;
+ int i;
+ char *nssl;
+
+ 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;
+ }
+ log_debug("\tOption: %u (len: %u)", nd_opt_hdr->nd_opt_type,
+ nd_opt_hdr->nd_opt_len * 8);
+ switch (nd_opt_hdr->nd_opt_type) {
+ case ND_OPT_SOURCE_LINKADDR:
+ if (nd_opt_hdr->nd_opt_len == 1)
+ log_debug("\t\tND_OPT_SOURCE_LINKADDR: "
+ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+ p[0], p[1], p[2], p[3], p[4], p[5], p[6],
+ p[7]);
+ else
+ log_debug("\t\tND_OPT_SOURCE_LINKADDR");
+ break;
+ case ND_OPT_TARGET_LINKADDR:
+ if (nd_opt_hdr->nd_opt_len == 1)
+ log_debug("\t\tND_OPT_TARGET_LINKADDR: "
+ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+ p[0], p[1], p[2], p[3], p[4], p[5], p[6],
+ p[7]);
+ else
+ log_debug("\t\tND_OPT_TARGET_LINKADDR");
+ break;
+ case ND_OPT_PREFIX_INFORMATION:
+ if (nd_opt_hdr->nd_opt_len != 4) {
+ log_warn("invalid ND_OPT_PREFIX_INFORMATION: "
+ "len != 4");
+ return;
+ }
+ prf = (struct nd_opt_prefix_info*) nd_opt_hdr;
+
+ log_debug("\t\tND_OPT_PREFIX_INFORMATION: %s/%u",
+ inet_ntop(AF_INET6, &prf->nd_opt_pi_prefix,
+ ntopbuf, INET6_ADDRSTRLEN),
+ prf->nd_opt_pi_prefix_len);
+ log_debug("\t\t\tOn-link: %d",
+ prf->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_ONLINK ? 1:0);
+ log_debug("\t\t\tAutonomous address-configuration: %d",
+ prf->nd_opt_pi_flags_reserved &
+ ND_OPT_PI_FLAG_AUTO ? 1 : 0);
+ log_debug("\t\t\tvltime: %u",
+ ntohl(prf->nd_opt_pi_valid_time));
+ log_debug("\t\t\tpltime: %u",
+ ntohl(prf->nd_opt_pi_preferred_time));
+ break;
+ case ND_OPT_REDIRECTED_HEADER:
+ log_debug("\t\tND_OPT_REDIRECTED_HEADER");
+ break;
+ case ND_OPT_MTU:
+ if (nd_opt_hdr->nd_opt_len != 1) {
+ log_warn("invalid ND_OPT_MTU: len != 1");
+ return;
+ }
+ mtu = (struct nd_opt_mtu*) nd_opt_hdr;
+ log_debug("\t\tND_OPT_MTU: %u",
+ ntohl(mtu->nd_opt_mtu_mtu));
+ break;
+ case ND_OPT_ROUTE_INFO:
+ log_debug("\t\tND_OPT_ROUTE_INFO");
+ break;
+ case ND_OPT_RDNSS:
+ if (nd_opt_hdr->nd_opt_len < 3) {
+ log_warnx("invalid ND_OPT_RDNSS: len < 24");
+ return;
+ }
+ if ((nd_opt_hdr->nd_opt_len - 1) % 2 != 0) {
+ log_warnx("invalid ND_OPT_RDNSS: length with"
+ "out header is not multiply of 16: %d",
+ (nd_opt_hdr->nd_opt_len - 1) * 8);
+ return;
+ }
+ rdnss = (struct nd_opt_rdnss*) nd_opt_hdr;
+ log_debug("\t\tND_OPT_RDNSS: lifetime: %u", ntohl(
+ rdnss->nd_opt_rdnss_lifetime));
+ in6 = (struct in6_addr*) (p + 6);
+ for (i=0; i < (nd_opt_hdr->nd_opt_len - 1)/2; i++,
+ in6++) {
+ log_debug("\t\t\t%s", inet_ntop(AF_INET6, in6,
+ ntopbuf, INET6_ADDRSTRLEN));
+ }
+ break;
+ case ND_OPT_DNSSL:
+ if (nd_opt_hdr->nd_opt_len < 2) {
+ log_warnx("invalid ND_OPT_DNSSL: len < 16");
+ return;
+ }
+ dnssl = (struct nd_opt_dnssl*) nd_opt_hdr;
+ nssl = parse_dnssl(p + 6, (nd_opt_hdr->nd_opt_len - 1)
+ * 8);
+
+ if (nssl == NULL)
+ return;
+
+ log_debug("\t\tND_OPT_DNSSL: lifetime: %u", ntohl(
+ dnssl->nd_opt_dnssl_lifetime));
+ log_debug("\t\t\tsearch: %s", nssl);
+
+ free(nssl);
+ 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;
+ }
+}
+
+char*
+parse_dnssl(char* data, int datalen)
+{
+ int len, pos;
+ char *nssl, *nsslp;
+
+ if((nssl = calloc(1, datalen + 1)) == NULL) {
+ log_warn("malloc");
+ return NULL;
+ }
+ nsslp = nssl;
+
+ pos = 0;
+
+ do {
+ len = data[pos];
+ if (len > 63 || len + pos + 1 > datalen) {
+ free(nssl);
+ log_warnx("invalid label in DNSSL");
+ return NULL;
+ }
+ if (len == 0) {
+ if (pos < datalen && data[pos + 1] != 0)
+ *nsslp++ = ' '; /* seperator for next domain */
+ else
+ break;
+ } else {
+ if (pos != 0 && data[pos - 1] != 0) /* no . at front */
+ *nsslp++ = '.';
+ memcpy(nsslp, data + pos + 1, len);
+ nsslp += len;
+ }
+ pos += len + 1;
+ } while(pos < datalen);
+ if (len != 0) {
+ free(nssl);
+ log_warnx("invalid label in DNSSL");
+ return NULL;
+ }
+ return nssl;
+}
+
+void update_iface_ra(struct slaacd_iface *iface, struct radv *ra)
+{
+ struct radv *old_ra;
+ struct radv_prefix *prefix;
+ struct address_proposal *addr_proposal;
+ struct dfr_proposal *dfr_proposal, *tmp;
+ int found, found_privacy;
+ char hbuf[NI_MAXHOST];
+
+ if ((old_ra = find_ra(iface, &ra->from)) == NULL)
+ LIST_INSERT_HEAD(&iface->radvs, ra, entries);
+ else {
+ LIST_REPLACE(old_ra, ra, entries);
+ free_ra(old_ra);
+ }
+ if (ra->router_lifetime == 0) {
+ LIST_FOREACH_SAFE(dfr_proposal, &iface->dfr_proposals, entries,
+ tmp) {
+ if (memcmp(&dfr_proposal->addr,
+ &ra->from, sizeof(struct sockaddr_in6)) ==
+ 0) {
+ free_dfr_proposal(dfr_proposal);
+ }
+ }
+ } else {
+ found = 0;
+ LIST_FOREACH(dfr_proposal, &iface->dfr_proposals, entries) {
+ if (memcmp(&dfr_proposal->addr,
+ &ra->from, sizeof(struct sockaddr_in6)) ==
+ 0) {
+ found = 1;
+ if (real_lifetime(&dfr_proposal->uptime,
+ dfr_proposal->router_lifetime) >=
+ ra->router_lifetime)
+ log_warn("ignoring router advertisement"
+ " that lowers router lifetime");
+ else {
+ dfr_proposal->when = ra->when;
+ dfr_proposal->uptime = ra->uptime;
+ dfr_proposal->router_lifetime =
+ ra->router_lifetime;
+
+ log_debug("%s, dfr state: %s, rl: %d",
+ __func__, proposal_state_name[
+ dfr_proposal->state],
+ real_lifetime(&dfr_proposal->uptime,
+ dfr_proposal->router_lifetime));
+
+ switch (dfr_proposal->state) {
+ case PROPOSAL_CONFIGURED:
+ case PROPOSAL_NEARLY_EXPIRED:
+ log_debug("updating dfr");
+ configure_dfr(dfr_proposal);
+ break;
+ default:
+ if (getnameinfo((struct
+ sockaddr *)
+ &dfr_proposal->addr,
+ dfr_proposal->
+ addr.sin6_len, hbuf,
+ sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST |
+ NI_NUMERICSERV)) {
+ log_warn("cannot get "
+ "proposal IP");
+ strlcpy(hbuf, "unknown",
+ sizeof(hbuf));
+ }
+ log_debug("%s: iface %d: %s",
+ __func__, iface->if_index,
+ hbuf);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ if (!found)
+ /* new proposal */
+ gen_dfr_proposal(iface, ra);
+
+ LIST_FOREACH(prefix, &ra->prefixes, entries) {
+ found = 0;
+ found_privacy = 0;
+ LIST_FOREACH(addr_proposal, &iface->addr_proposals,
+ entries) {
+ if (prefix->prefix_len ==
+ addr_proposal-> prefix_len &&
+ memcmp(&prefix->prefix,
+ &addr_proposal->prefix,
+ sizeof(struct in6_addr)) != 0)
+ continue;
+
+ if (memcmp(&addr_proposal->hw_address,
+ &iface->hw_address,
+ sizeof(addr_proposal->hw_address)) != 0)
+ continue;
+
+ if (addr_proposal->privacy) {
+ /*
+ * create new privacy address if old
+ * expires
+ */
+ if (addr_proposal->state !=
+ PROPOSAL_NEARLY_EXPIRED)
+ found_privacy = 1;
+
+ if (!iface->autoconfprivacy)
+ log_debug("%s XXX need to "
+ "remove privacy address",
+ __func__);
+
+ log_debug("%s, privacy addr state: %s",
+ __func__, proposal_state_name[
+ addr_proposal->state]);
+
+ /* privacy addresses just expire */
+ continue;
+ }
+
+ found = 1;
+
+ if (real_lifetime(&addr_proposal->uptime,
+ addr_proposal->vltime) >= prefix->vltime) {
+ log_warn("ignoring router advertisement"
+ " that lowers vltime");
+ continue;
+ }
+
+ addr_proposal->when = ra->when;
+ addr_proposal->uptime = ra->uptime;
+ addr_proposal->vltime = prefix->vltime;
+ addr_proposal->pltime = prefix->pltime;
+
+ log_debug("%s, addr state: %s", __func__,
+ proposal_state_name[addr_proposal->state]);
+
+ switch (addr_proposal->state) {
+ case PROPOSAL_CONFIGURED:
+ case PROPOSAL_NEARLY_EXPIRED:
+ log_debug("updating address");
+ configure_address(addr_proposal);
+ break;
+ default:
+ if (getnameinfo((struct sockaddr *)
+ &addr_proposal->addr,
+ addr_proposal->addr.sin6_len, hbuf,
+ sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get proposal "
+ "IP");
+ strlcpy(hbuf, "unknown",
+ sizeof(hbuf));
+ }
+ log_debug("%s: iface %d: %s", __func__,
+ iface->if_index, hbuf);
+ break;
+ }
+ }
+
+ if (!found)
+ /* new proposal */
+ gen_address_proposal(iface, ra, prefix, 0);
+
+ if (!found_privacy && iface->autoconfprivacy)
+ /* new privacy proposal */
+ gen_address_proposal(iface, ra, prefix, 1);
+ }
+ }
+}
+
+void
+configure_address(struct address_proposal *addr_proposal)
+{
+ struct imsg_configure_address address;
+ struct timeval tv;
+
+ addr_proposal->next_timeout = addr_proposal->pltime -
+ MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1);
+
+ tv.tv_sec = addr_proposal->next_timeout;
+ tv.tv_usec = arc4random_uniform(1000000);
+ evtimer_add(&addr_proposal->timer, &tv);
+
+ addr_proposal->state = PROPOSAL_CONFIGURED;
+
+ log_debug("%s: %d", __func__, addr_proposal->if_index);
+
+ address.if_index = addr_proposal->if_index;
+ memcpy(&address.addr, &addr_proposal->addr, sizeof(address.addr));
+ memcpy(&address.mask, &addr_proposal->mask, sizeof(address.mask));
+ address.vltime = addr_proposal->vltime;
+ address.pltime = addr_proposal->pltime;
+ address.privacy = addr_proposal->privacy;
+
+ engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address,
+ sizeof(address));
+}
+
+void
+gen_address_proposal(struct slaacd_iface *iface, struct radv *ra, struct
+ radv_prefix *prefix, int privacy)
+{
+ struct address_proposal *addr_proposal;
+ struct timeval tv;
+ char hbuf[NI_MAXHOST];
+
+ if ((addr_proposal = calloc(1, sizeof(*addr_proposal))) == NULL)
+ fatal("calloc");
+ evtimer_set(&addr_proposal->timer, address_proposal_timeout,
+ addr_proposal);
+ addr_proposal->next_timeout = 1;
+ addr_proposal->timeout_count = 0;
+ addr_proposal->state = PROPOSAL_NOT_CONFIGURED;
+ addr_proposal->when = ra->when;
+ addr_proposal->uptime = ra->uptime;
+ addr_proposal->if_index = iface->if_index;
+ memcpy(&addr_proposal->hw_address, &iface->hw_address,
+ sizeof(addr_proposal->hw_address));
+ addr_proposal->privacy = privacy;
+ memcpy(&addr_proposal->prefix, &prefix->prefix,
+ sizeof(addr_proposal->prefix));
+ addr_proposal->prefix_len = prefix->prefix_len;
+
+ if (privacy) {
+ if (prefix->vltime > ND6_PRIV_VALID_LIFETIME)
+ addr_proposal->vltime = ND6_PRIV_VALID_LIFETIME;
+ else
+ addr_proposal->vltime = prefix->vltime;
+
+ if (prefix->pltime > ND6_PRIV_PREFERRED_LIFETIME)
+ addr_proposal->pltime = ND6_PRIV_PREFERRED_LIFETIME;
+ else
+ addr_proposal->pltime = prefix->pltime;
+ } else {
+ addr_proposal->vltime = prefix->vltime;
+ addr_proposal->pltime = prefix->pltime;
+ }
+
+ gen_addr(iface, prefix, addr_proposal, privacy);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ evtimer_add(&addr_proposal->timer, &tv);
+
+ LIST_INSERT_HEAD(&iface->addr_proposals, addr_proposal, entries);
+
+ if (getnameinfo((struct sockaddr *)&addr_proposal->addr,
+ addr_proposal->addr.sin6_len, hbuf, sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get router IP");
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ }
+ log_debug("%s: iface %d: %s: %lld s", __func__,
+ iface->if_index, hbuf, tv.tv_sec);
+}
+
+void
+gen_dfr_proposal(struct slaacd_iface *iface, struct radv *ra)
+{
+ struct dfr_proposal *dfr_proposal;
+ struct timeval tv;
+ char hbuf[NI_MAXHOST];
+
+ if ((dfr_proposal = calloc(1, sizeof(*dfr_proposal))) == NULL)
+ fatal("calloc");
+ evtimer_set(&dfr_proposal->timer, dfr_proposal_timeout,
+ dfr_proposal);
+ dfr_proposal->next_timeout = 1;
+ dfr_proposal->timeout_count = 0;
+ dfr_proposal->state = PROPOSAL_NOT_CONFIGURED;
+ dfr_proposal->when = ra->when;
+ dfr_proposal->uptime = ra->uptime;
+ dfr_proposal->if_index = iface->if_index;
+ memcpy(&dfr_proposal->addr, &ra->from,
+ sizeof(dfr_proposal->addr));
+ dfr_proposal->router_lifetime = ra->router_lifetime;
+ dfr_proposal->rpref = ra->rpref;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ evtimer_add(&dfr_proposal->timer, &tv);
+
+ LIST_INSERT_HEAD(&iface->dfr_proposals, dfr_proposal, entries);
+
+ if (getnameinfo((struct sockaddr *)&dfr_proposal->addr,
+ dfr_proposal->addr.sin6_len, hbuf, sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get router IP");
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ }
+ log_debug("%s: iface %d: %s: %lld s", __func__,
+ iface->if_index, hbuf, tv.tv_sec);
+}
+
+void
+configure_dfr(struct dfr_proposal *dfr_proposal)
+{
+ struct imsg_configure_dfr dfr;
+ struct timeval tv;
+ enum proposal_state prev_state;
+
+ dfr_proposal->next_timeout = dfr_proposal->router_lifetime -
+ MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1);
+
+ tv.tv_sec = dfr_proposal->next_timeout;
+ tv.tv_usec = arc4random_uniform(1000000);
+ evtimer_add(&dfr_proposal->timer, &tv);
+
+ prev_state = dfr_proposal->state;
+
+ dfr_proposal->state = PROPOSAL_CONFIGURED;
+
+ log_debug("%s: %d", __func__, dfr_proposal->if_index);
+
+ if (prev_state == PROPOSAL_CONFIGURED || prev_state ==
+ PROPOSAL_NEARLY_EXPIRED) {
+ /*
+ * nothing to do here, routes do not expire in the kernel
+ * XXX check if the route got deleted and re-add it?
+ */
+ return;
+ }
+
+ dfr.if_index = dfr_proposal->if_index;
+ memcpy(&dfr.addr, &dfr_proposal->addr, sizeof(dfr.addr));
+ dfr.router_lifetime = dfr_proposal->router_lifetime;
+
+ engine_imsg_compose_main(IMSG_CONFIGURE_DFR, 0, &dfr, sizeof(dfr));
+}
+
+void
+withdraw_dfr(struct dfr_proposal *dfr_proposal)
+{
+ struct imsg_configure_dfr dfr;
+
+ log_debug("%s: %d", __func__, dfr_proposal->if_index);
+
+ dfr.if_index = dfr_proposal->if_index;
+ memcpy(&dfr.addr, &dfr_proposal->addr, sizeof(dfr.addr));
+ dfr.router_lifetime = dfr_proposal->router_lifetime;
+
+ engine_imsg_compose_main(IMSG_WITHDRAW_DFR, 0, &dfr, sizeof(dfr));
+}
+
+void
+free_dfr_proposal(struct dfr_proposal *dfr_proposal)
+{
+
+ LIST_REMOVE(dfr_proposal, entries);
+ evtimer_del(&dfr_proposal->timer);
+ switch (dfr_proposal->state) {
+ case PROPOSAL_CONFIGURED:
+ case PROPOSAL_NEARLY_EXPIRED:
+ withdraw_dfr(dfr_proposal);
+ break;
+ default:
+ break;
+ }
+ free(dfr_proposal);
+}
+
+void
+send_proposal(struct imsg_proposal *proposal)
+{
+#ifndef SKIP_PROPOSAL
+ engine_imsg_compose_main(IMSG_PROPOSAL, 0, proposal, sizeof(*proposal));
+#else
+ struct imsg_proposal_ack ack;
+ ack.id = proposal->id;
+ ack.pid = proposal->pid;
+ ack.if_index = proposal->if_index;
+ engine_imsg_compose_frontend(IMSG_FAKE_ACK, 0, &ack, sizeof(ack));
+#endif
+}
+
+void
+start_probe(struct slaacd_iface *iface)
+{
+ struct timeval tv;
+
+ iface->state = IF_DELAY;
+ iface->probes = 0;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY_USEC);
+
+ log_debug("%s: iface %d: sleeping for %ldusec", __func__,
+ iface->if_index, tv.tv_usec);
+
+ evtimer_add(&iface->timer, &tv);
+}
+
+void
+address_proposal_timeout(int fd, short events, void *arg)
+{
+ struct address_proposal *addr_proposal;
+ struct imsg_proposal proposal;
+ struct timeval tv;
+ char hbuf[NI_MAXHOST];
+
+ addr_proposal = (struct address_proposal *)arg;
+
+ if (getnameinfo((struct sockaddr *)&addr_proposal->addr,
+ addr_proposal->addr.sin6_len, hbuf, sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get router IP");
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ }
+ log_debug("%s: iface %d: %s [%s], priv: %s", __func__,
+ addr_proposal->if_index, hbuf,
+ proposal_state_name[addr_proposal->state],
+ addr_proposal->privacy ? "y" : "n");
+
+ switch (addr_proposal->state) {
+ case PROPOSAL_NOT_CONFIGURED:
+ case PROPOSAL_SENT:
+ if (addr_proposal->timeout_count++ < 6) {
+ addr_proposal->id = ++proposal_id;
+
+ memset(&proposal, 0, sizeof(proposal));
+ proposal.if_index = addr_proposal->if_index;
+ proposal.pid = getpid();
+ proposal.id = addr_proposal->id;
+ memcpy(&proposal.addr, &addr_proposal->addr,
+ sizeof(proposal.addr));
+ memcpy(&proposal.mask, &addr_proposal->mask,
+ sizeof(proposal.mask));
+
+ proposal.rtm_addrs = RTA_NETMASK | RTA_IFA;
+
+ addr_proposal->state = PROPOSAL_SENT;
+
+ send_proposal(&proposal);
+
+ tv.tv_sec = addr_proposal->next_timeout;
+ tv.tv_usec = arc4random_uniform(1000000);
+ addr_proposal->next_timeout *= 2;
+ evtimer_add(&addr_proposal->timer, &tv);
+ log_debug("%s: scheduling new timeout in %llds.%06ld",
+ __func__, tv.tv_sec, tv.tv_usec);
+ } else {
+ log_debug("%s: giving up, no response to proposal",
+ __func__);
+ LIST_REMOVE(addr_proposal, entries);
+ evtimer_del(&addr_proposal->timer);
+ free(addr_proposal);
+ }
+ break;
+ case PROPOSAL_CONFIGURED:
+ log_debug("PROPOSAL_CONFIGURED timeout: id: %lld, privacy: %s",
+ addr_proposal->id, addr_proposal->privacy ? "y" : "n");
+
+ addr_proposal->next_timeout = 1;
+ addr_proposal->timeout_count = 0;
+ addr_proposal->state = PROPOSAL_NEARLY_EXPIRED;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ evtimer_add(&addr_proposal->timer, &tv);
+
+ break;
+ case PROPOSAL_NEARLY_EXPIRED:
+ log_debug("%s: rl: %d", __func__,
+ real_lifetime(&addr_proposal->uptime,
+ addr_proposal->vltime));
+ /*
+ * we should have gotten a RTM_DELADDR from the kernel,
+ * in case we missed it, delete to not waste memory
+ */
+ if (real_lifetime(&addr_proposal->uptime,
+ addr_proposal->vltime) == 0) {
+ evtimer_del(&addr_proposal->timer);
+ LIST_REMOVE(addr_proposal, entries);
+ free(addr_proposal);
+ log_debug("%s: removing address proposal", __func__);
+ break;
+ }
+ if (addr_proposal->privacy)
+ break; /* just let it expire */
+
+ engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION,
+ 0, &addr_proposal->if_index,
+ sizeof(addr_proposal->if_index));
+ tv.tv_sec = addr_proposal->next_timeout;
+ tv.tv_usec = arc4random_uniform(1000000);
+ addr_proposal->next_timeout *= 2;
+ evtimer_add(&addr_proposal->timer, &tv);
+ log_debug("%s: scheduling new timeout in %llds.%06ld",
+ __func__, tv.tv_sec, tv.tv_usec);
+ break;
+ default:
+ log_debug("%s: unhandled state: %s", __func__,
+ proposal_state_name[addr_proposal->state]);
+ }
+}
+
+void
+dfr_proposal_timeout(int fd, short events, void *arg)
+{
+ struct dfr_proposal *dfr_proposal;
+ struct imsg_proposal proposal;
+ struct timeval tv;
+ char hbuf[NI_MAXHOST];
+
+ dfr_proposal = (struct dfr_proposal *)arg;
+
+ if (getnameinfo((struct sockaddr *)&dfr_proposal->addr,
+ dfr_proposal->addr.sin6_len, hbuf, sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST | NI_NUMERICSERV)) {
+ log_warn("cannot get router IP");
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ }
+ log_debug("%s: iface %d: %s [%s]", __func__, dfr_proposal->if_index,
+ hbuf, proposal_state_name[dfr_proposal->state]);
+
+ switch (dfr_proposal->state) {
+ case PROPOSAL_NOT_CONFIGURED:
+ case PROPOSAL_SENT:
+ if (dfr_proposal->timeout_count++ < 6) {
+ dfr_proposal->id = ++proposal_id;
+
+ memset(&proposal, 0, sizeof(proposal));
+ proposal.if_index = dfr_proposal->if_index;
+ proposal.pid = getpid();
+ proposal.id = dfr_proposal->id;
+ memcpy(&proposal.addr, &dfr_proposal->addr,
+ sizeof(proposal.addr));
+
+ proposal.rtm_addrs = RTA_GATEWAY;
+
+ dfr_proposal->state = PROPOSAL_SENT;
+
+ send_proposal(&proposal);
+
+ tv.tv_sec = dfr_proposal->next_timeout;
+ tv.tv_usec = arc4random_uniform(1000000);
+ dfr_proposal->next_timeout *= 2;
+ evtimer_add(&dfr_proposal->timer, &tv);
+ log_debug("%s: scheduling new timeout in %llds.%06ld",
+ __func__, tv.tv_sec, tv.tv_usec);
+ } else {
+ log_debug("%s: giving up, no response to proposal",
+ __func__);
+ free_dfr_proposal(dfr_proposal);
+ }
+ break;
+ case PROPOSAL_CONFIGURED:
+ log_debug("PROPOSAL_CONFIGURED timeout: id: %lld",
+ dfr_proposal->id);
+
+ dfr_proposal->next_timeout = 1;
+ dfr_proposal->timeout_count = 0;
+ dfr_proposal->state = PROPOSAL_NEARLY_EXPIRED;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ evtimer_add(&dfr_proposal->timer, &tv);
+
+ break;
+ case PROPOSAL_NEARLY_EXPIRED:
+ if (real_lifetime(&dfr_proposal->uptime,
+ dfr_proposal->router_lifetime) == 0) {
+ free_dfr_proposal(dfr_proposal);
+ log_debug("%s: removing dfr proposal", __func__);
+ break;
+ }
+ engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION,
+ 0, &dfr_proposal->if_index,
+ sizeof(dfr_proposal->if_index));
+ tv.tv_sec = dfr_proposal->next_timeout;
+ tv.tv_usec = arc4random_uniform(1000000);
+ dfr_proposal->next_timeout *= 2;
+ evtimer_add(&dfr_proposal->timer, &tv);
+ log_debug("%s: scheduling new timeout in %llds.%06ld",
+ __func__, tv.tv_sec, tv.tv_usec);
+ break;
+ default:
+ log_debug("%s: unhandled state: %s", __func__,
+ proposal_state_name[dfr_proposal->state]);
+ }
+}
+
+void
+iface_timeout(int fd, short events, void *arg)
+{
+ struct slaacd_iface *iface = (struct slaacd_iface *)arg;
+ struct timeval tv;
+
+ log_debug("%s[%d]: %s", __func__, iface->if_index,
+ if_state_name[iface->state]);
+
+ switch (iface->state) {
+ case IF_DELAY:
+ case IF_PROBE:
+ iface->state = IF_PROBE;
+ engine_imsg_compose_frontend(
+ IMSG_CTL_SEND_SOLICITATION, 0, &iface->if_index,
+ sizeof(iface->if_index));
+ if (++iface->probes >= MAX_RTR_SOLICITATIONS)
+ iface->state = IF_IDLE;
+ else {
+ tv.tv_sec = RTR_SOLICITATION_INTERVAL;
+ tv.tv_usec = arc4random_uniform(1000000);
+ evtimer_add(&iface->timer, &tv);
+ }
+ break;
+ case IF_DOWN:
+ case IF_IDLE:
+ default:
+ break;
+ }
+}
+
+struct radv*
+find_ra(struct slaacd_iface *iface, struct sockaddr_in6 *from)
+{
+ struct radv *ra;
+
+ LIST_FOREACH (ra, &iface->radvs, entries) {
+ if (memcmp(&ra->from.sin6_addr, &from->sin6_addr,
+ sizeof(from->sin6_addr)) == 0)
+ return (ra);
+ }
+
+ return (NULL);
+}
+
+struct address_proposal*
+find_address_proposal_by_id(struct slaacd_iface *iface, int64_t id)
+{
+ struct address_proposal *addr_proposal;
+
+ LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) {
+ if (addr_proposal->id == id)
+ return (addr_proposal);
+ }
+
+ return (NULL);
+}
+
+struct address_proposal*
+find_address_proposal_by_addr(struct slaacd_iface *iface, struct sockaddr_in6
+ *addr)
+{
+ struct address_proposal *addr_proposal;
+
+ LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) {
+ if (memcmp(&addr_proposal->addr, addr, sizeof(*addr)) == 0)
+ return (addr_proposal);
+ }
+
+ return (NULL);
+}
+
+struct dfr_proposal*
+find_dfr_proposal_by_id(struct slaacd_iface *iface, int64_t id)
+{
+ struct dfr_proposal *dfr_proposal;
+
+ LIST_FOREACH (dfr_proposal, &iface->dfr_proposals, entries) {
+ if (dfr_proposal->id == id)
+ return (dfr_proposal);
+ }
+
+ return (NULL);
+}
+
+
+/* XXX currently unused */
+void
+find_prefix(struct slaacd_iface *iface, struct address_proposal *addr_proposal,
+ struct radv **result_ra, struct radv_prefix **result_prefix)
+{
+ struct radv *ra;
+ struct radv_prefix *prefix;
+ uint32_t lifetime, max_lifetime = 0;
+
+ *result_ra = NULL;
+ *result_prefix = NULL;
+
+ LIST_FOREACH(ra, &iface->radvs, entries) {
+ LIST_FOREACH(prefix, &ra->prefixes, entries) {
+ if (memcmp(&prefix->prefix, &addr_proposal->prefix,
+ sizeof(addr_proposal->prefix)) != 0)
+ continue;
+ lifetime = real_lifetime(&ra->uptime,
+ prefix->vltime);
+ if (lifetime > max_lifetime) {
+ max_lifetime = lifetime;
+ *result_ra = ra;
+ *result_prefix = prefix;
+ }
+ }
+ }
+}
+
+uint32_t
+real_lifetime(struct timespec *received_uptime, uint32_t ltime)
+{
+ struct timespec now, diff;
+ int64_t remaining;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ fatal("clock_gettime");
+
+ timespecsub(&now, received_uptime, &diff);
+
+ remaining = ((int64_t)ltime) - diff.tv_sec;
+
+ if (remaining < 0)
+ remaining = 0;
+
+ return (remaining);
+}
diff --git a/sbin/slaacd/engine.h b/sbin/slaacd/engine.h
new file mode 100644
index 00000000000..e7f520f1fba
--- /dev/null
+++ b/sbin/slaacd/engine.h
@@ -0,0 +1,47 @@
+/* $OpenBSD: engine.h,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct imsg_proposal {
+ uint32_t if_index;
+ pid_t pid;
+ int64_t id;
+ struct sockaddr_in6 addr;
+ struct in6_addr mask;
+ struct sockaddr_in6 gateway;
+ struct sockaddr_rtdns rdns;
+ struct sockaddr_rtsearch dnssl;
+ int rtm_addrs;
+};
+
+struct imsg_configure_address {
+ uint32_t if_index;
+ struct sockaddr_in6 addr;
+ struct in6_addr mask;
+ uint32_t vltime;
+ uint32_t pltime;
+ int privacy;
+};
+
+struct imsg_configure_dfr {
+ uint32_t if_index;
+ struct sockaddr_in6 addr;
+ uint32_t router_lifetime;
+};
+
+void engine(int, int);
+int engine_imsg_compose_frontend(int, pid_t, void *, uint16_t);
diff --git a/sbin/slaacd/frontend.c b/sbin/slaacd/frontend.c
new file mode 100644
index 00000000000..18fb1affa40
--- /dev/null
+++ b/sbin/slaacd/frontend.c
@@ -0,0 +1,826 @@
+/* $OpenBSD: frontend.c,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2017 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <net/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 <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 "slaacd.h"
+#include "frontend.h"
+#include "control.h"
+
+#define ROUTE_SOCKET_BUF_SIZE 16384
+#define ALLROUTER "ff02::2"
+
+__dead void frontend_shutdown(void);
+void frontend_sig_handler(int, short, void *);
+void update_iface(uint32_t, char*);
+void frontend_startup(void);
+void route_receive(int, short, void *);
+void handle_route_message(struct rt_msghdr *, struct sockaddr **);
+void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
+void icmp6_receive(int, short, void *);
+int get_flags(char *);
+int get_xflags(char *);
+void get_lladdr(char *, struct ether_addr *, struct sockaddr_in6 *);
+void send_solicitation(uint32_t);
+
+struct imsgev *iev_main;
+struct imsgev *iev_engine;
+struct event ev_route;
+struct msghdr sndmhdr;
+struct iovec sndiov[4];
+struct nd_router_solicit rs;
+struct nd_opt_hdr nd_opt_hdr;
+struct ether_addr nd_opt_source_link_addr;
+struct sockaddr_in6 dst;
+int icmp6sock, routesock, xflagssock;
+
+struct icmp6_ev {
+ struct event ev;
+ uint8_t answer[1500];
+ struct msghdr rcvmhdr;
+ struct iovec rcviov[1];
+ struct sockaddr_in6 from;
+} icmp6ev;
+
+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;
+ struct icmp6_filter filt;
+ struct in6_pktinfo *pi;
+ struct cmsghdr *cm;
+ size_t rcvcmsglen, sndcmsglen;
+ int hoplimit = 255, on = 1, rtfilter;
+ uint8_t *rcvcmsgbuf, *sndcmsgbuf;
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ /* Create slaacd control socket outside chroot. */
+ if (control_init(sockname) == -1)
+ fatalx("control socket setup failed");
+
+ if ((pw = getpwnam(SLAACD_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ slaacd_process = PROC_FRONTEND;
+ setproctitle("%s", log_procnames[slaacd_process]);
+ log_procinit(log_procnames[slaacd_process]);
+
+ if ((icmp6sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
+ fatal("ICMPv6 socket");
+
+ if ((routesock = socket(PF_ROUTE, SOCK_RAW, 0)) < 0)
+ fatal("route socket");
+
+ if ((xflagssock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ fatal("socket");
+
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("can't drop privileges");
+
+ if (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 */
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ if (setsockopt(icmp6sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) == -1)
+ fatal("ICMP6_FILTER");
+
+ rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_NEWADDR) |
+ ROUTE_FILTER(RTM_DELADDR) | ROUTE_FILTER(RTM_PROPOSAL);
+ if (setsockopt(routesock, PF_ROUTE, ROUTE_MSGFILTER,
+ &rtfilter, sizeof(rtfilter)) < 0)
+ fatal("setsockopt(ROUTE_MSGFILTER)");
+
+ if (pledge("stdio inet recvfd route", NULL) == -1)
+ fatal("pledge");
+
+ event_init();
+
+ /* Setup signal handler. */
+ signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ /* Setup pipe and event handler to the parent process. */
+ if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+ fatal(NULL);
+ imsg_init(&iev_main->ibuf, 3);
+ iev_main->handler = frontend_dispatch_main;
+ iev_main->events = EV_READ;
+ event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+ iev_main->handler, iev_main);
+ event_add(&iev_main->ev, NULL);
+
+ /* Listen on control socket. */
+ TAILQ_INIT(&ctl_conns);
+ control_listen();
+
+ event_set(&ev_route, routesock, EV_READ | EV_PERSIST, route_receive,
+ NULL);
+
+ event_set(&icmp6ev.ev, icmp6sock, EV_READ | EV_PERSIST, icmp6_receive,
+ 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;
+
+ sndcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+
+ if ((sndcmsgbuf = malloc(sndcmsglen)) == NULL)
+ fatal("malloc");
+
+ rs.nd_rs_type = ND_ROUTER_SOLICIT;
+ rs.nd_rs_code = 0;
+ rs.nd_rs_cksum = 0;
+ rs.nd_rs_reserved = 0;
+
+ nd_opt_hdr.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ nd_opt_hdr.nd_opt_len = 1;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, ALLROUTER, &dst.sin6_addr.s6_addr) != 1)
+ fatal("inet_pton");
+
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iov = sndiov;
+ sndmhdr.msg_iovlen = 3;
+ sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
+ sndmhdr.msg_controllen = sndcmsglen;
+
+ sndmhdr.msg_name = (caddr_t)&dst;
+ sndmhdr.msg_iov[0].iov_base = (caddr_t)&rs;
+ sndmhdr.msg_iov[0].iov_len = sizeof(rs);
+ sndmhdr.msg_iov[1].iov_base = (caddr_t)&nd_opt_hdr;
+ sndmhdr.msg_iov[1].iov_len = sizeof(nd_opt_hdr);
+ sndmhdr.msg_iov[2].iov_base = (caddr_t)&nd_opt_source_link_addr;
+ sndmhdr.msg_iov[2].iov_len = sizeof(nd_opt_source_link_addr);
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+
+ 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 = 0;
+
+ 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));
+
+ event_dispatch();
+
+ frontend_shutdown();
+}
+
+__dead void
+frontend_shutdown(void)
+{
+ /* Close pipes. */
+ msgbuf_write(&iev_engine->ibuf.w);
+ msgbuf_clear(&iev_engine->ibuf.w);
+ close(iev_engine->ibuf.fd);
+ msgbuf_write(&iev_main->ibuf.w);
+ msgbuf_clear(&iev_main->ibuf.w);
+ close(iev_main->ibuf.fd);
+
+ free(iev_engine);
+ free(iev_main);
+
+ log_info("frontend exiting");
+ exit(0);
+}
+
+int
+frontend_imsg_compose_main(int type, pid_t pid, void *data,
+ uint16_t datalen)
+{
+ return (imsg_compose_event(iev_main, type, 0, pid, -1, data,
+ datalen));
+}
+
+int
+frontend_imsg_compose_engine(int type, uint32_t peerid, pid_t pid,
+ void *data, uint16_t datalen)
+{
+ return (imsg_compose_event(iev_engine, type, peerid, pid, -1,
+ data, datalen));
+}
+
+void
+frontend_dispatch_main(int fd, short event, void *bula)
+{
+ struct imsg imsg;
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ ssize_t n;
+ int shut = 0;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_SOCKET_IPC:
+ /*
+ * Setup pipe and event handler to the 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);
+
+ if (pledge("stdio inet route", NULL) == -1)
+ fatal("pledge");
+
+ break;
+ case IMSG_STARTUP:
+ frontend_startup();
+ break;
+ case IMSG_CTL_END:
+ control_imsg_relay(&imsg);
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+frontend_dispatch_engine(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int shut = 0;
+ uint32_t if_index;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_END:
+ case IMSG_CTL_SHOW_INTERFACE_INFO:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_RA:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_RA_DNSSL:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS:
+ case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL:
+ control_imsg_relay(&imsg);
+ break;
+ case IMSG_CTL_SEND_SOLICITATION:
+ if_index = *((uint32_t *)imsg.data);
+ send_solicitation(if_index);
+ break;
+ case IMSG_FAKE_ACK:
+ frontend_imsg_compose_engine(IMSG_PROPOSAL_ACK,
+ 0, 0, imsg.data, sizeof(struct imsg_proposal_ack));
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+int
+get_flags(char *if_name)
+{
+ struct ifreq ifr;
+
+ (void) strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+ if (ioctl(xflagssock, SIOCGIFFLAGS, (caddr_t)&ifr) < 0)
+ fatal("SIOCGIFFLAGS");
+ return ifr.ifr_flags;
+}
+
+int
+get_xflags(char *if_name)
+{
+ struct ifreq ifr;
+
+ (void) strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+ if (ioctl(xflagssock, SIOCGIFXFLAGS, (caddr_t)&ifr) < 0)
+ fatal("SIOCGIFXFLAGS");
+ return ifr.ifr_flags;
+}
+
+void
+update_iface(uint32_t if_index, char* if_name)
+{
+ struct imsg_ifinfo imsg_ifinfo;
+ int flags, xflags;
+
+ flags = get_flags(if_name);
+ xflags = get_xflags(if_name);
+
+ if (!(xflags & IFXF_AUTOCONF6))
+ return;
+
+ memset(&imsg_ifinfo, 0, sizeof(imsg_ifinfo));
+
+ imsg_ifinfo.if_index = if_index;
+ imsg_ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP |
+ IFF_RUNNING);
+ imsg_ifinfo.autoconfprivacy = !(xflags & IFXF_INET6_NOPRIVACY);
+ get_lladdr(if_name, &imsg_ifinfo.hw_address, &imsg_ifinfo.ll_address);
+
+ memcpy(&nd_opt_source_link_addr, &imsg_ifinfo.hw_address,
+ sizeof(nd_opt_source_link_addr));
+
+ frontend_imsg_compose_engine(IMSG_UPDATE_IF, 0, 0, &imsg_ifinfo,
+ sizeof(imsg_ifinfo));
+}
+
+void
+frontend_startup(void)
+{
+ struct if_nameindex *ifnidxp, *ifnidx;
+
+ event_add(&ev_route, NULL);
+ event_add(&icmp6ev.ev, NULL);
+
+ if ((ifnidxp = if_nameindex()) == NULL)
+ fatalx("if_nameindex");
+
+ for(ifnidx = ifnidxp; ifnidx->if_index !=0 && ifnidx->if_name != NULL;
+ ifnidx++)
+ update_iface(ifnidx->if_index, ifnidx->if_name);
+
+ if_freenameindex(ifnidxp);
+}
+
+void
+route_receive(int fd, short events, void *arg)
+{
+ static uint8_t buf[ROUTE_SOCKET_BUF_SIZE];
+
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ size_t len, offset;
+ ssize_t n;
+ char *next;
+
+ if ((n = read(fd, &buf, sizeof(buf))) == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ return;
+ log_warn("dispatch_rtmsg: read error");
+ return;
+ }
+
+ if (n == 0) {
+ log_warnx("routing socket closed");
+ return;
+ }
+
+ len = n;
+ for (offset = 0; offset < len; offset += rtm->rtm_msglen) {
+ next = buf + offset;
+ rtm = (struct rt_msghdr *)next;
+ if (len < offset + sizeof(u_short) ||
+ len < offset + rtm->rtm_msglen)
+ fatalx("rtmsg_process: partial rtm in buffer");
+ if (rtm->rtm_version != RTM_VERSION)
+ continue;
+
+ sa = (struct sockaddr *)(next + rtm->rtm_hdrlen);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+ handle_route_message(rtm, rti_info);
+ }
+}
+
+void
+handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
+{
+ struct if_msghdr *ifm;
+ struct imsg_proposal_ack proposal_ack;
+ struct imsg_del_addr del_addr;
+ struct sockaddr_rtlabel *rl;
+ int64_t id, pid;
+ int flags, xflags, if_index;
+ char ifnamebuf[IFNAMSIZ];
+ char *if_name;
+ char **ap, *argv[4], *p;
+ const char *errstr;
+
+ switch (rtm->rtm_type) {
+ case RTM_IFINFO:
+ ifm = (struct if_msghdr *)rtm;
+ if_name = if_indextoname(ifm->ifm_index, ifnamebuf);
+ if (if_name == NULL) {
+ log_debug("RTM_IFINFO: lost if %d", ifm->ifm_index);
+ if_index = ifm->ifm_index;
+ frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
+ &if_index, sizeof(if_index));
+ } else {
+ xflags = get_xflags(if_name);
+ flags = get_flags(if_name);
+ if (!(xflags & IFXF_AUTOCONF6)) {
+ log_debug("RTM_IFINFO: %s(%d) no(longer) "
+ "autoconf6", if_name, ifm->ifm_index);
+ if_index = ifm->ifm_index;
+ frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0,
+ 0, &if_index, sizeof(if_index));
+ } else
+ update_iface(ifm->ifm_index, if_name);
+ }
+ break;
+ case RTM_NEWADDR:
+ ifm = (struct if_msghdr *)rtm;
+ if_name = if_indextoname(ifm->ifm_index, ifnamebuf);
+ log_debug("RTM_NEWADDR: %s[%u]", if_name, ifm->ifm_index);
+ update_iface(ifm->ifm_index, if_name);
+ break;
+ case RTM_DELADDR:
+ ifm = (struct if_msghdr *)rtm;
+ if_name = if_indextoname(ifm->ifm_index, ifnamebuf);
+ if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family
+ == AF_INET6) {
+ del_addr.if_index = ifm->ifm_index;
+ memcpy(&del_addr.addr, rti_info[RTAX_IFA], sizeof(
+ del_addr.addr));
+ frontend_imsg_compose_engine(IMSG_DEL_ADDRESS,
+ 0, 0, &del_addr, sizeof(del_addr));
+ log_debug("RTM_DELADDR: %s[%u]", if_name,
+ ifm->ifm_index);
+ }
+ break;
+ case RTM_PROPOSAL:
+ ifm = (struct if_msghdr *)rtm;
+ if_name = if_indextoname(ifm->ifm_index, ifnamebuf);
+
+ if ((rtm->rtm_flags & (RTF_DONE | RTF_PROTO1)) ==
+ (RTF_DONE | RTF_PROTO1) && rtm->rtm_addrs == RTA_LABEL) {
+ rl = (struct sockaddr_rtlabel *)rti_info[RTAX_LABEL];
+ /* XXX validate rl */
+
+ p = rl->sr_label;
+
+ for (ap = argv; ap < &argv[3] && (*ap =
+ strsep(&p, " ")) != NULL;) {
+ if (**ap != '\0')
+ ap++;
+ }
+ *ap = NULL;
+
+ if (argv[0] != NULL && strncmp(argv[0], "slaacd:",
+ strlen("slaacd:")) == 0 && argv[1] != NULL &&
+ argv[2] != NULL && argv[3] == NULL) {
+ id = strtonum(argv[1], 0, INT64_MAX, &errstr);
+ if (errstr != NULL) {
+ log_warn("%s: proposal seq is %s: %s",
+ __func__, errstr, argv[1]);
+ break;
+ }
+ pid = strtonum(argv[2], 0, INT32_MAX, &errstr);
+ if (errstr != NULL) {
+ log_warn("%s: pid is %s: %s",
+ __func__, errstr, argv[2]);
+ break;
+ }
+ proposal_ack.id = id;
+ proposal_ack.pid = pid;
+ proposal_ack.if_index = ifm->ifm_index;
+
+ frontend_imsg_compose_engine(IMSG_PROPOSAL_ACK,
+ 0, 0, &proposal_ack, sizeof(proposal_ack));
+ } else {
+ log_debug("cannot parse: %s", rl->sr_label);
+ }
+ } else {
+#if 0
+ log_debug("%s: got flags %x, expcted %x", __func__,
+ rtm->rtm_flags, (RTF_DONE | RTF_PROTO1));
+#endif
+ }
+
+ break;
+ default:
+ log_debug("unexpected RTM: %d", rtm->rtm_type);
+ break;
+ }
+
+}
+
+#define ROUNDUP(a) \
+ (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a))
+
+void
+get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
+{
+ int i;
+
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (addrs & (1 << i)) {
+ rti_info[i] = sa;
+ sa = (struct sockaddr *)((char *)(sa) +
+ ROUNDUP(sa->sa_len));
+ } else
+ rti_info[i] = NULL;
+ }
+}
+
+void
+get_lladdr(char *if_name, struct ether_addr *mac, struct sockaddr_in6 *ll)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl *sdl;
+ struct sockaddr_in6 *sin6;
+
+ if (getifaddrs(&ifap) != 0)
+ fatal("getifaddrs");
+
+ memset(mac, 0, sizeof(*mac));
+ memset(ll, 0, sizeof(*ll));
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if (strcmp(if_name, ifa->ifa_name) != 0)
+ continue;
+
+ switch(ifa->ifa_addr->sa_family) {
+ case AF_LINK:
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ if (sdl->sdl_type != IFT_ETHER ||
+ sdl->sdl_alen != ETHER_ADDR_LEN)
+ continue;
+
+ memcpy(mac->ether_addr_octet, LLADDR(sdl),
+ ETHER_ADDR_LEN);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ sin6->sin6_scope_id = ntohs(*(u_int16_t *)
+ &sin6->sin6_addr.s6_addr[2]);
+ sin6->sin6_addr.s6_addr[2] =
+ sin6->sin6_addr.s6_addr[3] = 0;
+ memcpy(ll, sin6, sizeof(*ll));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ freeifaddrs(ifap);
+}
+
+void
+icmp6_receive(int fd, short events, void *arg)
+{
+ struct imsg_ra ra;
+
+ struct in6_pktinfo *pi = NULL;
+ struct cmsghdr *cm;
+ ssize_t len;
+ int if_index = 0, *hlimp = NULL;
+ char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ uint8_t *p;
+
+ p = icmp6ev.answer;
+
+ 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_warn("invalid RA 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;
+ }
+
+ if ((size_t)len > sizeof(ra.packet)) {
+ log_warn("invalid RA 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.if_index = if_index;
+ memcpy(&ra.from, &icmp6ev.from, sizeof(ra.from));
+ ra.len = len;
+ memcpy(ra.packet, icmp6ev.answer, len);
+
+ frontend_imsg_compose_engine(IMSG_RA, 0, 0, &ra, sizeof(ra));
+}
+
+void
+send_solicitation(uint32_t if_index)
+{
+ struct in6_pktinfo *pi;
+ struct cmsghdr *cm;
+
+ log_debug("%s(%u)", __func__, if_index);
+
+ dst.sin6_scope_id = if_index;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ pi = (struct in6_pktinfo *)CMSG_DATA(cm);
+ pi->ipi6_ifindex = if_index;
+
+ if (sendmsg(icmp6sock, &sndmhdr, 0) != sizeof(rs) +
+ sizeof(nd_opt_hdr) + sizeof(nd_opt_source_link_addr))
+ log_warn("sendmsg");
+}
diff --git a/sbin/slaacd/frontend.h b/sbin/slaacd/frontend.h
new file mode 100644
index 00000000000..ced99d3921d
--- /dev/null
+++ b/sbin/slaacd/frontend.h
@@ -0,0 +1,26 @@
+/* $OpenBSD: frontend.h,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+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, uint32_t, pid_t, void *,
+ uint16_t);
diff --git a/sbin/slaacd/log.c b/sbin/slaacd/log.c
new file mode 100644
index 00000000000..fa7718246e5
--- /dev/null
+++ b/sbin/slaacd/log.c
@@ -0,0 +1,199 @@
+/* $OpenBSD: log.c,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int debug;
+static int verbose;
+static const char *log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(__progname);
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+ tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+ if (procname != NULL)
+ log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+ verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+ return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+ int saved_errno = errno;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+
+ errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+ int saved_errno = errno;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg,
+ strerror(saved_errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_ERR, emsg, ap);
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ } else {
+ vlog(LOG_ERR, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+
+ errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_ERR, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+ static char s[BUFSIZ];
+ const char *sep;
+
+ if (emsg != NULL) {
+ (void)vsnprintf(s, sizeof(s), emsg, ap);
+ sep = ": ";
+ } else {
+ s[0] = '\0';
+ sep = "";
+ }
+ if (code)
+ logit(LOG_CRIT, "fatal in %s: %s%s%s",
+ log_procname, s, sep, strerror(code));
+ else
+ logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(errno, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(0, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
diff --git a/sbin/slaacd/log.h b/sbin/slaacd/log.h
new file mode 100644
index 00000000000..8653cffd8c8
--- /dev/null
+++ b/sbin/slaacd/log.h
@@ -0,0 +1,46 @@
+/* $OpenBSD: log.h,v 1.1 2017/06/03 10:00:29 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/sbin/slaacd/slaacd.8 b/sbin/slaacd/slaacd.8
new file mode 100644
index 00000000000..b753403ebb1
--- /dev/null
+++ b/sbin/slaacd/slaacd.8
@@ -0,0 +1,116 @@
+.\" $OpenBSD: slaacd.8,v 1.1 2017/06/03 10:00:29 florian Exp $
+.\"
+.\" Copyright (c) 2017 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: June 3 2017 $
+.Dt SLAACD 8
+.Os
+.Sh NAME
+.Nm slaacd
+.Nd a stateless address autoconfiguration daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dv
+.Op Fl s Ar socket
+.Sh DESCRIPTION
+.Nm
+is a stateless address autoconfiguration (SLAAC) daemon.
+It listens for IPv6 router advertisement messages on interfaces with the
+.Sy AUTOCONF6
+flag set.
+.Nm
+derives IPv6 addresses and default routes from received router
+advertisements and installs them in the kernel.
+See
+.Xr hostname.if 5
+and
+.Xr ifconfig 8
+on how to enable auto configuration on an interface.
+.Pp
+.Nm
+monitors network interface states (interface going up or down,
+auto configuration enabled or disabled etc.) and sends router solicitations
+when necessary.
+.Pp
+A running
+.Nm
+can be controlled with the
+.Xr slaacctl 8
+utility.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl s Ar socket
+Use an alternate location for the default control socket.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/slaacd.sockXX" -compact
+.It Pa /var/run/slaacd.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr slaacctl 8 .
+.El
+.Sh SEE ALSO
+.Xr hostname.if 5 ,
+.Xr ifconfig 8 ,
+.Xr slaacctl 8
+.Sh STANDARDS
+.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 J. Jeong
+.%A S. Park
+.%A L. Beloeil
+.%A S. Madanapalli
+.%D November 2010
+.%R RFC 6106
+.%T IPv6 Router Advertisement Options for DNS Configuration
+.Re
+.Pp
+.Rs
+.%A R. Draves
+.%A D. Thaler
+.%D November 2005
+.%R RFC 4191
+.%T Default Router Preferences and More-Specific Routes
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 6.2 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Florian Obser Aq Mt florian@openbsd.org .
diff --git a/sbin/slaacd/slaacd.c b/sbin/slaacd/slaacd.c
new file mode 100644
index 00000000000..04ae07444e6
--- /dev/null
+++ b/sbin/slaacd/slaacd.c
@@ -0,0 +1,768 @@
+/* $OpenBSD: slaacd.c,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2017 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet6/in6_var.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "slaacd.h"
+#include "frontend.h"
+#include "engine.h"
+#include "control.h"
+
+const char* imsg_type_name[] = {
+ "IMSG_NONE",
+ "IMSG_CTL_LOG_VERBOSE",
+ "IMSG_CTL_SHOW_INTERFACE_INFO",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_RA",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_RA_DNSSL",
+ "IMSG_CTL_SHOW_FRONTEND_INFO",
+ "IMSG_CTL_SHOW_MAIN_INFO",
+ "IMSG_CTL_END",
+ "IMSG_SOCKET_IPC",
+ "IMSG_STARTUP",
+ "IMSG_UPDATE_IF",
+ "IMSG_REMOVE_IF",
+ "IMSG_RA",
+ "IMSG_CTL_SEND_SOLICITATION",
+ "IMSG_PROPOSAL",
+ "IMSG_PROPOSAL_ACK",
+ "IMSG_CONFIGURE_ADDRESS",
+ "IMSG_DEL_ADDRESS",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL",
+ "IMSG_FAKE_ACK",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS",
+ "IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL",
+ "IMSG_CONFIGURE_DFR",
+ "IMSG_WITHDRAW_DFR",
+};
+
+__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 *);
+void handle_proposal(struct imsg_proposal *);
+void configure_interface(struct imsg_configure_address *);
+void configure_gateway(struct imsg_configure_dfr *, uint8_t);
+void add_gateway(struct imsg_configure_dfr *);
+void delete_gateway(struct imsg_configure_dfr *);
+
+static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *);
+
+struct imsgev *iev_frontend;
+struct imsgev *iev_engine;
+
+pid_t frontend_pid;
+pid_t engine_pid;
+
+uint32_t cmd_opts;
+
+int routesock, ioctl_sock;
+
+char *csock;
+
+int rtm_seq = 0;
+
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ main_shutdown();
+ case SIGHUP:
+ log_debug("sighub received");
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-dv] [-s socket]\n",
+ __progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct event ev_sigint, ev_sigterm, ev_sighup;
+ int ch;
+ int debug = 0, engine_flag = 0, frontend_flag = 0;
+ char *saved_argv0;
+ int pipe_main2frontend[2];
+ int pipe_main2engine[2];
+
+ csock = SLAACD_SOCKET;
+
+ log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
+ log_setverbose(1);
+
+ saved_argv0 = argv[0];
+ if (saved_argv0 == NULL)
+ saved_argv0 = "slaacd";
+
+ while ((ch = getopt(argc, argv, "dEFs:v")) != -1) {
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'E':
+ engine_flag = 1;
+ break;
+ case 'F':
+ frontend_flag = 1;
+ break;
+ case 's':
+ csock = optarg;
+ break;
+ case 'v':
+ 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);
+
+ /* Check for root privileges. */
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ /* Check for assigned daemon user */
+ if (getpwnam(SLAACD_USER) == NULL)
+ errx(1, "unknown user %s", SLAACD_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);
+
+ slaacd_process = PROC_MAIN;
+
+ log_procinit(log_procnames[slaacd_process]);
+
+ if ((routesock = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC |
+ SOCK_NONBLOCK, 0)) < 0)
+ fatal("route socket");
+
+ event_init();
+
+ /* Setup signal handler. */
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sighup, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Setup pipes to children. */
+
+ if ((iev_frontend = malloc(sizeof(struct imsgev))) == NULL ||
+ (iev_engine = malloc(sizeof(struct imsgev))) == NULL)
+ fatal(NULL);
+ imsg_init(&iev_frontend->ibuf, pipe_main2frontend[0]);
+ iev_frontend->handler = main_dispatch_frontend;
+ imsg_init(&iev_engine->ibuf, pipe_main2engine[0]);
+ iev_engine->handler = main_dispatch_engine;
+
+ /* Setup event handlers for pipes to engine & frontend. */
+ iev_frontend->events = EV_READ;
+ event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
+ iev_frontend->events, iev_frontend->handler, iev_frontend);
+ event_add(&iev_frontend->ev, NULL);
+
+ iev_engine->events = EV_READ;
+ event_set(&iev_engine->ev, iev_engine->ibuf.fd, iev_engine->events,
+ iev_engine->handler, iev_engine);
+ event_add(&iev_engine->ev, NULL);
+
+ if (main_imsg_send_ipc_sockets(&iev_frontend->ibuf, &iev_engine->ibuf))
+ fatal("could not establish imsg links");
+
+ if ((ioctl_sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0)
+ fatal("socket");
+
+#if 0
+ /* XXX ioctl SIOCAIFADDR_IN6 */
+ if (pledge("rpath stdio sendfd cpath", NULL) == -1)
+ fatal("pledge");
+#endif
+
+ main_imsg_compose_frontend(IMSG_STARTUP, 0, NULL, 0);
+
+ event_dispatch();
+
+ main_shutdown();
+ return (0);
+}
+
+__dead void
+main_shutdown(void)
+{
+ pid_t pid;
+ int status;
+
+ /* Close pipes. */
+ msgbuf_clear(&iev_frontend->ibuf.w);
+ close(iev_frontend->ibuf.fd);
+ msgbuf_clear(&iev_engine->ibuf.w);
+ close(iev_engine->ibuf.fd);
+
+ log_debug("waiting for children to terminate");
+ do {
+ pid = wait(&status);
+ if (pid == -1) {
+ if (errno != EINTR && errno != ECHILD)
+ fatal("wait");
+ } else if (WIFSIGNALED(status))
+ log_warnx("%s terminated; signal %d",
+ (pid == engine_pid) ? "engine" :
+ "frontend", WTERMSIG(status));
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ free(iev_frontend);
+ free(iev_engine);
+
+ 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_CTL_LOG_VERBOSE:
+ /* Already checked by frontend. */
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+main_dispatch_engine(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ struct imsg_proposal proposal;
+ struct imsg_configure_address address;
+ struct imsg_configure_dfr dfr;
+ ssize_t n;
+ int shut = 0;
+
+ ibuf = &iev->ibuf;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("imsg_get");
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_PROPOSAL:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(proposal))
+ fatal("%s: IMSG_PROPOSAL wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&proposal, imsg.data, sizeof(proposal));
+ handle_proposal(&proposal);
+ break;
+ case IMSG_CONFIGURE_ADDRESS:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(address))
+ fatal("%s: IMSG_CONFIGURE_ADDRESS wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&address, imsg.data, sizeof(address));
+ configure_interface(&address);
+ break;
+ case IMSG_CONFIGURE_DFR:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(dfr))
+ fatal("%s: IMSG_CONFIGURE_DFR wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&dfr, imsg.data, sizeof(dfr));
+ add_gateway(&dfr);
+ break;
+ case IMSG_WITHDRAW_DFR:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(dfr))
+ fatal("%s: IMSG_CONFIGURE_DFR wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&dfr, imsg.data, sizeof(dfr));
+ delete_gateway(&dfr);
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+int
+main_imsg_compose_frontend(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ if (iev_frontend)
+ return (imsg_compose_event(iev_frontend, type, 0, pid, -1, data,
+ datalen));
+ else
+ return (-1);
+}
+
+int
+main_imsg_compose_engine(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ if (iev_engine)
+ return(imsg_compose_event(iev_engine, type, 0, pid, -1, data,
+ datalen));
+ else
+ return (-1);
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+ pid_t pid, int fd, void *data, uint16_t datalen)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data,
+ datalen)) != -1)
+ imsg_event_add(iev);
+
+ return (ret);
+}
+
+static int
+main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf,
+ struct imsgbuf *engine_buf)
+{
+ int pipe_frontend2engine[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_frontend2engine) == -1)
+ return (-1);
+
+ if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC, 0, 0,
+ pipe_frontend2engine[0], NULL, 0) == -1)
+ return (-1);
+ imsg_flush(frontend_buf);
+ if (imsg_compose(engine_buf, IMSG_SOCKET_IPC, 0, 0,
+ pipe_frontend2engine[1], NULL, 0) == -1)
+ return (-1);
+ imsg_flush(engine_buf);
+ return (0);
+}
+
+#define ROUNDUP(a) \
+ (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a))
+
+void
+handle_proposal(struct imsg_proposal *proposal)
+{
+ struct rt_msghdr rtm;
+ struct sockaddr_in6 ifa, mask;
+ struct sockaddr_rtlabel rl;
+ struct iovec iov[13];
+ long pad = 0;
+ int iovcnt = 0, padlen;
+
+ memset(&rtm, 0, sizeof(rtm));
+
+ rtm.rtm_version = RTM_VERSION;
+ rtm.rtm_type = RTM_PROPOSAL;
+ rtm.rtm_msglen = sizeof(rtm);
+ rtm.rtm_tableid = 0; /* XXX imsg->rdomain; */
+ rtm.rtm_index = proposal->if_index;
+ rtm.rtm_seq = ++rtm_seq;
+ rtm.rtm_priority = RTP_PROPOSAL_SLAAC;
+ rtm.rtm_addrs = (proposal->rtm_addrs & (RTA_NETMASK | RTA_IFA)) |
+ RTA_LABEL;
+ rtm.rtm_flags = RTF_UP;
+
+ iov[iovcnt].iov_base = &rtm;
+ iov[iovcnt++].iov_len = sizeof(rtm);
+
+ if (rtm.rtm_addrs & RTA_NETMASK) {
+ memset(&mask, 0, sizeof(mask));
+ mask.sin6_family = AF_INET6;
+ mask.sin6_len = sizeof(struct sockaddr_in6);
+ mask.sin6_addr = proposal->mask;
+
+ iov[iovcnt].iov_base = &mask;
+ iov[iovcnt++].iov_len = sizeof(mask);
+ rtm.rtm_msglen += sizeof(mask);
+ padlen = ROUNDUP(sizeof(mask)) - sizeof(mask);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ rtm.rtm_msglen += padlen;
+ }
+ }
+
+ if (rtm.rtm_addrs & RTA_IFA) {
+ memcpy(&ifa, &proposal->addr, sizeof(ifa));
+
+ if (ifa.sin6_family != AF_INET6 || ifa.sin6_len !=
+ sizeof(struct sockaddr_in6)) {
+ log_warnx("%s: invalid address", __func__);
+ return;
+ }
+
+ iov[iovcnt].iov_base = &ifa;
+ iov[iovcnt++].iov_len = sizeof(ifa);
+ rtm.rtm_msglen += sizeof(ifa);
+ padlen = ROUNDUP(sizeof(ifa)) - sizeof(ifa);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ rtm.rtm_msglen += padlen;
+ }
+ }
+
+ rl.sr_len = sizeof(rl);
+ rl.sr_family = AF_UNSPEC;
+ if (snprintf(rl.sr_label, sizeof(rl.sr_label), "%s: %lld %d", "slaacd",
+ proposal->id, (int32_t)proposal->pid) >=
+ (ssize_t)sizeof(rl.sr_label))
+ log_warnx("route label truncated");
+
+ iov[iovcnt].iov_base = &rl;
+ iov[iovcnt++].iov_len = sizeof(rl);
+ rtm.rtm_msglen += sizeof(rl);
+ padlen = ROUNDUP(sizeof(rl)) - sizeof(rl);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ rtm.rtm_msglen += padlen;
+ }
+
+ if (writev(routesock, iov, iovcnt) == -1)
+ log_warn("failed to send proposal");
+}
+
+void
+configure_interface(struct imsg_configure_address *address)
+{
+
+ struct in6_aliasreq in6_addreq;
+ time_t t;
+ char *if_name;
+
+ memset(&in6_addreq, 0, sizeof(in6_addreq));
+
+ if_name = if_indextoname(address->if_index, in6_addreq.ifra_name);
+ if (if_name == NULL) {
+ log_warn("%s: cannot find interface %d", __func__,
+ address->if_index);
+ return;
+ }
+
+ memcpy(&in6_addreq.ifra_addr, &address->addr,
+ sizeof(in6_addreq.ifra_addr));
+ memcpy(&in6_addreq.ifra_prefixmask.sin6_addr, &address->mask,
+ sizeof(in6_addreq.ifra_prefixmask.sin6_addr));
+ in6_addreq.ifra_prefixmask.sin6_family = AF_INET6;
+ in6_addreq.ifra_prefixmask.sin6_len =
+ sizeof(in6_addreq.ifra_prefixmask);
+
+ t = time(NULL);
+
+ in6_addreq.ifra_lifetime.ia6t_expire = t + address->vltime;
+ in6_addreq.ifra_lifetime.ia6t_vltime = address->vltime;
+
+ in6_addreq.ifra_lifetime.ia6t_preferred = t + address->pltime;
+ in6_addreq.ifra_lifetime.ia6t_pltime = address->pltime;
+
+ in6_addreq.ifra_flags |= IN6_IFF_AUTOCONF;
+
+ if (address->privacy)
+ in6_addreq.ifra_flags |= IN6_IFF_PRIVACY;
+
+ log_debug("%s: %s", __func__, if_name);
+
+ if (ioctl(ioctl_sock, SIOCAIFADDR_IN6, &in6_addreq) < 0)
+ fatal("SIOCAIFADDR_IN6");
+}
+
+void
+configure_gateway(struct imsg_configure_dfr *dfr, uint8_t rtm_type)
+{
+ struct rt_msghdr rtm;
+ struct sockaddr_in6 dst, gw, mask;
+ struct iovec iov[8];
+ long pad = 0;
+ int iovcnt = 0, padlen;
+
+ memset(&rtm, 0, sizeof(rtm));
+
+ rtm.rtm_version = RTM_VERSION;
+ rtm.rtm_type = rtm_type;
+ rtm.rtm_msglen = sizeof(rtm);
+ rtm.rtm_tableid = 0; /* XXX imsg->rdomain; */
+ rtm.rtm_index = dfr->if_index;
+ rtm.rtm_seq = ++rtm_seq;
+ rtm.rtm_priority = RTP_DEFAULT;
+ rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+ rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+
+ iov[iovcnt].iov_base = &rtm;
+ iov[iovcnt++].iov_len = sizeof(rtm);
+
+ memset(&dst, 0, sizeof(mask));
+ dst.sin6_family = AF_INET6;
+ dst.sin6_len = sizeof(struct sockaddr_in6);
+
+ iov[iovcnt].iov_base = &dst;
+ iov[iovcnt++].iov_len = sizeof(dst);
+ rtm.rtm_msglen += sizeof(dst);
+ padlen = ROUNDUP(sizeof(dst)) - sizeof(dst);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ rtm.rtm_msglen += padlen;
+ }
+
+ memcpy(&gw, &dfr->addr, sizeof(gw));
+ *(u_int16_t *)& gw.sin6_addr.s6_addr[2] = htons(gw.sin6_scope_id);
+ /* gw.sin6_scope_id = 0; XXX route(8) does this*/
+ iov[iovcnt].iov_base = &gw;
+ iov[iovcnt++].iov_len = sizeof(gw);
+ rtm.rtm_msglen += sizeof(gw);
+ padlen = ROUNDUP(sizeof(gw)) - sizeof(gw);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ rtm.rtm_msglen += padlen;
+ }
+
+ memset(&mask, 0, sizeof(mask));
+ mask.sin6_family = AF_INET6;
+ mask.sin6_len = 0;//sizeof(struct sockaddr_in6);
+ iov[iovcnt].iov_base = &mask;
+ iov[iovcnt++].iov_len = sizeof(mask);
+ rtm.rtm_msglen += sizeof(mask);
+ padlen = ROUNDUP(sizeof(mask)) - sizeof(mask);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ rtm.rtm_msglen += padlen;
+ }
+
+ if (writev(routesock, iov, iovcnt) == -1)
+ log_warn("failed to send route message");
+}
+
+void
+add_gateway(struct imsg_configure_dfr *dfr)
+{
+ configure_gateway(dfr, RTM_ADD);
+}
+
+void
+delete_gateway(struct imsg_configure_dfr *dfr)
+{
+ configure_gateway(dfr, RTM_DELETE);
+}
diff --git a/sbin/slaacd/slaacd.h b/sbin/slaacd/slaacd.h
new file mode 100644
index 00000000000..632ed46d256
--- /dev/null
+++ b/sbin/slaacd/slaacd.h
@@ -0,0 +1,188 @@
+/* $OpenBSD: slaacd.h,v 1.1 2017/06/03 10:00:29 florian Exp $ */
+
+/*
+ * Copyright (c) 2017 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 SLAACD_SOCKET "/var/run/slaacd.sock"
+#define SLAACD_USER "_slaacd"
+
+#define OPT_VERBOSE 0x00000001
+#define OPT_VERBOSE2 0x00000002
+
+#define SLAACD_MAXTEXT 256
+#define SLAACD_MAXGROUPNAME 16
+
+/* MAXDNAME from arpa/namesr.h */
+#define SLAACD_MAX_DNSSL 1025
+
+static const char * const log_procnames[] = {
+ "main",
+ "frontend",
+ "engine"
+};
+
+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_SHOW_INTERFACE_INFO,
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA,
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX,
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS,
+ IMSG_CTL_SHOW_INTERFACE_INFO_RA_DNSSL,
+ IMSG_CTL_END,
+ IMSG_SOCKET_IPC,
+ IMSG_STARTUP,
+ IMSG_UPDATE_IF,
+ IMSG_REMOVE_IF,
+ IMSG_RA,
+ IMSG_CTL_SEND_SOLICITATION,
+ IMSG_PROPOSAL,
+ IMSG_PROPOSAL_ACK,
+ IMSG_CONFIGURE_ADDRESS,
+ IMSG_DEL_ADDRESS,
+ IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS,
+ IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL,
+ IMSG_FAKE_ACK,
+ IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS,
+ IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL,
+ IMSG_CONFIGURE_DFR,
+ IMSG_WITHDRAW_DFR,
+};
+
+extern const char* imsg_type_name[];
+
+enum {
+ PROC_MAIN,
+ PROC_ENGINE,
+ PROC_FRONTEND
+} slaacd_process;
+
+struct ctl_engine_info {
+ uint32_t if_index;
+ int running;
+ int autoconfprivacy;
+ struct ether_addr hw_address;
+ struct sockaddr_in6 ll_address;
+};
+
+enum rpref {
+ LOW,
+ MEDIUM,
+ HIGH,
+};
+
+struct ctl_engine_info_ra {
+ struct sockaddr_in6 from;
+ struct timespec when;
+ struct timespec uptime;
+ uint8_t curhoplimit;
+ int managed;
+ int other;
+ char rpref[sizeof("MEDIUM")];
+ uint16_t router_lifetime; /* in seconds */
+ uint32_t reachable_time; /* in milliseconds */
+ uint32_t retrans_time; /* in milliseconds */
+};
+
+struct ctl_engine_info_ra_prefix {
+ struct in6_addr prefix;
+ uint8_t prefix_len;
+ int onlink;
+ int autonomous;
+ uint32_t vltime;
+ uint32_t pltime;
+};
+
+struct ctl_engine_info_ra_rdns {
+ uint32_t lifetime;
+ struct in6_addr rdns;
+};
+
+struct ctl_engine_info_ra_dnssl {
+ uint32_t lifetime;
+ char dnssl[SLAACD_MAX_DNSSL];
+};
+
+struct ctl_engine_info_address_proposal {
+ int64_t id;
+ char state[sizeof("PROPOSAL_NEARLY_EXPIRED")];
+ int next_timeout;
+ int timeout_count;
+ struct timespec when;
+ struct timespec uptime;
+ struct sockaddr_in6 addr;
+ struct in6_addr prefix;
+ int privacy;
+ uint8_t prefix_len;
+ uint32_t vltime;
+ uint32_t pltime;
+};
+
+struct ctl_engine_info_dfr_proposal {
+ int64_t id;
+ char state[sizeof("PROPOSAL_NEARLY_EXPIRED")];
+ int next_timeout;
+ int timeout_count;
+ struct timespec when;
+ struct timespec uptime;
+ struct sockaddr_in6 addr;
+ uint32_t router_lifetime;
+ char rpref[sizeof("MEDIUM")];
+};
+
+struct imsg_ifinfo {
+ uint32_t if_index;
+ int running;
+ int autoconfprivacy;
+ struct ether_addr hw_address;
+ struct sockaddr_in6 ll_address;
+};
+
+struct imsg_del_addr {
+ uint32_t if_index;
+ struct sockaddr_in6 addr;
+};
+
+struct imsg_proposal_ack {
+ int64_t id;
+ pid_t pid;
+ uint32_t if_index;
+};
+
+struct imsg_ra {
+ uint32_t if_index;
+ struct sockaddr_in6 from;
+ ssize_t len;
+ uint8_t packet[1500];
+};
+
+extern uint32_t cmd_opts;
+
+/* slaacd.c */
+int main_imsg_compose_frontend(int, pid_t, void *, uint16_t);
+int main_imsg_compose_engine(int, pid_t, void *, uint16_t);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
+ int, void *, uint16_t);