[BACK]Return to frontend.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / rad

File: [local] / src / usr.sbin / rad / frontend.c (download)

Revision 1.46, Fri May 17 06:50:14 2024 UTC (3 weeks, 3 days ago) by florian
Branch: MAIN
Changes since 1.45: +44 -1 lines

Send source link-layer address option in router advertisements.

With this, hosts immediately learn the layer 2 (i.e. ethernet mac)
address of their default router and don't need to do another round
trip.

It also turns out that apple devices (macOS & iOS) install the default
route as what they call "interface scoped" if a DNS option is present
and the source link-layer address option is absent. This effectively
makes the default route unusable.

Problem with fruit devices tracked down & diff by Ryan Vogt (rvogt.ca
AT gmail), thanks!

OK sthen, bket

/*	$OpenBSD: frontend.c,v 1.46 2024/05/17 06:50:14 florian Exp $	*/

/*
 * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
 * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/uio.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>

#include <arpa/inet.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet6/nd6.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>

#include <ctype.h>
#include <errno.h>
#include <event.h>
#include <ifaddrs.h>
#include <imsg.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "log.h"
#include "rad.h"
#include "frontend.h"
#include "control.h"

#define	RA_MAX_SIZE		1500
#define	ROUTE_SOCKET_BUF_SIZE	16384

struct icmp6_ev {
	struct event		 ev;
	uint8_t			 answer[1500];
	struct msghdr		 rcvmhdr;
	struct iovec		 rcviov[1];
	struct sockaddr_in6	 from;
	int			 refcnt;
};

struct ra_iface {
	TAILQ_ENTRY(ra_iface)		 entry;
	struct icmp6_ev			*icmp6ev;
	struct ra_prefix_conf_head	 prefixes;
	char				 name[IF_NAMESIZE];
	char				 conf_name[IF_NAMESIZE];
	uint32_t			 if_index;
	int				 rdomain;
	int				 removed;
	int				 link_state;
	int				 prefix_count;
	size_t				 datalen;
	uint8_t				 data[RA_MAX_SIZE];
};

#define ND_OPT_PREF64	38
struct nd_opt_pref64 {
	u_int8_t	nd_opt_pref64_type;
	u_int8_t	nd_opt_pref64_len;
	u_int16_t	nd_opt_pref64_sltime_plc;
	u_int8_t	nd_opt_pref64[12];
};

struct nd_opt_source_link_addr {
	u_int8_t		nd_opt_source_link_addr_type;
	u_int8_t		nd_opt_source_link_addr_len;
	struct ether_addr	nd_opt_source_link_addr_hw_addr;
};

TAILQ_HEAD(, ra_iface)	ra_interfaces;

__dead void		 frontend_shutdown(void);
void			 frontend_sig_handler(int, short, void *);
void			 frontend_startup(void);
void			 icmp6_receive(int, short, void *);
void			 join_all_routers_mcast_group(struct ra_iface *);
void			 leave_all_routers_mcast_group(struct ra_iface *);
int			 get_link_state(char *);
int			 get_ifrdomain(char *);
void			 merge_ra_interface(char *, char *);
void			 merge_ra_interfaces(void);
struct ra_iface		*find_ra_iface_by_id(uint32_t);
struct ra_iface		*find_ra_iface_by_name(char *);
struct ra_iface_conf	*find_ra_iface_conf(struct ra_iface_conf_head *,
			    char *);
struct ra_prefix_conf	*find_ra_prefix_conf(struct ra_prefix_conf_head*,
			    struct in6_addr *, int);
struct icmp6_ev		*get_icmp6ev_by_rdomain(int);
void			 unref_icmp6ev(struct ra_iface *);
void			 set_icmp6sock(int, int);
void			 add_new_prefix_to_ra_iface(struct ra_iface *r,
			    struct in6_addr *, int, struct ra_prefix_conf *);
void			 free_ra_iface(struct ra_iface *);
int			 in6_mask2prefixlen(struct in6_addr *);
void			 get_interface_prefixes(struct ra_iface *,
			     struct ra_prefix_conf *);
int			 interface_has_linklocal_address(char *);
void			 build_packet(struct ra_iface *);
void			 build_leaving_packet(struct ra_iface *);
void			 ra_output(struct ra_iface *, struct sockaddr_in6 *);
void			 get_rtaddrs(int, struct sockaddr *,
			     struct sockaddr **);
void			 route_receive(int, short, void *);
void			 handle_route_message(struct rt_msghdr *,
			     struct sockaddr **);

struct rad_conf	*frontend_conf;
static struct imsgev	*iev_main;
static struct imsgev	*iev_engine;
struct event		 ev_route;
int			 ioctlsock = -1, routesock = -1;
struct ipv6_mreq	 all_routers;
struct msghdr		 sndmhdr;
struct iovec		 sndiov[2];

void
frontend_sig_handler(int sig, short event, void *bula)
{
	/*
	 * Normal signal handler rules don't apply because libevent
	 * decouples for us.
	 */

	switch (sig) {
	case SIGINT:
	case SIGTERM:
		frontend_shutdown();
	default:
		fatalx("unexpected signal");
	}
}

void
frontend(int debug, int verbose)
{
	struct event		 ev_sigint, ev_sigterm;
	struct passwd		*pw;
	size_t			 sndcmsgbuflen;
	uint8_t			*sndcmsgbuf = NULL;

	frontend_conf = config_new_empty();

	log_init(debug, LOG_DAEMON);
	log_setverbose(verbose);

	if ((pw = getpwnam(RAD_USER)) == NULL)
		fatal("getpwnam");

	if (chroot(pw->pw_dir) == -1)
		fatal("chroot");
	if (chdir("/") == -1)
		fatal("chdir(\"/\")");

	setproctitle("%s", "frontend");
	log_procinit("frontend");

	if (setgroups(1, &pw->pw_gid) ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
		fatal("can't drop privileges");

	/* XXX pass in from main */
	if ((ioctlsock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1)
		fatal("socket");

	if (pledge("stdio inet unix recvfd route mcast", NULL) == -1)
		fatal("pledge");

	event_init();

	/* Setup signal handler. */
	signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL);
	signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL);
	signal_add(&ev_sigint, NULL);
	signal_add(&ev_sigterm, NULL);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, SIG_IGN);

	/* Setup pipe and event handler to the parent process. */
	if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
		fatal(NULL);
	imsg_init(&iev_main->ibuf, 3);
	iev_main->handler = frontend_dispatch_main;
	iev_main->events = EV_READ;
	event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
	    iev_main->handler, iev_main);
	event_add(&iev_main->ev, NULL);

	if (inet_pton(AF_INET6, "ff02::2",
	    &all_routers.ipv6mr_multiaddr.s6_addr) == -1)
		fatal("inet_pton");

	sndcmsgbuflen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
	    CMSG_SPACE(sizeof(int));
	if ((sndcmsgbuf = malloc(sndcmsgbuflen)) == NULL)
		fatal("%s", __func__);

	sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
	sndmhdr.msg_iov = sndiov;
	sndmhdr.msg_iovlen = 1;
	sndmhdr.msg_control = sndcmsgbuf;
	sndmhdr.msg_controllen = sndcmsgbuflen;

	TAILQ_INIT(&ra_interfaces);

	event_dispatch();

	frontend_shutdown();
}

__dead void
frontend_shutdown(void)
{
	/* Close pipes. */
	msgbuf_write(&iev_engine->ibuf.w);
	msgbuf_clear(&iev_engine->ibuf.w);
	close(iev_engine->ibuf.fd);
	msgbuf_write(&iev_main->ibuf.w);
	msgbuf_clear(&iev_main->ibuf.w);
	close(iev_main->ibuf.fd);

	config_clear(frontend_conf);

	free(iev_engine);
	free(iev_main);

	log_info("frontend exiting");
	exit(0);
}

int
frontend_imsg_compose_main(int type, pid_t pid, void *data, uint16_t datalen)
{
	return (imsg_compose_event(iev_main, type, 0, pid, -1, data,
	    datalen));
}

int
frontend_imsg_compose_engine(int type, pid_t pid, void *data, uint16_t datalen)
{
	return (imsg_compose_event(iev_engine, type, 0, pid, -1, data,
	    datalen));
}

void
frontend_dispatch_main(int fd, short event, void *bula)
{
	static struct rad_conf		*nconf;
	static struct ra_iface_conf	*ra_iface_conf;
	static struct ra_options_conf	*ra_options;
	struct imsg			 imsg;
	struct imsgev			*iev = bula;
	struct imsgbuf			*ibuf = &iev->ibuf;
	struct ra_prefix_conf		*ra_prefix_conf;
	struct ra_rdnss_conf		*ra_rdnss_conf;
	struct ra_dnssl_conf		*ra_dnssl_conf;
	struct ra_pref64_conf		*pref64;
	int				 n, shut = 0, icmp6sock, rdomain;

	if (event & EV_READ) {
		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
			fatal("imsg_read error");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}
	if (event & EV_WRITE) {
		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
			fatal("msgbuf_write");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}

	for (;;) {
		if ((n = imsg_get(ibuf, &imsg)) == -1)
			fatal("%s: imsg_get error", __func__);
		if (n == 0)	/* No more messages. */
			break;

		switch (imsg.hdr.type) {
		case IMSG_SOCKET_IPC:
			/*
			 * Setup pipe and event handler to the engine
			 * process.
			 */
			if (iev_engine)
				fatalx("%s: received unexpected imsg fd to "
				    "frontend", __func__);
			if ((fd = imsg_get_fd(&imsg)) == -1)
				fatalx("%s: expected to receive imsg fd to "
				   "frontend but didn't receive any",
				   __func__);

			iev_engine = malloc(sizeof(struct imsgev));
			if (iev_engine == NULL)
				fatal(NULL);

			imsg_init(&iev_engine->ibuf, fd);
			iev_engine->handler = frontend_dispatch_engine;
			iev_engine->events = EV_READ;

			event_set(&iev_engine->ev, iev_engine->ibuf.fd,
			iev_engine->events, iev_engine->handler, iev_engine);
			event_add(&iev_engine->ev, NULL);
			break;
		case IMSG_RECONF_CONF:
			if (nconf != NULL)
				fatalx("%s: IMSG_RECONF_CONF already in "
				    "progress", __func__);
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct rad_conf))
				fatalx("%s: IMSG_RECONF_CONF wrong length: %lu",
				    __func__, IMSG_DATA_SIZE(imsg));
			if ((nconf = malloc(sizeof(struct rad_conf))) ==
			    NULL)
				fatal(NULL);
			memcpy(nconf, imsg.data, sizeof(struct rad_conf));
			SIMPLEQ_INIT(&nconf->ra_iface_list);
			SIMPLEQ_INIT(&nconf->ra_options.ra_rdnss_list);
			SIMPLEQ_INIT(&nconf->ra_options.ra_dnssl_list);
			SIMPLEQ_INIT(&nconf->ra_options.ra_pref64_list);
			ra_options = &nconf->ra_options;
			break;
		case IMSG_RECONF_RA_IFACE:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    ra_iface_conf))
				fatalx("%s: IMSG_RECONF_RA_IFACE wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((ra_iface_conf = malloc(sizeof(struct
			    ra_iface_conf))) == NULL)
				fatal(NULL);
			memcpy(ra_iface_conf, imsg.data, sizeof(struct
			    ra_iface_conf));
			ra_iface_conf->autoprefix = NULL;
			SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list);
			SIMPLEQ_INIT(&ra_iface_conf->ra_options.ra_rdnss_list);
			SIMPLEQ_INIT(&ra_iface_conf->ra_options.ra_dnssl_list);
			SIMPLEQ_INIT(&ra_iface_conf->ra_options.ra_pref64_list);
			SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list,
			    ra_iface_conf, entry);
			ra_options = &ra_iface_conf->ra_options;
			break;
		case IMSG_RECONF_RA_AUTOPREFIX:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    ra_prefix_conf))
				fatalx("%s: IMSG_RECONF_RA_AUTOPREFIX wrong "
				    "length: %lu", __func__,
				    IMSG_DATA_SIZE(imsg));
			if ((ra_iface_conf->autoprefix = malloc(sizeof(struct
			    ra_prefix_conf))) == NULL)
				fatal(NULL);
			memcpy(ra_iface_conf->autoprefix, imsg.data,
			    sizeof(struct ra_prefix_conf));
			break;
		case IMSG_RECONF_RA_PREFIX:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    ra_prefix_conf))
				fatalx("%s: IMSG_RECONF_RA_PREFIX wrong "
				    "length: %lu", __func__,
				    IMSG_DATA_SIZE(imsg));
			if ((ra_prefix_conf = malloc(sizeof(struct
			    ra_prefix_conf))) == NULL)
				fatal(NULL);
			memcpy(ra_prefix_conf, imsg.data,
			    sizeof(struct ra_prefix_conf));
			SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list,
			    ra_prefix_conf, entry);
			break;
		case IMSG_RECONF_RA_RDNSS:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    ra_rdnss_conf))
				fatalx("%s: IMSG_RECONF_RA_RDNSS wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((ra_rdnss_conf = malloc(sizeof(struct
			    ra_rdnss_conf))) == NULL)
				fatal(NULL);
			memcpy(ra_rdnss_conf, imsg.data, sizeof(struct
			    ra_rdnss_conf));
			SIMPLEQ_INSERT_TAIL(&ra_options->ra_rdnss_list,
			    ra_rdnss_conf, entry);
			break;
		case IMSG_RECONF_RA_DNSSL:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    ra_dnssl_conf))
				fatalx("%s: IMSG_RECONF_RA_DNSSL wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((ra_dnssl_conf = malloc(sizeof(struct
			    ra_dnssl_conf))) == NULL)
				fatal(NULL);
			memcpy(ra_dnssl_conf, imsg.data, sizeof(struct
			    ra_dnssl_conf));
			SIMPLEQ_INSERT_TAIL(&ra_options->ra_dnssl_list,
			    ra_dnssl_conf, entry);
			break;
		case IMSG_RECONF_RA_PREF64:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    ra_pref64_conf))
				fatalx("%s: IMSG_RECONF_RA_PREF64 wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((pref64 = malloc(sizeof(struct ra_pref64_conf))) ==
			    NULL)
				fatal(NULL);
			memcpy(pref64, imsg.data, sizeof(struct ra_pref64_conf));
			SIMPLEQ_INSERT_TAIL(&ra_options->ra_pref64_list, pref64,
			    entry);
			break;
		case IMSG_RECONF_END:
			if (nconf == NULL)
				fatalx("%s: IMSG_RECONF_END without "
				    "IMSG_RECONF_CONF", __func__);
			merge_config(frontend_conf, nconf);
			merge_ra_interfaces();
			nconf = NULL;
			break;
		case IMSG_ICMP6SOCK:
			if ((icmp6sock = imsg_get_fd(&imsg)) == -1)
				fatalx("%s: expected to receive imsg "
				    "ICMPv6 fd but didn't receive any",
				    __func__);
			if (IMSG_DATA_SIZE(imsg) != sizeof(rdomain))
				fatalx("%s: IMSG_ICMP6SOCK wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&rdomain, imsg.data, sizeof(rdomain));
			set_icmp6sock(icmp6sock, rdomain);
			break;
		case IMSG_ROUTESOCK:
			if (routesock != -1)
				fatalx("%s: received unexpected routesock fd",
				    __func__);
			if ((routesock = imsg_get_fd(&imsg)) == -1)
				fatalx("%s: expected to receive imsg "
				    "routesocket fd but didn't receive any",
				    __func__);
			event_set(&ev_route, routesock, EV_READ | EV_PERSIST,
			    route_receive, NULL);
			break;
		case IMSG_STARTUP:
			frontend_startup();
			break;
		case IMSG_CONTROLFD:
			if ((fd = imsg_get_fd(&imsg)) == -1)
				fatalx("%s: expected to receive imsg "
				    "control fd but didn't receive any",
				    __func__);
			/* Listen on control socket. */
			control_listen(fd);
			break;
		default:
			log_debug("%s: error handling imsg %d", __func__,
			    imsg.hdr.type);
			break;
		}
		imsg_free(&imsg);
	}
	if (!shut)
		imsg_event_add(iev);
	else {
		/* This pipe is dead. Remove its event handler. */
		event_del(&iev->ev);
		event_loopexit(NULL);
	}
}

void
frontend_dispatch_engine(int fd, short event, void *bula)
{
	struct imsgev		*iev = bula;
	struct imsgbuf		*ibuf = &iev->ibuf;
	struct imsg		 imsg;
	struct imsg_send_ra	 send_ra;
	struct ra_iface		*ra_iface;
	uint32_t		 if_index;
	int			 n, shut = 0;

	if (event & EV_READ) {
		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
			fatal("imsg_read error");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}
	if (event & EV_WRITE) {
		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
			fatal("msgbuf_write");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}

	for (;;) {
		if ((n = imsg_get(ibuf, &imsg)) == -1)
			fatal("%s: imsg_get error", __func__);
		if (n == 0)	/* No more messages. */
			break;

		switch (imsg.hdr.type) {
		case IMSG_SEND_RA:
			if (IMSG_DATA_SIZE(imsg) != sizeof(send_ra))
				fatalx("%s: IMSG_SEND_RA wrong length: %lu",
				    __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&send_ra, imsg.data, sizeof(send_ra));
			ra_iface = find_ra_iface_by_id(send_ra.if_index);
			if (ra_iface)
				ra_output(ra_iface, &send_ra.to);
			break;
		case IMSG_REMOVE_IF:
			if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
				fatalx("%s: IMSG_REMOVE_IF wrong length: %lu",
				    __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&if_index, imsg.data, sizeof(if_index));
			ra_iface = find_ra_iface_by_id(if_index);
			if (ra_iface) {
				TAILQ_REMOVE(&ra_interfaces, ra_iface, entry);
				free_ra_iface(ra_iface);
			}
			break;
		default:
			log_debug("%s: error handling imsg %d", __func__,
			    imsg.hdr.type);
			break;
		}
		imsg_free(&imsg);
	}
	if (!shut)
		imsg_event_add(iev);
	else {
		/* This pipe is dead. Remove its event handler. */
		event_del(&iev->ev);
		event_loopexit(NULL);
	}
}

void
frontend_startup(void)
{
	if (!event_initialized(&ev_route))
		fatalx("%s: did not receive a route socket from the main "
		    "process", __func__);

	event_add(&ev_route, NULL);
}


void
icmp6_receive(int fd, short events, void *arg)
{
	struct icmp6_ev		*icmp6ev;
	struct icmp6_hdr	*icmp6_hdr;
	struct imsg_ra_rs	 ra_rs;
	struct in6_pktinfo	*pi = NULL;
	struct cmsghdr		*cm;
	ssize_t			 len;
	int			 if_index = 0, *hlimp = NULL;
	char			 ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];

	icmp6ev = arg;
	if ((len = recvmsg(fd, &icmp6ev->rcvmhdr, 0)) == -1) {
		log_warn("recvmsg");
		return;
	}

	if ((size_t)len < sizeof(struct icmp6_hdr))
		return;

	icmp6_hdr = (struct icmp6_hdr *)icmp6ev->answer;
	if (icmp6_hdr->icmp6_type != ND_ROUTER_ADVERT &&
	    icmp6_hdr->icmp6_type != ND_ROUTER_SOLICIT)
		return;

	/* extract optional information via Advanced API */
	for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&icmp6ev->rcvmhdr); cm;
	    cm = (struct cmsghdr *)CMSG_NXTHDR(&icmp6ev->rcvmhdr, cm)) {
		if (cm->cmsg_level == IPPROTO_IPV6 &&
		    cm->cmsg_type == IPV6_PKTINFO &&
		    cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
			pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
			if_index = pi->ipi6_ifindex;
		}
		if (cm->cmsg_level == IPPROTO_IPV6 &&
		    cm->cmsg_type == IPV6_HOPLIMIT &&
		    cm->cmsg_len == CMSG_LEN(sizeof(int)))
			hlimp = (int *)CMSG_DATA(cm);
	}

	if (if_index == 0) {
		log_warnx("failed to get receiving interface");
		return;
	}

	if (hlimp == NULL) {
		log_warnx("failed to get receiving hop limit");
		return;
	}

	if (*hlimp != 255) {
		log_warnx("invalid RA or RS with hop limit of %d from %s on %s",
		    *hlimp, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr,
		    ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
		    ifnamebuf));
		return;
	}

	log_debug("RA or RS with hop limit of %d from %s on %s",
	    *hlimp, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr,
	    ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
	    ifnamebuf));

	if ((size_t)len > sizeof(ra_rs.packet)) {
		log_warnx("invalid RA or RS with size %ld from %s on %s",
		    len, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr,
		    ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
		    ifnamebuf));
		return;
	}

	ra_rs.if_index = if_index;
	memcpy(&ra_rs.from,  &icmp6ev->from, sizeof(ra_rs.from));
	ra_rs.len = len;
	memcpy(ra_rs.packet, icmp6ev->answer, len);

	frontend_imsg_compose_engine(IMSG_RA_RS, 0, &ra_rs, sizeof(ra_rs));
}

void
join_all_routers_mcast_group(struct ra_iface *ra_iface)
{
	if (!event_initialized(&ra_iface->icmp6ev->ev))
		return;
	log_debug("joining multicast group on %s", ra_iface->name);
	all_routers.ipv6mr_interface = ra_iface->if_index;
	if (setsockopt(EVENT_FD(&ra_iface->icmp6ev->ev), IPPROTO_IPV6,
	    IPV6_JOIN_GROUP, &all_routers, sizeof(all_routers)) == -1)
		fatal("IPV6_JOIN_GROUP(%s)", ra_iface->name);
}

void
leave_all_routers_mcast_group(struct ra_iface *ra_iface)
{
	if (!event_initialized(&ra_iface->icmp6ev->ev))
		return;
	log_debug("leaving multicast group on %s", ra_iface->name);
	all_routers.ipv6mr_interface = ra_iface->if_index;
	setsockopt(EVENT_FD(&ra_iface->icmp6ev->ev), IPPROTO_IPV6,
	    IPV6_LEAVE_GROUP, &all_routers, sizeof(all_routers));
}

struct ra_iface*
find_ra_iface_by_id(uint32_t if_index)
{
	struct ra_iface	*ra_iface;

	TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) {
		if (ra_iface->if_index == if_index)
			return ra_iface;
	}
	return (NULL);
}

struct ra_iface*
find_ra_iface_by_name(char *if_name)
{
	struct ra_iface	*ra_iface;

	TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) {
		if (strcmp(ra_iface->name, if_name) == 0)
			return ra_iface;
	}
	return (NULL);
}

struct ra_iface_conf*
find_ra_iface_conf(struct ra_iface_conf_head *head, char *if_name)
{
	struct ra_iface_conf	*ra_iface_conf;

	SIMPLEQ_FOREACH(ra_iface_conf, head, entry) {
		if (strcmp(ra_iface_conf->name, if_name) == 0)
			return ra_iface_conf;
	}
	return (NULL);
}

int
get_link_state(char *if_name)
{
	struct ifaddrs	*ifap, *ifa;
	int		 ls = LINK_STATE_UNKNOWN;

	if (getifaddrs(&ifap) != 0) {
		log_warn("getifaddrs");
		return LINK_STATE_UNKNOWN;
	}
	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL ||
		    ifa->ifa_addr->sa_family != AF_LINK)
			continue;
		if (strcmp(if_name, ifa->ifa_name) != 0)
			continue;

		ls = ((struct if_data*)ifa->ifa_data)->ifi_link_state;
		break;
	}
	freeifaddrs(ifap);
	return ls;
}

int
get_ifrdomain(char *if_name)
{
	struct ifreq		 ifr;

	strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
	if (ioctl(ioctlsock, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) {
		log_warn("SIOCGIFRDOMAIN");
		return -1;
	}
	return ifr.ifr_rdomainid;
}

void
merge_ra_interface(char *name, char *conf_name)
{
	struct ra_iface		*ra_iface;
	uint32_t		 if_index;
	int			 link_state, has_linklocal, ifrdomain;

	link_state = get_link_state(name);
	has_linklocal = interface_has_linklocal_address(name);
	ifrdomain = get_ifrdomain(name);

	if ((ra_iface = find_ra_iface_by_name(name)) != NULL) {
		ra_iface->link_state = link_state;
		if (!LINK_STATE_IS_UP(link_state)) {
			log_debug("%s down, removing", name);
			ra_iface->removed = 1;
		} else if (!has_linklocal) {
			log_debug("%s has no IPv6 link-local address, "
			    "removing", name);
			ra_iface->removed = 1;
		} else if (ifrdomain == -1) {
			log_debug("can't get rdomain for %s, removing", name);
			ra_iface->removed = 1;
		} else if (ra_iface->rdomain != ifrdomain) {
			leave_all_routers_mcast_group(ra_iface);
			unref_icmp6ev(ra_iface);
			ra_iface->rdomain = ifrdomain;
			ra_iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain);
			join_all_routers_mcast_group(ra_iface);
			ra_iface->removed = 0;
		} else {
			log_debug("keeping interface %s", name);
			ra_iface->removed = 0;
		}
		return;
	}

	if (!LINK_STATE_IS_UP(link_state)) {
		log_debug("%s down, ignoring", name);
		return;
	}

	if (!has_linklocal) {
		log_debug("%s has no IPv6 link-local address, ignoring", name);
		return;
	}

	log_debug("new interface %s", name);
	if ((if_index = if_nametoindex(name)) == 0)
		return;

	log_debug("adding interface %s", name);
	if ((ra_iface = calloc(1, sizeof(*ra_iface))) == NULL)
		fatal("%s", __func__);

	strlcpy(ra_iface->name, name, sizeof(ra_iface->name));
	strlcpy(ra_iface->conf_name, conf_name,
	    sizeof(ra_iface->conf_name));

	ra_iface->if_index = if_index;
	ra_iface->rdomain = ifrdomain;

	SIMPLEQ_INIT(&ra_iface->prefixes);

	ra_iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain);
	join_all_routers_mcast_group(ra_iface);
	TAILQ_INSERT_TAIL(&ra_interfaces, ra_iface, entry);
}

void
merge_ra_interfaces(void)
{
	struct ra_iface_conf	*ra_iface_conf;
	struct ra_prefix_conf	*ra_prefix_conf;
	struct ra_iface		*ra_iface;
	struct ifgroupreq	 ifgr;
	struct ifg_req		*ifg;
	char			*conf_name;
	unsigned int		 len;

	TAILQ_FOREACH(ra_iface, &ra_interfaces, entry)
		ra_iface->removed = 1;

	SIMPLEQ_FOREACH(ra_iface_conf, &frontend_conf->ra_iface_list, entry) {
		conf_name = ra_iface_conf->name;

		/* check if network interface or group */
		if (isdigit((unsigned char)conf_name[strlen(conf_name) - 1])) {
			merge_ra_interface(conf_name, conf_name);
		} else {
			log_debug("interface group %s", conf_name);

			memset(&ifgr, 0, sizeof(ifgr));
			strlcpy(ifgr.ifgr_name, conf_name,
			    sizeof(ifgr.ifgr_name));
			if (ioctl(ioctlsock, SIOCGIFGMEMB,
			    (caddr_t)&ifgr) == -1)
				continue;

			len = ifgr.ifgr_len;
			if ((ifgr.ifgr_groups = calloc(1, len)) == NULL)
				fatal("%s: calloc", __func__);
			if (ioctl(ioctlsock, SIOCGIFGMEMB,
			    (caddr_t)&ifgr) == -1) {
				log_debug("group %s without members",
				    conf_name);
				free(ifgr.ifgr_groups);
				continue;
			}

			for (ifg = ifgr.ifgr_groups;
			    (ifg != NULL) && (len >= sizeof(struct ifg_req));
			    ifg++) {
				len -= sizeof(struct ifg_req);
				merge_ra_interface(ifg->ifgrq_member,
				    conf_name);
			}
			free(ifgr.ifgr_groups);
		}
	}

	TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) {
		while ((ra_prefix_conf = SIMPLEQ_FIRST(&ra_iface->prefixes))
		    != NULL) {
			SIMPLEQ_REMOVE_HEAD(&ra_iface->prefixes,
			    entry);
			free(ra_prefix_conf);
		}
		ra_iface->prefix_count = 0;

		if (ra_iface->removed) {
			log_debug("iface removed: %s", ra_iface->name);
			build_leaving_packet(ra_iface);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0,
			    &ra_iface->if_index, sizeof(ra_iface->if_index));
			continue;
		}

		ra_iface_conf = find_ra_iface_conf(
		    &frontend_conf->ra_iface_list, ra_iface->conf_name);

		log_debug("add static prefixes for %s", ra_iface->name);

		SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface_conf->ra_prefix_list,
		    entry) {
			add_new_prefix_to_ra_iface(ra_iface,
			    &ra_prefix_conf->prefix,
			    ra_prefix_conf->prefixlen, ra_prefix_conf);
		}

		if (ra_iface_conf->autoprefix)
			get_interface_prefixes(ra_iface,
			    ra_iface_conf->autoprefix);

		build_packet(ra_iface);
	}
}

void
free_ra_iface(struct ra_iface *ra_iface)
{
	struct ra_prefix_conf	*prefix;

	leave_all_routers_mcast_group(ra_iface);

	while ((prefix = SIMPLEQ_FIRST(&ra_iface->prefixes)) != NULL) {
		SIMPLEQ_REMOVE_HEAD(&ra_iface->prefixes, entry);
		free(prefix);
	}

	unref_icmp6ev(ra_iface);
	free(ra_iface);
}

/* from kame via ifconfig, where it's called prefix() */
int
in6_mask2prefixlen(struct in6_addr *in6)
{
	u_char *nam = (u_char *)in6;
	int byte, bit, plen = 0, size = sizeof(struct in6_addr);

	for (byte = 0; byte < size; byte++, plen += 8)
		if (nam[byte] != 0xff)
			break;
	if (byte == size)
		return (plen);
	for (bit = 7; bit != 0; bit--, plen++)
		if (!(nam[byte] & (1 << bit)))
			break;
	for (; bit != 0; bit--)
		if (nam[byte] & (1 << bit))
			return (0);
	byte++;
	for (; byte < size; byte++)
		if (nam[byte])
			return (0);
	return (plen);
}

int
interface_has_linklocal_address(char *name)
{
	struct ifaddrs		*ifap, *ifa;
	struct sockaddr_in6	*sin6;
	struct in6_ifreq	 ifr6;
	int			 ret = 0;

	if (getifaddrs(&ifap) != 0)
		fatal("getifaddrs");

	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
		if (strcmp(name, ifa->ifa_name) != 0)
			continue;
		if (ifa->ifa_addr == NULL ||
		    ifa->ifa_addr->sa_family != AF_INET6)
			continue;

		sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;

		if (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
			continue;

		memset(&ifr6, 0, sizeof(ifr6));
		strlcpy(ifr6.ifr_name, name, sizeof(ifr6.ifr_name));
		memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
		if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6) == -1) {
			log_warn("SIOCGIFAFLAG_IN6");
			continue;
		}

		if (ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_TENTATIVE |
		    IN6_IFF_DUPLICATED))
			continue;

		ret = 1;
		break;
	}
	freeifaddrs(ifap);
	return (ret);
}

void
get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf
    *autoprefix)
{
	struct in6_ifreq	 ifr6;
	struct ifaddrs		*ifap, *ifa;
	struct sockaddr_in6	*sin6;
	int			 prefixlen;

	log_debug("%s: %s", __func__, ra_iface->name);

	if (getifaddrs(&ifap) != 0)
		fatal("getifaddrs");

	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
		if (strcmp(ra_iface->name, ifa->ifa_name) != 0)
			continue;
		if (ifa->ifa_addr == NULL ||
		    ifa->ifa_addr->sa_family != AF_INET6)
			continue;

		sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;

		if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
			continue;

		memset(&ifr6, 0, sizeof(ifr6));
		strlcpy(ifr6.ifr_name, ra_iface->name, sizeof(ifr6.ifr_name));
		memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));

		if (ioctl(ioctlsock, SIOCGIFNETMASK_IN6, (caddr_t)&ifr6) == -1)
			continue; /* addr got deleted while we were looking */

		prefixlen = in6_mask2prefixlen(&((struct sockaddr_in6 *)
		    &ifr6.ifr_addr)->sin6_addr);

		if (prefixlen == 128)
			continue;

		mask_prefix(&sin6->sin6_addr, prefixlen);

		add_new_prefix_to_ra_iface(ra_iface, &sin6->sin6_addr,
		    prefixlen, autoprefix);
	}
	freeifaddrs(ifap);
}

struct ra_prefix_conf*
find_ra_prefix_conf(struct ra_prefix_conf_head* head, struct in6_addr *prefix,
    int prefixlen)
{
	struct ra_prefix_conf	*ra_prefix_conf;

	SIMPLEQ_FOREACH(ra_prefix_conf, head, entry) {
		if (ra_prefix_conf->prefixlen == prefixlen &&
		    memcmp(&ra_prefix_conf->prefix, prefix, sizeof(*prefix)) ==
		    0)
			return (ra_prefix_conf);
	}
	return (NULL);
}

void
add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr,
    int prefixlen, struct ra_prefix_conf *ra_prefix_conf)
{
	struct ra_prefix_conf	*new_ra_prefix_conf;

	if (find_ra_prefix_conf(&ra_iface->prefixes, addr, prefixlen)) {
		log_debug("ignoring duplicate %s/%d prefix",
		    in6_to_str(addr), prefixlen);
		return;
	}

	log_debug("adding %s/%d prefix", in6_to_str(addr), prefixlen);

	if ((new_ra_prefix_conf = calloc(1, sizeof(*ra_prefix_conf))) == NULL)
		fatal("%s", __func__);
	new_ra_prefix_conf->prefix = *addr;
	new_ra_prefix_conf->prefixlen = prefixlen;
	new_ra_prefix_conf->vltime = ra_prefix_conf->vltime;
	new_ra_prefix_conf->pltime = ra_prefix_conf->pltime;
	new_ra_prefix_conf->aflag = ra_prefix_conf->aflag;
	new_ra_prefix_conf->lflag = ra_prefix_conf->lflag;
	SIMPLEQ_INSERT_TAIL(&ra_iface->prefixes, new_ra_prefix_conf, entry);
	ra_iface->prefix_count++;
}

void
build_packet(struct ra_iface *ra_iface)
{
	struct nd_router_advert		*ra;
	struct nd_opt_source_link_addr	*ndopt_source_link_addr;
	struct nd_opt_mtu		*ndopt_mtu;
	struct nd_opt_prefix_info	*ndopt_pi;
	struct ra_iface_conf		*ra_iface_conf;
	struct ra_options_conf		*ra_options_conf;
	struct ra_prefix_conf		*ra_prefix_conf;
	struct nd_opt_rdnss		*ndopt_rdnss;
	struct nd_opt_dnssl		*ndopt_dnssl;
	struct nd_opt_pref64		*ndopt_pref64;
	struct ra_rdnss_conf		*ra_rdnss;
	struct ra_dnssl_conf		*ra_dnssl;
	struct ra_pref64_conf		*pref64;
	struct ifaddrs			*ifap, *ifa;
	struct sockaddr_dl		*sdl;
	size_t				 len, label_len;
	uint8_t				*p, buf[RA_MAX_SIZE];
	char				*label_start, *label_end;

	ra_iface_conf = find_ra_iface_conf(&frontend_conf->ra_iface_list,
	    ra_iface->conf_name);
	ra_options_conf = &ra_iface_conf->ra_options;

	len = sizeof(*ra);
	if (ra_iface_conf->ra_options.source_link_addr)
		len += sizeof(*ndopt_source_link_addr);
	if (ra_options_conf->mtu > 0)
		len += sizeof(*ndopt_mtu);
	len += sizeof(*ndopt_pi) * ra_iface->prefix_count;
	if (ra_iface_conf->ra_options.rdnss_count > 0)
		len += sizeof(*ndopt_rdnss) +
		    ra_iface_conf->ra_options.rdnss_count *
		    sizeof(struct in6_addr);

	if (ra_iface_conf->ra_options.dnssl_len > 0)
		/* round up to 8 byte boundary */
		len += sizeof(*ndopt_dnssl) +
		    ((ra_iface_conf->ra_options.dnssl_len + 7) & ~7);

	SIMPLEQ_FOREACH(pref64, &ra_iface_conf->ra_options.ra_pref64_list,
	    entry)
		len += sizeof(struct nd_opt_pref64);

	if (len > sizeof(ra_iface->data))
		fatalx("%s: packet too big", __func__); /* XXX send multiple */

	p = buf;

	ra = (struct nd_router_advert *)p;

	memset(ra, 0, sizeof(*ra));

	ra->nd_ra_type = ND_ROUTER_ADVERT;
	ra->nd_ra_curhoplimit = ra_options_conf->cur_hl;
	if (ra_options_conf->m_flag)
		ra->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
	if (ra_options_conf->o_flag)
		ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;
	if (ra_iface->removed)
		/* tell clients that we are no longer a default router */
		ra->nd_ra_router_lifetime = 0;
	else if (ra_options_conf->dfr) {
		ra->nd_ra_router_lifetime =
		    htons(ra_options_conf->router_lifetime);
		/*
		 * RFC 4191
		 * If the Router Lifetime is zero, the preference value MUST be
		 * set to (00) by the sender and MUST be ignored by the
		 * receiver.
		 */
		if (ra_options_conf->router_lifetime > 0)
			ra->nd_ra_flags_reserved |= ra_options_conf->rtpref;
	}
	ra->nd_ra_reachable = htonl(ra_options_conf->reachable_time);
	ra->nd_ra_retransmit = htonl(ra_options_conf->retrans_timer);
	p += sizeof(*ra);

	if (ra_iface_conf->ra_options.source_link_addr) {
		ndopt_source_link_addr = (struct nd_opt_source_link_addr *)p;
		ndopt_source_link_addr->nd_opt_source_link_addr_type =
		    ND_OPT_SOURCE_LINKADDR;
		ndopt_source_link_addr->nd_opt_source_link_addr_len = 1;
		if (getifaddrs(&ifap) != 0) {
			ifap = NULL;
			log_warn("getifaddrs");
		}
		for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
			if (ifa->ifa_addr == NULL ||
			    ifa->ifa_addr->sa_family != AF_LINK)
				continue;
			if (strcmp(ra_iface->name, ifa->ifa_name) != 0)
				continue;
			sdl = (struct sockaddr_dl *)ifa->ifa_addr;
			if (sdl->sdl_type != IFT_ETHER ||
			    sdl->sdl_alen != ETHER_ADDR_LEN)
				continue;
			memcpy(&ndopt_source_link_addr->
			    nd_opt_source_link_addr_hw_addr,
			    LLADDR(sdl), ETHER_ADDR_LEN);
			break;
		}
		if (ifap != NULL) {
			freeifaddrs(ifap);
			p += sizeof(*ndopt_source_link_addr);
		} else
			len -= sizeof(*ndopt_source_link_addr);
	}

	if (ra_options_conf->mtu > 0) {
		ndopt_mtu = (struct nd_opt_mtu *)p;
		ndopt_mtu->nd_opt_mtu_type = ND_OPT_MTU;
		ndopt_mtu->nd_opt_mtu_len = 1;
		ndopt_mtu->nd_opt_mtu_reserved = 0;
		ndopt_mtu->nd_opt_mtu_mtu = htonl(ra_options_conf->mtu);
		p += sizeof(*ndopt_mtu);
	}

	SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface->prefixes, entry) {
		ndopt_pi = (struct nd_opt_prefix_info *)p;
		memset(ndopt_pi, 0, sizeof(*ndopt_pi));
		ndopt_pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
		ndopt_pi->nd_opt_pi_len = 4;
		ndopt_pi->nd_opt_pi_prefix_len = ra_prefix_conf->prefixlen;
		if (ra_prefix_conf->lflag)
			ndopt_pi->nd_opt_pi_flags_reserved |=
			    ND_OPT_PI_FLAG_ONLINK;
		if (ra_prefix_conf->aflag)
			ndopt_pi->nd_opt_pi_flags_reserved |=
			    ND_OPT_PI_FLAG_AUTO;
		ndopt_pi->nd_opt_pi_valid_time = htonl(ra_prefix_conf->vltime);
		ndopt_pi->nd_opt_pi_preferred_time =
		    htonl(ra_prefix_conf->pltime);
		ndopt_pi->nd_opt_pi_prefix = ra_prefix_conf->prefix;

		p += sizeof(*ndopt_pi);
	}

	if (ra_iface_conf->ra_options.rdnss_count > 0) {
		ndopt_rdnss = (struct nd_opt_rdnss *)p;
		ndopt_rdnss->nd_opt_rdnss_type = ND_OPT_RDNSS;
		ndopt_rdnss->nd_opt_rdnss_len = 1 +
		    ra_iface_conf->ra_options.rdnss_count * 2;
		ndopt_rdnss->nd_opt_rdnss_reserved = 0;
		ndopt_rdnss->nd_opt_rdnss_lifetime =
		    htonl(ra_iface_conf->ra_options.rdns_lifetime);
		p += sizeof(struct nd_opt_rdnss);
		SIMPLEQ_FOREACH(ra_rdnss,
		    &ra_iface_conf->ra_options.ra_rdnss_list, entry) {
			memcpy(p, &ra_rdnss->rdnss, sizeof(ra_rdnss->rdnss));
			p += sizeof(ra_rdnss->rdnss);
		}
	}

	if (ra_iface_conf->ra_options.dnssl_len > 0) {
		ndopt_dnssl = (struct nd_opt_dnssl *)p;
		ndopt_dnssl->nd_opt_dnssl_type = ND_OPT_DNSSL;
		/* round up to 8 byte boundary */
		ndopt_dnssl->nd_opt_dnssl_len = 1 +
		    ((ra_iface_conf->ra_options.dnssl_len + 7) & ~7) / 8;
		ndopt_dnssl->nd_opt_dnssl_reserved = 0;
		ndopt_dnssl->nd_opt_dnssl_lifetime =
		    htonl(ra_iface_conf->ra_options.rdns_lifetime);
		p += sizeof(struct nd_opt_dnssl);

		SIMPLEQ_FOREACH(ra_dnssl,
		    &ra_iface_conf->ra_options.ra_dnssl_list, entry) {
			label_start = ra_dnssl->search;
			while ((label_end = strchr(label_start, '.')) != NULL) {
				label_len = label_end - label_start;
				*p++ = label_len;
				memcpy(p, label_start, label_len);
				p += label_len;
				label_start = label_end + 1;
			}
			*p++ = '\0'; /* last dot */
		}
		/* zero pad */
		while (((uintptr_t)p) % 8 != 0)
			*p++ = '\0';
	}

	SIMPLEQ_FOREACH(pref64, &ra_iface_conf->ra_options.ra_pref64_list,
	    entry) {
		uint16_t	sltime_plc;

		/* scaled lifetime in units of 8 seconds */
		sltime_plc = pref64->ltime / 8;
		sltime_plc = sltime_plc << 3;
		/* encode prefix length in lower 3 bits */
		switch (pref64->prefixlen) {
		case 96:
			sltime_plc |= 0;
			break;
		case 64:
			sltime_plc |= 1;
			break;
		case 56:
			sltime_plc |= 2;
			break;
		case 48:
			sltime_plc |= 3;
			break;
		case 40:
			sltime_plc |= 4;
			break;
		case 32:
			sltime_plc |= 5;
			break;
		default:
			fatalx("%s: invalid pref64 length: %d", __func__,
			    pref64->prefixlen);
		}
		ndopt_pref64 = (struct nd_opt_pref64 *)p;
		ndopt_pref64->nd_opt_pref64_type = ND_OPT_PREF64;
		ndopt_pref64->nd_opt_pref64_len = 2;
		ndopt_pref64->nd_opt_pref64_sltime_plc = htons(sltime_plc);
		memcpy(ndopt_pref64->nd_opt_pref64, &pref64->prefix,
		    sizeof(ndopt_pref64->nd_opt_pref64));
		p += sizeof(struct nd_opt_pref64);
	}

	if (len != ra_iface->datalen || memcmp(buf, ra_iface->data, len)
	    != 0) {
		memcpy(ra_iface->data, buf, len);
		ra_iface->datalen = len;
		/* packet changed; tell engine to send new advertisements */
		if (event_initialized(&ra_iface->icmp6ev->ev))
			frontend_imsg_compose_engine(IMSG_UPDATE_IF, 0,
			    &ra_iface->if_index, sizeof(ra_iface->if_index));
	}
}

void
build_leaving_packet(struct ra_iface *ra_iface)
{
	struct nd_router_advert		 ra;

	memset(&ra, 0, sizeof(ra));

	ra.nd_ra_type = ND_ROUTER_ADVERT;

	memcpy(ra_iface->data, &ra, sizeof(ra));
	ra_iface->datalen = sizeof(ra);
}

void
ra_output(struct ra_iface *ra_iface, struct sockaddr_in6 *to)
{

	struct cmsghdr		*cm;
	struct in6_pktinfo	*pi;
	ssize_t			 len;
	int			 hoplimit = 255;

	if (!LINK_STATE_IS_UP(ra_iface->link_state))
		return;

	sndmhdr.msg_name = to;
	sndmhdr.msg_iov[0].iov_base = ra_iface->data;
	sndmhdr.msg_iov[0].iov_len = ra_iface->datalen;

	cm = CMSG_FIRSTHDR(&sndmhdr);
	/* specify the outgoing interface */
	cm->cmsg_level = IPPROTO_IPV6;
	cm->cmsg_type = IPV6_PKTINFO;
	cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
	pi = (struct in6_pktinfo *)CMSG_DATA(cm);
	memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr));
	pi->ipi6_ifindex = ra_iface->if_index;

	/* specify the hop limit of the packet */
	cm = CMSG_NXTHDR(&sndmhdr, cm);
	cm->cmsg_level = IPPROTO_IPV6;
	cm->cmsg_type = IPV6_HOPLIMIT;
	cm->cmsg_len = CMSG_LEN(sizeof(int));
	memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));

	log_debug("send RA on %s", ra_iface->name);

	len = sendmsg(EVENT_FD(&ra_iface->icmp6ev->ev), &sndmhdr, 0);
	if (len == -1)
		log_warn("sendmsg on %s", ra_iface->name);

}

#define ROUNDUP(a) \
	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))

void
get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
	int	i;

	for (i = 0; i < RTAX_MAX; i++) {
		if (addrs & (1 << i)) {
			rti_info[i] = sa;
			sa = (struct sockaddr *)((char *)(sa) +
			    ROUNDUP(sa->sa_len));
		} else
			rti_info[i] = NULL;
	}
}

void
route_receive(int fd, short events, void *arg)
{
	static uint8_t			 *buf;

	struct rt_msghdr		*rtm;
	struct sockaddr			*sa, *rti_info[RTAX_MAX];
	ssize_t				 n;

	if (buf == NULL) {
		buf = malloc(ROUTE_SOCKET_BUF_SIZE);
		if (buf == NULL)
			fatal("malloc");
	}
	rtm = (struct rt_msghdr *)buf;
	if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
		if (errno == EAGAIN || errno == EINTR)
			return;
		log_warn("dispatch_rtmsg: read error");
		return;
	}

	if (n == 0)
		fatal("routing socket closed");

	if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
		log_warnx("partial rtm of %zd in buffer", n);
		return;
	}

	if (rtm->rtm_version != RTM_VERSION)
		return;

	sa = (struct sockaddr *)(buf + rtm->rtm_hdrlen);
	get_rtaddrs(rtm->rtm_addrs, sa, rti_info);

	handle_route_message(rtm, rti_info);
}

void
handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
{
	switch (rtm->rtm_type) {
	case RTM_IFINFO:
	case RTM_NEWADDR:
	case RTM_DELADDR:
	case RTM_CHGADDRATTR:
		/*
		 * do the same thing as after a config reload when interfaces
		 * change or IPv6 addresses show up / disappear
		 */
		merge_ra_interfaces();
		break;
	default:
		log_debug("unexpected RTM: %d", rtm->rtm_type);
		break;
	}
}

struct icmp6_ev*
get_icmp6ev_by_rdomain(int rdomain)
{
	struct ra_iface	*ra_iface;
	struct icmp6_ev	*icmp6ev = NULL;

	TAILQ_FOREACH (ra_iface, &ra_interfaces, entry) {
		if (ra_iface->rdomain == rdomain) {
			icmp6ev = ra_iface->icmp6ev;
			break;
		}
	}

	if (icmp6ev == NULL) {
		if ((icmp6ev = calloc(1, sizeof(*icmp6ev))) == NULL)
			fatal("calloc");

		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_controllen =
		    CMSG_SPACE(sizeof(struct in6_pktinfo)) +
		    CMSG_SPACE(sizeof(int));
		if ((icmp6ev->rcvmhdr.msg_control = malloc(icmp6ev->
		    rcvmhdr.msg_controllen)) == NULL)
			fatal("malloc");
		frontend_imsg_compose_main(IMSG_OPEN_ICMP6SOCK, 0,
		    &rdomain, sizeof(rdomain));
	}

	icmp6ev->refcnt++;
	return (icmp6ev);
}

void
unref_icmp6ev(struct ra_iface *ra_iface)
{
	struct icmp6_ev *icmp6ev = ra_iface->icmp6ev;

	ra_iface->icmp6ev = NULL;

	if (icmp6ev != NULL) {
		icmp6ev->refcnt--;
		if (icmp6ev->refcnt == 0) {
			event_del(&icmp6ev->ev);
			close(EVENT_FD(&icmp6ev->ev));
			free(icmp6ev);
		}
	}
}

void
set_icmp6sock(int icmp6sock, int rdomain)
{
	struct ra_iface	*ra_iface;

	TAILQ_FOREACH (ra_iface, &ra_interfaces, entry) {
		if (!event_initialized(&ra_iface->icmp6ev->ev) &&
		    ra_iface->rdomain == rdomain) {
			event_set(&ra_iface->icmp6ev->ev, icmp6sock, EV_READ |
			    EV_PERSIST, icmp6_receive, ra_iface->icmp6ev);
			event_add(&ra_iface->icmp6ev->ev, NULL);
			icmp6sock = -1;
			break;
		}
	}

	if (icmp6sock != -1) {
		/*
		 * The interface disappeared or changed rdomain while we were
		 * waiting for the parent process to open the raw socket.
		 */
		close(icmp6sock);
		return;
	}

	TAILQ_FOREACH (ra_iface, &ra_interfaces, entry) {
		if (ra_iface->rdomain == rdomain) {
			join_all_routers_mcast_group(ra_iface);
			frontend_imsg_compose_engine(IMSG_UPDATE_IF, 0,
			    &ra_iface->if_index, sizeof(ra_iface->if_index));
		}
	}
}