summaryrefslogtreecommitdiff
path: root/sbin/slaacd/frontend.c
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/slaacd/frontend.c')
-rw-r--r--sbin/slaacd/frontend.c826
1 files changed, 826 insertions, 0 deletions
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");
+}