view src/dhcp6.c @ 5231:a2c342295221 draft

privsep: Enable Capsicum for all processes. Except for the priviledged process. This is quite an in-depth change: * ARP is now one process per address * BPF flags are now returned via privsep * BPF write filters are locked when supported * The root process sends to the network The last step is done by opening RAW sockets and then sending a UDP header (where applicable) to avoid binding to an address which is already in use by the reader sockets. This is slightly wasteful for OS's without sandboxing but does have the very nice side effect of not needing a source address to unicast DHCPs replies from which makes the code smaller.
author Roy Marples <roy@marples.name>
date Tue, 19 May 2020 16:19:05 +0100
parents 06575a46bbdd
children bcd021398c1d
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * dhcpcd - DHCP client daemon
 * 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/utsname.h>
#include <sys/types.h>

#include <netinet/in.h>
#include <netinet/ip6.h>

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

#define ELOOP_QUEUE	ELOOP_DHCP6
#include "config.h"
#include "common.h"
#include "dhcp.h"
#include "dhcp6.h"
#include "duid.h"
#include "eloop.h"
#include "if.h"
#include "if-options.h"
#include "ipv6nd.h"
#include "logerr.h"
#include "privsep.h"
#include "script.h"

#ifdef HAVE_SYS_BITOPS_H
#include <sys/bitops.h>
#else
#include "compat/bitops.h"
#endif

/* DHCPCD Project has been assigned an IANA PEN of 40712 */
#define DHCPCD_IANA_PEN 40712

/* Unsure if I want this */
//#define VENDOR_SPLIT

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

#ifdef DHCP6

/* Assert the correct structure size for on wire */
struct dhcp6_message {
	uint8_t type;
	uint8_t xid[3];
	/* followed by options */
};
__CTASSERT(sizeof(struct dhcp6_message) == 4);

struct dhcp6_option {
	uint16_t code;
	uint16_t len;
	/* followed by data */
};
__CTASSERT(sizeof(struct dhcp6_option) == 4);

struct dhcp6_ia_na {
	uint8_t iaid[4];
	uint32_t t1;
	uint32_t t2;
};
__CTASSERT(sizeof(struct dhcp6_ia_na) == 12);

struct dhcp6_ia_ta {
	uint8_t iaid[4];
};
__CTASSERT(sizeof(struct dhcp6_ia_ta) == 4);

struct dhcp6_ia_addr {
	struct in6_addr addr;
	uint32_t pltime;
	uint32_t vltime;
};
__CTASSERT(sizeof(struct dhcp6_ia_addr) == 16 + 8);

/* XXX FIXME: This is the only packed structure and it does not align.
 * Maybe manually decode it? */
struct dhcp6_pd_addr {
	uint32_t pltime;
	uint32_t vltime;
	uint8_t prefix_len;
	struct in6_addr prefix;
} __packed;
__CTASSERT(sizeof(struct dhcp6_pd_addr) == 8 + 1 + 16);

struct dhcp6_op {
	uint16_t type;
	const char *name;
};

static const struct dhcp6_op dhcp6_ops[] = {
	{ DHCP6_SOLICIT, "SOLICIT6" },
	{ DHCP6_ADVERTISE, "ADVERTISE6" },
	{ DHCP6_REQUEST, "REQUEST6" },
	{ DHCP6_REPLY, "REPLY6" },
	{ DHCP6_RENEW, "RENEW6" },
	{ DHCP6_REBIND, "REBIND6" },
	{ DHCP6_CONFIRM, "CONFIRM6" },
	{ DHCP6_INFORMATION_REQ, "INFORM6" },
	{ DHCP6_RELEASE, "RELEASE6" },
	{ DHCP6_RECONFIGURE, "RECONFIGURE6" },
	{ DHCP6_DECLINE, "DECLINE6" },
	{ 0, NULL }
};

struct dhcp_compat {
	uint8_t dhcp_opt;
	uint16_t dhcp6_opt;
};

const struct dhcp_compat dhcp_compats[] = {
	{ DHO_DNSSERVER,	D6_OPTION_DNS_SERVERS },
	{ DHO_HOSTNAME,		D6_OPTION_FQDN },
	{ DHO_DNSDOMAIN,	D6_OPTION_FQDN },
	{ DHO_NISSERVER,	D6_OPTION_NIS_SERVERS },
	{ DHO_NTPSERVER,	D6_OPTION_SNTP_SERVERS },
	{ DHO_RAPIDCOMMIT,	D6_OPTION_RAPID_COMMIT },
	{ DHO_FQDN,		D6_OPTION_FQDN },
	{ DHO_VIVCO,		D6_OPTION_VENDOR_CLASS },
	{ DHO_VIVSO,		D6_OPTION_VENDOR_OPTS },
	{ DHO_DNSSEARCH,	D6_OPTION_DOMAIN_LIST },
	{ 0, 0 }
};

static const char * const dhcp6_statuses[] = {
	"Success",
	"Unspecified Failure",
	"No Addresses Available",
	"No Binding",
	"Not On Link",
	"Use Multicast",
	"No Prefix Available"
};

static void dhcp6_bind(struct interface *, const char *, const char *);
static void dhcp6_failinform(void *);
static void dhcp6_recvaddr(void *);
static void dhcp6_startdecline(struct interface *);

#ifdef SMALL
#define dhcp6_hasprefixdelegation(a)	(0)
#else
static int dhcp6_hasprefixdelegation(struct interface *);
#endif

#define DECLINE_IA(ia) \
	((ia)->addr_flags & IN6_IFF_DUPLICATED && \
	(ia)->ia_type != 0 && (ia)->ia_type != D6_OPTION_IA_PD && \
	!((ia)->flags & IPV6_AF_STALE) && \
	(ia)->prefix_vltime != 0)

void
dhcp6_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->dhcp6_opts;
	    i < ctx->dhcp6_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("%05d %s", opt->option, opt->var);
			dhcp_print_option_encoding(opt, cols);
		}
	}
	for (i = 0, opt = opts; i < opts_len; i++, opt++) {
		cols = printf("%05d %s", opt->option, opt->var);
		dhcp_print_option_encoding(opt, cols);
	}
}

static size_t
dhcp6_makeuser(void *data, const struct interface *ifp)
{
	const struct if_options *ifo = ifp->options;
	struct dhcp6_option o;
	uint8_t *p;
	const uint8_t *up, *ue;
	uint16_t ulen, unlen;
	size_t olen;

	/* Convert the DHCPv4 user class option to DHCPv6 */
	up = ifo->userclass;
	ulen = *up++;
	if (ulen == 0)
		return 0;

	p = data;
	olen = 0;
	if (p != NULL)
		p += sizeof(o);

	ue = up + ulen;
	for (; up < ue; up += ulen) {
		ulen = *up++;
		olen += sizeof(ulen) + ulen;
		if (data == NULL)
			continue;
		unlen = htons(ulen);
		memcpy(p, &unlen, sizeof(unlen));
		p += sizeof(unlen);
		memcpy(p, up, ulen);
		p += ulen;
	}
	if (data != NULL) {
		o.code = htons(D6_OPTION_USER_CLASS);
		o.len = htons((uint16_t)olen);
		memcpy(data, &o, sizeof(o));
	}

	return sizeof(o) + olen;
}

static size_t
dhcp6_makevendor(void *data, const struct interface *ifp)
{
	const struct if_options *ifo;
	size_t len, vlen, i;
	uint8_t *p;
	const struct vivco *vivco;
	char vendor[VENDORCLASSID_MAX_LEN];
	struct dhcp6_option o;

	ifo = ifp->options;
	len = sizeof(uint32_t); /* IANA PEN */
	if (ifo->vivco_en) {
		for (i = 0, vivco = ifo->vivco;
		    i < ifo->vivco_len;
		    i++, vivco++)
			len += sizeof(uint16_t) + vivco->len;
		vlen = 0; /* silence bogus gcc warning */
	} else {
		vlen = strlcpy(vendor, ifp->ctx->vendor, sizeof(vendor));
		len += sizeof(uint16_t) + vlen;
	}

	if (len > UINT16_MAX) {
		logerrx("%s: DHCPv6 Vendor Class too big", ifp->name);
		return 0;
	}

	if (data != NULL) {
		uint32_t pen;
		uint16_t hvlen;

		p = data;
		o.code = htons(D6_OPTION_VENDOR_CLASS);
		o.len = htons((uint16_t)len);
		memcpy(p, &o, sizeof(o));
		p += sizeof(o);
		pen = htonl(ifo->vivco_en ? ifo->vivco_en : DHCPCD_IANA_PEN);
		memcpy(p, &pen, sizeof(pen));
		p += sizeof(pen);

		if (ifo->vivco_en) {
			for (i = 0, vivco = ifo->vivco;
			    i < ifo->vivco_len;
			    i++, vivco++)
			{
				hvlen = htons((uint16_t)vivco->len);
				memcpy(p, &hvlen, sizeof(hvlen));
				p += sizeof(hvlen);
				memcpy(p, vivco->data, vivco->len);
				p += vivco->len;
			}
		} else if (vlen) {
			hvlen = htons((uint16_t)vlen);
			memcpy(p, &hvlen, sizeof(hvlen));
			p += sizeof(hvlen);
			memcpy(p, vendor, (size_t)vlen);
		}
	}

	return sizeof(o) + len;
}

static void *
dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len)
{
	uint8_t *d;
	struct dhcp6_option o;

	code = htons(code);
	for (d = data; data_len != 0; d += o.len, data_len -= o.len) {
		if (data_len < sizeof(o)) {
			errno = EINVAL;
			return NULL;
		}
		memcpy(&o, d, sizeof(o));
		d += sizeof(o);
		data_len -= sizeof(o);
		o.len = htons(o.len);
		if (data_len < o.len) {
			errno = EINVAL;
			return NULL;
		}
		if (o.code == code) {
			if (len != NULL)
				*len = o.len;
			return d;
		}
	}

	errno = ENOENT;
	return NULL;
}

static void *
dhcp6_findmoption(void *data, size_t data_len, uint16_t code,
    uint16_t *len)
{
	uint8_t *d;

	if (data_len < sizeof(struct dhcp6_message)) {
		errno = EINVAL;
		return false;
	}
	d = data;
	d += sizeof(struct dhcp6_message);
	data_len -= sizeof(struct dhcp6_message);
	return dhcp6_findoption(d, data_len, code, len);
}

static const uint8_t *
dhcp6_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 dhcp6_option o;
	size_t i;
	struct dhcp_opt *opt;

	if (od != NULL) {
		*os = sizeof(o);
		if (ol < *os) {
			errno = EINVAL;
			return NULL;
		}
		memcpy(&o, od, sizeof(o));
		*len = ntohs(o.len);
		if (*len > ol - *os) {
			errno = ERANGE;
			return NULL;
		}
		*code = ntohs(o.code);
	}

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

	if (od != NULL)
		return od + sizeof(o);
	return NULL;
}

static bool
dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, size_t len)
{
	uint8_t *opt;
	uint16_t opt_len;
	struct dhcp6_state *state;
	struct timespec tv;
	unsigned long long hsec;
	uint16_t sec;

	opt = dhcp6_findmoption(m, len, D6_OPTION_ELAPSED, &opt_len);
	if (opt == NULL)
		return false;
	if (opt_len != sizeof(sec)) {
		errno = EINVAL;
		return false;
	}

	state = D6_STATE(ifp);
	clock_gettime(CLOCK_MONOTONIC, &tv);
	if (state->RTC == 0) {
		/* An RTC of zero means we're the first message
		 * out of the door, so the elapsed time is zero. */
		state->started = tv;
		hsec = 0;
	} else {
		unsigned long long secs;
		unsigned int nsecs;

		secs = eloop_timespec_diff(&tv, &state->started, &nsecs);
		/* Elapsed time is measured in centiseconds.
		 * We need to be sure it will not potentially overflow. */
		if (secs >= (UINT16_MAX / CSEC_PER_SEC) + 1)
			hsec = UINT16_MAX;
		else {
			hsec = (secs * CSEC_PER_SEC) +
			    (nsecs / NSEC_PER_CSEC);
			if (hsec > UINT16_MAX)
				hsec = UINT16_MAX;
		}
	}
	sec = htons((uint16_t)hsec);
	memcpy(opt, &sec, sizeof(sec));
	return true;
}

static void
dhcp6_newxid(const struct interface *ifp, struct dhcp6_message *m)
{
	const struct interface *ifp1;
	const struct dhcp6_state *state1;
	uint32_t xid;

	if (ifp->options->options & DHCPCD_XID_HWADDR &&
	    ifp->hwlen >= sizeof(xid))
		/* The lower bits are probably more unique on the network */
		memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid),
		    sizeof(xid));
	else {
again:
		xid = arc4random();
	}

	m->xid[0] = (xid >> 16) & 0xff;
	m->xid[1] = (xid >> 8) & 0xff;
	m->xid[2] = xid & 0xff;

	/* Ensure it's unique */
	TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) {
		if (ifp == ifp1)
			continue;
		if ((state1 = D6_CSTATE(ifp1)) == NULL)
			continue;
		if (state1->send != NULL &&
		    state1->send->xid[0] == m->xid[0] &&
		    state1->send->xid[1] == m->xid[1] &&
		    state1->send->xid[2] == m->xid[2])
			break;
	}

	if (ifp1 != NULL) {
		if (ifp->options->options & DHCPCD_XID_HWADDR &&
		    ifp->hwlen >= sizeof(xid))
		{
			logerrx("%s: duplicate xid on %s",
			    ifp->name, ifp1->name);
			    return;
		}
		goto again;
	}
}

#ifndef SMALL
static const struct if_sla *
dhcp6_findselfsla(struct interface *ifp)
{
	size_t i, j;
	struct if_ia *ia;

	for (i = 0; i < ifp->options->ia_len; i++) {
		ia = &ifp->options->ia[i];
		if (ia->ia_type != D6_OPTION_IA_PD)
			continue;
		for (j = 0; j < ia->sla_len; j++) {
			if (strcmp(ia->sla[j].ifname, ifp->name) == 0)
				return &ia->sla[j];
		}
	}
	return NULL;
}

static int
dhcp6_delegateaddr(struct in6_addr *addr, struct interface *ifp,
    const struct ipv6_addr *prefix, const struct if_sla *sla, struct if_ia *ia)
{
	struct dhcp6_state *state;
	struct if_sla asla;
	char sabuf[INET6_ADDRSTRLEN];
	const char *sa;

	state = D6_STATE(ifp);
	if (state == NULL) {
		ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state));
		state = D6_STATE(ifp);
		if (state == NULL) {
			logerr(__func__);
			return -1;
		}

		TAILQ_INIT(&state->addrs);
		state->state = DH6S_DELEGATED;
		state->reason = "DELEGATED6";
	}

	if (sla == NULL || sla->sla_set == 0) {
		/* No SLA set, so make an assumption of
		 * desired SLA and prefix length. */
		asla.sla = ifp->index;
		asla.prefix_len = 0;
		asla.sla_set = 0;
		sla = &asla;
	} else if (sla->prefix_len == 0) {
		/* An SLA was given, but prefix length was not.
		 * We need to work out a suitable prefix length for
		 * potentially more than one interface. */
		asla.sla = sla->sla;
		asla.prefix_len = 0;
		asla.sla_set = 0;
		sla = &asla;
	}

	if (sla->prefix_len == 0) {
		uint32_t sla_max;
		int bits;

		if (ia->sla_max == 0) {
			const struct interface *ifi;

			sla_max = 0;
			TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) {
				if (ifi->index > sla_max)
					sla_max = ifi->index;
			}
		} else
			sla_max = ia->sla_max;

		bits = fls32(sla_max);

		if (prefix->prefix_len + bits > (int)UINT8_MAX)
			asla.prefix_len = UINT8_MAX;
		else {
			asla.prefix_len = (uint8_t)(prefix->prefix_len + bits);

			/* Make a 64 prefix by default, as this makes SLAAC
			 * possible.
			 * Otherwise round up to the nearest 4 bits. */
			if (asla.prefix_len <= 64)
				asla.prefix_len = 64;
			else
				asla.prefix_len =
				    (uint8_t)ROUNDUP4(asla.prefix_len);
		}

#define BIT(n) (1UL << (n))
#define BIT_MASK(len) (BIT(len) - 1)
		if (ia->sla_max == 0) {
			/* Work out the real sla_max from our bits used */
			bits = asla.prefix_len - prefix->prefix_len;
			/* Make static analysis happy.
			 * Bits cannot be bigger than 32 thanks to fls32. */
			assert(bits <= 32);
			ia->sla_max = (uint32_t)BIT_MASK(bits);
		}
	}

	if (ipv6_userprefix(&prefix->prefix, prefix->prefix_len,
		sla->sla, addr, sla->prefix_len) == -1)
	{
		sa = inet_ntop(AF_INET6, &prefix->prefix,
		    sabuf, sizeof(sabuf));
		logerr("%s: invalid prefix %s/%d + %d/%d",
		    ifp->name, sa, prefix->prefix_len,
		    sla->sla, sla->prefix_len);
		return -1;
	}

	if (prefix->prefix_exclude_len &&
	    IN6_ARE_ADDR_EQUAL(addr, &prefix->prefix_exclude))
	{
		sa = inet_ntop(AF_INET6, &prefix->prefix_exclude,
		    sabuf, sizeof(sabuf));
		logerrx("%s: cannot delegate excluded prefix %s/%d",
		    ifp->name, sa, prefix->prefix_exclude_len);
		return -1;
	}

	return sla->prefix_len;
}
#endif

static int
dhcp6_makemessage(struct interface *ifp)
{
	struct dhcp6_state *state;
	struct dhcp6_message *m;
	struct dhcp6_option o;
	uint8_t *p, *si, *unicast, IA;
	size_t n, l, len, ml, hl;
	uint8_t type;
	uint16_t si_len, uni_len, n_options;
	uint8_t *o_lenp;
	struct if_options *ifo;
	const struct dhcp_opt *opt, *opt2;
	const struct ipv6_addr *ap;
	char hbuf[HOSTNAME_MAX_LEN + 1];
	const char *hostname;
	int fqdn;
	struct dhcp6_ia_na ia_na;
	uint16_t ia_na_len;
	struct if_ia *ifia;
#ifdef AUTH
	uint16_t auth_len;
#endif
	uint8_t duid[DUID_LEN];
	size_t duid_len = 0;

	state = D6_STATE(ifp);
	if (state->send) {
		free(state->send);
		state->send = NULL;
	}

	ifo = ifp->options;
	fqdn = ifo->fqdn;

	if (fqdn == FQDN_DISABLE && ifo->options & DHCPCD_HOSTNAME) {
		/* We're sending the DHCPv4 hostname option, so send FQDN as
		 * DHCPv6 has no FQDN option and DHCPv4 must not send
		 * hostname and FQDN according to RFC4702 */
		fqdn = FQDN_BOTH;
	}
	if (fqdn != FQDN_DISABLE)
		hostname = dhcp_get_hostname(hbuf, sizeof(hbuf), ifo);
	else
		hostname = NULL; /* appearse gcc */

	/* Work out option size first */
	n_options = 0;
	len = 0;
	si = NULL;
	hl = 0; /* Appease gcc */
	if (state->state != DH6S_RELEASE && state->state != DH6S_DECLINE) {
		for (l = 0, opt = ifp->ctx->dhcp6_opts;
		    l < ifp->ctx->dhcp6_opts_len;
		    l++, opt++)
		{
			for (n = 0, opt2 = ifo->dhcp6_override;
			    n < ifo->dhcp6_override_len;
			    n++, opt2++)
			{
				if (opt->option == opt2->option)
					break;
			}
			if (n < ifo->dhcp6_override_len)
				continue;
			if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
				continue;
			n_options++;
			len += sizeof(o.len);
		}
#ifndef SMALL
		for (l = 0, opt = ifo->dhcp6_override;
		    l < ifo->dhcp6_override_len;
		    l++, opt++)
		{
			if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
				continue;
			n_options++;
			len += sizeof(o.len);
		}
		if (dhcp6_findselfsla(ifp)) {
			n_options++;
			len += sizeof(o.len);
		}
#endif
		if (len)
			len += sizeof(o);

		if (fqdn != FQDN_DISABLE) {
			hl = encode_rfc1035(hostname, NULL);
			len += sizeof(o) + 1 + hl;
		}

		if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) &&
		    ifo->mudurl[0])
			len += sizeof(o) + ifo->mudurl[0];

#ifdef AUTH
		if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
		    DHCPCD_AUTH_SENDREQUIRE &&
		    DHC_REQ(ifo->requestmask6, ifo->nomask6,
		    D6_OPTION_RECONF_ACCEPT))
			len += sizeof(o); /* Reconfigure Accept */
#endif
	}

	len += sizeof(*state->send);
	len += sizeof(o) + sizeof(uint16_t); /* elapsed */

	if (ifo->options & DHCPCD_ANONYMOUS) {
		duid_len = duid_make(duid, ifp, DUID_LL);
		len += sizeof(o) + duid_len;
	} else {
		len += sizeof(o) + ifp->ctx->duid_len;
	}

	if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS))
		len += dhcp6_makeuser(NULL, ifp);
	if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
		len += dhcp6_makevendor(NULL, ifp);

	/* IA */
	m = NULL;
	ml = 0;
	switch(state->state) {
	case DH6S_REQUEST:
		m = state->recv;
		ml = state->recv_len;
		/* FALLTHROUGH */
	case DH6S_DECLINE:
		/* FALLTHROUGH */
	case DH6S_RELEASE:
		/* FALLTHROUGH */
	case DH6S_RENEW:
		if (m == NULL) {
			m = state->new;
			ml = state->new_len;
		}
		si = dhcp6_findmoption(m, ml, D6_OPTION_SERVERID, &si_len);
		if (si == NULL)
			return -1;
		len += sizeof(o) + si_len;
		/* FALLTHROUGH */
	case DH6S_REBIND:
		/* FALLTHROUGH */
	case DH6S_CONFIRM:
		/* FALLTHROUGH */
	case DH6S_DISCOVER:
		if (m == NULL) {
			m = state->new;
			ml = state->new_len;
		}
		TAILQ_FOREACH(ap, &state->addrs, next) {
			if (ap->flags & IPV6_AF_STALE)
				continue;
			if (!(ap->flags & IPV6_AF_REQUEST) &&
			    (ap->prefix_vltime == 0 ||
			    state->state == DH6S_DISCOVER))
				continue;
			if (DECLINE_IA(ap) && state->state != DH6S_DECLINE)
				continue;
			if (ap->ia_type == D6_OPTION_IA_PD) {
#ifndef SMALL
				len += sizeof(o) + sizeof(struct dhcp6_pd_addr);
				if (ap->prefix_exclude_len)
					len += sizeof(o) + 1 +
					    (uint8_t)((ap->prefix_exclude_len -
					    ap->prefix_len - 1) / NBBY) + 1;
#endif
			} else
				len += sizeof(o) + sizeof(struct dhcp6_ia_addr);
		}
		/* FALLTHROUGH */
	case DH6S_INIT:
		for (l = 0; l < ifo->ia_len; l++) {
			len += sizeof(o) + sizeof(uint32_t); /* IAID */
			/* IA_TA does not have T1 or T2 timers */
			if (ifo->ia[l].ia_type != D6_OPTION_IA_TA)
				len += sizeof(uint32_t) + sizeof(uint32_t);
		}
		IA = 1;
		break;
	default:
		IA = 0;
	}

	if (state->state == DH6S_DISCOVER &&
	    !(ifp->ctx->options & DHCPCD_TEST) &&
	    DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT))
		len += sizeof(o);

	if (m == NULL) {
		m = state->new;
		ml = state->new_len;
	}

	switch(state->state) {
	case DH6S_INIT: /* FALLTHROUGH */
	case DH6S_DISCOVER:
		type = DHCP6_SOLICIT;
		break;
	case DH6S_REQUEST:
		type = DHCP6_REQUEST;
		break;
	case DH6S_CONFIRM:
		type = DHCP6_CONFIRM;
		break;
	case DH6S_REBIND:
		type = DHCP6_REBIND;
		break;
	case DH6S_RENEW:
		type = DHCP6_RENEW;
		break;
	case DH6S_INFORM:
		type = DHCP6_INFORMATION_REQ;
		break;
	case DH6S_RELEASE:
		type = DHCP6_RELEASE;
		break;
	case DH6S_DECLINE:
		type = DHCP6_DECLINE;
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	switch(state->state) {
	case DH6S_REQUEST: /* FALLTHROUGH */
	case DH6S_RENEW:   /* FALLTHROUGH */
	case DH6S_RELEASE:
		if (has_option_mask(ifo->nomask6, D6_OPTION_UNICAST)) {
			unicast = NULL;
			break;
		}
		unicast = dhcp6_findmoption(m, ml, D6_OPTION_UNICAST, &uni_len);
		break;
	default:
		unicast = NULL;
		break;
	}

	/* In non master mode we listen and send from fixed addresses.
	 * We should try and match an address we have to unicast to,
	 * but for now this is the safest policy. */
	if (unicast != NULL && !(ifp->ctx->options & DHCPCD_MASTER)) {
		logdebugx("%s: ignoring unicast option as not master",
		    ifp->name);
		unicast = NULL;
	}

#ifdef AUTH
	auth_len = 0;
	if (ifo->auth.options & DHCPCD_AUTH_SEND) {
		ssize_t alen = dhcp_auth_encode(&ifo->auth,
		    state->auth.token, NULL, 0, 6, type, NULL, 0);
		if (alen != -1 && alen > UINT16_MAX) {
			errno = ERANGE;
			alen = -1;
		}
		if (alen == -1)
			logerr("%s: %s: dhcp_auth_encode", __func__, ifp->name);
		else if (alen != 0) {
			auth_len = (uint16_t)alen;
			len += sizeof(o) + auth_len;
		}
	}
#endif

	state->send = malloc(len);
	if (state->send == NULL)
		return -1;

	state->send_len = len;
	state->send->type = type;

	/* If we found a unicast option, copy it to our state for sending */
	if (unicast && uni_len == sizeof(state->unicast))
		memcpy(&state->unicast, unicast, sizeof(state->unicast));
	else
		state->unicast = in6addr_any;

	dhcp6_newxid(ifp, state->send);

#define COPYIN1(_code, _len)		{	\
	o.code = htons((_code));		\
	o.len = htons((_len));			\
	memcpy(p, &o, sizeof(o));		\
	p += sizeof(o);				\
}
#define COPYIN(_code, _data, _len)	do {	\
	COPYIN1((_code), (_len));		\
	if ((_len) != 0) {			\
		memcpy(p, (_data), (_len));	\
		p += (_len);			\
	}					\
} while (0 /* CONSTCOND */)
#define NEXTLEN (p + offsetof(struct dhcp6_option, len))

	/* Options are listed in numerical order as per RFC 7844 Section 4.1
	 * XXX: They should be randomised. */

	p = (uint8_t *)state->send + sizeof(*state->send);
	if (ifo->options & DHCPCD_ANONYMOUS)
		COPYIN(D6_OPTION_CLIENTID, duid,
		    (uint16_t)duid_len);
	else
		COPYIN(D6_OPTION_CLIENTID, ifp->ctx->duid,
		    (uint16_t)ifp->ctx->duid_len);

	if (si != NULL)
		COPYIN(D6_OPTION_SERVERID, si, si_len);

	for (l = 0; IA && l < ifo->ia_len; l++) {
		ifia = &ifo->ia[l];
		o_lenp = NEXTLEN;
		/* TA structure is the same as the others,
		 * it just lacks the T1 and T2 timers.
		 * These happen to be at the end of the struct,
		 * so we just don't copy them in. */
		if (ifia->ia_type == D6_OPTION_IA_TA)
			ia_na_len = sizeof(struct dhcp6_ia_ta);
		else
			ia_na_len = sizeof(ia_na);
		memcpy(ia_na.iaid, ifia->iaid, sizeof(ia_na.iaid));
		ia_na.t1 = 0;
		ia_na.t2 = 0;
		COPYIN(ifia->ia_type, &ia_na, ia_na_len);
		TAILQ_FOREACH(ap, &state->addrs, next) {
			if (ap->flags & IPV6_AF_STALE)
				continue;
			if (!(ap->flags & IPV6_AF_REQUEST) &&
			    (ap->prefix_vltime == 0 ||
			    state->state == DH6S_DISCOVER))
				continue;
			if (DECLINE_IA(ap) && state->state != DH6S_DECLINE)
				continue;
			if (ap->ia_type != ifia->ia_type)
				continue;
			if (memcmp(ap->iaid, ifia->iaid, sizeof(ap->iaid)))
				continue;
			if (ap->ia_type == D6_OPTION_IA_PD) {
#ifndef SMALL
				struct dhcp6_pd_addr pdp;

				pdp.pltime = htonl(ap->prefix_pltime);
				pdp.vltime = htonl(ap->prefix_vltime);
				pdp.prefix_len = ap->prefix_len;
				/* pdp.prefix is not aligned, so copy it in. */
				memcpy(&pdp.prefix, &ap->prefix, sizeof(pdp.prefix));
				COPYIN(D6_OPTION_IAPREFIX, &pdp, sizeof(pdp));
				ia_na_len = (uint16_t)
				    (ia_na_len + sizeof(o) + sizeof(pdp));

				/* RFC6603 Section 4.2 */
				if (ap->prefix_exclude_len) {
					uint8_t exb[16], *ep, u8;
					const uint8_t *pp;

					n = (size_t)((ap->prefix_exclude_len -
					    ap->prefix_len - 1) / NBBY) + 1;
					ep = exb;
					*ep++ = (uint8_t)ap->prefix_exclude_len;
					pp = ap->prefix_exclude.s6_addr;
					pp += (size_t)
					    ((ap->prefix_len - 1) / NBBY) +
					    (n - 1);
					u8 = ap->prefix_len % NBBY;
					if (u8)
						n--;
					while (n-- > 0)
						*ep++ = *pp--;
					if (u8)
						*ep = (uint8_t)(*pp << u8);
					n++;
					COPYIN(D6_OPTION_PD_EXCLUDE, exb,
					    (uint16_t)n);
					ia_na_len = (uint16_t)
					    (ia_na_len + sizeof(o) + n);
				}
#endif
			} else {
				struct dhcp6_ia_addr ia;

				ia.addr = ap->addr;
				ia.pltime = htonl(ap->prefix_pltime);
				ia.vltime = htonl(ap->prefix_vltime);
				COPYIN(D6_OPTION_IA_ADDR, &ia, sizeof(ia));
				ia_na_len = (uint16_t)
				    (ia_na_len + sizeof(o) + sizeof(ia));
			}
		}

		/* Update the total option lenth. */
		ia_na_len = htons(ia_na_len);
		memcpy(o_lenp, &ia_na_len, sizeof(ia_na_len));
	}

	if (state->send->type != DHCP6_RELEASE &&
	    state->send->type != DHCP6_DECLINE &&
	    n_options)
	{
		o_lenp = NEXTLEN;
		o.len = 0;
		COPYIN1(D6_OPTION_ORO, 0);
		for (l = 0, opt = ifp->ctx->dhcp6_opts;
		    l < ifp->ctx->dhcp6_opts_len;
		    l++, opt++)
		{
#ifndef SMALL
			for (n = 0, opt2 = ifo->dhcp6_override;
			    n < ifo->dhcp6_override_len;
			    n++, opt2++)
			{
				if (opt->option == opt2->option)
					break;
			}
			if (n < ifo->dhcp6_override_len)
			    continue;
#endif
			if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
				continue;
			o.code = htons((uint16_t)opt->option);
			memcpy(p, &o.code, sizeof(o.code));
			p += sizeof(o.code);
			o.len = (uint16_t)(o.len + sizeof(o.code));
		}
#ifndef SMALL
		for (l = 0, opt = ifo->dhcp6_override;
		    l < ifo->dhcp6_override_len;
		    l++, opt++)
		{
			if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6))
				continue;
			o.code = htons((uint16_t)opt->option);
			memcpy(p, &o.code, sizeof(o.code));
			p += sizeof(o.code);
			o.len = (uint16_t)(o.len + sizeof(o.code));
		}
		if (dhcp6_findselfsla(ifp)) {
			o.code = htons(D6_OPTION_PD_EXCLUDE);
			memcpy(p, &o.code, sizeof(o.code));
			p += sizeof(o.code);
			o.len = (uint16_t)(o.len + sizeof(o.code));
		}
#endif
		o.len = htons(o.len);
		memcpy(o_lenp, &o.len, sizeof(o.len));
	}

	si_len = 0;
	COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len));

	if (state->state == DH6S_DISCOVER &&
	    !(ifp->ctx->options & DHCPCD_TEST) &&
	    DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT))
		COPYIN1(D6_OPTION_RAPID_COMMIT, 0);

	if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS))
		p += dhcp6_makeuser(p, ifp);
	if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
		p += dhcp6_makevendor(p, ifp);

	if (state->send->type != DHCP6_RELEASE &&
	    state->send->type != DHCP6_DECLINE)
	{
		if (fqdn != FQDN_DISABLE) {
			o_lenp = NEXTLEN;
			COPYIN1(D6_OPTION_FQDN, 0);
			if (hl == 0)
				*p = D6_FQDN_NONE;
			else {
				switch (fqdn) {
				case FQDN_BOTH:
					*p = D6_FQDN_BOTH;
					break;
				case FQDN_PTR:
					*p = D6_FQDN_PTR;
					break;
				default:
					*p = D6_FQDN_NONE;
					break;
				}
			}
			p++;
			encode_rfc1035(hostname, p);
			p += hl;
			o.len = htons((uint16_t)(hl + 1));
			memcpy(o_lenp, &o.len, sizeof(o.len));
		}

		if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) &&
		    ifo->mudurl[0])
			COPYIN(D6_OPTION_MUDURL,
			    ifo->mudurl + 1, ifo->mudurl[0]);

#ifdef AUTH
		if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
		    DHCPCD_AUTH_SENDREQUIRE &&
		    DHC_REQ(ifo->requestmask6, ifo->nomask6,
		    D6_OPTION_RECONF_ACCEPT))
			COPYIN1(D6_OPTION_RECONF_ACCEPT, 0);
#endif

	}

#ifdef AUTH
	/* This has to be the last option */
	if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) {
		COPYIN1(D6_OPTION_AUTH, auth_len);
		/* data will be filled at send message time */
	}
#endif

	return 0;
}

static const char *
dhcp6_get_op(uint16_t type)
{
	const struct dhcp6_op *d;

	for (d = dhcp6_ops; d->name; d++)
		if (d->type == type)
			return d->name;
	return NULL;
}

static void
dhcp6_freedrop_addrs(struct interface *ifp, int drop,
    const struct interface *ifd)
{
	struct dhcp6_state *state;

	state = D6_STATE(ifp);
	if (state) {
		ipv6_freedrop_addrs(&state->addrs, drop, ifd);
		if (drop)
			rt_build(ifp->ctx, AF_INET6);
	}
}

#ifndef SMALL
static void dhcp6_delete_delegates(struct interface *ifp)
{
	struct interface *ifp0;

	if (ifp->ctx->ifaces) {
		TAILQ_FOREACH(ifp0, ifp->ctx->ifaces, next) {
			if (ifp0 != ifp)
				dhcp6_freedrop_addrs(ifp0, 1, ifp);
		}
	}
}
#endif

#ifdef AUTH
static ssize_t
dhcp6_update_auth(struct interface *ifp, struct dhcp6_message *m, size_t len)
{
	struct dhcp6_state *state;
	uint8_t *opt;
	uint16_t opt_len;

	opt = dhcp6_findmoption(m, len, D6_OPTION_AUTH, &opt_len);
	if (opt == NULL)
		return -1;

	state = D6_STATE(ifp);
	return dhcp_auth_encode(&ifp->options->auth, state->auth.token,
	    (uint8_t *)state->send, state->send_len,
	    6, state->send->type, opt, opt_len);
}
#endif

static const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT;
static int
dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *))
{
	struct dhcp6_state *state = D6_STATE(ifp);
	struct dhcpcd_ctx *ctx = ifp->ctx;
	unsigned int RT;
	bool broadcast = true;
	struct sockaddr_in6 dst = {
	    .sin6_family = AF_INET6,
	    .sin6_port = htons(DHCP6_SERVER_PORT),
	};
	struct udphdr udp = {
	    .uh_sport = htons(DHCP6_CLIENT_PORT),
	    .uh_dport = htons(DHCP6_SERVER_PORT),
	    .uh_ulen = htons(sizeof(udp) + state->send_len),
	};
	struct iovec iov[] = {
	    { .iov_base = &udp, .iov_len = sizeof(udp), },
	    { .iov_base = state->send, .iov_len = state->send_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 = __arraycount(iov),
	};
	char uaddr[INET6_ADDRSTRLEN];

	if (!callback && ifp->carrier <= LINK_DOWN)
		return 0;

	if (!IN6_IS_ADDR_UNSPECIFIED(&state->unicast)) {
		switch (state->send->type) {
		case DHCP6_SOLICIT:	/* FALLTHROUGH */
		case DHCP6_CONFIRM:	/* FALLTHROUGH */
		case DHCP6_REBIND:
			/* Unicasting is denied for these types. */
			break;
		default:
			broadcast = false;
			inet_ntop(AF_INET6, &state->unicast, uaddr,
			    sizeof(uaddr));
			break;
		}
	}
	dst.sin6_addr = broadcast ? alldhcp : state->unicast;

	if (!callback) {
		logdebugx("%s: %s %s with xid 0x%02x%02x%02x%s%s",
		    ifp->name,
		    broadcast ? "broadcasting" : "unicasting",
		    dhcp6_get_op(state->send->type),
		    state->send->xid[0],
		    state->send->xid[1],
		    state->send->xid[2],
		    !broadcast ? " " : "",
		    !broadcast ? uaddr : "");
		RT = 0;
	} else {
		if (state->IMD &&
		    !(ifp->options->options & DHCPCD_INITIAL_DELAY))
			state->IMD = 0;
		if (state->IMD) {
			state->RT = state->IMD * MSEC_PER_SEC;
			/* Some buggy PPP servers close the link too early
			 * after sending an invalid status in their reply
			 * which means this host won't see it.
			 * 1 second grace seems to be the sweet spot. */
			if (ifp->flags & IFF_POINTOPOINT)
				state->RT += MSEC_PER_SEC;
		} else if (state->RTC == 0)
			state->RT = state->IRT * MSEC_PER_SEC;

		if (state->MRT != 0) {
			unsigned int mrt = state->MRT * MSEC_PER_SEC;

			if (state->RT > mrt)
				state->RT = mrt;
		}

		/* Add -.1 to .1 * RT randomness as per RFC8415 section 15 */
		uint32_t lru = arc4random_uniform(
		    state->RTC == 0 ? DHCP6_RAND_MAX
		    : DHCP6_RAND_MAX - DHCP6_RAND_MIN);
		int lr = (int)lru - (state->RTC == 0 ? 0 : DHCP6_RAND_MAX);
		RT = state->RT
		    + (unsigned int)((float)state->RT
		    * ((float)lr / DHCP6_RAND_DIV));

		if (ifp->carrier > LINK_DOWN)
			logdebugx("%s: %s %s (xid 0x%02x%02x%02x)%s%s,"
			    " next in %0.1f seconds",
			    ifp->name,
			    state->IMD != 0 ? "delaying" :
			    broadcast ? "broadcasting" : "unicasting",
			    dhcp6_get_op(state->send->type),
			    state->send->xid[0],
			    state->send->xid[1],
			    state->send->xid[2],
			    state->IMD == 0 && !broadcast ? " " : "",
			    state->IMD == 0 && !broadcast ? uaddr : "",
			    (float)RT / MSEC_PER_SEC);

		/* Wait the initial delay */
		if (state->IMD != 0) {
			state->IMD = 0;
			eloop_timeout_add_msec(ctx->eloop, RT, callback, ifp);
			return 0;
		}
	}

	if (ifp->carrier <= LINK_DOWN)
		return 0;

	/* Update the elapsed time */
	dhcp6_updateelapsed(ifp, state->send, state->send_len);
#ifdef AUTH
	if (ifp->options->auth.options & DHCPCD_AUTH_SEND &&
	    dhcp6_update_auth(ifp, state->send, state->send_len) == -1)
	{
		logerr("%s: %s: dhcp6_updateauth", __func__, ifp->name);
		if (errno != ESRCH)
			return -1;
	}
#endif

	/* Set the outbound interface */
	if (broadcast) {
		struct cmsghdr *cm;
		struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };

		dst.sin6_scope_id = ifp->index;
		msg.msg_control = cmsgbuf.buf;
		msg.msg_controllen = sizeof(cmsgbuf.buf);
		cm = CMSG_FIRSTHDR(&msg);
		if (cm == NULL) /* unlikely */
			return -1;
		cm->cmsg_level = IPPROTO_IPV6;
		cm->cmsg_type = IPV6_PKTINFO;
		cm->cmsg_len = CMSG_LEN(sizeof(pi));
		memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
	}

#ifdef PRIVSEP
	if (IN_PRIVSEP(ifp->ctx)) {
		if (ps_inet_senddhcp6(ifp, &msg) == -1)
			logerr(__func__);
		goto sent;
	}
#endif

	if (sendmsg(ctx->dhcp6_wfd, &msg, 0) == -1) {
		logerr("%s: %s: sendmsg", __func__, ifp->name);
		/* Allow DHCPv6 to continue .... the errors
		 * would be rate limited by the protocol.
		 * Generally the error is ENOBUFS when struggling to
		 * associate with an access point. */
	}

#ifdef PRIVSEP
sent:
#endif
	state->RTC++;
	if (callback) {
		state->RT = RT * 2;
		if (state->RT < RT) /* Check overflow */
			state->RT = RT;
		if (state->MRC == 0 || state->RTC < state->MRC)
			eloop_timeout_add_msec(ctx->eloop,
			    RT, callback, ifp);
		else if (state->MRC != 0 && state->MRCcallback)
			eloop_timeout_add_msec(ctx->eloop,
			    RT, state->MRCcallback, ifp);
		else
			logwarnx("%s: sent %d times with no reply",
			    ifp->name, state->RTC);
	}
	return 0;
}

static void
dhcp6_sendinform(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_sendinform);
}

static void
dhcp6_senddiscover(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_senddiscover);
}

static void
dhcp6_sendrequest(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_sendrequest);
}

static void
dhcp6_sendrebind(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_sendrebind);
}

static void
dhcp6_sendrenew(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_sendrenew);
}

static void
dhcp6_sendconfirm(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_sendconfirm);
}

static void
dhcp6_senddecline(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_senddecline);
}

static void
dhcp6_sendrelease(void *arg)
{

	dhcp6_sendmessage(arg, dhcp6_sendrelease);
}

static void
dhcp6_startrenew(void *arg)
{
	struct interface *ifp;
	struct dhcp6_state *state;

	ifp = arg;
	if ((state = D6_STATE(ifp)) == NULL)
		return;

	/* Only renew in the bound or renew states */
	if (state->state != DH6S_BOUND &&
	    state->state != DH6S_RENEW)
		return;

	/* Remove the timeout as the renew may have been forced. */
	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startrenew, ifp);

	state->state = DH6S_RENEW;
	state->RTC = 0;
	state->IMD = REN_MAX_DELAY;
	state->IRT = REN_TIMEOUT;
	state->MRT = REN_MAX_RT;
	state->MRC = 0;

	if (dhcp6_makemessage(ifp) == -1)
		logerr("%s: %s", __func__, ifp->name);
	else
		dhcp6_sendrenew(ifp);
}

void dhcp6_renew(struct interface *ifp)
{

	dhcp6_startrenew(ifp);
}

int
dhcp6_dadcompleted(const struct interface *ifp)
{
	const struct dhcp6_state *state;
	const struct ipv6_addr *ap;

	state = D6_CSTATE(ifp);
	TAILQ_FOREACH(ap, &state->addrs, next) {
		if (ap->flags & IPV6_AF_ADDED &&
		    !(ap->flags & IPV6_AF_DADCOMPLETED))
			return 0;
	}
	return 1;
}

static void
dhcp6_dadcallback(void *arg)
{
	struct ipv6_addr *ia = arg;
	struct interface *ifp;
	struct dhcp6_state *state;
	struct ipv6_addr *ia2;
	bool completed, valid, oneduplicated;

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

#ifdef ND6_ADVERTISE
	else
		ipv6nd_advertise(ia);
#endif
	if (completed)
		return;

	ifp = ia->iface;
	state = D6_STATE(ifp);
	if (state->state != DH6S_BOUND && state->state != DH6S_DELEGATED)
		return;

#ifdef SMALL
	valid = true;
#else
	valid = (ia->delegating_prefix == NULL);
#endif
	completed = true;
	oneduplicated = false;
	TAILQ_FOREACH(ia2, &state->addrs, next) {
		if (ia2->flags & IPV6_AF_ADDED &&
		    !(ia2->flags & IPV6_AF_DADCOMPLETED))
		{
			completed = false;
			break;
		}
		if (DECLINE_IA(ia))
			oneduplicated = true;
	}
	if (!completed)
		return;

	logdebugx("%s: DHCPv6 DAD completed", ifp->name);

	if (oneduplicated && state->state == DH6S_BOUND) {
		dhcp6_startdecline(ifp);
		return;
	}

	script_runreason(ifp,
#ifndef SMALL
	    ia->delegating_prefix ? "DELEGATED6" :
#endif
	    state->reason);
	if (valid)
		dhcpcd_daemonise(ifp->ctx);
}

static void
dhcp6_addrequestedaddrs(struct interface *ifp)
{
	struct dhcp6_state *state;
	size_t i;
	struct if_ia *ia;
	struct ipv6_addr *a;

	state = D6_STATE(ifp);
	/* Add any requested prefixes / addresses */
	for (i = 0; i < ifp->options->ia_len; i++) {
		ia = &ifp->options->ia[i];
		if (!((ia->ia_type == D6_OPTION_IA_PD && ia->prefix_len) ||
		    !IN6_IS_ADDR_UNSPECIFIED(&ia->addr)))
			continue;
		a = ipv6_newaddr(ifp, &ia->addr,
			/*
			 * RFC 5942 Section 5
			 * We cannot assume any prefix length, nor tie the
			 * address to an existing one as it could expire
			 * before the address.
			 * As such we just give it a 128 prefix.
			 */
		    ia->ia_type == D6_OPTION_IA_PD ? ia->prefix_len : 128,
		    IPV6_AF_REQUEST);
		if (a == NULL)
			continue;
		a->dadcallback = dhcp6_dadcallback;
		memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid));
		a->ia_type = ia->ia_type;
		TAILQ_INSERT_TAIL(&state->addrs, a, next);
	}
}

static void
dhcp6_startdiscover(void *arg)
{
	struct interface *ifp;
	struct dhcp6_state *state;
	int llevel;

	ifp = arg;
	state = D6_STATE(ifp);
#ifndef SMALL
	if (state->reason == NULL || strcmp(state->reason, "TIMEOUT6") != 0)
		dhcp6_delete_delegates(ifp);
#endif
	if (state->new == NULL && !state->failed)
		llevel = LOG_INFO;
	else
		llevel = LOG_DEBUG;
	logmessage(llevel, "%s: soliciting a DHCPv6 lease", ifp->name);
	state->state = DH6S_DISCOVER;
	state->RTC = 0;
	state->IMD = SOL_MAX_DELAY;
	state->IRT = SOL_TIMEOUT;
	state->MRT = state->sol_max_rt;
	state->MRC = SOL_MAX_RC;

	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	free(state->new);
	state->new = NULL;
	state->new_len = 0;

	if (dhcp6_makemessage(ifp) == -1)
		logerr("%s: %s", __func__, ifp->name);
	else
		dhcp6_senddiscover(ifp);
}

static void
dhcp6_startinform(void *arg)
{
	struct interface *ifp;
	struct dhcp6_state *state;
	int llevel;

	ifp = arg;
	state = D6_STATE(ifp);
	if (state->new == NULL && !state->failed)
		llevel = LOG_INFO;
	else
		llevel = LOG_DEBUG;
	logmessage(llevel, "%s: requesting DHCPv6 information", ifp->name);
	state->state = DH6S_INFORM;
	state->RTC = 0;
	state->IMD = INF_MAX_DELAY;
	state->IRT = INF_TIMEOUT;
	state->MRT = state->inf_max_rt;
	state->MRC = 0;

	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	if (dhcp6_makemessage(ifp) == -1) {
		logerr("%s: %s", __func__, ifp->name);
		return;
	}
	dhcp6_sendinform(ifp);
	/* RFC3315 18.1.2 says that if CONFIRM failed then the prior addresses
	 * SHOULD be used. The wording here is poor, because the addresses are
	 * merely one facet of the lease as a whole.
	 * This poor wording might explain the lack of similar text for INFORM
	 * in 18.1.5 because there are no addresses in the INFORM message. */
	eloop_timeout_add_sec(ifp->ctx->eloop,
	    INF_MAX_RD, dhcp6_failinform, ifp);
}

static bool
dhcp6_startdiscoinform(struct interface *ifp)
{
	unsigned long long opts = ifp->options->options;

	if (opts & DHCPCD_IA_FORCED || ipv6nd_hasradhcp(ifp, true))
		dhcp6_startdiscover(ifp);
	else if (opts & DHCPCD_INFORM6 || ipv6nd_hasradhcp(ifp, false))
		dhcp6_startinform(ifp);
	else
		return false;
	return true;
}

static void
dhcp6_leaseextend(struct interface *ifp)
{
	struct dhcp6_state *state = D6_STATE(ifp);
	struct ipv6_addr *ia;

	logwarnx("%s: extending DHCPv6 lease", ifp->name);
	TAILQ_FOREACH(ia, &state->addrs, next) {
		ia->flags |= IPV6_AF_EXTENDED;
		/* Set infinite lifetimes. */
		ia->prefix_pltime = ND6_INFINITE_LIFETIME;
		ia->prefix_vltime = ND6_INFINITE_LIFETIME;
	}
}

static void
dhcp6_fail(struct interface *ifp)
{
	struct dhcp6_state *state = D6_STATE(ifp);

	state->failed = true;

	/* RFC3315 18.1.2 says that prior addresses SHOULD be used on failure.
	 * RFC2131 3.2.3 says that MAY chose to use the prior address.
	 * Because dhcpcd was written first for RFC2131, we have the LASTLEASE
	 * option which defaults to off as that makes the most sense for
	 * mobile clients.
	 * dhcpcd also has LASTLEASE_EXTEND to extend this lease past it's
	 * expiry, but this is strictly not RFC compliant in any way or form. */
	if (state->new != NULL &&
	    ifp->options->options & DHCPCD_LASTLEASE_EXTEND)
	{
		dhcp6_leaseextend(ifp);
		dhcp6_bind(ifp, NULL, NULL);
	} else {
		dhcp6_freedrop_addrs(ifp, 1, NULL);
#ifndef SMALL
		dhcp6_delete_delegates(ifp);
#endif
		free(state->old);
		state->old = state->new;
		state->old_len = state->new_len;
		state->new = NULL;
		state->new_len = 0;
		if (state->old != NULL)
			script_runreason(ifp, "EXPIRE6");
		dhcp_unlink(ifp->ctx, state->leasefile);
	}

	if (!dhcp6_startdiscoinform(ifp)) {
		logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name);
		state->state = DH6S_INIT;
		eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	}
}

static int
dhcp6_failloglevel(struct interface *ifp)
{
	const struct dhcp6_state *state = D6_CSTATE(ifp);

	return state->failed ? LOG_DEBUG : LOG_ERR;
}

static void
dhcp6_failconfirm(void *arg)
{
	struct interface *ifp = arg;
	int llevel = dhcp6_failloglevel(ifp);

	logmessage(llevel, "%s: failed to confirm prior DHCPv6 address",
	    ifp->name);
	dhcp6_fail(ifp);
}

static void
dhcp6_failrequest(void *arg)
{
	struct interface *ifp = arg;
	int llevel = dhcp6_failloglevel(ifp);

	logmessage(llevel, "%s: failed to request DHCPv6 address", ifp->name);
	dhcp6_fail(ifp);
}

static void
dhcp6_failinform(void *arg)
{
	struct interface *ifp = arg;
	int llevel = dhcp6_failloglevel(ifp);

	logmessage(llevel, "%s: failed to request DHCPv6 information",
	    ifp->name);
	dhcp6_fail(ifp);
}

#ifndef SMALL
static void
dhcp6_failrebind(void *arg)
{
	struct interface *ifp = arg;

	logerrx("%s: failed to rebind prior DHCPv6 delegation", ifp->name);
	dhcp6_fail(ifp);
}

static int
dhcp6_hasprefixdelegation(struct interface *ifp)
{
	size_t i;
	uint16_t t;

	t = 0;
	for (i = 0; i < ifp->options->ia_len; i++) {
		if (t && t != ifp->options->ia[i].ia_type) {
			if (t == D6_OPTION_IA_PD ||
			    ifp->options->ia[i].ia_type == D6_OPTION_IA_PD)
				return 2;
		}
		t = ifp->options->ia[i].ia_type;
	}
	return t == D6_OPTION_IA_PD ? 1 : 0;
}
#endif

static void
dhcp6_startrebind(void *arg)
{
	struct interface *ifp;
	struct dhcp6_state *state;
#ifndef SMALL
	int pd;
#endif

	ifp = arg;
	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp);
	state = D6_STATE(ifp);
	if (state->state == DH6S_RENEW)
		logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name);
	else
		loginfox("%s: rebinding prior DHCPv6 lease", ifp->name);
	state->state = DH6S_REBIND;
	state->RTC = 0;
	state->MRC = 0;

#ifndef SMALL
	/* RFC 3633 section 12.1 */
	pd = dhcp6_hasprefixdelegation(ifp);
	if (pd) {
		state->IMD = CNF_MAX_DELAY;
		state->IRT = CNF_TIMEOUT;
		state->MRT = CNF_MAX_RT;
	} else
#endif
	{
		state->IMD = REB_MAX_DELAY;
		state->IRT = REB_TIMEOUT;
		state->MRT = REB_MAX_RT;
	}

	if (dhcp6_makemessage(ifp) == -1)
		logerr("%s: %s", __func__, ifp->name);
	else
		dhcp6_sendrebind(ifp);

#ifndef SMALL
	/* RFC 3633 section 12.1 */
	if (pd)
		eloop_timeout_add_sec(ifp->ctx->eloop,
		    CNF_MAX_RD, dhcp6_failrebind, ifp);
#endif
}


static void
dhcp6_startrequest(struct interface *ifp)
{
	struct dhcp6_state *state;

	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp);
	state = D6_STATE(ifp);
	state->state = DH6S_REQUEST;
	state->RTC = 0;
	state->IMD = 0;
	state->IRT = REQ_TIMEOUT;
	state->MRT = REQ_MAX_RT;
	state->MRC = REQ_MAX_RC;
	state->MRCcallback = dhcp6_failrequest;

	if (dhcp6_makemessage(ifp) == -1) {
		logerr("%s: %s", __func__, ifp->name);
		return;
	}

	dhcp6_sendrequest(ifp);
}

static void
dhcp6_startconfirm(struct interface *ifp)
{
	struct dhcp6_state *state;
	struct ipv6_addr *ia;

	state = D6_STATE(ifp);

	TAILQ_FOREACH(ia, &state->addrs, next) {
		if (!DECLINE_IA(ia))
			continue;
		logerrx("%s: prior DHCPv6 has a duplicated address", ifp->name);
		dhcp6_startdecline(ifp);
		return;
	}

	state->state = DH6S_CONFIRM;
	state->RTC = 0;
	state->IMD = CNF_MAX_DELAY;
	state->IRT = CNF_TIMEOUT;
	state->MRT = CNF_MAX_RT;
	state->MRC = CNF_MAX_RC;

	loginfox("%s: confirming prior DHCPv6 lease", ifp->name);

	if (dhcp6_makemessage(ifp) == -1) {
		logerr("%s: %s", __func__, ifp->name);
		return;
	}
	dhcp6_sendconfirm(ifp);
	eloop_timeout_add_sec(ifp->ctx->eloop,
	    CNF_MAX_RD, dhcp6_failconfirm, ifp);
}

static void
dhcp6_startexpire(void *arg)
{
	struct interface *ifp;

	ifp = arg;
	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp);

	logerrx("%s: DHCPv6 lease expired", ifp->name);
	dhcp6_fail(ifp);
}

static void
dhcp6_faildecline(void *arg)
{
	struct interface *ifp = arg;

	logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name);
	dhcp6_fail(ifp);
}

static void
dhcp6_startdecline(struct interface *ifp)
{
	struct dhcp6_state *state;

	state = D6_STATE(ifp);
	loginfox("%s: declining failed DHCPv6 addresses", ifp->name);
	state->state = DH6S_DECLINE;
	state->RTC = 0;
	state->IMD = 0;
	state->IRT = DEC_TIMEOUT;
	state->MRT = 0;
	state->MRC = DEC_MAX_RC;
	state->MRCcallback = dhcp6_faildecline;

	if (dhcp6_makemessage(ifp) == -1)
		logerr("%s: %s", __func__, ifp->name);
	else
		dhcp6_senddecline(ifp);
}

static void
dhcp6_finishrelease(void *arg)
{
	struct interface *ifp;
	struct dhcp6_state *state;

	ifp = (struct interface *)arg;
	if ((state = D6_STATE(ifp)) != NULL) {
		state->state = DH6S_RELEASED;
		dhcp6_drop(ifp, "RELEASE6");
	}
}

static void
dhcp6_startrelease(struct interface *ifp)
{
	struct dhcp6_state *state;

	state = D6_STATE(ifp);
	if (state->state != DH6S_BOUND)
		return;

	state->state = DH6S_RELEASE;
	state->RTC = 0;
	state->IMD = REL_MAX_DELAY;
	state->IRT = REL_TIMEOUT;
	state->MRT = REL_MAX_RT;
	/* MRC of REL_MAX_RC is optional in RFC 3315 18.1.6 */
#if 0
	state->MRC = REL_MAX_RC;
	state->MRCcallback = dhcp6_finishrelease;
#else
	state->MRC = 0;
	state->MRCcallback = NULL;
#endif

	if (dhcp6_makemessage(ifp) == -1)
		logerr("%s: %s", __func__, ifp->name);
	else {
		dhcp6_sendrelease(ifp);
		dhcp6_finishrelease(ifp);
	}
}

static int
dhcp6_checkstatusok(const struct interface *ifp,
    struct dhcp6_message *m, uint8_t *p, size_t len)
{
	struct dhcp6_state *state;
	uint8_t *opt;
	uint16_t opt_len, code;
	size_t mlen;
	void * (*f)(void *, size_t, uint16_t, uint16_t *), *farg;
	char buf[32], *sbuf;
	const char *status;
	int loglevel;

	state = D6_STATE(ifp);
	f = p ? dhcp6_findoption : dhcp6_findmoption;
	if (p)
		farg = p;
	else
		farg = m;
	if ((opt = f(farg, len, D6_OPTION_STATUS_CODE, &opt_len)) == NULL) {
		//logdebugx("%s: no status", ifp->name);
		state->lerror = 0;
		errno = ESRCH;
		return 0;
	}

	if (opt_len < sizeof(code)) {
		logerrx("%s: status truncated", ifp->name);
		return -1;
	}
	memcpy(&code, opt, sizeof(code));
	code = ntohs(code);
	if (code == D6_STATUS_OK) {
		state->lerror = 0;
		errno = 0;
		return 0;
	}

	/* Anything after the code is a message. */
	opt += sizeof(code);
	mlen = opt_len - sizeof(code);
	if (mlen == 0) {
		sbuf = NULL;
		if (code < sizeof(dhcp6_statuses) / sizeof(char *))
			status = dhcp6_statuses[code];
		else {
			snprintf(buf, sizeof(buf), "Unknown Status (%d)", code);
			status = buf;
		}
	} else {
		if ((sbuf = malloc(mlen + 1)) == NULL) {
			logerr(__func__);
			return -1;
		}
		memcpy(sbuf, opt, mlen);
		sbuf[mlen] = '\0';
		status = sbuf;
	}

	if (state->lerror == code || state->state == DH6S_INIT)
		loglevel = LOG_DEBUG;
	else
		loglevel = LOG_ERR;
	logmessage(loglevel, "%s: DHCPv6 REPLY: %s", ifp->name, status);
	free(sbuf);
	state->lerror = code;
	errno = 0;
	return (int)code;
}

const struct ipv6_addr *
dhcp6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr,
    unsigned int flags)
{
	const struct dhcp6_state *state;
	const struct ipv6_addr *ap;

	if ((state = D6_STATE(ifp)) != NULL) {
		TAILQ_FOREACH(ap, &state->addrs, next) {
			if (ipv6_findaddrmatch(ap, addr, flags))
				return ap;
		}
	}
	return NULL;
}

struct ipv6_addr *
dhcp6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr,
    unsigned int flags)
{
	struct interface *ifp;
	struct ipv6_addr *ap;
	struct dhcp6_state *state;

	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		if ((state = D6_STATE(ifp)) != NULL) {
			TAILQ_FOREACH(ap, &state->addrs, next) {
				if (ipv6_findaddrmatch(ap, addr, flags))
					return ap;
			}
		}
	}
	return NULL;
}

static int
dhcp6_findna(struct interface *ifp, uint16_t ot, const uint8_t *iaid,
    uint8_t *d, size_t l, const struct timespec *acquired)
{
	struct dhcp6_state *state;
	uint8_t *o, *nd;
	uint16_t ol;
	struct ipv6_addr *a;
	int i;
	struct dhcp6_ia_addr ia;

	i = 0;
	state = D6_STATE(ifp);
	while ((o = dhcp6_findoption(d, l, D6_OPTION_IA_ADDR, &ol))) {
		/* Set d and l first to ensure we find the next option. */
		nd = o + ol;
		l -= (size_t)(nd - d);
		d = nd;
		if (ol < sizeof(ia)) {
			errno = EINVAL;
			logerrx("%s: IA Address option truncated", ifp->name);
			continue;
		}
		memcpy(&ia, o, sizeof(ia));
		ia.pltime = ntohl(ia.pltime);
		ia.vltime = ntohl(ia.vltime);
		/* RFC 3315 22.6 */
		if (ia.pltime > ia.vltime) {
			errno = EINVAL;
			logerr("%s: IA Address pltime %"PRIu32
			    " > vltime %"PRIu32,
			    ifp->name, ia.pltime, ia.vltime);
			continue;
		}
		TAILQ_FOREACH(a, &state->addrs, next) {
			if (ipv6_findaddrmatch(a, &ia.addr, 0))
				break;
		}
		if (a == NULL) {
			/*
			 * RFC 5942 Section 5
			 * We cannot assume any prefix length, nor tie the
			 * address to an existing one as it could expire
			 * before the address.
			 * As such we just give it a 128 prefix.
			 */
			a = ipv6_newaddr(ifp, &ia.addr, 128, IPV6_AF_ONLINK);
			a->dadcallback = dhcp6_dadcallback;
			a->ia_type = ot;
			memcpy(a->iaid, iaid, sizeof(a->iaid));
			a->created = *acquired;

			TAILQ_INSERT_TAIL(&state->addrs, a, next);
		} else {
			if (!(a->flags & IPV6_AF_ONLINK))
				a->flags |= IPV6_AF_ONLINK | IPV6_AF_NEW;
			a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED);
		}
		a->acquired = *acquired;
		a->prefix_pltime = ia.pltime;
		if (a->prefix_vltime != ia.vltime) {
			a->flags |= IPV6_AF_NEW;
			a->prefix_vltime = ia.vltime;
		}
		if (a->prefix_pltime && a->prefix_pltime < state->lowpl)
		    state->lowpl = a->prefix_pltime;
		if (a->prefix_vltime && a->prefix_vltime > state->expire)
		    state->expire = a->prefix_vltime;
		i++;
	}
	return i;
}

#ifndef SMALL
static int
dhcp6_findpd(struct interface *ifp, const uint8_t *iaid,
    uint8_t *d, size_t l, const struct timespec *acquired)
{
	struct dhcp6_state *state;
	uint8_t *o, *nd;
	struct ipv6_addr *a;
	int i;
	uint8_t nb, *pw;
	uint16_t ol;
	struct dhcp6_pd_addr pdp;
	struct in6_addr pdp_prefix;

	i = 0;
	state = D6_STATE(ifp);
	while ((o = dhcp6_findoption(d, l, D6_OPTION_IAPREFIX, &ol))) {
		/* Set d and l first to ensure we find the next option. */
		nd = o + ol;
		l -= (size_t)(nd - d);
		d = nd;
		if (ol < sizeof(pdp)) {
			errno = EINVAL;
			logerrx("%s: IA Prefix option truncated", ifp->name);
			continue;
		}

		memcpy(&pdp, o, sizeof(pdp));
		pdp.pltime = ntohl(pdp.pltime);
		pdp.vltime = ntohl(pdp.vltime);
		/* RFC 3315 22.6 */
		if (pdp.pltime > pdp.vltime) {
			errno = EINVAL;
			logerrx("%s: IA Prefix pltime %"PRIu32
			    " > vltime %"PRIu32,
			    ifp->name, pdp.pltime, pdp.vltime);
			continue;
		}

		o += sizeof(pdp);
		ol = (uint16_t)(ol - sizeof(pdp));

		/* pdp.prefix is not aligned so copy it out. */
		memcpy(&pdp_prefix, &pdp.prefix, sizeof(pdp_prefix));
		TAILQ_FOREACH(a, &state->addrs, next) {
			if (IN6_ARE_ADDR_EQUAL(&a->prefix, &pdp_prefix))
				break;
		}

		if (a == NULL) {
			a = ipv6_newaddr(ifp, &pdp_prefix, pdp.prefix_len,
			    IPV6_AF_DELEGATEDPFX);
			if (a == NULL)
				break;
			a->created = *acquired;
			a->dadcallback = dhcp6_dadcallback;
			a->ia_type = D6_OPTION_IA_PD;
			memcpy(a->iaid, iaid, sizeof(a->iaid));
			TAILQ_INSERT_TAIL(&state->addrs, a, next);
		} else {
			if (!(a->flags & IPV6_AF_DELEGATEDPFX))
				a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX;
			a->flags &= ~(IPV6_AF_STALE |
			              IPV6_AF_EXTENDED |
			              IPV6_AF_REQUEST);
			if (a->prefix_vltime != pdp.vltime)
				a->flags |= IPV6_AF_NEW;
		}

		a->acquired = *acquired;
		a->prefix_pltime = pdp.pltime;
		a->prefix_vltime = pdp.vltime;

		if (a->prefix_pltime && a->prefix_pltime < state->lowpl)
			state->lowpl = a->prefix_pltime;
		if (a->prefix_vltime && a->prefix_vltime > state->expire)
			state->expire = a->prefix_vltime;
		i++;

		a->prefix_exclude_len = 0;
		memset(&a->prefix_exclude, 0, sizeof(a->prefix_exclude));
		o = dhcp6_findoption(o, ol, D6_OPTION_PD_EXCLUDE, &ol);
		if (o == NULL)
			continue;

		/* RFC 6603 4.2 says option length MUST be between 2 and 17.
		 * This allows 1 octet for prefix length and 16 for the
		 * subnet ID. */
		if (ol < 2 || ol > 17) {
			logerrx("%s: invalid PD Exclude option", ifp->name);
			continue;
		}

		/* RFC 6603 4.2 says prefix length MUST be between the
		 * length of the IAPREFIX prefix length + 1 and 128. */
		if (*o < a->prefix_len + 1 || *o > 128) {
			logerrx("%s: invalid PD Exclude length", ifp->name);
			continue;
		}

		ol--;
		/* Check option length matches prefix length. */
		if (((*o - a->prefix_len - 1) / NBBY) + 1 != ol) {
			logerrx("%s: PD Exclude length mismatch", ifp->name);
			continue;
		}
		a->prefix_exclude_len = *o++;

		memcpy(&a->prefix_exclude, &a->prefix,
		    sizeof(a->prefix_exclude));
		nb = a->prefix_len % NBBY;
		if (nb)
			ol--;
		pw = a->prefix_exclude.s6_addr +
		    (a->prefix_exclude_len / NBBY) - 1;
		while (ol-- > 0)
			*pw-- = *o++;
		if (nb)
			*pw = (uint8_t)(*pw | (*o >> nb));
	}
	return i;
}
#endif

static int
dhcp6_findia(struct interface *ifp, struct dhcp6_message *m, size_t l,
    const char *sfrom, const struct timespec *acquired)
{
	struct dhcp6_state *state;
	const struct if_options *ifo;
	struct dhcp6_option o;
	uint8_t *d, *p;
	struct dhcp6_ia_na ia;
	int i, e, error;
	size_t j;
	uint16_t nl;
	uint8_t iaid[4];
	char buf[sizeof(iaid) * 3];
	struct ipv6_addr *ap;
	struct if_ia *ifia;

	if (l < sizeof(*m)) {
		/* Should be impossible with guards at packet in
		 * and reading leases */
		errno = EINVAL;
		return -1;
	}

	ifo = ifp->options;
	i = e = 0;
	state = D6_STATE(ifp);
	TAILQ_FOREACH(ap, &state->addrs, next) {
		if (!(ap->flags & IPV6_AF_DELEGATED))
			ap->flags |= IPV6_AF_STALE;
	}

	d = (uint8_t *)m + sizeof(*m);
	l -= sizeof(*m);
	while (l > sizeof(o)) {
		memcpy(&o, d, sizeof(o));
		o.len = ntohs(o.len);
		if (o.len > l || sizeof(o) + o.len > l) {
			errno = EINVAL;
			logerrx("%s: option overflow", ifp->name);
			break;
		}
		p = d + sizeof(o);
		d = p + o.len;
		l -= sizeof(o) + o.len;

		o.code = ntohs(o.code);
		switch(o.code) {
		case D6_OPTION_IA_TA:
			nl = 4;
			break;
		case D6_OPTION_IA_NA:
		case D6_OPTION_IA_PD:
			nl = 12;
			break;
		default:
			continue;
		}
		if (o.len < nl) {
			errno = EINVAL;
			logerrx("%s: IA option truncated", ifp->name);
			continue;
		}

		memcpy(&ia, p, nl);
		p += nl;
		o.len = (uint16_t)(o.len - nl);

		for (j = 0; j < ifo->ia_len; j++) {
			ifia = &ifo->ia[j];
			if (ifia->ia_type == o.code &&
			    memcmp(ifia->iaid, ia.iaid, sizeof(ia.iaid)) == 0)
				break;
		}
		if (j == ifo->ia_len &&
		    !(ifo->ia_len == 0 && ifp->ctx->options & DHCPCD_DUMPLEASE))
		{
			logdebugx("%s: ignoring unrequested IAID %s",
			    ifp->name,
			    hwaddr_ntoa(ia.iaid, sizeof(ia.iaid),
			    buf, sizeof(buf)));
			continue;
		}

		if (o.code != D6_OPTION_IA_TA) {
			ia.t1 = ntohl(ia.t1);
			ia.t2 = ntohl(ia.t2);
			/* RFC 3315 22.4 */
			if (ia.t2 > 0 && ia.t1 > ia.t2) {
				logwarnx("%s: IAID %s T1(%d) > T2(%d) from %s",
				    ifp->name,
				    hwaddr_ntoa(iaid, sizeof(iaid), buf,
				                sizeof(buf)),
				    ia.t1, ia.t2, sfrom);
				continue;
			}
		} else
			ia.t1 = ia.t2 = 0; /* appease gcc */
		if ((error = dhcp6_checkstatusok(ifp, NULL, p, o.len)) != 0) {
			if (error == D6_STATUS_NOBINDING)
				state->has_no_binding = true;
			e = 1;
			continue;
		}
		if (o.code == D6_OPTION_IA_PD) {
#ifndef SMALL
			if (dhcp6_findpd(ifp, ia.iaid, p, o.len,
					 acquired) == 0)
			{
				logwarnx("%s: %s: DHCPv6 REPLY missing Prefix",
				    ifp->name, sfrom);
				continue;
			}
#endif
		} else {
			if (dhcp6_findna(ifp, o.code, ia.iaid, p, o.len,
					 acquired) == 0)
			{
				logwarnx("%s: %s: DHCPv6 REPLY missing "
				    "IA Address",
				    ifp->name, sfrom);
				continue;
			}
		}
		if (o.code != D6_OPTION_IA_TA) {
			if (ia.t1 != 0 &&
			    (ia.t1 < state->renew || state->renew == 0))
				state->renew = ia.t1;
			if (ia.t2 != 0 &&
			    (ia.t2 < state->rebind || state->rebind == 0))
				state->rebind = ia.t2;
		}
		i++;
	}

	if (i == 0 && e)
		return -1;
	return i;
}

#ifndef SMALL
static void
dhcp6_deprecatedele(struct ipv6_addr *ia)
{
	struct ipv6_addr *da, *dan, *dda;
	struct timespec now;
	struct dhcp6_state *state;

	timespecclear(&now);
	TAILQ_FOREACH_SAFE(da, &ia->pd_pfxs, pd_next, dan) {
		if (ia->prefix_vltime == 0) {
			if (da->prefix_vltime != 0)
				da->prefix_vltime = 0;
			else
				continue;
		} else if (da->prefix_pltime != 0)
			da->prefix_pltime = 0;
		else
			continue;

		if (ipv6_doaddr(da, &now) != -1)
			continue;

		/* Delegation deleted, forget it. */
		TAILQ_REMOVE(&ia->pd_pfxs, da, pd_next);

		/* Delete it from the interface. */
		state = D6_STATE(da->iface);
		TAILQ_FOREACH(dda, &state->addrs, next) {
			if (IN6_ARE_ADDR_EQUAL(&dda->addr, &da->addr))
				break;
		}
		if (dda != NULL) {
			TAILQ_REMOVE(&state->addrs, dda, next);
			ipv6_freeaddr(dda);
		}
	}
}
#endif

static void
dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs)
{
	struct ipv6_addr *ia, *ian;

	TAILQ_FOREACH_SAFE(ia, addrs, next, ian) {
		if (ia->flags & IPV6_AF_EXTENDED)
			;
		else if (ia->flags & IPV6_AF_STALE) {
			if (ia->prefix_vltime != 0)
				logdebugx("%s: %s: became stale",
				    ia->iface->name, ia->saddr);
			/* Technically this violates RFC 8415 18.2.10.1,
			 * but we need a mechanism to tell the kernel to
			 * try and prefer other addresses. */
			ia->prefix_pltime = 0;
		} else if (ia->prefix_vltime == 0)
			loginfox("%s: %s: no valid lifetime",
			    ia->iface->name, ia->saddr);
		else
			continue;

#ifndef SMALL
		/* If we delegated from this prefix, deprecate or remove
		 * the delegations. */
		if (ia->flags & IPV6_AF_DELEGATEDPFX)
			dhcp6_deprecatedele(ia);
#endif

		if (ia->flags & IPV6_AF_REQUEST) {
			ia->prefix_vltime = ia->prefix_pltime = 0;
			eloop_q_timeout_delete(ia->iface->ctx->eloop,
			    ELOOP_QUEUE_ALL, NULL, ia);
			continue;
		}
		TAILQ_REMOVE(addrs, ia, next);
		if (ia->flags & IPV6_AF_EXTENDED)
			ipv6_deleteaddr(ia);
		ipv6_freeaddr(ia);
	}
}

static int
dhcp6_validatelease(struct interface *ifp,
    struct dhcp6_message *m, size_t len,
    const char *sfrom, const struct timespec *acquired)
{
	struct dhcp6_state *state;
	int nia, ok_errno;
	struct timespec aq;

	if (len <= sizeof(*m)) {
		logerrx("%s: DHCPv6 lease truncated", ifp->name);
		return -1;
	}

	state = D6_STATE(ifp);
	errno = 0;
	if (dhcp6_checkstatusok(ifp, m, NULL, len) != 0)
		return -1;
	ok_errno = errno;

	state->renew = state->rebind = state->expire = 0;
	state->lowpl = ND6_INFINITE_LIFETIME;
	if (!acquired) {
		clock_gettime(CLOCK_MONOTONIC, &aq);
		acquired = &aq;
	}
	state->has_no_binding = false;
	nia = dhcp6_findia(ifp, m, len, sfrom, acquired);
	if (nia == 0) {
		if (state->state != DH6S_CONFIRM && ok_errno != 0) {
			logerrx("%s: no useable IA found in lease", ifp->name);
			return -1;
		}

		/* We are confirming and have an OK,
		 * so look for ia's in our old lease.
		 * IA's must have existed here otherwise we would
		 * have rejected it earlier. */
		assert(state->new != NULL && state->new_len != 0);
		state->has_no_binding = false;
		nia = dhcp6_findia(ifp, state->new, state->new_len,
		    sfrom, acquired);
	}
	return nia;
}

static ssize_t
dhcp6_readlease(struct interface *ifp, int validate)
{
	union {
		struct dhcp6_message dhcp6;
		uint8_t buf[UDPLEN_MAX];
	} buf;
	struct dhcp6_state *state;
	ssize_t bytes;
	int fd;
	time_t mtime, now;
#ifdef AUTH
	uint8_t *o;
	uint16_t ol;
#endif

	state = D6_STATE(ifp);
	if (state->leasefile[0] == '\0') {
		logdebugx("reading standard input");
		bytes = read(fileno(stdin), buf.buf, sizeof(buf.buf));
	} else {
		logdebugx("%s: reading lease `%s'",
		    ifp->name, state->leasefile);
		bytes = dhcp_readfile(ifp->ctx, state->leasefile,
		    buf.buf, sizeof(buf.buf));
	}
	if (bytes == -1)
		goto ex;

	if (ifp->ctx->options & DHCPCD_DUMPLEASE || state->leasefile[0] == '\0')
		goto out;

	if (bytes == 0)
		goto ex;

	/* If not validating IA's and if they have expired,
	 * skip to the auth check. */
	if (!validate)
		goto auth;

	if (dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == -1)
		goto ex;
	clock_gettime(CLOCK_MONOTONIC, &state->acquired);
	if ((now = time(NULL)) == -1)
		goto ex;
	state->acquired.tv_sec -= now - mtime;

	/* Check to see if the lease is still valid */
	fd = dhcp6_validatelease(ifp, &buf.dhcp6, (size_t)bytes, NULL,
	    &state->acquired);
	if (fd == -1)
		goto ex;

	if (state->expire != ND6_INFINITE_LIFETIME &&
	    (time_t)state->expire < now - mtime &&
	    !(ifp->options->options & DHCPCD_LASTLEASE_EXTEND))
	{
		logdebugx("%s: discarding expired lease", ifp->name);
		bytes = 0;
		goto ex;
	}

auth:
#ifdef AUTH
	/* Authenticate the message */
	o = dhcp6_findmoption(&buf.dhcp6, (size_t)bytes, D6_OPTION_AUTH, &ol);
	if (o) {
		if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
		    buf.buf, (size_t)bytes, 6, buf.dhcp6.type, o, ol) == NULL)
		{
			logerr("%s: authentication failed", ifp->name);
			bytes = 0;
			goto ex;
		}
		if (state->auth.token)
			logdebugx("%s: validated using 0x%08" PRIu32,
			    ifp->name, state->auth.token->secretid);
		else
			loginfox("%s: accepted reconfigure key", ifp->name);
	} else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) ==
	    DHCPCD_AUTH_SENDREQUIRE)
	{
		logerrx("%s: authentication now required", ifp->name);
		goto ex;
	}
#endif

out:
	free(state->new);
	state->new = malloc((size_t)bytes);
	if (state->new == NULL) {
		logerr(__func__);
		goto ex;
	}

	memcpy(state->new, buf.buf, (size_t)bytes);
	state->new_len = (size_t)bytes;
	return bytes;

ex:
	dhcp6_freedrop_addrs(ifp, 0, NULL);
	dhcp_unlink(ifp->ctx, state->leasefile);
	free(state->new);
	state->new = NULL;
	state->new_len = 0;
	return bytes == 0 ? 0 : -1;
}

static void
dhcp6_startinit(struct interface *ifp)
{
	struct dhcp6_state *state;
	ssize_t r;
	uint8_t has_ta, has_non_ta;
	size_t i;

	state = D6_STATE(ifp);
	state->state = DH6S_INIT;
	state->expire = ND6_INFINITE_LIFETIME;
	state->lowpl = ND6_INFINITE_LIFETIME;

	dhcp6_addrequestedaddrs(ifp);
	has_ta = has_non_ta = 0;
	for (i = 0; i < ifp->options->ia_len; i++) {
		switch (ifp->options->ia[i].ia_type) {
		case D6_OPTION_IA_TA:
			has_ta = 1;
			break;
		default:
			has_non_ta = 1;
		}
	}

	if (!(ifp->ctx->options & DHCPCD_TEST) &&
	    !(has_ta && !has_non_ta) &&
	    ifp->options->reboot != 0)
	{
		r = dhcp6_readlease(ifp, 1);
		if (r == -1) {
			if (errno != ENOENT && errno != ESRCH)
				logerr("%s: %s", __func__, state->leasefile);
		} else if (r != 0 &&
		    !(ifp->options->options & DHCPCD_ANONYMOUS))
		{
			/* RFC 3633 section 12.1 */
#ifndef SMALL
			if (dhcp6_hasprefixdelegation(ifp))
				dhcp6_startrebind(ifp);
			else
#endif
				dhcp6_startconfirm(ifp);
			return;
		}
	}
	dhcp6_startdiscoinform(ifp);
}

#ifndef SMALL
static struct ipv6_addr *
dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix,
    const struct if_sla *sla, struct if_ia *if_ia)
{
	struct dhcp6_state *state;
	struct in6_addr addr, daddr;
	struct ipv6_addr *ia;
	int pfxlen, dadcounter;
	uint64_t vl;

	/* RFC6603 Section 4.2 */
	if (strcmp(ifp->name, prefix->iface->name) == 0) {
		if (prefix->prefix_exclude_len == 0) {
			/* Don't spam the log automatically */
			if (sla != NULL)
				logwarnx("%s: DHCPv6 server does not support "
				    "OPTION_PD_EXCLUDE",
				    ifp->name);
			return NULL;
		}
		pfxlen = prefix->prefix_exclude_len;
		memcpy(&addr, &prefix->prefix_exclude, sizeof(addr));
	} else if ((pfxlen = dhcp6_delegateaddr(&addr, ifp, prefix,
	    sla, if_ia)) == -1)
		return NULL;

	if (sla != NULL && fls64(sla->suffix) > 128 - pfxlen) {
		logerrx("%s: suffix %" PRIu64 " + prefix_len %d > 128",
		    ifp->name, sla->suffix, pfxlen);
		return NULL;
	}

	/* Add our suffix */
	if (sla != NULL && sla->suffix != 0) {
		daddr = addr;
		vl = be64dec(addr.s6_addr + 8);
		vl |= sla->suffix;
		be64enc(daddr.s6_addr + 8, vl);
	} else {
		dadcounter = ipv6_makeaddr(&daddr, ifp, &addr, pfxlen, 0);
		if (dadcounter == -1) {
			logerrx("%s: error adding slaac to prefix_len %d",
			    ifp->name, pfxlen);
			return NULL;
		}
	}

	/* Find an existing address */
	state = D6_STATE(ifp);
	TAILQ_FOREACH(ia, &state->addrs, next) {
		if (IN6_ARE_ADDR_EQUAL(&ia->addr, &daddr))
			break;
	}
	if (ia == NULL) {
		ia = ipv6_newaddr(ifp, &daddr, (uint8_t)pfxlen, IPV6_AF_ONLINK);
		if (ia == NULL)
			return NULL;
		ia->dadcallback = dhcp6_dadcallback;
		memcpy(&ia->iaid, &prefix->iaid, sizeof(ia->iaid));
		ia->created = prefix->acquired;

		TAILQ_INSERT_TAIL(&state->addrs, ia, next);
		TAILQ_INSERT_TAIL(&prefix->pd_pfxs, ia, pd_next);
	}
	ia->delegating_prefix = prefix;
	ia->prefix = addr;
	ia->prefix_len = (uint8_t)pfxlen;
	ia->acquired = prefix->acquired;
	ia->prefix_pltime = prefix->prefix_pltime;
	ia->prefix_vltime = prefix->prefix_vltime;

	/* If the prefix length hasn't changed,
	 * don't install a reject route. */
	if (prefix->prefix_len == pfxlen)
		prefix->flags |= IPV6_AF_NOREJECT;
	else
		prefix->flags &= ~IPV6_AF_NOREJECT;

	return ia;
}
#endif

static void
dhcp6_script_try_run(struct interface *ifp, int delegated)
{
	struct dhcp6_state *state;
	struct ipv6_addr *ap;
	int completed;

	state = D6_STATE(ifp);
	completed = 1;
	/* If all addresses have completed DAD run the script */
	TAILQ_FOREACH(ap, &state->addrs, next) {
		if (!(ap->flags & IPV6_AF_ADDED))
			continue;
		if (ap->flags & IPV6_AF_ONLINK) {
			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
#ifndef SMALL
			    && ((delegated && ap->delegating_prefix) ||
			    (!delegated && !ap->delegating_prefix))
#endif
			    )
			{
				completed = 0;
				break;
			}
		}
	}
	if (completed) {
		script_runreason(ifp, delegated ? "DELEGATED6" : state->reason);
		if (!delegated)
			dhcpcd_daemonise(ifp->ctx);
	} else
		logdebugx("%s: waiting for DHCPv6 DAD to complete", ifp->name);
}

#ifdef SMALL
size_t
dhcp6_find_delegates(__unused struct interface *ifp)
{

	return 0;
}
#else
static void
dhcp6_delegate_prefix(struct interface *ifp)
{
	struct if_options *ifo;
	struct dhcp6_state *state;
	struct ipv6_addr *ap;
	size_t i, j, k;
	struct if_ia *ia;
	struct if_sla *sla;
	struct interface *ifd;
	bool carrier_warned;

	ifo = ifp->options;
	state = D6_STATE(ifp);

	/* Clear the logged flag. */
	TAILQ_FOREACH(ap, &state->addrs, next) {
		ap->flags &= ~IPV6_AF_DELEGATEDLOG;
	}

	TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) {
		if (!ifd->active)
			continue;
		k = 0;
		carrier_warned = false;
		TAILQ_FOREACH(ap, &state->addrs, next) {
			if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
				continue;
			if (!(ap->flags & IPV6_AF_DELEGATEDLOG)) {
				int loglevel;

				if (ap->flags & IPV6_AF_NEW)
					loglevel = LOG_INFO;
				else
					loglevel = LOG_DEBUG;
				/* We only want to log this the once as we loop
				 * through many interfaces first. */
				ap->flags |= IPV6_AF_DELEGATEDLOG;
				logmessage(loglevel, "%s: delegated prefix %s",
				    ifp->name, ap->saddr);
				ap->flags &= ~IPV6_AF_NEW;
			}
			for (i = 0; i < ifo->ia_len; i++) {
				ia = &ifo->ia[i];
				if (ia->ia_type != D6_OPTION_IA_PD)
					continue;
				if (memcmp(ia->iaid, ap->iaid,
				    sizeof(ia->iaid)))
					continue;
				if (ia->sla_len == 0) {
					/* no SLA configured, so lets
					 * automate it */
					if (ifd->carrier != LINK_UP) {
						logdebugx(
						    "%s: has no carrier, cannot"
						    " delegate addresses",
						    ifd->name);
						carrier_warned = true;
						break;
					}
					if (dhcp6_ifdelegateaddr(ifd, ap,
					    NULL, ia))
						k++;
				}
				for (j = 0; j < ia->sla_len; j++) {
					sla = &ia->sla[j];
					if (strcmp(ifd->name, sla->ifname))
						continue;
					if (ifd->carrier != LINK_UP) {
						logdebugx(
						    "%s: has no carrier, cannot"
						    " delegate addresses",
						    ifd->name);
						carrier_warned = true;
						break;
					}
					if (dhcp6_ifdelegateaddr(ifd, ap,
					    sla, ia))
						k++;
				}
				if (carrier_warned)
					break;
			}
			if (carrier_warned)
				break;
		}
		if (k && !carrier_warned) {
			struct dhcp6_state *s = D6_STATE(ifd);

			ipv6_addaddrs(&s->addrs);
			dhcp6_script_try_run(ifd, 1);
		}
	}

	/* Now all addresses have been added, rebuild the routing table. */
	rt_build(ifp->ctx, AF_INET6);
}

static void
dhcp6_find_delegates1(void *arg)
{

	dhcp6_find_delegates(arg);
}

size_t
dhcp6_find_delegates(struct interface *ifp)
{
	struct if_options *ifo;
	struct dhcp6_state *state;
	struct ipv6_addr *ap;
	size_t i, j, k;
	struct if_ia *ia;
	struct if_sla *sla;
	struct interface *ifd;

	k = 0;
	TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) {
		ifo = ifd->options;
		state = D6_STATE(ifd);
		if (state == NULL || state->state != DH6S_BOUND)
			continue;
		TAILQ_FOREACH(ap, &state->addrs, next) {
			if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
				continue;
			for (i = 0; i < ifo->ia_len; i++) {
				ia = &ifo->ia[i];
				if (ia->ia_type != D6_OPTION_IA_PD)
					continue;
				if (memcmp(ia->iaid, ap->iaid,
				    sizeof(ia->iaid)))
					continue;
				for (j = 0; j < ia->sla_len; j++) {
					sla = &ia->sla[j];
					if (strcmp(ifp->name, sla->ifname))
						continue;
					if (ipv6_linklocal(ifp) == NULL) {
						logdebugx(
						    "%s: delaying adding"
						    " delegated addresses for"
						    " LL address",
						    ifp->name);
						ipv6_addlinklocalcallback(ifp,
						    dhcp6_find_delegates1, ifp);
						return 1;
					}
					if (dhcp6_ifdelegateaddr(ifp, ap,
					    sla, ia))
					    k++;
				}
			}
		}
	}

	if (k) {
		loginfox("%s: adding delegated prefixes", ifp->name);
		state = D6_STATE(ifp);
		state->state = DH6S_DELEGATED;
		ipv6_addaddrs(&state->addrs);
		rt_build(ifp->ctx, AF_INET6);
		dhcp6_script_try_run(ifp, 1);
	}
	return k;
}
#endif

static void
dhcp6_bind(struct interface *ifp, const char *op, const char *sfrom)
{
	struct dhcp6_state *state = D6_STATE(ifp);
	bool timedout = (op == NULL), has_new = false, confirmed;
	struct ipv6_addr *ia;
	int loglevel;
	struct timespec now;

	TAILQ_FOREACH(ia, &state->addrs, next) {
		if (ia->flags & IPV6_AF_NEW) {
			has_new = true;
			break;
		}
	}
	loglevel = has_new || state->state != DH6S_RENEW ? LOG_INFO : LOG_DEBUG;
	if (!timedout) {
		logmessage(loglevel, "%s: %s received from %s",
		    ifp->name, op, sfrom);
#ifndef SMALL
		/* If we delegated from an unconfirmed lease we MUST drop
		 * them now. Hopefully we have new delegations. */
		if (state->reason != NULL &&
		    strcmp(state->reason, "TIMEOUT6") == 0)
			dhcp6_delete_delegates(ifp);
#endif
		state->reason = NULL;
	} else
		state->reason = "TIMEOUT6";

	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
	clock_gettime(CLOCK_MONOTONIC, &now);

	switch(state->state) {
	case DH6S_INFORM:
	{
		struct dhcp6_option *o;
		uint16_t ol;

		if (state->reason == NULL)
			state->reason = "INFORM6";
		o = dhcp6_findmoption(state->new, state->new_len,
		                      D6_OPTION_INFO_REFRESH_TIME, &ol);
		if (o == NULL || ol != sizeof(uint32_t))
			state->renew = IRT_DEFAULT;
		else {
			memcpy(&state->renew, o, ol);
			state->renew = ntohl(state->renew);
			if (state->renew < IRT_MINIMUM)
				state->renew = IRT_MINIMUM;
		}
		state->rebind = 0;
		state->expire = ND6_INFINITE_LIFETIME;
		state->lowpl = ND6_INFINITE_LIFETIME;
	}
		break;

	case DH6S_REQUEST:
		if (state->reason == NULL)
			state->reason = "BOUND6";
		/* FALLTHROUGH */
	case DH6S_RENEW:
		if (state->reason == NULL)
			state->reason = "RENEW6";
		/* FALLTHROUGH */
	case DH6S_REBIND:
		if (state->reason == NULL)
			state->reason = "REBIND6";
		/* FALLTHROUGH */
	case DH6S_CONFIRM:
		if (state->reason == NULL)
			state->reason = "REBOOT6";
		if (state->renew != 0) {
			bool all_expired = true;

			TAILQ_FOREACH(ia, &state->addrs, next) {
				if (ia->flags & IPV6_AF_STALE)
					continue;
				if (!(state->renew == ND6_INFINITE_LIFETIME
				    && ia->prefix_vltime == ND6_INFINITE_LIFETIME)
				    && ia->prefix_vltime != 0
				    && ia->prefix_vltime <= state->renew)
					logwarnx(
					    "%s: %s will expire before renewal",
					    ifp->name, ia->saddr);
				else
					all_expired = false;
			}
			if (all_expired) {
				/* All address's vltime happens at or before
				 * the configured T1 in the IA.
				 * This is a badly configured server and we
				 * have to use our own notion of what
				 * T1 and T2 should be as a result.
				 *
				 * Doing this violates RFC 3315 22.4:
				 * In a message sent by a server to a client,
				 * the client MUST use the values in the T1
				 * and T2 fields for the T1 and T2 parameters,
				 * unless those values in those fields are 0.
				 */
				logwarnx("%s: ignoring T1 %"PRIu32
				    " due to address expiry",
				    ifp->name, state->renew);
				state->renew = state->rebind = 0;
			}
		}
		if (state->renew == 0 && state->lowpl != ND6_INFINITE_LIFETIME)
			state->renew = (uint32_t)(state->lowpl * 0.5);
		if (state->rebind == 0 && state->lowpl != ND6_INFINITE_LIFETIME)
			state->rebind = (uint32_t)(state->lowpl * 0.8);
		break;
	default:
		state->reason = "UNKNOWN6";
		break;
	}

	if (state->state != DH6S_CONFIRM && !timedout) {
		state->acquired = now;
		free(state->old);
		state->old = state->new;
		state->old_len = state->new_len;
		state->new = state->recv;
		state->new_len = state->recv_len;
		state->recv = NULL;
		state->recv_len = 0;
		confirmed = false;
	} else {
		/* Reduce timers based on when we got the lease. */
		uint32_t elapsed;

		elapsed = (uint32_t)eloop_timespec_diff(&now,
		    &state->acquired, NULL);
		if (state->renew && state->renew != ND6_INFINITE_LIFETIME) {
			if (state->renew > elapsed)
				state->renew -= elapsed;
			else
				state->renew = 0;
		}
		if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) {
			if (state->rebind > elapsed)
				state->rebind -= elapsed;
			else
				state->rebind = 0;
		}
		if (state->expire && state->expire != ND6_INFINITE_LIFETIME) {
			if (state->expire > elapsed)
				state->expire -= elapsed;
			else
				state->expire = 0;
		}
		confirmed = true;
	}

	if (ifp->ctx->options & DHCPCD_TEST)
		script_runreason(ifp, "TEST");
	else {
		if (state->state == DH6S_INFORM)
			state->state = DH6S_INFORMED;
		else
			state->state = DH6S_BOUND;
		state->failed = false;

		if ((state->renew != 0 || state->rebind != 0) &&
		    state->renew != ND6_INFINITE_LIFETIME)
			eloop_timeout_add_sec(ifp->ctx->eloop,
			    state->renew,
			    state->state == DH6S_INFORMED ?
			    dhcp6_startinform : dhcp6_startrenew, ifp);
		if ((state->rebind != 0 || state->expire != 0) &&
		    state->rebind != ND6_INFINITE_LIFETIME)
			eloop_timeout_add_sec(ifp->ctx->eloop,
			    state->rebind, dhcp6_startrebind, ifp);
		if (state->expire != ND6_INFINITE_LIFETIME)
			eloop_timeout_add_sec(ifp->ctx->eloop,
			    state->expire, dhcp6_startexpire, ifp);

		ipv6_addaddrs(&state->addrs);
		if (!timedout)
			dhcp6_deprecateaddrs(&state->addrs);

		if (state->state == DH6S_INFORMED)
			logmessage(loglevel, "%s: refresh in %"PRIu32" seconds",
			    ifp->name, state->renew);
		else if (state->renew == ND6_INFINITE_LIFETIME)
			logmessage(loglevel, "%s: leased for infinity",
			    ifp->name);
		else if (state->renew || state->rebind)
			logmessage(loglevel, "%s: renew in %"PRIu32", "
			    "rebind in %"PRIu32", "
			    "expire in %"PRIu32" seconds",
			    ifp->name,
			    state->renew, state->rebind, state->expire);
		else if (state->expire == 0)
			logmessage(loglevel, "%s: will expire", ifp->name);
		else
			logmessage(loglevel, "%s: expire in %"PRIu32" seconds",
			    ifp->name, state->expire);
		rt_build(ifp->ctx, AF_INET6);
		if (!confirmed && !timedout) {
			logdebugx("%s: writing lease `%s'",
			    ifp->name, state->leasefile);
			if (dhcp_writefile(ifp->ctx, state->leasefile, 0640,
			    state->new, state->new_len) == -1)
				logerr("dhcp_writefile: %s",state->leasefile);
		}
#ifndef SMALL
		dhcp6_delegate_prefix(ifp);
#endif
		dhcp6_script_try_run(ifp, 0);
	}

	if (ifp->ctx->options & DHCPCD_TEST ||
	    (ifp->options->options & DHCPCD_INFORM &&
	    !(ifp->ctx->options & DHCPCD_MASTER)))
	{
		eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
	}
}

static void
dhcp6_recvif(struct interface *ifp, const char *sfrom,
    struct dhcp6_message *r, size_t len)
{
	struct dhcpcd_ctx *ctx;
	size_t i;
	const char *op;
	struct dhcp6_state *state;
	uint8_t *o;
	uint16_t ol;
	const struct dhcp_opt *opt;
	const struct if_options *ifo;
	bool valid_op;
#ifdef AUTH
	uint8_t *auth;
	uint16_t auth_len;
#endif

	ctx = ifp->ctx;
	state = D6_STATE(ifp);
	if (state == NULL || state->send == NULL) {
		logdebugx("%s: DHCPv6 reply received but not running",
		    ifp->name);
		return;
	}

	/* We're already bound and this message is for another machine */
	/* XXX DELEGATED? */
	if (r->type != DHCP6_RECONFIGURE &&
	    (state->state == DH6S_BOUND || state->state == DH6S_INFORMED))
	{
		logdebugx("%s: DHCPv6 reply received but already bound",
		    ifp->name);
		return;
	}

	if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) {
		logdebugx("%s: no DHCPv6 server ID from %s", ifp->name, sfrom);
		return;
	}

	ifo = ifp->options;
	for (i = 0, opt = ctx->dhcp6_opts;
	    i < ctx->dhcp6_opts_len;
	    i++, opt++)
	{
		if (has_option_mask(ifo->requiremask6, opt->option) &&
		    !dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL))
		{
			logwarnx("%s: reject DHCPv6 (no option %s) from %s",
			    ifp->name, opt->var, sfrom);
			return;
		}
		if (has_option_mask(ifo->rejectmask6, opt->option) &&
		    dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL))
		{
			logwarnx("%s: reject DHCPv6 (option %s) from %s",
			    ifp->name, opt->var, sfrom);
			return;
		}
	}

#ifdef AUTH
	/* Authenticate the message */
	auth = dhcp6_findmoption(r, len, D6_OPTION_AUTH, &auth_len);
	if (auth != NULL) {
		if (dhcp_auth_validate(&state->auth, &ifo->auth,
		    (uint8_t *)r, len, 6, r->type, auth, auth_len) == NULL)
		{
			logerr("%s: authentication failed from %s",
			    ifp->name, sfrom);
			return;
		}
		if (state->auth.token)
			logdebugx("%s: validated using 0x%08" PRIu32,
			    ifp->name, state->auth.token->secretid);
		else
			loginfox("%s: accepted reconfigure key", ifp->name);
	} else if (ifo->auth.options & DHCPCD_AUTH_SEND) {
		if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
			logerr("%s: no authentication from %s",
			    ifp->name, sfrom);
			return;
		}
		logwarnx("%s: no authentication from %s", ifp->name, sfrom);
	}
#endif

	op = dhcp6_get_op(r->type);
	valid_op = op != NULL;
	switch(r->type) {
	case DHCP6_REPLY:
		switch(state->state) {
		case DH6S_INFORM:
			if (dhcp6_checkstatusok(ifp, r, NULL, len) != 0)
				return;
			break;
		case DH6S_CONFIRM:
			if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
			{
				dhcp6_startdiscoinform(ifp);
				return;
			}
			break;
		case DH6S_DISCOVER:
			/* Only accept REPLY in DISCOVER for RAPID_COMMIT.
			 * Normally we get an ADVERTISE for a DISCOVER. */
			if (!has_option_mask(ifo->requestmask6,
			    D6_OPTION_RAPID_COMMIT) ||
			    !dhcp6_findmoption(r, len, D6_OPTION_RAPID_COMMIT,
					      NULL))
			{
				valid_op = false;
				break;
			}
			/* Validate lease before setting state to REQUEST. */
			/* FALLTHROUGH */
		case DH6S_REQUEST: /* FALLTHROUGH */
		case DH6S_RENEW: /* FALLTHROUGH */
		case DH6S_REBIND:
			if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
			{
				/*
				 * If we can't use the lease, fallback to
				 * DISCOVER and try and get a new one.
				 *
				 * This is needed become some servers
				 * renumber the prefix or address
				 * and deny the current one before it expires
				 * rather than sending it back with a zero
				 * lifetime along with the new prefix or
				 * address to use.
				 * This behavior is wrong, but moving to the
				 * DISCOVER phase works around it.
				 *
				 * The currently held lease is still valid
				 * until a new one is found.
				 */
				if (state->state != DH6S_DISCOVER)
					dhcp6_startdiscoinform(ifp);
				return;
			}
			/* RFC8415 18.2.10.1 */
			if ((state->state == DH6S_RENEW ||
			    state->state == DH6S_REBIND) &&
			    state->has_no_binding)
			{
				dhcp6_startrequest(ifp);
				return;
			}
			if (state->state == DH6S_DISCOVER)
				state->state = DH6S_REQUEST;
			break;
		case DH6S_DECLINE:
			/* This isnt really a failure, but an
			 * acknowledgement of one. */
			loginfox("%s: %s acknowledged DECLINE6",
			    ifp->name, sfrom);
			dhcp6_fail(ifp);
			return;
		default:
			valid_op = false;
			break;
		}
		break;
	case DHCP6_ADVERTISE:
		if (state->state != DH6S_DISCOVER) {
			valid_op = false;
			break;
		}
		/* RFC7083 */
		o = dhcp6_findmoption(r, len, D6_OPTION_SOL_MAX_RT, &ol);
		if (o && ol == sizeof(uint32_t)) {
			uint32_t max_rt;

			memcpy(&max_rt, o, sizeof(max_rt));
			max_rt = ntohl(max_rt);
			if (max_rt >= 60 && max_rt <= 86400) {
				logdebugx("%s: SOL_MAX_RT %llu -> %u",
				    ifp->name,
				    (unsigned long long)state->sol_max_rt,
				    max_rt);
				state->sol_max_rt = max_rt;
			} else
				logerr("%s: invalid SOL_MAX_RT %u",
				    ifp->name, max_rt);
		}
		o = dhcp6_findmoption(r, len, D6_OPTION_INF_MAX_RT, &ol);
		if (o && ol == sizeof(uint32_t)) {
			uint32_t max_rt;

			memcpy(&max_rt, o, sizeof(max_rt));
			max_rt = ntohl(max_rt);
			if (max_rt >= 60 && max_rt <= 86400) {
				logdebugx("%s: INF_MAX_RT %llu -> %u",
				    ifp->name,
				    (unsigned long long)state->inf_max_rt,
				    max_rt);
				state->inf_max_rt = max_rt;
			} else
				logerrx("%s: invalid INF_MAX_RT %u",
				    ifp->name, max_rt);
		}
		if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
			return;
		break;
	case DHCP6_RECONFIGURE:
#ifdef AUTH
		if (auth == NULL) {
#endif
			logerrx("%s: unauthenticated %s from %s",
			    ifp->name, op, sfrom);
			if (ifo->auth.options & DHCPCD_AUTH_REQUIRE)
				return;
#ifdef AUTH
		}
		loginfox("%s: %s from %s", ifp->name, op, sfrom);
		o = dhcp6_findmoption(r, len, D6_OPTION_RECONF_MSG, &ol);
		if (o == NULL) {
			logerrx("%s: missing Reconfigure Message option",
			    ifp->name);
			return;
		}
		if (ol != 1) {
			logerrx("%s: missing Reconfigure Message type",
			    ifp->name);
			return;
		}
		switch(*o) {
		case DHCP6_RENEW:
			if (state->state != DH6S_BOUND) {
				logerrx("%s: not bound, ignoring %s",
				    ifp->name, op);
				return;
			}
			dhcp6_startrenew(ifp);
			break;
		case DHCP6_INFORMATION_REQ:
			if (state->state != DH6S_INFORMED) {
				logerrx("%s: not informed, ignoring %s",
				    ifp->name, op);
				return;
			}
			eloop_timeout_delete(ifp->ctx->eloop,
			    dhcp6_sendinform, ifp);
			dhcp6_startinform(ifp);
			break;
		default:
			logerr("%s: unsupported %s type %d",
			    ifp->name, op, *o);
			break;
		}
		return;
#else
		break;
#endif
	default:
		logerrx("%s: invalid DHCP6 type %s (%d)",
		    ifp->name, op, r->type);
		return;
	}
	if (!valid_op) {
		logwarnx("%s: invalid state for DHCP6 type %s (%d)",
		    ifp->name, op, r->type);
		return;
	}

	if (state->recv_len < (size_t)len) {
		free(state->recv);
		state->recv = malloc(len);
		if (state->recv == NULL) {
			logerr(__func__);
			return;
		}
	}
	memcpy(state->recv, r, len);
	state->recv_len = len;

	if (r->type == DHCP6_ADVERTISE) {
		struct ipv6_addr *ia;

		if (state->state == DH6S_REQUEST) /* rapid commit */
			goto bind;
		TAILQ_FOREACH(ia, &state->addrs, next) {
			if (!(ia->flags & (IPV6_AF_STALE | IPV6_AF_REQUEST)))
				break;
		}
		if (ia == NULL)
			ia = TAILQ_FIRST(&state->addrs);
		if (ia == NULL)
			loginfox("%s: ADV (no address) from %s",
			    ifp->name, sfrom);
		else
			loginfox("%s: ADV %s from %s",
			    ifp->name, ia->saddr, sfrom);
		dhcp6_startrequest(ifp);
		return;
	}

bind:
	dhcp6_bind(ifp, op, sfrom);
}

void
dhcp6_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, struct ipv6_addr *ia)
{
	struct sockaddr_in6 *from = msg->msg_name;
	size_t len = msg->msg_iov[0].iov_len;
	char sfrom[INET6_ADDRSTRLEN];
	struct interface *ifp;
	struct dhcp6_message *r;
	const struct dhcp6_state *state;
	uint8_t *o;
	uint16_t ol;

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

	if (ia != NULL)
		ifp = ia->iface;
	else {
		ifp = if_findifpfromcmsg(ctx, msg, NULL);
		if (ifp == NULL) {
			logerr(__func__);
			return;
		}
	}

	r = (struct dhcp6_message *)msg->msg_iov[0].iov_base;

	uint8_t duid[DUID_LEN], *dp;
	size_t duid_len;
	o = dhcp6_findmoption(r, len, D6_OPTION_CLIENTID, &ol);
	if (ifp->options->options & DHCPCD_ANONYMOUS) {
		duid_len = duid_make(duid, ifp, DUID_LL);
		dp = duid;
	} else {
		duid_len = ctx->duid_len;
		dp = ctx->duid;
	}
	if (o == NULL || ol != duid_len || memcmp(o, dp, ol) != 0) {
		logdebugx("%s: incorrect client ID from %s",
		    ifp->name, sfrom);
		return;
	}

	if (dhcp6_findmoption(r, len, D6_OPTION_SERVERID, NULL) == NULL) {
		logdebugx("%s: no DHCPv6 server ID from %s",
		    ifp->name, sfrom);
		return;
	}

	if (r->type == DHCP6_RECONFIGURE) {
		logdebugx("%s: RECONFIGURE6 recv from %s,"
		    " sending to all interfaces",
		    ifp->name, sfrom);
		TAILQ_FOREACH(ifp, ctx->ifaces, next) {
			state = D6_CSTATE(ifp);
			if (state != NULL && state->send != NULL)
				dhcp6_recvif(ifp, sfrom, r, len);
		}
		return;
	}

	state = D6_CSTATE(ifp);
	if (state == NULL ||
	    r->xid[0] != state->send->xid[0] ||
	    r->xid[1] != state->send->xid[1] ||
	    r->xid[2] != state->send->xid[2])
	{
		struct interface *ifp1;
		const struct dhcp6_state *state1;

		/* Find an interface with a matching xid. */
		TAILQ_FOREACH(ifp1, ctx->ifaces, next) {
			state1 = D6_CSTATE(ifp1);
			if (state1 == NULL || state1->send == NULL)
				continue;
			if (r->xid[0] == state1->send->xid[0] &&
			    r->xid[1] == state1->send->xid[1] &&
			    r->xid[2] == state1->send->xid[2])
				break;
		}

		if (ifp1 == NULL) {
			if (state != NULL)
				logdebugx("%s: wrong xid 0x%02x%02x%02x"
				    " (expecting 0x%02x%02x%02x) from %s",
				    ifp->name,
				    r->xid[0], r->xid[1], r->xid[2],
				    state->send->xid[0],
				    state->send->xid[1],
				    state->send->xid[2],
				    sfrom);
			return;
		}
		logdebugx("%s: redirecting DHCP6 message to %s",
		    ifp->name, ifp1->name);
		ifp = ifp1;
	}

#if 0
	/*
	 * Handy code to inject raw DHCPv6 packets over responses
	 * from our server.
	 * This allows me to take a 3rd party wireshark trace and
	 * replay it in my code.
	 */
	static int replyn = 0;
	char fname[PATH_MAX], tbuf[UDPLEN_MAX];
	int fd;
	ssize_t tlen;
	uint8_t *si1, *si2;
	uint16_t si_len1, si_len2;

	snprintf(fname, sizeof(fname),
	    "/tmp/dhcp6.reply%d.raw", replyn++);
	fd = open(fname, O_RDONLY, 0);
	if (fd == -1) {
		logerr("%s: open `%s'", __func__, fname);
		return;
	}
	tlen = read(fd, tbuf, sizeof(tbuf));
	if (tlen == -1)
		logerr("%s: read `%s'", __func__, fname);
	close(fd);

	/* Copy across ServerID so we can work with our own server. */
	si1 = dhcp6_findmoption(r, len, D6_OPTION_SERVERID, &si_len1);
	si2 = dhcp6_findmoption(tbuf, (size_t)tlen,
	    D6_OPTION_SERVERID, &si_len2);
	if (si1 != NULL && si2 != NULL && si_len1 == si_len2)
		memcpy(si2, si1, si_len2);
	r = (struct dhcp6_message *)tbuf;
	len = (size_t)tlen;
#endif

	dhcp6_recvif(ifp, sfrom, r, len);
}

static void
dhcp6_recv(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia)
{
	struct sockaddr_in6 from;
	union {
		struct dhcp6_message dhcp6;
		uint8_t buf[UDPLEN_MAX]; /* Maximum UDP message 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))];
	} 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),
	};
	int s;
	ssize_t bytes;

	s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_rfd;
	bytes = recvmsg(s, &msg, 0);
	if (bytes == -1) {
		logerr(__func__);
		return;
	}

	iov.iov_len = (size_t)bytes;
	dhcp6_recvmsg(ctx, &msg, ia);
}

static void
dhcp6_recvaddr(void *arg)
{
	struct ipv6_addr *ia = arg;

	dhcp6_recv(ia->iface->ctx, ia);
}

static void
dhcp6_recvctx(void *arg)
{
	struct dhcpcd_ctx *ctx = arg;

	dhcp6_recv(ctx, NULL);
}

int
dhcp6_openraw(void)
{
	int fd, v;

	fd = socket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
	if (fd == -1)
		return -1;

	v = 1;
	if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &v, sizeof(v)) == -1)
		return -1;

	v = offsetof(struct udphdr, uh_sum);
	if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &v, sizeof(v)) == -1)
		return -1;

	return fd;
}

int
dhcp6_openudp(unsigned int ifindex, struct in6_addr *ia)
{
	struct sockaddr_in6 sa;
	int n, s;

	s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP);
	if (s == -1)
		goto errexit;

	memset(&sa, 0, sizeof(sa));
	sa.sin6_family = AF_INET6;
	sa.sin6_port = htons(DHCP6_CLIENT_PORT);
#ifdef BSD
	sa.sin6_len = sizeof(sa);
#endif

	if (ia != NULL) {
		memcpy(&sa.sin6_addr, ia, sizeof(sa.sin6_addr));
		ipv6_setscope(&sa, ifindex);
	}

	if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
		goto errexit;

	n = 1;
	if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &n, sizeof(n)) == -1)
		goto errexit;

#ifdef SO_RERROR
	n = 1;
	if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1)
		goto errexit;
#endif

	return s;

errexit:
	logerr(__func__);
	if (s != -1)
		close(s);
	return -1;
}

#ifndef SMALL
static void
dhcp6_activateinterfaces(struct interface *ifp)
{
	struct interface *ifd;
	size_t i, j;
	struct if_ia *ia;
	struct if_sla *sla;

	for (i = 0; i < ifp->options->ia_len; i++) {
		ia = &ifp->options->ia[i];
		if (ia->ia_type != D6_OPTION_IA_PD)
			continue;
		for (j = 0; j < ia->sla_len; j++) {
			sla = &ia->sla[j];
			ifd = if_find(ifp->ctx->ifaces, sla->ifname);
			if (ifd == NULL) {
				logwarn("%s: cannot delegate to %s",
				    ifp->name, sla->ifname);
				continue;
			}
			if (!ifd->active) {
				loginfox("%s: activating for delegation",
				    sla->ifname);
				dhcpcd_activateinterface(ifd,
				    DHCPCD_IPV6 | DHCPCD_DHCP6);
			}
		}
	}
}
#endif

static void
dhcp6_start1(void *arg)
{
	struct interface *ifp = arg;
	struct dhcpcd_ctx *ctx = ifp->ctx;
	struct if_options *ifo = ifp->options;
	struct dhcp6_state *state;
	size_t i;
	const struct dhcp_compat *dhc;

	if ((ctx->options & (DHCPCD_MASTER|DHCPCD_PRIVSEP)) == DHCPCD_MASTER &&
	    ctx->dhcp6_rfd == -1)
	{
		ctx->dhcp6_rfd = dhcp6_openudp(0, NULL);
		if (ctx->dhcp6_rfd == -1) {
			logerr(__func__);
			return;
		}
		eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, dhcp6_recvctx, ctx);
	}

	if (!IN_PRIVSEP(ctx) && ctx->dhcp6_wfd == -1) {
		ctx->dhcp6_wfd = dhcp6_openraw();
		if (ctx->dhcp6_wfd == -1) {
			logerr(__func__);
			return;
		}
	}

	state = D6_STATE(ifp);
	/* If no DHCPv6 options are configured,
	   match configured DHCPv4 options to DHCPv6 equivalents. */
	for (i = 0; i < sizeof(ifo->requestmask6); i++) {
		if (ifo->requestmask6[i] != '\0')
			break;
	}
	if (i == sizeof(ifo->requestmask6)) {
		for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) {
			if (DHC_REQ(ifo->requestmask, ifo->nomask, dhc->dhcp_opt))
				add_option_mask(ifo->requestmask6,
				    dhc->dhcp6_opt);
		}
		if (ifo->fqdn != FQDN_DISABLE || ifo->options & DHCPCD_HOSTNAME)
			add_option_mask(ifo->requestmask6, D6_OPTION_FQDN);
	}

#ifndef SMALL
	/* Rapid commit won't work with Prefix Delegation Exclusion */
	if (dhcp6_findselfsla(ifp))
		del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT);
#endif

	if (state->state == DH6S_INFORM) {
		add_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
		dhcp6_startinform(ifp);
	} else {
		del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
		dhcp6_startinit(ifp);
	}

#ifndef SMALL
	dhcp6_activateinterfaces(ifp);
#endif
}

int
dhcp6_start(struct interface *ifp, enum DH6S init_state)
{
	struct dhcp6_state *state;

	state = D6_STATE(ifp);
	if (state != NULL) {
		switch (init_state) {
		case DH6S_INIT:
			goto gogogo;
		case DH6S_INFORM:
			if (state->state == DH6S_INIT ||
			    state->state == DH6S_INFORMED ||
			    (state->state == DH6S_DISCOVER &&
			    !(ifp->options->options & DHCPCD_IA_FORCED) &&
			    !ipv6nd_hasradhcp(ifp, true)))
				dhcp6_startinform(ifp);
			break;
		case DH6S_REQUEST:
			if (ifp->options->options & DHCPCD_DHCP6 &&
			    (state->state == DH6S_INIT ||
			     state->state == DH6S_INFORM ||
			     state->state == DH6S_INFORMED ||
			     state->state == DH6S_DELEGATED))
			{
				/* Change from stateless to stateful */
				init_state = DH6S_INIT;
				goto gogogo;
			}
			break;
		case DH6S_CONFIRM:
			init_state = DH6S_INIT;
			goto gogogo;
		default:
			/* Not possible, but sushes some compiler warnings. */
			break;
		}
		return 0;
	} else {
		switch (init_state) {
		case DH6S_CONFIRM:
			/* No DHCPv6 config, no existing state
			 * so nothing to do. */
			return 0;
		case DH6S_INFORM:
			break;
		default:
			init_state = DH6S_INIT;
			break;
		}
	}

	if (!(ifp->options->options & DHCPCD_DHCP6))
		return 0;

	ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state));
	state = D6_STATE(ifp);
	if (state == NULL)
		return -1;

	state->sol_max_rt = SOL_MAX_RT;
	state->inf_max_rt = INF_MAX_RT;
	TAILQ_INIT(&state->addrs);

gogogo:
	state->state = init_state;
	state->lerror = 0;
	state->failed = false;
	dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
	    AF_INET6, ifp);
	if (ipv6_linklocal(ifp) == NULL) {
		logdebugx("%s: delaying DHCPv6 for LL address", ifp->name);
		ipv6_addlinklocalcallback(ifp, dhcp6_start1, ifp);
		return 0;
	}

	dhcp6_start1(ifp);
	return 0;
}

void
dhcp6_reboot(struct interface *ifp)
{
	struct dhcp6_state *state;

	state = D6_STATE(ifp);
	if (state == NULL)
		return;

	state->lerror = 0;
	switch (state->state) {
	case DH6S_BOUND:
		dhcp6_startrebind(ifp);
		break;
	default:
		dhcp6_startdiscoinform(ifp);
		break;
	}
}

static void
dhcp6_freedrop(struct interface *ifp, int drop, const char *reason)
{
	struct dhcp6_state *state;
	struct dhcpcd_ctx *ctx;
	unsigned long long options;

	if (ifp->options)
		options = ifp->options->options;
	else
		options = ifp->ctx->options;

	if (ifp->ctx->eloop)
		eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);

#ifndef SMALL
	/* If we're dropping the lease, drop delegated addresses.
	 * If, for whatever reason, we don't drop them in the future
	 * then they should at least be marked as deprecated (pltime 0). */
	if (drop && (options & DHCPCD_NODROP) != DHCPCD_NODROP)
		dhcp6_delete_delegates(ifp);
#endif

	state = D6_STATE(ifp);
	if (state) {
		/* Failure to send the release may cause this function to
		 * re-enter */
		if (state->state == DH6S_RELEASE) {
			dhcp6_finishrelease(ifp);
			return;
		}

		if (drop && options & DHCPCD_RELEASE &&
		    state->state != DH6S_DELEGATED)
		{
			if (ifp->carrier == LINK_UP &&
			    state->state != DH6S_RELEASED &&
			    state->state != DH6S_INFORMED)
			{
				dhcp6_startrelease(ifp);
				return;
			}
			dhcp_unlink(ifp->ctx, state->leasefile);
		}
		dhcp6_freedrop_addrs(ifp, drop, NULL);
		free(state->old);
		state->old = state->new;
		state->old_len = state->new_len;
		state->new = NULL;
		state->new_len = 0;
		if (drop && state->old &&
		    (options & DHCPCD_NODROP) != DHCPCD_NODROP)
		{
			if (reason == NULL)
				reason = "STOP6";
			script_runreason(ifp, reason);
		}
		free(state->old);
		free(state->send);
		free(state->recv);
		free(state);
		ifp->if_data[IF_DATA_DHCP6] = NULL;
	}

	/* If we don't have any more DHCP6 enabled interfaces,
	 * close the global socket and release resources */
	ctx = ifp->ctx;
	if (ctx->ifaces) {
		TAILQ_FOREACH(ifp, ctx->ifaces, next) {
			if (D6_STATE(ifp))
				break;
		}
	}
	if (ifp == NULL && ctx->dhcp6_rfd != -1) {
		eloop_event_delete(ctx->eloop, ctx->dhcp6_rfd);
		close(ctx->dhcp6_rfd);
		ctx->dhcp6_rfd = -1;
	}
}

void
dhcp6_drop(struct interface *ifp, const char *reason)
{

	dhcp6_freedrop(ifp, 1, reason);
}

void
dhcp6_free(struct interface *ifp)
{

	dhcp6_freedrop(ifp, 0, NULL);
}

void
dhcp6_abort(struct interface *ifp)
{
	struct dhcp6_state *state;
#ifdef ND6_ADVERTISE
	struct ipv6_addr *ia;
#endif

	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp);
	state = D6_STATE(ifp);
	if (state == NULL)
		return;

#ifdef ND6_ADVERTISE
	TAILQ_FOREACH(ia, &state->addrs, next) {
		ipv6nd_advertise(ia);
	}
#endif

	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startdiscover, ifp);
	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp);
	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startinform, ifp);
	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendinform, ifp);

	switch (state->state) {
	case DH6S_DISCOVER:	/* FALLTHROUGH */
	case DH6S_REQUEST:	/* FALLTHROUGH */
	case DH6S_INFORM:
		state->state = DH6S_INIT;
		break;
	default:
		break;
	}
}

void
dhcp6_handleifa(int cmd, struct ipv6_addr *ia, pid_t pid)
{
	struct dhcp6_state *state;
	struct interface *ifp = ia->iface;

	/* If not running in master mode, listen to this address */
	if (cmd == RTM_NEWADDR &&
	    !(ia->addr_flags & IN6_IFF_NOTUSEABLE) &&
	    ifp->active == IF_ACTIVE_USER &&
	    !(ifp->ctx->options & DHCPCD_MASTER) &&
	    ifp->options->options & DHCPCD_DHCP6)
	{
#ifdef PRIVSEP
		if (IN_PRIVSEP_SE(ifp->ctx)) {
			if (ps_inet_opendhcp6(ia) == -1)
				logerr(__func__);
		} else
#endif
		{
			if (ia->dhcp6_fd == -1)
				ia->dhcp6_fd = dhcp6_openudp(ia->iface->index,
				    &ia->addr);
			if (ia->dhcp6_fd != -1)
				eloop_event_add(ia->iface->ctx->eloop,
				ia->dhcp6_fd, dhcp6_recvaddr, ia);
		}
	}


	if ((state = D6_STATE(ifp)) != NULL)
		ipv6_handleifa_addrs(cmd, &state->addrs, ia, pid);
}

ssize_t
dhcp6_env(FILE *fp, const char *prefix, const struct interface *ifp,
    const struct dhcp6_message *m, size_t len)
{
	const struct if_options *ifo;
	struct dhcp_opt *opt, *vo;
	const uint8_t *p;
	struct dhcp6_option o;
	size_t i;
	char *pfx;
	uint32_t en;
	const struct dhcpcd_ctx *ctx;
#ifndef SMALL
	const struct dhcp6_state *state;
	const struct ipv6_addr *ap;
#endif

	if (m == NULL)
		goto delegated;

	if (len < sizeof(*m)) {
		/* Should be impossible with guards at packet in
		 * and reading leases */
		errno = EINVAL;
		return -1;
	}

	ifo = ifp->options;
	ctx = ifp->ctx;

	/* Zero our indexes */
	for (i = 0, opt = ctx->dhcp6_opts;
	    i < ctx->dhcp6_opts_len;
	    i++, opt++)
		dhcp_zero_index(opt);
	for (i = 0, opt = ifp->options->dhcp6_override;
	    i < ifp->options->dhcp6_override_len;
	    i++, opt++)
		dhcp_zero_index(opt);
	for (i = 0, opt = ctx->vivso;
	    i < ctx->vivso_len;
	    i++, opt++)
		dhcp_zero_index(opt);
	if (asprintf(&pfx, "%s_dhcp6", prefix) == -1)
		return -1;

	/* Unlike DHCP, DHCPv6 options *may* occur more than once.
	 * There is also no provision for option concatenation unlike DHCP. */
	p = (const uint8_t *)m + sizeof(*m);
	len -= sizeof(*m);
	for (; len != 0; p += o.len, len -= o.len) {
		if (len < sizeof(o)) {
			errno = EINVAL;
			break;
		}
		memcpy(&o, p, sizeof(o));
		p += sizeof(o);
		len -= sizeof(o);
		o.len = ntohs(o.len);
		if (len < o.len) {
			errno =	EINVAL;
			break;
		}
		o.code = ntohs(o.code);
		if (has_option_mask(ifo->nomask6, o.code))
			continue;
		for (i = 0, opt = ifo->dhcp6_override;
		    i < ifo->dhcp6_override_len;
		    i++, opt++)
			if (opt->option == o.code)
				break;
		if (i == ifo->dhcp6_override_len &&
		    o.code == D6_OPTION_VENDOR_OPTS &&
		    o.len > sizeof(en))
		{
			memcpy(&en, p, sizeof(en));
			en = ntohl(en);
			vo = vivso_find(en, ifp);
		} else
			vo = NULL;
		if (i == ifo->dhcp6_override_len) {
			for (i = 0, opt = ctx->dhcp6_opts;
			    i < ctx->dhcp6_opts_len;
			    i++, opt++)
				if (opt->option == o.code)
					break;
			if (i == ctx->dhcp6_opts_len)
				opt = NULL;
		}
		if (opt) {
			dhcp_envoption(ifp->ctx,
			    fp, pfx, ifp->name,
			    opt, dhcp6_getoption, p, o.len);
		}
		if (vo) {
			dhcp_envoption(ifp->ctx,
			    fp, pfx, ifp->name,
			    vo, dhcp6_getoption,
			    p + sizeof(en),
			    o.len - sizeof(en));
		}
	}
	free(pfx);

delegated:
#ifndef SMALL
        /* Needed for Delegated Prefixes */
	state = D6_CSTATE(ifp);
	TAILQ_FOREACH(ap, &state->addrs, next) {
		if (ap->delegating_prefix)
			break;
	}
	if (ap == NULL)
		return 1;
	if (fprintf(fp, "%s_delegated_dhcp6_prefix=", prefix) == -1)
		return -1;
	TAILQ_FOREACH(ap, &state->addrs, next) {
		if (ap->delegating_prefix == NULL)
			continue;
		if (ap != TAILQ_FIRST(&state->addrs)) {
			if (fputc(' ', fp) == EOF)
				return -1;
		}
		if (fprintf(fp, "%s", ap->saddr) == -1)
			return -1;
        }
	if (fputc('\0', fp) == EOF)
		return -1;
#endif

	return 1;
}
#endif