diff options
author | Florian Obser <florian@cvs.openbsd.org> | 2017-06-03 10:00:30 +0000 |
---|---|---|
committer | Florian Obser <florian@cvs.openbsd.org> | 2017-06-03 10:00:30 +0000 |
commit | f5d3e9ec6a694be409202febd15b21d110c9ccd2 (patch) | |
tree | 6da146b33744ff127b42eec00f54d21a3352e87f /sbin | |
parent | d2e739346f9e461b7a5afbecfc9a5fb2644db1ab (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/Makefile | 4 | ||||
-rw-r--r-- | sbin/slaacd/Makefile | 21 | ||||
-rw-r--r-- | sbin/slaacd/control.c | 301 | ||||
-rw-r--r-- | sbin/slaacd/control.h | 35 | ||||
-rw-r--r-- | sbin/slaacd/engine.c | 2177 | ||||
-rw-r--r-- | sbin/slaacd/engine.h | 47 | ||||
-rw-r--r-- | sbin/slaacd/frontend.c | 826 | ||||
-rw-r--r-- | sbin/slaacd/frontend.h | 26 | ||||
-rw-r--r-- | sbin/slaacd/log.c | 199 | ||||
-rw-r--r-- | sbin/slaacd/log.h | 46 | ||||
-rw-r--r-- | sbin/slaacd/slaacd.8 | 116 | ||||
-rw-r--r-- | sbin/slaacd/slaacd.c | 768 | ||||
-rw-r--r-- | sbin/slaacd/slaacd.h | 188 |
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); |