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

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

Revision 1.66, Sun Feb 11 21:29:12 2024 UTC (3 months, 2 weeks ago) by bluhm
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.65: +1 -2 lines

Remove needless includes of netinet6/ip6_var.h header in userland.

OK millert@

/*	$OpenBSD: frontend.c,v 1.66 2024/02/11 21:29:12 bluhm 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 <netinet6/nd6.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.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"

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

struct iface {
	LIST_ENTRY(iface)	 entries;
	struct icmp6_ev		*icmp6ev;
	struct ether_addr	 hw_address;
	uint32_t		 if_index;
	int			 rdomain;
	int			 send_solicitation;
	int			 ll_tentative;
};

__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 *);
int		 get_ifrdomain(char *);
struct iface	*get_iface_by_id(uint32_t);
void		 remove_iface(uint32_t);
struct icmp6_ev	*get_icmp6ev_by_rdomain(int);
void		 unref_icmp6ev(struct iface *);
void		 set_icmp6sock(int, int);
void		 send_solicitation(uint32_t);
#ifndef	SMALL
const char	*flags_to_str(int);
#endif	/* SMALL */

LIST_HEAD(, iface)		 interfaces;
static struct imsgev		*iev_main;
static 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				 ioctlsock;

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;
	struct in6_pktinfo	*pi;
	struct cmsghdr		*cm;
	size_t			 sndcmsglen;
	int			 hoplimit = 255;
	uint8_t			*sndcmsgbuf;

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

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

	if (chdir("/") == -1)
		fatal("chdir(\"/\")");

	if (unveil("/", "") == -1)
		fatal("unveil /");
	if (unveil(NULL, NULL) == -1)
		fatal("unveil");

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

	if ((ioctlsock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1)
		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 (pledge("stdio unix 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);

	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));

	LIST_INIT(&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);

	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, 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_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 ((fd = imsg_get_fd(&imsg)) == -1)
				fatalx("%s: expected to receive imsg "
				    "routesocket fd but didn't receive any",
				    __func__);
			event_set(&ev_route, fd, EV_READ | EV_PERSIST,
			    route_receive, NULL);
			break;
		case IMSG_STARTUP:
			frontend_startup();
			break;
#ifndef	SMALL
		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;
		case IMSG_CTL_END:
			control_imsg_relay(&imsg);
			break;
#endif	/* SMALL */
		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) {
#ifndef	SMALL
		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_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:
		case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS:
		case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL:
			control_imsg_relay(&imsg);
			break;
#endif	/* SMALL */
		case IMSG_CTL_SEND_SOLICITATION:
			if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
				fatalx("%s: IMSG_CTL_SEND_SOLICITATION wrong "
				    "length: %lu", __func__,
				    IMSG_DATA_SIZE(imsg));
			if_index = *((uint32_t *)imsg.data);
			send_solicitation(if_index);
			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;

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

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

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

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
update_iface(uint32_t if_index, char* if_name)
{
	struct iface		*iface;
	struct ifaddrs		*ifap, *ifa;
	struct imsg_ifinfo	 imsg_ifinfo;
	struct sockaddr_dl	*sdl;
	struct sockaddr_in6	*sin6;
	struct in6_ifreq	 ifr6;
	int			 flags, xflags, ifrdomain;

	if ((flags = get_flags(if_name)) == -1 || (xflags =
	    get_xflags(if_name)) == -1)
		return;

	if (!(xflags & (IFXF_AUTOCONF6 | IFXF_AUTOCONF6TEMP)))
		return;

	if((ifrdomain = get_ifrdomain(if_name)) == -1)
		return;

	iface = get_iface_by_id(if_index);

	if (iface != NULL) {
		if (iface->rdomain != ifrdomain) {
			unref_icmp6ev(iface);
			iface->rdomain = ifrdomain;
			iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain);
		}
	} else {
		if ((iface = calloc(1, sizeof(*iface))) == NULL)
			fatal("calloc");
		iface->if_index = if_index;
		iface->rdomain = ifrdomain;
		iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain);
		iface->ll_tentative = 1;

		LIST_INSERT_HEAD(&interfaces, iface, entries);
	}

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

	imsg_ifinfo.if_index = if_index;
	imsg_ifinfo.rdomain = ifrdomain;
	imsg_ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP |
	    IFF_RUNNING);
	imsg_ifinfo.autoconf = (xflags & IFXF_AUTOCONF6);
	imsg_ifinfo.temporary = (xflags & IFXF_AUTOCONF6TEMP);
	imsg_ifinfo.soii = !(xflags & IFXF_INET6_NOSOII);

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

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

		switch(ifa->ifa_addr->sa_family) {
		case AF_LINK:
			imsg_ifinfo.link_state =
			    ((struct if_data *)ifa->ifa_data)->ifi_link_state;
			sdl = (struct sockaddr_dl *)ifa->ifa_addr;
			if (sdl->sdl_type != IFT_ETHER ||
			    sdl->sdl_alen != ETHER_ADDR_LEN)
				continue;
			memcpy(iface->hw_address.ether_addr_octet,
			    LLADDR(sdl), ETHER_ADDR_LEN);
			memcpy(imsg_ifinfo.hw_address.ether_addr_octet,
			    LLADDR(sdl), ETHER_ADDR_LEN);
		case AF_INET6:
			sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
#ifdef __KAME__
			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) &&
			    sin6->sin6_scope_id == 0) {
				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;
			}
#endif
			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
				memcpy(&imsg_ifinfo.ll_address, sin6,
				    sizeof(imsg_ifinfo.ll_address));

				if (!iface->ll_tentative)
					break;

				memset(&ifr6, 0, sizeof(ifr6));
				strlcpy(ifr6.ifr_name, if_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");
					break;
				}

				if (!(ifr6.ifr_ifru.ifru_flags6 &
				    IN6_IFF_TENTATIVE)) {
					iface->ll_tentative = 0;
					if (iface->send_solicitation)
						send_solicitation(
						    iface->if_index);
				}
			}
			break;
		default:
			break;
		}
	}

	freeifaddrs(ifap);

	frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &imsg_ifinfo,
	    sizeof(imsg_ifinfo));
}

#ifndef	SMALL
const char*
flags_to_str(int flags)
{
	static char	buf[sizeof(" anycast tentative duplicated detached "
			    "deprecated autoconf temporary")];

	buf[0] = '\0';
	if (flags & IN6_IFF_ANYCAST)
		strlcat(buf, " anycast", sizeof(buf));
	if (flags & IN6_IFF_TENTATIVE)
		strlcat(buf, " tentative", sizeof(buf));
	if (flags & IN6_IFF_DUPLICATED)
		strlcat(buf, " duplicated", sizeof(buf));
	if (flags & IN6_IFF_DETACHED)
		strlcat(buf, " detached", sizeof(buf));
	if (flags & IN6_IFF_DEPRECATED)
		strlcat(buf, " deprecated", sizeof(buf));
	if (flags & IN6_IFF_AUTOCONF)
		strlcat(buf, " autoconf", sizeof(buf));
	if (flags & IN6_IFF_TEMPORARY)
		strlcat(buf, " temporary", sizeof(buf));

	return (buf);
}
#endif	/* SMALL */

void
frontend_startup(void)
{
	struct if_nameindex	*ifnidxp, *ifnidx;

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

	event_add(&ev_route, 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;

	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)
{
	struct if_msghdr		*ifm;
	struct if_announcemsghdr	*ifan;
	struct imsg_del_addr		 del_addr;
	struct imsg_del_route		 del_route;
	struct imsg_dup_addr		 dup_addr;
	struct sockaddr_rtlabel		*rl;
	struct sockaddr_in6		*sin6;
	struct in6_ifreq		 ifr6;
	struct in6_addr			*in6;
	int				 xflags, if_index;
	char				 ifnamebuf[IFNAMSIZ];
	char				*if_name;

	switch (rtm->rtm_type) {
	case RTM_IFINFO:
		ifm = (struct if_msghdr *)rtm;
		if_index = ifm->ifm_index;
		if_name = if_indextoname(if_index, ifnamebuf);
		if (if_name == NULL) {
			log_debug("RTM_IFINFO: lost if %d", if_index);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
			    &if_index, sizeof(if_index));
			remove_iface(if_index);
			break;
		}
		xflags = get_xflags(if_name);
		if (xflags == -1 || !(xflags & (IFXF_AUTOCONF6 |
		    IFXF_AUTOCONF6TEMP))) {
			log_debug("RTM_IFINFO: %s(%d) no(longer) autoconf6",
			    if_name, if_index);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0,
			    0, &if_index, sizeof(if_index));
			remove_iface(if_index);
		} else {
			update_iface(if_index, if_name);
		}
		break;
	case RTM_IFANNOUNCE:
		ifan = (struct if_announcemsghdr *)rtm;
		if_index = ifan->ifan_index;
                if (ifan->ifan_what == IFAN_DEPARTURE) {
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
			    &if_index, sizeof(if_index));
			remove_iface(if_index);
		}
		break;
	case RTM_NEWADDR:
		ifm = (struct if_msghdr *)rtm;
		if_index = ifm->ifm_index;
		if_name = if_indextoname(if_index, ifnamebuf);
		if (if_name == NULL) {
			log_debug("RTM_NEWADDR: lost if %d", if_index);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
			    &if_index, sizeof(if_index));
			remove_iface(if_index);
			break;
		}

		log_debug("RTM_NEWADDR: %s[%u]", if_name, if_index);
		update_iface(if_index, if_name);
		break;
	case RTM_DELADDR:
		ifm = (struct if_msghdr *)rtm;
		if_index = ifm->ifm_index;
		if_name = if_indextoname(if_index, ifnamebuf);
		if (if_name == NULL) {
			log_debug("RTM_DELADDR: lost if %d", if_index);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
			    &if_index, sizeof(if_index));
			remove_iface(if_index);
			break;
		}
		if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family
		    == AF_INET6) {
			del_addr.if_index = if_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, if_index);
		}
		break;
	case RTM_CHGADDRATTR:
		ifm = (struct if_msghdr *)rtm;
		if_index = ifm->ifm_index;
		if_name = if_indextoname(if_index, ifnamebuf);
		if (if_name == NULL) {
			log_debug("RTM_CHGADDRATTR: lost if %d", if_index);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
			    &if_index, sizeof(if_index));
			remove_iface(if_index);
			break;
		}
		if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family
		    == AF_INET6) {
			sin6 = (struct sockaddr_in6 *) rti_info[RTAX_IFA];

			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
				update_iface(if_index, if_name);
				break;
			}

			memset(&ifr6, 0, sizeof(ifr6));
			strlcpy(ifr6.ifr_name, if_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");
				break;
			}

#ifndef	SMALL
			log_debug("RTM_CHGADDRATTR: %s - %s",
			    sin6_to_str(sin6),
			    flags_to_str(ifr6.ifr_ifru.ifru_flags6));
#endif	/* SMALL */

			if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DUPLICATED) {
				dup_addr.if_index = if_index;
				dup_addr.addr = *sin6;
				frontend_imsg_compose_engine(IMSG_DUP_ADDRESS,
				    0, 0, &dup_addr, sizeof(dup_addr));
			}

		}
		break;
	case RTM_DELETE:
		ifm = (struct if_msghdr *)rtm;
		if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY | RTA_LABEL)) !=
		    (RTA_DST | RTA_GATEWAY | RTA_LABEL))
			break;
		if (rti_info[RTAX_DST]->sa_family != AF_INET6)
			break;
		if (!IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)
		    rti_info[RTAX_DST])->sin6_addr))
			break;
		if (rti_info[RTAX_GATEWAY]->sa_family != AF_INET6)
			break;
		if (rti_info[RTAX_LABEL]->sa_len !=
		    sizeof(struct sockaddr_rtlabel))
			break;

		rl = (struct sockaddr_rtlabel *)rti_info[RTAX_LABEL];
		if (strcmp(rl->sr_label, SLAACD_RTA_LABEL) != 0)
			break;
		if_index = ifm->ifm_index;
		if_name = if_indextoname(if_index, ifnamebuf);
		if (if_name == NULL) {
			log_debug("RTM_DELETE: lost if %d", if_index);
			frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
			    &if_index, sizeof(if_index));
			remove_iface(if_index);
			break;
		}

		del_route.if_index = if_index;
		memcpy(&del_route.gw, rti_info[RTAX_GATEWAY],
		    sizeof(del_route.gw));
		in6 = &del_route.gw.sin6_addr;
#ifdef __KAME__
		/* XXX from route(8) p_sockaddr() */
		if ((IN6_IS_ADDR_LINKLOCAL(in6) ||
		    IN6_IS_ADDR_MC_LINKLOCAL(in6) ||
		    IN6_IS_ADDR_MC_INTFACELOCAL(in6)) &&
		    del_route.gw.sin6_scope_id == 0) {
			del_route.gw.sin6_scope_id =
			    (u_int32_t)ntohs(*(u_short *) &in6->s6_addr[2]);
			*(u_short *)&in6->s6_addr[2] = 0;
		}
#endif
		frontend_imsg_compose_engine(IMSG_DEL_ROUTE,
		    0, 0, &del_route, sizeof(del_route));
		log_debug("RTM_DELETE: %s[%u]", if_name,
		    ifm->ifm_index);

		break;
#ifndef	SMALL
	case RTM_PROPOSAL:
		if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
			log_debug("RTP_PROPOSAL_SOLICIT");
			frontend_imsg_compose_engine(IMSG_REPROPOSE_RDNS,
			    0, 0, NULL, 0);
		}
		break;
#endif	/* SMALL */
	default:
		log_debug("unexpected RTM: %d", rtm->rtm_type);
		break;
	}

}

#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
icmp6_receive(int fd, short events, void *arg)
{
	struct imsg_ra		 ra;
	struct icmp6_hdr	*icmp6_hdr;
	struct icmp6_ev		*icmp6ev;
	struct in6_pktinfo	*pi = NULL;
	struct cmsghdr		*cm;
	ssize_t			 len;
	int			 if_index = 0, *hlimp = NULL;
	char			 ntopbuf[INET6_ADDRSTRLEN];
#ifndef SMALL
	char			 ifnamebuf[IFNAMSIZ];
#endif	/* SMALL */

	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)
		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 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_warnx("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;
	struct iface		*iface;

	log_debug("%s(%u)", __func__, if_index);

	if ((iface = get_iface_by_id(if_index)) == NULL)
		return;

	if (!event_initialized(&iface->icmp6ev->ev)) {
		iface->send_solicitation = 1;
		return;
	} else if (iface->ll_tentative) {
		iface->send_solicitation = 1;
		return;
	}

	iface->send_solicitation = 0;

	dst.sin6_scope_id = if_index;

	cm = CMSG_FIRSTHDR(&sndmhdr);
	pi = (struct in6_pktinfo *)CMSG_DATA(cm);
	pi->ipi6_ifindex = if_index;

	memcpy(&nd_opt_source_link_addr, &iface->hw_address,
	    sizeof(nd_opt_source_link_addr));

	if (sendmsg(EVENT_FD(&iface->icmp6ev->ev), &sndmhdr, 0) != sizeof(rs) +
	    sizeof(nd_opt_hdr) + sizeof(nd_opt_source_link_addr))
		log_warn("sendmsg");
}

struct iface*
get_iface_by_id(uint32_t if_index)
{
	struct iface	*iface;

	LIST_FOREACH (iface, &interfaces, entries) {
		if (iface->if_index == if_index)
			return (iface);
	}

	return (NULL);
}

void
remove_iface(uint32_t if_index)
{
	struct iface	*iface;

	iface = get_iface_by_id(if_index);

	if (iface == NULL)
		return;

	LIST_REMOVE(iface, entries);

	unref_icmp6ev(iface);
	free(iface);
}

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

	LIST_FOREACH (iface, &interfaces, entries) {
		if (iface->rdomain == rdomain) {
			icmp6ev = 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 iface *iface)
{
	struct icmp6_ev *icmp6ev = iface->icmp6ev;

	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 iface	*iface;

	LIST_FOREACH (iface, &interfaces, entries) {
		if (!event_initialized(&iface->icmp6ev->ev) && iface->rdomain
		    == rdomain) {
			event_set(&iface->icmp6ev->ev, icmp6sock, EV_READ |
			    EV_PERSIST, icmp6_receive, iface->icmp6ev);
			event_add(&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;
	}

	LIST_FOREACH (iface, &interfaces, entries) {
		if (event_initialized(&iface->icmp6ev->ev) &&
		    iface->send_solicitation)
			send_solicitation(iface->if_index);
	}
}