view src/ipv6nd.c @ 5557:e65d193a1960 draft

Linux: Support wireless IP roaming This is achieved by checking that the interface is wireless, IFF_UP and IFF_LOWER_UP are present, but IFF_RUNNING is missing. This gives exactly the same support as modern NetBSD when carrier loss is detected, but without the address verifications when the carrier comes back as that needs to be handled in the kernel. While IP setup is maintained, other configuration data is discarded. Note that this should be improved in the future. Thanks to Boris Krasnovskiy <borkra@gmail.com> for helping with this.
author Roy Marples <roy@marples.name>
date Sat, 12 Dec 2020 13:12:26 +0000
parents a0d828e25482
children
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * dhcpcd - IPv6 ND handling
 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#define ELOOP_QUEUE	ELOOP_IPV6ND
#include "common.h"
#include "dhcpcd.h"
#include "dhcp-common.h"
#include "dhcp6.h"
#include "eloop.h"
#include "if.h"
#include "ipv6.h"
#include "ipv6nd.h"
#include "logerr.h"
#include "privsep.h"
#include "route.h"
#include "script.h"

/* Debugging Router Solicitations is a lot of spam, so disable it */
//#define DEBUG_RS

#ifndef ND_RA_FLAG_HOME_AGENT
#define	ND_RA_FLAG_HOME_AGENT	0x20	/* Home Agent flag in RA */
#endif
#ifndef ND_RA_FLAG_PROXY
#define	ND_RA_FLAG_PROXY	0x04	/* Proxy */
#endif
#ifndef ND_OPT_PI_FLAG_ROUTER
#define	ND_OPT_PI_FLAG_ROUTER	0x20	/* Router flag in PI */
#endif

#ifndef ND_OPT_RDNSS
#define ND_OPT_RDNSS			25
struct nd_opt_rdnss {           /* RDNSS option RFC 6106 */
	uint8_t		nd_opt_rdnss_type;
	uint8_t		nd_opt_rdnss_len;
	uint16_t	nd_opt_rdnss_reserved;
	uint32_t	nd_opt_rdnss_lifetime;
        /* followed by list of IP prefixes */
};
__CTASSERT(sizeof(struct nd_opt_rdnss) == 8);
#endif

#ifndef ND_OPT_DNSSL
#define ND_OPT_DNSSL			31
struct nd_opt_dnssl {		/* DNSSL option RFC 6106 */
	uint8_t		nd_opt_dnssl_type;
	uint8_t		nd_opt_dnssl_len;
	uint16_t	nd_opt_dnssl_reserved;
	uint32_t	nd_opt_dnssl_lifetime;
	/* followed by list of DNS servers */
};
__CTASSERT(sizeof(struct nd_opt_rdnss) == 8);
#endif

/* Impossible options, so we can easily add extras */
#define _ND_OPT_PREFIX_ADDR	255 + 1

/* Minimal IPv6 MTU */
#ifndef IPV6_MMTU
#define IPV6_MMTU 1280
#endif

#ifndef ND_RA_FLAG_RTPREF_HIGH
#define ND_RA_FLAG_RTPREF_MASK		0x18
#define ND_RA_FLAG_RTPREF_HIGH		0x08
#define ND_RA_FLAG_RTPREF_MEDIUM	0x00
#define ND_RA_FLAG_RTPREF_LOW		0x18
#define ND_RA_FLAG_RTPREF_RSV		0x10
#endif

#define	EXPIRED_MAX	5	/* Remember 5 expired routers to avoid
				   logspam. */

#define MIN_RANDOM_FACTOR	500				/* millisecs */
#define MAX_RANDOM_FACTOR	1500				/* millisecs */
#define MIN_RANDOM_FACTOR_U	MIN_RANDOM_FACTOR * 1000	/* usecs */
#define MAX_RANDOM_FACTOR_U	MAX_RANDOM_FACTOR * 1000	/* usecs */

#if BYTE_ORDER == BIG_ENDIAN
#define IPV6_ADDR_INT32_ONE     1
#define IPV6_ADDR_INT16_MLL     0xff02
#elif BYTE_ORDER == LITTLE_ENDIAN
#define IPV6_ADDR_INT32_ONE     0x01000000
#define IPV6_ADDR_INT16_MLL     0x02ff
#endif

/* Debugging Neighbor Solicitations is a lot of spam, so disable it */
//#define DEBUG_NS
//

static void ipv6nd_handledata(void *);

/*
 * Android ships buggy ICMP6 filter headers.
 * Supply our own until they fix their shit.
 * References:
 *     https://android-review.googlesource.com/#/c/58438/
 *     http://code.google.com/p/android/issues/original?id=32621&seq=24
 */
#ifdef __ANDROID__
#undef ICMP6_FILTER_WILLPASS
#undef ICMP6_FILTER_WILLBLOCK
#undef ICMP6_FILTER_SETPASS
#undef ICMP6_FILTER_SETBLOCK
#undef ICMP6_FILTER_SETPASSALL
#undef ICMP6_FILTER_SETBLOCKALL
#define ICMP6_FILTER_WILLPASS(type, filterp) \
	((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0)
#define ICMP6_FILTER_WILLBLOCK(type, filterp) \
	((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0)
#define ICMP6_FILTER_SETPASS(type, filterp) \
	((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))))
#define ICMP6_FILTER_SETBLOCK(type, filterp) \
	((((filterp)->icmp6_filt[(type) >> 5]) |=  (1 << ((type) & 31))))
#define ICMP6_FILTER_SETPASSALL(filterp) \
	memset(filterp, 0, sizeof(struct icmp6_filter));
#define ICMP6_FILTER_SETBLOCKALL(filterp) \
	memset(filterp, 0xff, sizeof(struct icmp6_filter));
#endif

/* Support older systems with different defines */
#if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT)
#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
#endif
#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)
#define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif

/* Handy defines */
#define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra),  0)
#define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra),  1)

void
ipv6nd_printoptions(const struct dhcpcd_ctx *ctx,
    const struct dhcp_opt *opts, size_t opts_len)
{
	size_t i, j;
	const struct dhcp_opt *opt, *opt2;
	int cols;

	for (i = 0, opt = ctx->nd_opts;
	    i < ctx->nd_opts_len; i++, opt++)
	{
		for (j = 0, opt2 = opts; j < opts_len; j++, opt2++)
			if (opt2->option == opt->option)
				break;
		if (j == opts_len) {
			cols = printf("%03d %s", opt->option, opt->var);
			dhcp_print_option_encoding(opt, cols);
		}
	}
	for (i = 0, opt = opts; i < opts_len; i++, opt++) {
		cols = printf("%03d %s", opt->option, opt->var);
		dhcp_print_option_encoding(opt, cols);
	}
}

int
ipv6nd_open(bool recv)
{
	int fd, on;
	struct icmp6_filter filt;

	fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6);
	if (fd == -1)
		return -1;

	ICMP6_FILTER_SETBLOCKALL(&filt);

	/* RFC4861 4.1 */
	on = 255;
	if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
	    &on, sizeof(on)) == -1)
		goto eexit;

	if (recv) {
		on = 1;
		if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
		    &on, sizeof(on)) == -1)
			goto eexit;

		on = 1;
		if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
		    &on, sizeof(on)) == -1)
			goto eexit;

		ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);

#ifdef SO_RERROR
		on = 1;
		if (setsockopt(fd, SOL_SOCKET, SO_RERROR,
		    &on, sizeof(on)) == -1)
			goto eexit;
#endif
	}

	if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER,
	    &filt, sizeof(filt)) == -1)
		goto eexit;

	return fd;

eexit:
	close(fd);
	return -1;
}

#ifdef __sun
int
ipv6nd_openif(struct interface *ifp)
{
	int fd;
	struct ipv6_mreq mreq = {
	    .ipv6mr_multiaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT,
	    .ipv6mr_interface = ifp->index
	};
	struct rs_state *state = RS_STATE(ifp);
	uint_t ifindex = ifp->index;

	if (state->nd_fd != -1)
		return state->nd_fd;

	fd = ipv6nd_open(true);
	if (fd == -1)
		return -1;

	if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF,
	    &ifindex, sizeof(ifindex)) == -1)
	{
		close(fd);
		return -1;
	}

	if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
	    &mreq, sizeof(mreq)) == -1)
	{
		close(fd);
		return -1;
	}

	state->nd_fd = fd;
	eloop_event_add(ifp->ctx->eloop, fd, ipv6nd_handledata, ifp);
	return fd;
}
#endif

static int
ipv6nd_makersprobe(struct interface *ifp)
{
	struct rs_state *state;
	struct nd_router_solicit *rs;

	state = RS_STATE(ifp);
	free(state->rs);
	state->rslen = sizeof(*rs);
	if (ifp->hwlen != 0)
		state->rslen += (size_t)ROUNDUP8(ifp->hwlen + 2);
	state->rs = calloc(1, state->rslen);
	if (state->rs == NULL)
		return -1;
	rs = state->rs;
	rs->nd_rs_type = ND_ROUTER_SOLICIT;
	//rs->nd_rs_code = 0;
	//rs->nd_rs_cksum = 0;
	//rs->nd_rs_reserved = 0;

	if (ifp->hwlen != 0) {
		struct nd_opt_hdr *nd;

		nd = (struct nd_opt_hdr *)(state->rs + 1);
		nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
		nd->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
		memcpy(nd + 1, ifp->hwaddr, ifp->hwlen);
	}
	return 0;
}

static void
ipv6nd_sendrsprobe(void *arg)
{
	struct interface *ifp = arg;
	struct rs_state *state = RS_STATE(ifp);
	struct sockaddr_in6 dst = {
		.sin6_family = AF_INET6,
		.sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT,
		.sin6_scope_id = ifp->index,
	};
	struct iovec iov = { .iov_base = state->rs, .iov_len = state->rslen };
	union {
		struct cmsghdr hdr;
		uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
	} cmsgbuf = { .buf = { 0 } };
	struct msghdr msg = {
	    .msg_name = &dst, .msg_namelen = sizeof(dst),
	    .msg_iov = &iov, .msg_iovlen = 1,
	    .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
	};
	struct cmsghdr *cm;
	struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
	int s;
#ifndef __sun
	struct dhcpcd_ctx *ctx = ifp->ctx;
#endif

	if (ipv6_linklocal(ifp) == NULL) {
		logdebugx("%s: delaying Router Solicitation for LL address",
		    ifp->name);
		ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp);
		return;
	}

#ifdef HAVE_SA_LEN
	dst.sin6_len = sizeof(dst);
#endif

	/* Set the outbound interface */
	cm = CMSG_FIRSTHDR(&msg);
	if (cm == NULL) /* unlikely */
		return;
	cm->cmsg_level = IPPROTO_IPV6;
	cm->cmsg_type = IPV6_PKTINFO;
	cm->cmsg_len = CMSG_LEN(sizeof(pi));
	memcpy(CMSG_DATA(cm), &pi, sizeof(pi));

	logdebugx("%s: sending Router Solicitation", ifp->name);
#ifdef PRIVSEP
	if (IN_PRIVSEP(ifp->ctx)) {
		if (ps_inet_sendnd(ifp, &msg) == -1)
			logerr(__func__);
		goto sent;
	}
#endif
#ifdef __sun
	if (state->nd_fd == -1) {
		if (ipv6nd_openif(ifp) == -1) {
			logerr(__func__);
			return;
		}
	}
	s = state->nd_fd;
#else
	if (ctx->nd_fd == -1) {
		ctx->nd_fd = ipv6nd_open(true);
		if (ctx->nd_fd == -1) {
			logerr(__func__);
			return;
		}
		eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx);
	}
	s = ifp->ctx->nd_fd;
#endif
	if (sendmsg(s, &msg, 0) == -1) {
		logerr(__func__);
		/* Allow IPv6ND to continue .... at most a few errors
		 * would be logged.
		 * Generally the error is ENOBUFS when struggling to
		 * associate with an access point. */
	}

#ifdef PRIVSEP
sent:
#endif
	if (state->rsprobes++ < MAX_RTR_SOLICITATIONS)
		eloop_timeout_add_sec(ifp->ctx->eloop,
		    RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp);
	else
		logwarnx("%s: no IPv6 Routers available", ifp->name);
}

#ifdef ND6_ADVERTISE
static void
ipv6nd_sendadvertisement(void *arg)
{
	struct ipv6_addr *ia = arg;
	struct interface *ifp = ia->iface;
	struct dhcpcd_ctx *ctx = ifp->ctx;
	struct sockaddr_in6 dst = {
	    .sin6_family = AF_INET6,
	    .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT,
	    .sin6_scope_id = ifp->index,
	};
	struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len };
	union {
		struct cmsghdr hdr;
		uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
	} cmsgbuf = { .buf = { 0 } };
	struct msghdr msg = {
	    .msg_name = &dst, .msg_namelen = sizeof(dst),
	    .msg_iov = &iov, .msg_iovlen = 1,
	    .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
	};
	struct cmsghdr *cm;
	struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
	const struct rs_state *state = RS_CSTATE(ifp);
	int s;

	if (state == NULL || !if_is_link_up(ifp))
		goto freeit;

#ifdef SIN6_LEN
	dst.sin6_len = sizeof(dst);
#endif

	/* Set the outbound interface. */
	cm = CMSG_FIRSTHDR(&msg);
	assert(cm != NULL);
	cm->cmsg_level = IPPROTO_IPV6;
	cm->cmsg_type = IPV6_PKTINFO;
	cm->cmsg_len = CMSG_LEN(sizeof(pi));
	memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
	logdebugx("%s: sending NA for %s", ifp->name, ia->saddr);

#ifdef PRIVSEP
	if (IN_PRIVSEP(ifp->ctx)) {
		if (ps_inet_sendnd(ifp, &msg) == -1)
			logerr(__func__);
		goto sent;
	}
#endif
#ifdef __sun
	s = state->nd_fd;
#else
	s = ctx->nd_fd;
#endif
	if (sendmsg(s, &msg, 0) == -1)
		logerr(__func__);

#ifdef PRIVSEP
sent:
#endif
	if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) {
		eloop_timeout_add_sec(ctx->eloop,
		    state->retrans / 1000, ipv6nd_sendadvertisement, ia);
		return;
	}

freeit:
	free(ia->na);
	ia->na = NULL;
	ia->na_count = 0;
}

void
ipv6nd_advertise(struct ipv6_addr *ia)
{
	struct dhcpcd_ctx *ctx;
	struct interface *ifp;
	struct ipv6_state *state;
	struct ipv6_addr *iap, *iaf;
	struct nd_neighbor_advert *na;

	if (IN6_IS_ADDR_MULTICAST(&ia->addr))
		return;

#ifdef __sun
	if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX)
		return;
#endif

	ctx = ia->iface->ctx;
	/* Find the most preferred address to advertise. */
	iaf = NULL;
	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		state = IPV6_STATE(ifp);
		if (state == NULL || !if_is_link_up(ifp))
			continue;

		TAILQ_FOREACH(iap, &state->addrs, next) {
			if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr))
				continue;

			/* Cancel any current advertisement. */
			eloop_timeout_delete(ctx->eloop,
			    ipv6nd_sendadvertisement, iap);

			/* Don't advertise what we can't use. */
			if (iap->prefix_vltime == 0 ||
			    iap->addr_flags & IN6_IFF_NOTUSEABLE)
				continue;

			if (iaf == NULL ||
			    iaf->iface->metric > iap->iface->metric)
				iaf = iap;
		}
	}
	if (iaf == NULL)
		return;

	/* Make the packet. */
	ifp = iaf->iface;
	iaf->na_len = sizeof(*na);
	if (ifp->hwlen != 0)
		iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2);
	na = calloc(1, iaf->na_len);
	if (na == NULL) {
		logerr(__func__);
		return;
	}

	na->nd_na_type = ND_NEIGHBOR_ADVERT;
	na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE;
#if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE))
	if (IN_PRIVSEP(ctx)) {
		if (ps_root_ip6forwarding(ctx, ifp->name) != 0)
			na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
	} else
#endif
	if (ip6_forwarding(ifp->name) != 0)
		na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
	na->nd_na_target = ia->addr;

	if (ifp->hwlen != 0) {
		struct nd_opt_hdr *opt;

		opt = (struct nd_opt_hdr *)(na + 1);
		opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
		opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
		memcpy(opt + 1, ifp->hwaddr, ifp->hwlen);
	}

	iaf->na_count = 0;
	free(iaf->na);
	iaf->na = na;
	eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf);
	ipv6nd_sendadvertisement(iaf);
}
#elif !defined(SMALL)
#warning kernel does not support userland sending ND6 advertisements
#endif /* ND6_ADVERTISE */

static void
ipv6nd_expire(void *arg)
{
	struct interface *ifp = arg;
	struct ra *rap;

	if (ifp->ctx->ra_routers == NULL)
		return;

	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface == ifp && rap->willexpire)
			rap->doexpire = true;
	}
	ipv6nd_expirera(ifp);
}

void
ipv6nd_startexpire(struct interface *ifp)
{
	struct ra *rap;

	if (ifp->ctx->ra_routers == NULL)
		return;

	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface == ifp)
			rap->willexpire = true;
	}
	eloop_q_timeout_add_sec(ifp->ctx->eloop, ELOOP_IPV6RA_EXPIRE,
	    RTR_CARRIER_EXPIRE, ipv6nd_expire, ifp);
}

int
ipv6nd_rtpref(struct ra *rap)
{

	switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) {
	case ND_RA_FLAG_RTPREF_HIGH:
		return RTPREF_HIGH;
	case ND_RA_FLAG_RTPREF_MEDIUM:
	case ND_RA_FLAG_RTPREF_RSV:
		return RTPREF_MEDIUM;
	case ND_RA_FLAG_RTPREF_LOW:
		return RTPREF_LOW;
	default:
		logerrx("%s: impossible RA flag %x", __func__, rap->flags);
		return RTPREF_INVALID;
	}
	/* NOTREACHED */
}

static void
ipv6nd_sortrouters(struct dhcpcd_ctx *ctx)
{
	struct ra_head sorted_routers = TAILQ_HEAD_INITIALIZER(sorted_routers);
	struct ra *ra1, *ra2;

	while ((ra1 = TAILQ_FIRST(ctx->ra_routers)) != NULL) {
		TAILQ_REMOVE(ctx->ra_routers, ra1, next);
		TAILQ_FOREACH(ra2, &sorted_routers, next) {
			if (ra1->iface->metric > ra2->iface->metric)
				continue;
			if (ra1->expired && !ra2->expired)
				continue;
			if (ra1->willexpire && !ra2->willexpire)
				continue;
			if (ra1->lifetime == 0 && ra2->lifetime != 0)
				continue;
			if (!ra1->isreachable && ra2->reachable)
				continue;
			if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2))
				continue;
			/* All things being equal, prefer older routers. */
			/* We don't need to check time, becase newer
			 * routers are always added to the tail and then
			 * sorted. */
			TAILQ_INSERT_BEFORE(ra2, ra1, next);
			break;
		}
		if (ra2 == NULL)
			TAILQ_INSERT_TAIL(&sorted_routers, ra1, next);
	}

	TAILQ_CONCAT(ctx->ra_routers, &sorted_routers, next);
}

static void
ipv6nd_applyra(struct interface *ifp)
{
	struct ra *rap;
	struct rs_state *state = RS_STATE(ifp);
	struct ra defra = {
		.iface = ifp,
		.hoplimit = IPV6_DEFHLIM ,
		.reachable = REACHABLE_TIME,
		.retrans = RETRANS_TIMER,
	};

	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface == ifp)
			break;
	}

	/* If we have no Router Advertisement, then set default values. */
	if (rap == NULL || rap->expired || rap->willexpire)
		rap = &defra;

	state->retrans = rap->retrans;
	if (if_applyra(rap) == -1 && errno != ENOENT)
		logerr(__func__);
}

/*
 * Neighbour reachability.
 *
 * RFC 4681 6.2.5 says when a node is no longer a router it MUST
 * send a RA with a zero lifetime.
 * All OS's I know of set the NA router flag if they are a router
 * or not and disregard that they are actively advertising or
 * shutting down. If the interface is disabled, it cant't send a NA at all.
 *
 * As such we CANNOT rely on the NA Router flag and MUST use
 * unreachability or receive a RA with a lifetime of zero to remove
 * the node as a default router.
 */
void
ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, bool reachable)
{
	struct ra *rap, *rapr;

	if (ctx->ra_routers == NULL)
		return;

	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
		if (IN6_ARE_ADDR_EQUAL(&rap->from, addr))
			break;
	}

	if (rap == NULL || rap->expired || rap->isreachable == reachable)
		return;

	rap->isreachable = reachable;
	loginfox("%s: %s is %s", rap->iface->name, rap->sfrom,
	    reachable ? "reachable again" : "unreachable");

	/* See if we can install a reachable default router. */
	ipv6nd_sortrouters(ctx);
	ipv6nd_applyra(rap->iface);
	rt_build(ctx, AF_INET6);

	if (reachable)
		return;

	/* If we have no reachable default routers, try and solicit one. */
	TAILQ_FOREACH(rapr, ctx->ra_routers, next) {
		if (rap == rapr || rap->iface != rapr->iface)
			continue;
		if (rapr->isreachable && !rapr->expired && rapr->lifetime)
			break;
	}

	if (rapr == NULL)
		ipv6nd_startrs(rap->iface);
}

const struct ipv6_addr *
ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr,
    unsigned int flags)
{
	struct ra *rap;
	struct ipv6_addr *ap;

	if (ifp->ctx->ra_routers == NULL)
		return NULL;

	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface != ifp)
			continue;
		TAILQ_FOREACH(ap, &rap->addrs, next) {
			if (ipv6_findaddrmatch(ap, addr, flags))
				return ap;
		}
	}
	return NULL;
}

struct ipv6_addr *
ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr,
    unsigned int flags)
{
	struct ra *rap;
	struct ipv6_addr *ap;

	if (ctx->ra_routers == NULL)
		return NULL;

	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
		TAILQ_FOREACH(ap, &rap->addrs, next) {
			if (ipv6_findaddrmatch(ap, addr, flags))
				return ap;
		}
	}
	return NULL;
}

static struct ipv6_addr *
ipv6nd_rapfindprefix(struct ra *rap,
    const struct in6_addr *pfx, uint8_t pfxlen)
{
	struct ipv6_addr *ia;

	TAILQ_FOREACH(ia, &rap->addrs, next) {
		if (ia->prefix_vltime == 0)
			continue;
		if (ia->prefix_len == pfxlen &&
		    IN6_ARE_ADDR_EQUAL(&ia->prefix, pfx))
			break;
	}
	return ia;
}

struct ipv6_addr *
ipv6nd_iffindprefix(struct interface *ifp,
    const struct in6_addr *pfx, uint8_t pfxlen)
{
	struct ra *rap;
	struct ipv6_addr *ia;

	ia = NULL;
	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface != ifp)
			continue;
		ia = ipv6nd_rapfindprefix(rap, pfx, pfxlen);
		if (ia != NULL)
			break;
	}
	return ia;
}

static void
ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra)
{

	eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface);
	eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap);
	if (remove_ra)
		TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next);
	ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL);
	free(rap->data);
	free(rap);
}

static void
ipv6nd_freedrop_ra(struct ra *rap, int drop)
{

	ipv6nd_removefreedrop_ra(rap, 1, drop);
}

ssize_t
ipv6nd_free(struct interface *ifp)
{
	struct rs_state *state;
	struct ra *rap, *ran;
	struct dhcpcd_ctx *ctx;
	ssize_t n;

	state = RS_STATE(ifp);
	if (state == NULL)
		return 0;

	ctx = ifp->ctx;
#ifdef __sun
	eloop_event_delete(ctx->eloop, state->nd_fd);
	close(state->nd_fd);
#endif
	free(state->rs);
	free(state);
	ifp->if_data[IF_DATA_IPV6ND] = NULL;
	n = 0;
	TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
		if (rap->iface == ifp) {
			ipv6nd_free_ra(rap);
			n++;
		}
	}

#ifndef __sun
	/* If we don't have any more IPv6 enabled interfaces,
	 * close the global socket and release resources */
	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		if (RS_STATE(ifp))
			break;
	}
	if (ifp == NULL) {
		if (ctx->nd_fd != -1) {
			eloop_event_delete(ctx->eloop, ctx->nd_fd);
			close(ctx->nd_fd);
			ctx->nd_fd = -1;
		}
	}
#endif

	return n;
}

static void
ipv6nd_scriptrun(struct ra *rap)
{
	int hasdns, hasaddress;
	struct ipv6_addr *ap;

	hasaddress = 0;
	/* If all addresses have completed DAD run the script */
	TAILQ_FOREACH(ap, &rap->addrs, next) {
		if ((ap->flags & (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) ==
		    (IPV6_AF_AUTOCONF | IPV6_AF_ADDED))
		{
			hasaddress = 1;
			if (!(ap->flags & IPV6_AF_DADCOMPLETED) &&
			    ipv6_iffindaddr(ap->iface, &ap->addr,
			    IN6_IFF_TENTATIVE))
				ap->flags |= IPV6_AF_DADCOMPLETED;
			if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) {
				logdebugx("%s: waiting for Router Advertisement"
				    " DAD to complete",
				    rap->iface->name);
				return;
			}
		}
	}

	/* If we don't require RDNSS then set hasdns = 1 so we fork */
	if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS))
		hasdns = 1;
	else {
		hasdns = rap->hasdns;
	}

	script_runreason(rap->iface, "ROUTERADVERT");
	if (hasdns && (hasaddress ||
	    !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))))
		dhcpcd_daemonise(rap->iface->ctx);
#if 0
	else if (options & DHCPCD_DAEMONISE &&
	    !(options & DHCPCD_DAEMONISED) && new_data)
		logwarnx("%s: did not fork due to an absent"
		    " RDNSS option in the RA",
		    ifp->name);
#endif
}

static void
ipv6nd_addaddr(void *arg)
{
	struct ipv6_addr *ap = arg;

	ipv6_addaddr(ap, NULL);
}

int
ipv6nd_dadcompleted(const struct interface *ifp)
{
	const struct ra *rap;
	const struct ipv6_addr *ap;

	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface != ifp)
			continue;
		TAILQ_FOREACH(ap, &rap->addrs, next) {
			if (ap->flags & IPV6_AF_AUTOCONF &&
			    ap->flags & IPV6_AF_ADDED &&
			    !(ap->flags & IPV6_AF_DADCOMPLETED))
				return 0;
		}
	}
	return 1;
}

static void
ipv6nd_dadcallback(void *arg)
{
	struct ipv6_addr *ia = arg, *rapap;
	struct interface *ifp;
	struct ra *rap;
	int wascompleted, found;
	char buf[INET6_ADDRSTRLEN];
	const char *p;
	int dadcounter;

	ifp = ia->iface;
	wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED);
	ia->flags |= IPV6_AF_DADCOMPLETED;
	if (ia->addr_flags & IN6_IFF_DUPLICATED) {
		ia->dadcounter++;
		logwarnx("%s: DAD detected %s", ifp->name, ia->saddr);

		/* Try and make another stable private address.
		 * Because ap->dadcounter is always increamented,
		 * a different address is generated. */
		/* XXX Cache DAD counter per prefix/id/ssid? */
		if (ifp->options->options & DHCPCD_SLAACPRIVATE &&
		    IA6_CANAUTOCONF(ia))
		{
			unsigned int delay;

			if (ia->dadcounter >= IDGEN_RETRIES) {
				logerrx("%s: unable to obtain a"
				    " stable private address",
				    ifp->name);
				goto try_script;
			}
			loginfox("%s: deleting address %s",
			    ifp->name, ia->saddr);
			if (if_address6(RTM_DELADDR, ia) == -1 &&
			    errno != EADDRNOTAVAIL && errno != ENXIO)
				logerr(__func__);
			dadcounter = ia->dadcounter;
			if (ipv6_makestableprivate(&ia->addr,
			    &ia->prefix, ia->prefix_len,
			    ifp, &dadcounter) == -1)
			{
				logerr("ipv6_makestableprivate");
				return;
			}
			ia->dadcounter = dadcounter;
			ia->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
			ia->flags |= IPV6_AF_NEW;
			p = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
			if (p)
				snprintf(ia->saddr,
				    sizeof(ia->saddr),
				    "%s/%d",
				    p, ia->prefix_len);
			else
				ia->saddr[0] = '\0';
			delay = arc4random_uniform(IDGEN_DELAY * MSEC_PER_SEC);
			eloop_timeout_add_msec(ifp->ctx->eloop, delay,
			    ipv6nd_addaddr, ia);
			return;
		}
	}

try_script:
	if (!wascompleted) {
		TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
			if (rap->iface != ifp)
				continue;
			wascompleted = 1;
			found = 0;
			TAILQ_FOREACH(rapap, &rap->addrs, next) {
				if (rapap->flags & IPV6_AF_AUTOCONF &&
				    rapap->flags & IPV6_AF_ADDED &&
				    (rapap->flags & IPV6_AF_DADCOMPLETED) == 0)
				{
					wascompleted = 0;
					break;
				}
				if (rapap == ia)
					found = 1;
			}

			if (wascompleted && found) {
				logdebugx("%s: Router Advertisement DAD "
				    "completed",
				    rap->iface->name);
				ipv6nd_scriptrun(rap);
			}
		}
#ifdef ND6_ADVERTISE
		ipv6nd_advertise(ia);
#endif
	}
}

static struct ipv6_addr *
ipv6nd_findmarkstale(struct ra *rap, struct ipv6_addr *ia, bool mark)
{
	struct dhcpcd_ctx *ctx = ia->iface->ctx;
	struct ra *rap2;
	struct ipv6_addr *ia2;

	TAILQ_FOREACH(rap2, ctx->ra_routers, next) {
		if (rap2 == rap ||
		    rap2->iface != rap->iface ||
		    rap2->expired)
			continue;
		TAILQ_FOREACH(ia2, &rap2->addrs, next) {
			if (!IN6_ARE_ADDR_EQUAL(&ia->prefix, &ia2->prefix))
				continue;
			if (!(ia2->flags & IPV6_AF_STALE))
				return ia2;
			if (mark)
				ia2->prefix_pltime = 0;
		}
	}
	return NULL;
}

#ifndef DHCP6
/* If DHCPv6 is compiled out, supply a shim to provide an error message
 * if IPv6RA requests DHCPv6. */
enum DH6S {
	DH6S_REQUEST,
	DH6S_INFORM,
};
static int
dhcp6_start(__unused struct interface *ifp, __unused enum DH6S init_state)
{

	errno = ENOTSUP;
	return -1;
}
#endif

static void
ipv6nd_handlera(struct dhcpcd_ctx *ctx,
    const struct sockaddr_in6 *from, const char *sfrom,
    struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit)
{
	size_t i, olen;
	struct nd_router_advert *nd_ra;
	struct nd_opt_hdr ndo;
	struct nd_opt_prefix_info pi;
	struct nd_opt_mtu mtu;
	struct nd_opt_rdnss rdnss;
	uint8_t *p;
	struct ra *rap;
	struct in6_addr pi_prefix;
	struct ipv6_addr *ia;
	struct dhcp_opt *dho;
	bool new_rap, new_data, has_address;
	uint32_t old_lifetime;
	int ifmtu;
	int loglevel;
	unsigned int flags;
#ifdef IPV6_MANAGETEMPADDR
	bool new_ia;
#endif

	if (ifp == NULL || RS_STATE(ifp) == NULL) {
#ifdef DEBUG_RS
		logdebugx("RA for unexpected interface from %s", sfrom);
#endif
		return;
	}

	if (len < sizeof(struct nd_router_advert)) {
		logerrx("IPv6 RA packet too short from %s", sfrom);
		return;
	}

	/* RFC 4861 7.1.2 */
	if (hoplimit != 255) {
		logerrx("invalid hoplimit(%d) in RA from %s", hoplimit, sfrom);
		return;
	}
	if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) {
		logerrx("RA from non local address %s", sfrom);
		return;
	}

	if (!(ifp->options->options & DHCPCD_IPV6RS)) {
#ifdef DEBUG_RS
		logerrx("%s: unexpected RA from %s", ifp->name, sfrom);
#endif
		return;
	}

	/* We could receive a RA before we sent a RS*/
	if (ipv6_linklocal(ifp) == NULL) {
#ifdef DEBUG_RS
		logdebugx("%s: received RA from %s (no link-local)",
		    ifp->name, sfrom);
#endif
		return;
	}

	if (ipv6_iffindaddr(ifp, &from->sin6_addr, IN6_IFF_TENTATIVE)) {
		logdebugx("%s: ignoring RA from ourself %s",
		    ifp->name, sfrom);
		return;
	}

	/*
	 * Because we preserve RA's and expire them quickly after
	 * carrier up, it's important to reset the kernels notion of
	 * reachable timers back to default values before applying
	 * new RA values.
	 */
	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
		if (ifp == rap->iface)
			break;
	}
	if (rap != NULL && rap->willexpire)
		ipv6nd_applyra(ifp);

	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
		if (ifp == rap->iface &&
		    IN6_ARE_ADDR_EQUAL(&rap->from, &from->sin6_addr))
			break;
	}

	nd_ra = (struct nd_router_advert *)icp;

	/* We don't want to spam the log with the fact we got an RA every
	 * 30 seconds or so, so only spam the log if it's different. */
	if (rap == NULL || (rap->data_len != len ||
	     memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0))
	{
		if (rap) {
			free(rap->data);
			rap->data_len = 0;
		}
		new_data = true;
	} else
		new_data = false;
	if (rap == NULL) {
		rap = calloc(1, sizeof(*rap));
		if (rap == NULL) {
			logerr(__func__);
			return;
		}
		rap->iface = ifp;
		rap->from = from->sin6_addr;
		strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
		TAILQ_INIT(&rap->addrs);
		new_rap = true;
		rap->isreachable = true;
	} else
		new_rap = false;
	if (rap->data_len == 0) {
		rap->data = malloc(len);
		if (rap->data == NULL) {
			logerr(__func__);
			if (new_rap)
				free(rap);
			return;
		}
		memcpy(rap->data, icp, len);
		rap->data_len = len;
	}

	/* We could change the debug level based on new_data, but some
	 * routers like to decrease the advertised valid and preferred times
	 * in accordance with the own prefix times which would result in too
	 * much needless log spam. */
	if (rap->willexpire)
		new_data = true;
	loglevel = new_rap || rap->willexpire || !rap->isreachable ?
	    LOG_INFO : LOG_DEBUG;
	logmessage(loglevel, "%s: Router Advertisement from %s",
	    ifp->name, rap->sfrom);

	clock_gettime(CLOCK_MONOTONIC, &rap->acquired);
	rap->flags = nd_ra->nd_ra_flags_reserved;
	old_lifetime = rap->lifetime;
	rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
	if (!new_rap && rap->lifetime == 0 && old_lifetime != 0)
		logwarnx("%s: %s: no longer a default router",
		    ifp->name, rap->sfrom);
	if (nd_ra->nd_ra_curhoplimit != 0)
		rap->hoplimit = nd_ra->nd_ra_curhoplimit;
	else
		rap->hoplimit = IPV6_DEFHLIM;
	if (nd_ra->nd_ra_reachable != 0) {
		rap->reachable = ntohl(nd_ra->nd_ra_reachable);
		if (rap->reachable > MAX_REACHABLE_TIME)
			rap->reachable = 0;
	} else
		rap->reachable = REACHABLE_TIME;
	if (nd_ra->nd_ra_retransmit != 0)
		rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
	else
		rap->retrans = RETRANS_TIMER;
	rap->expired = rap->willexpire = rap->doexpire = false;
	rap->hasdns = false;
	rap->isreachable = true;
	has_address = false;
	rap->mtu = 0;

#ifdef IPV6_AF_TEMPORARY
	ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY);
#endif
	TAILQ_FOREACH(ia, &rap->addrs, next) {
		ia->flags |= IPV6_AF_STALE;
	}

	len -= sizeof(struct nd_router_advert);
	p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
	for (; len > 0; p += olen, len -= olen) {
		if (len < sizeof(ndo)) {
			logerrx("%s: short option", ifp->name);
			break;
		}
		memcpy(&ndo, p, sizeof(ndo));
		olen = (size_t)ndo.nd_opt_len * 8;
		if (olen == 0) {
			logerrx("%s: zero length option", ifp->name);
			break;
		}
		if (olen > len) {
			logerrx("%s: option length exceeds message",
			    ifp->name);
			break;
		}

		if (has_option_mask(ifp->options->rejectmasknd,
		    ndo.nd_opt_type))
		{
			for (i = 0, dho = ctx->nd_opts;
			    i < ctx->nd_opts_len;
			    i++, dho++)
			{
				if (dho->option == ndo.nd_opt_type)
					break;
			}
			if (dho != NULL)
				logwarnx("%s: reject RA (option %s) from %s",
				    ifp->name, dho->var, rap->sfrom);
			else
				logwarnx("%s: reject RA (option %d) from %s",
				    ifp->name, ndo.nd_opt_type, rap->sfrom);
			if (new_rap)
				ipv6nd_removefreedrop_ra(rap, 0, 0);
			else
				ipv6nd_free_ra(rap);
			return;
		}

		if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type))
			continue;

		switch (ndo.nd_opt_type) {
		case ND_OPT_PREFIX_INFORMATION:
			loglevel = new_data ? LOG_ERR : LOG_DEBUG;
			if (ndo.nd_opt_len != 4) {
				logmessage(loglevel,
				    "%s: invalid option len for prefix",
				    ifp->name);
				continue;
			}
			memcpy(&pi, p, sizeof(pi));
			if (pi.nd_opt_pi_prefix_len > 128) {
				logmessage(loglevel, "%s: invalid prefix len",
				    ifp->name);
				continue;
			}
			/* nd_opt_pi_prefix is not aligned. */
			memcpy(&pi_prefix, &pi.nd_opt_pi_prefix,
			    sizeof(pi_prefix));
			if (IN6_IS_ADDR_MULTICAST(&pi_prefix) ||
			    IN6_IS_ADDR_LINKLOCAL(&pi_prefix))
			{
				logmessage(loglevel, "%s: invalid prefix in RA",
				    ifp->name);
				continue;
			}
			if (ntohl(pi.nd_opt_pi_preferred_time) >
			    ntohl(pi.nd_opt_pi_valid_time))
			{
				logmessage(loglevel, "%s: pltime > vltime",
				    ifp->name);
				continue;
			}

			flags = IPV6_AF_RAPFX;
			/* If no flags are set, that means the prefix is
			 * available via the router. */
			if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)
				flags |= IPV6_AF_ONLINK;
			if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO &&
			    rap->iface->options->options &
			    DHCPCD_IPV6RA_AUTOCONF)
				flags |= IPV6_AF_AUTOCONF;
			if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ROUTER)
				flags |= IPV6_AF_ROUTER;

			ia = ipv6nd_rapfindprefix(rap,
			    &pi_prefix, pi.nd_opt_pi_prefix_len);
			if (ia == NULL) {
				ia = ipv6_newaddr(rap->iface,
				    &pi_prefix, pi.nd_opt_pi_prefix_len, flags);
				if (ia == NULL)
					break;
				ia->prefix = pi_prefix;
				if (flags & IPV6_AF_AUTOCONF)
					ia->dadcallback = ipv6nd_dadcallback;
				ia->created = ia->acquired = rap->acquired;
				TAILQ_INSERT_TAIL(&rap->addrs, ia, next);

#ifdef IPV6_MANAGETEMPADDR
				/* New address to dhcpcd RA handling.
				 * If the address already exists and a valid
				 * temporary address also exists then
				 * extend the existing one rather than
				 * create a new one */
				if (flags & IPV6_AF_AUTOCONF &&
				    ipv6_iffindaddr(ifp, &ia->addr,
				    IN6_IFF_NOTUSEABLE) &&
				    ipv6_settemptime(ia, 0))
					new_ia = false;
				else
					new_ia = true;
#endif
			} else {
#ifdef IPV6_MANAGETEMPADDR
				new_ia = false;
#endif
				ia->flags |= flags;
				ia->flags &= ~IPV6_AF_STALE;
				ia->acquired = rap->acquired;
			}
			ia->prefix_vltime =
			    ntohl(pi.nd_opt_pi_valid_time);
			ia->prefix_pltime =
			    ntohl(pi.nd_opt_pi_preferred_time);
			if (ia->prefix_vltime != 0 &&
			    ia->flags & IPV6_AF_AUTOCONF)
				has_address = true;

#ifdef IPV6_MANAGETEMPADDR
			/* RFC4941 Section 3.3.3 */
			if (ia->flags & IPV6_AF_AUTOCONF &&
			    ia->iface->options->options & DHCPCD_SLAACTEMP &&
			    IA6_CANAUTOCONF(ia))
			{
				if (!new_ia) {
					if (ipv6_settemptime(ia, 1) == NULL)
						new_ia = true;
				}
				if (new_ia && ia->prefix_pltime) {
					if (ipv6_createtempaddr(ia,
					    &ia->acquired) == NULL)
						logerr("ipv6_createtempaddr");
				}
			}
#endif
			break;

		case ND_OPT_MTU:
			if (len < sizeof(mtu)) {
				logmessage(loglevel, "%s: short MTU option", ifp->name);
				break;
			}
			memcpy(&mtu, p, sizeof(mtu));
			mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu);
			if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) {
				logmessage(loglevel, "%s: invalid MTU %d",
				    ifp->name, mtu.nd_opt_mtu_mtu);
				break;
			}
			ifmtu = if_getmtu(ifp);
			if (ifmtu == -1)
				logerr("if_getmtu");
			else if (mtu.nd_opt_mtu_mtu > (uint32_t)ifmtu) {
				logmessage(loglevel, "%s: advertised MTU %d"
				    " is greater than link MTU %d",
				    ifp->name, mtu.nd_opt_mtu_mtu, ifmtu);
				rap->mtu = (uint32_t)ifmtu;
			} else
				rap->mtu = mtu.nd_opt_mtu_mtu;
			break;
		case ND_OPT_RDNSS:
			if (len < sizeof(rdnss)) {
				logmessage(loglevel, "%s: short RDNSS option", ifp->name);
				break;
			}
			memcpy(&rdnss, p, sizeof(rdnss));
			if (rdnss.nd_opt_rdnss_lifetime &&
			    rdnss.nd_opt_rdnss_len > 1)
				rap->hasdns = 1;
			break;
		default:
			continue;
		}
	}

	for (i = 0, dho = ctx->nd_opts;
	    i < ctx->nd_opts_len;
	    i++, dho++)
	{
		if (has_option_mask(ifp->options->requiremasknd,
		    dho->option))
		{
			logwarnx("%s: reject RA (no option %s) from %s",
			    ifp->name, dho->var, rap->sfrom);
			if (new_rap)
				ipv6nd_removefreedrop_ra(rap, 0, 0);
			else
				ipv6nd_free_ra(rap);
			return;
		}
	}

	TAILQ_FOREACH(ia, &rap->addrs, next) {
		if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0)
			continue;
		if (ipv6nd_findmarkstale(rap, ia, false) != NULL)
			continue;
		ipv6nd_findmarkstale(rap, ia, true);
		logdebugx("%s: %s: became stale", ifp->name, ia->saddr);
		/* Technically this violates RFC 4861 6.3.4,
		 * but we need a mechanism to tell the kernel to
		 * try and prefer other addresses. */
		ia->prefix_pltime = 0;
	}

	if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp))
		logwarnx("%s: no global addresses for default route",
		    ifp->name);

	if (new_rap)
		TAILQ_INSERT_TAIL(ctx->ra_routers, rap, next);
	if (new_data)
		ipv6nd_sortrouters(ifp->ctx);

	if (ifp->ctx->options & DHCPCD_TEST) {
		script_runreason(ifp, "TEST");
		goto handle_flag;
	}

	if (!(ifp->options->options & DHCPCD_CONFIGURE))
		goto run;

	ipv6nd_applyra(ifp);
	ipv6_addaddrs(&rap->addrs);
#ifdef IPV6_MANAGETEMPADDR
	ipv6_addtempaddrs(ifp, &rap->acquired);
#endif
	rt_build(ifp->ctx, AF_INET6);

run:
	ipv6nd_scriptrun(rap);

	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */

handle_flag:
	if (!(ifp->options->options & DHCPCD_DHCP6))
		goto nodhcp6;
/* Only log a DHCPv6 start error if compiled in or debugging is enabled. */
#ifdef DHCP6
#define LOG_DHCP6	logerr
#else
#define LOG_DHCP6	logdebug
#endif
	if (rap->flags & ND_RA_FLAG_MANAGED) {
		if (new_data && dhcp6_start(ifp, DH6S_REQUEST) == -1)
			LOG_DHCP6("dhcp6_start: %s", ifp->name);
	} else if (rap->flags & ND_RA_FLAG_OTHER) {
		if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1)
			LOG_DHCP6("dhcp6_start: %s", ifp->name);
	} else {
#ifdef DHCP6
		if (new_data)
			logdebugx("%s: No DHCPv6 instruction in RA", ifp->name);
#endif
nodhcp6:
		if (ifp->ctx->options & DHCPCD_TEST) {
			eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
			return;
		}
	}

	/* Expire should be called last as the rap object could be destroyed */
	ipv6nd_expirera(ifp);
}

bool
ipv6nd_hasralifetime(const struct interface *ifp, bool lifetime)
{
	const struct ra *rap;

	if (ifp->ctx->ra_routers) {
		TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next)
			if (rap->iface == ifp &&
			    !rap->expired &&
			    (!lifetime ||rap->lifetime))
				return true;
	}
	return false;
}

bool
ipv6nd_hasradhcp(const struct interface *ifp, bool managed)
{
	const struct ra *rap;

	if (ifp->ctx->ra_routers) {
		TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
			if (rap->iface == ifp &&
			    !rap->expired && !rap->willexpire &&
			    ((managed && rap->flags & ND_RA_FLAG_MANAGED) ||
			    (!managed && rap->flags & ND_RA_FLAG_OTHER)))
				return true;
		}
	}
	return false;
}

static const uint8_t *
ipv6nd_getoption(struct dhcpcd_ctx *ctx,
    size_t *os, unsigned int *code, size_t *len,
    const uint8_t *od, size_t ol, struct dhcp_opt **oopt)
{
	struct nd_opt_hdr ndo;
	size_t i;
	struct dhcp_opt *opt;

	if (od) {
		*os = sizeof(ndo);
		if (ol < *os) {
			errno = EINVAL;
			return NULL;
		}
		memcpy(&ndo, od, sizeof(ndo));
		i = (size_t)(ndo.nd_opt_len * 8);
		if (i > ol) {
			errno = EINVAL;
			return NULL;
		}
		*len = i;
		*code = ndo.nd_opt_type;
	}

	for (i = 0, opt = ctx->nd_opts;
	    i < ctx->nd_opts_len; i++, opt++)
	{
		if (opt->option == *code) {
			*oopt = opt;
			break;
		}
	}

	if (od)
		return od + sizeof(ndo);
	return NULL;
}

ssize_t
ipv6nd_env(FILE *fp, const struct interface *ifp)
{
	size_t i, j, n, len, olen;
	struct ra *rap;
	char ndprefix[32];
	struct dhcp_opt *opt;
	uint8_t *p;
	struct nd_opt_hdr ndo;
	struct ipv6_addr *ia;
	struct timespec now;
	int pref;

	clock_gettime(CLOCK_MONOTONIC, &now);
	i = n = 0;
	TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) {
		if (rap->iface != ifp || rap->expired)
			continue;
		i++;
		snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i);
		if (efprintf(fp, "%s_from=%s", ndprefix, rap->sfrom) == -1)
			return -1;
		if (efprintf(fp, "%s_acquired=%lld", ndprefix,
		    (long long)rap->acquired.tv_sec) == -1)
			return -1;
		if (efprintf(fp, "%s_now=%lld", ndprefix,
		    (long long)now.tv_sec) == -1)
			return -1;
		if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1)
			return -1;
		pref = ipv6nd_rtpref(rap);
		if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix,
		    rap->flags & ND_RA_FLAG_MANAGED    ? "M" : "",
		    rap->flags & ND_RA_FLAG_OTHER      ? "O" : "",
		    rap->flags & ND_RA_FLAG_HOME_AGENT ? "H" : "",
		    pref == RTPREF_HIGH ? "h" : pref == RTPREF_LOW ? "l" : "",
		    rap->flags & ND_RA_FLAG_PROXY      ? "P" : "") == -1)
			return -1;
		if (efprintf(fp, "%s_lifetime=%u", ndprefix, rap->lifetime) == -1)
			return -1;

		/* Zero our indexes */
		for (j = 0, opt = rap->iface->ctx->nd_opts;
		    j < rap->iface->ctx->nd_opts_len;
		    j++, opt++)
			dhcp_zero_index(opt);
		for (j = 0, opt = rap->iface->options->nd_override;
		    j < rap->iface->options->nd_override_len;
		    j++, opt++)
			dhcp_zero_index(opt);

		/* Unlike DHCP, ND6 options *may* occur more than once.
		 * There is also no provision for option concatenation
		 * unlike DHCP. */
		len = rap->data_len - sizeof(struct nd_router_advert);
		for (p = rap->data + sizeof(struct nd_router_advert);
		    len >= sizeof(ndo);
		    p += olen, len -= olen)
		{
			memcpy(&ndo, p, sizeof(ndo));
			olen = (size_t)(ndo.nd_opt_len * 8);
			if (olen > len) {
				errno =	EINVAL;
				break;
			}
			if (has_option_mask(rap->iface->options->nomasknd,
			    ndo.nd_opt_type))
				continue;
			for (j = 0, opt = rap->iface->options->nd_override;
			    j < rap->iface->options->nd_override_len;
			    j++, opt++)
				if (opt->option == ndo.nd_opt_type)
					break;
			if (j == rap->iface->options->nd_override_len) {
				for (j = 0, opt = rap->iface->ctx->nd_opts;
				    j < rap->iface->ctx->nd_opts_len;
				    j++, opt++)
					if (opt->option == ndo.nd_opt_type)
						break;
				if (j == rap->iface->ctx->nd_opts_len)
					opt = NULL;
			}
			if (opt == NULL)
				continue;
			dhcp_envoption(rap->iface->ctx, fp,
			    ndprefix, rap->iface->name,
			    opt, ipv6nd_getoption,
			    p + sizeof(ndo), olen - sizeof(ndo));
		}

		/* We need to output the addresses we actually made
		 * from the prefix information options as well. */
		j = 0;
		TAILQ_FOREACH(ia, &rap->addrs, next) {
			if (!(ia->flags & IPV6_AF_AUTOCONF) ||
#ifdef IPV6_AF_TEMPORARY
			    ia->flags & IPV6_AF_TEMPORARY ||
#endif
			    !(ia->flags & IPV6_AF_ADDED) ||
			    ia->prefix_vltime == 0)
				continue;
			if (efprintf(fp, "%s_addr%zu=%s",
			    ndprefix, ++j, ia->saddr) == -1)
				return -1;
		}
	}
	return 1;
}

void
ipv6nd_handleifa(int cmd, struct ipv6_addr *addr, pid_t pid)
{
	struct ra *rap;

	/* IPv6 init may not have happened yet if we are learning
	 * existing addresses when dhcpcd starts. */
	if (addr->iface->ctx->ra_routers == NULL)
		return;

	TAILQ_FOREACH(rap, addr->iface->ctx->ra_routers, next) {
		if (rap->iface != addr->iface)
			continue;
		ipv6_handleifa_addrs(cmd, &rap->addrs, addr, pid);
	}
}

void
ipv6nd_expirera(void *arg)
{
	struct interface *ifp;
	struct ra *rap, *ran;
	struct timespec now;
	uint32_t elapsed;
	bool expired, valid;
	struct ipv6_addr *ia;
	size_t len, olen;
	uint8_t *p;
	struct nd_opt_hdr ndo;
#if 0
	struct nd_opt_prefix_info pi;
#endif
	struct nd_opt_dnssl dnssl;
	struct nd_opt_rdnss rdnss;
	unsigned int next = 0, ltime;
	size_t nexpired = 0;

	ifp = arg;
	clock_gettime(CLOCK_MONOTONIC, &now);
	expired = false;

	TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
		if (rap->iface != ifp || rap->expired)
			continue;
		valid = false;
		if (rap->lifetime) {
			elapsed = (uint32_t)eloop_timespec_diff(&now,
			    &rap->acquired, NULL);
			if (elapsed >= rap->lifetime || rap->doexpire) {
				if (!rap->expired) {
					logwarnx("%s: %s: router expired",
					    ifp->name, rap->sfrom);
					rap->lifetime = 0;
					expired = true;
				}
			} else {
				valid = true;
				ltime = rap->lifetime - elapsed;
				if (next == 0 || ltime < next)
					next = ltime;
			}
		}

		/* Not every prefix is tied to an address which
		 * the kernel can expire, so we need to handle it ourself.
		 * Also, some OS don't support address lifetimes (Solaris). */
		TAILQ_FOREACH(ia, &rap->addrs, next) {
			if (ia->prefix_vltime == 0)
				continue;
			if (ia->prefix_vltime == ND6_INFINITE_LIFETIME &&
			    !rap->doexpire)
			{
				valid = true;
				continue;
			}
			elapsed = (uint32_t)eloop_timespec_diff(&now,
			    &ia->acquired, NULL);
			if (elapsed >= ia->prefix_vltime || rap->doexpire) {
				if (ia->flags & IPV6_AF_ADDED) {
					logwarnx("%s: expired %s %s",
					    ia->iface->name,
					    ia->flags & IPV6_AF_AUTOCONF ?
					    "address" : "prefix",
					    ia->saddr);
					if (if_address6(RTM_DELADDR, ia)== -1 &&
					    errno != EADDRNOTAVAIL &&
					    errno != ENXIO)
						logerr(__func__);
				}
				ia->prefix_vltime = ia->prefix_pltime = 0;
				ia->flags &=
				    ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
				expired = true;
			} else {
				valid = true;
				ltime = ia->prefix_vltime - elapsed;
				if (next == 0 || ltime < next)
					next = ltime;
			}
		}

		/* Work out expiry for ND options */
		elapsed = (uint32_t)eloop_timespec_diff(&now,
		    &rap->acquired, NULL);
		len = rap->data_len - sizeof(struct nd_router_advert);
		for (p = rap->data + sizeof(struct nd_router_advert);
		    len >= sizeof(ndo);
		    p += olen, len -= olen)
		{
			memcpy(&ndo, p, sizeof(ndo));
			olen = (size_t)(ndo.nd_opt_len * 8);
			if (olen > len) {
				errno =	EINVAL;
				break;
			}

			if (has_option_mask(rap->iface->options->nomasknd,
			    ndo.nd_opt_type))
				continue;

			switch (ndo.nd_opt_type) {
			/* Prefix info is already checked in the above loop. */
#if 0
			case ND_OPT_PREFIX_INFORMATION:
				if (len < sizeof(pi))
					break;
				memcpy(&pi, p, sizeof(pi));
				ltime = pi.nd_opt_pi_valid_time;
				break;
#endif
			case ND_OPT_DNSSL:
				if (len < sizeof(dnssl))
					continue;
				memcpy(&dnssl, p, sizeof(dnssl));
				ltime = dnssl.nd_opt_dnssl_lifetime;
				break;
			case ND_OPT_RDNSS:
				if (len < sizeof(rdnss))
					continue;
				memcpy(&rdnss, p, sizeof(rdnss));
				ltime = rdnss.nd_opt_rdnss_lifetime;
				break;
			default:
				continue;
			}

			if (ltime == 0)
				continue;
			if (rap->doexpire) {
				expired = true;
				continue;
			}
			if (ltime == ND6_INFINITE_LIFETIME) {
				valid = true;
				continue;
			}

			ltime = ntohl(ltime);
			if (elapsed >= ltime) {
				expired = true;
				continue;
			}

			valid = true;
			ltime -= elapsed;
			if (next == 0 || ltime < next)
				next = ltime;
		}

		if (valid)
			continue;

		/* Router has expired. Let's not keep a lot of them. */
		rap->expired = true;
		if (++nexpired > EXPIRED_MAX)
			ipv6nd_free_ra(rap);
	}

	if (next != 0)
		eloop_timeout_add_sec(ifp->ctx->eloop,
		    next, ipv6nd_expirera, ifp);
	if (expired) {
		logwarnx("%s: part of a Router Advertisement expired",
		    ifp->name);
		ipv6nd_sortrouters(ifp->ctx);
		ipv6nd_applyra(ifp);
		rt_build(ifp->ctx, AF_INET6);
		script_runreason(ifp, "ROUTERADVERT");
	}
}

void
ipv6nd_drop(struct interface *ifp)
{
	struct ra *rap, *ran;
	bool expired = false;

	if (ifp->ctx->ra_routers == NULL)
		return;

	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
		if (rap->iface == ifp) {
			rap->expired = expired = true;
			ipv6nd_drop_ra(rap);
		}
	}
	if (expired) {
		ipv6nd_applyra(ifp);
		rt_build(ifp->ctx, AF_INET6);
		if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP)
			script_runreason(ifp, "ROUTERADVERT");
	}
}

void
ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg)
{
	struct sockaddr_in6 *from = (struct sockaddr_in6 *)msg->msg_name;
	char sfrom[INET6_ADDRSTRLEN];
	int hoplimit = 0;
	struct icmp6_hdr *icp;
	struct interface *ifp;
	size_t len = msg->msg_iov[0].iov_len;

	inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom));
	if ((size_t)len < sizeof(struct icmp6_hdr)) {
		logerrx("IPv6 ICMP packet too short from %s", sfrom);
		return;
	}

	ifp = if_findifpfromcmsg(ctx, msg, &hoplimit);
	if (ifp == NULL) {
		logerr(__func__);
		return;
	}

	/* Don't do anything if the user hasn't configured it. */
	if (ifp->active != IF_ACTIVE_USER ||
	    !(ifp->options->options & DHCPCD_IPV6))
		return;

	icp = (struct icmp6_hdr *)msg->msg_iov[0].iov_base;
	if (icp->icmp6_code == 0) {
		switch(icp->icmp6_type) {
			case ND_ROUTER_ADVERT:
				ipv6nd_handlera(ctx, from, sfrom,
				    ifp, icp, (size_t)len, hoplimit);
				return;
		}
	}

	logerrx("invalid IPv6 type %d or code %d from %s",
	    icp->icmp6_type, icp->icmp6_code, sfrom);
}

static void
ipv6nd_handledata(void *arg)
{
	struct dhcpcd_ctx *ctx;
	int fd;
	struct sockaddr_in6 from;
	union {
		struct icmp6_hdr hdr;
		uint8_t buf[64 * 1024]; /* Maximum ICMPv6 size */
	} iovbuf;
	struct iovec iov = {
		.iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf),
	};
	union {
		struct cmsghdr hdr;
		uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) +
		    CMSG_SPACE(sizeof(int))];
	} cmsgbuf = { .buf = { 0 } };
	struct msghdr msg = {
	    .msg_name = &from, .msg_namelen = sizeof(from),
	    .msg_iov = &iov, .msg_iovlen = 1,
	    .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
	};
	ssize_t len;

#ifdef __sun
	struct interface *ifp;
	struct rs_state *state;

	ifp = arg;
	state = RS_STATE(ifp);
	ctx = ifp->ctx;
	fd = state->nd_fd;
#else
	ctx = arg;
	fd = ctx->nd_fd;
#endif
	len = recvmsg(fd, &msg, 0);
	if (len == -1) {
		logerr(__func__);
		return;
	}

	iov.iov_len = (size_t)len;
	ipv6nd_recvmsg(ctx, &msg);
}

static void
ipv6nd_startrs1(void *arg)
{
	struct interface *ifp = arg;
	struct rs_state *state;

	loginfox("%s: soliciting an IPv6 router", ifp->name);
	state = RS_STATE(ifp);
	if (state == NULL) {
		ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state));
		state = RS_STATE(ifp);
		if (state == NULL) {
			logerr(__func__);
			return;
		}
#ifdef __sun
		state->nd_fd = -1;
#endif
	}

	/* Always make a new probe as the underlying hardware
	 * address could have changed. */
	ipv6nd_makersprobe(ifp);
	if (state->rs == NULL) {
		logerr(__func__);
		return;
	}

	state->retrans = RETRANS_TIMER;
	state->rsprobes = 0;
	ipv6nd_sendrsprobe(ifp);
}

void
ipv6nd_startrs(struct interface *ifp)
{
	unsigned int delay;

	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) {
		ipv6nd_startrs1(ifp);
		return;
	}

	delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC);
	logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds",
	    ifp->name, (float)delay / MSEC_PER_SEC);
	eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp);
	return;
}