Module Name:    src
Committed By:   roy
Date:           Wed Feb 12 19:23:13 UTC 2025

Modified Files:
        src/external/bsd/dhcpcd/dist/src: bpf.c dhcp.c dhcp6.c dhcpcd.c
            if-bsd.c if-options.c ipv6.c ipv6.h ipv6nd.c logerr.c privsep.c
            script.c

Log Message:
Sync with dhcpcd-10.2.0


To generate a diff of this commit:
cvs rdiff -u -r1.22 -r1.23 src/external/bsd/dhcpcd/dist/src/bpf.c
cvs rdiff -u -r1.51 -r1.52 src/external/bsd/dhcpcd/dist/src/dhcp.c
cvs rdiff -u -r1.34 -r1.35 src/external/bsd/dhcpcd/dist/src/dhcp6.c
cvs rdiff -u -r1.56 -r1.57 src/external/bsd/dhcpcd/dist/src/dhcpcd.c
cvs rdiff -u -r1.31 -r1.32 src/external/bsd/dhcpcd/dist/src/if-bsd.c \
    src/external/bsd/dhcpcd/dist/src/ipv6nd.c
cvs rdiff -u -r1.37 -r1.38 src/external/bsd/dhcpcd/dist/src/if-options.c
cvs rdiff -u -r1.19 -r1.20 src/external/bsd/dhcpcd/dist/src/ipv6.c \
    src/external/bsd/dhcpcd/dist/src/privsep.c
cvs rdiff -u -r1.14 -r1.15 src/external/bsd/dhcpcd/dist/src/ipv6.h \
    src/external/bsd/dhcpcd/dist/src/logerr.c
cvs rdiff -u -r1.17 -r1.18 src/external/bsd/dhcpcd/dist/src/script.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/external/bsd/dhcpcd/dist/src/bpf.c
diff -u src/external/bsd/dhcpcd/dist/src/bpf.c:1.22 src/external/bsd/dhcpcd/dist/src/bpf.c:1.23
--- src/external/bsd/dhcpcd/dist/src/bpf.c:1.22	Mon Dec 18 16:03:26 2023
+++ src/external/bsd/dhcpcd/dist/src/bpf.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd: BPF arp and bootp filtering
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without

Index: src/external/bsd/dhcpcd/dist/src/dhcp.c
diff -u src/external/bsd/dhcpcd/dist/src/dhcp.c:1.51 src/external/bsd/dhcpcd/dist/src/dhcp.c:1.52
--- src/external/bsd/dhcpcd/dist/src/dhcp.c:1.51	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/dhcp.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -49,6 +49,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <stdalign.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -97,6 +98,8 @@
 __CTASSERT(sizeof(struct ip)		== 20);
 __CTASSERT(sizeof(struct udphdr)	== 8);
 __CTASSERT(sizeof(struct bootp)		== 300);
+#define IP_UDP_SIZE	sizeof(struct ip) + sizeof(struct udphdr)
+#define BOOTP_MIN_MTU	IP_UDP_SIZE + sizeof(struct bootp)
 
 struct dhcp_op {
 	uint8_t value;
@@ -302,7 +305,7 @@ get_option_uint32(struct dhcpcd_ctx *ctx
 	uint32_t d;
 
 	p = get_option(ctx, bootp, bootp_len, option, &len);
-	if (!p || len < (ssize_t)sizeof(d))
+	if (!p || len != (ssize_t)sizeof(d))
 		return -1;
 	memcpy(&d, p, sizeof(d));
 	if (i)
@@ -319,7 +322,7 @@ get_option_uint16(struct dhcpcd_ctx *ctx
 	uint16_t d;
 
 	p = get_option(ctx, bootp, bootp_len, option, &len);
-	if (!p || len < (ssize_t)sizeof(d))
+	if (!p || len != (ssize_t)sizeof(d))
 		return -1;
 	memcpy(&d, p, sizeof(d));
 	if (i)
@@ -335,7 +338,7 @@ get_option_uint8(struct dhcpcd_ctx *ctx,
 	size_t len;
 
 	p = get_option(ctx, bootp, bootp_len, option, &len);
-	if (!p || len < (ssize_t)sizeof(*p))
+	if (!p || len != (ssize_t)sizeof(*p))
 		return -1;
 	if (i)
 		*i = *(p);
@@ -676,6 +679,8 @@ dhcp_get_mtu(const struct interface *ifp
 	    get_option_uint16(ifp->ctx, &mtu,
 			      state->new, state->new_len, DHO_MTU) == -1)
 		return 0;
+	if (mtu < IPV4_MMTU)
+		return IPV4_MMTU;
 	return mtu;
 }
 
@@ -719,6 +724,77 @@ dhcp_message_add_addr(struct bootp *boot
 	return 0;
 }
 
+#ifndef SMALL
+struct rfc3396_ctx {
+	uint8_t code;
+	uint8_t *len;
+	uint8_t **buf;
+	size_t buflen;
+};
+
+/* Encode data as a DHCP Long Option, RFC 3396. */
+/* NOTE: Wireshark does not decode this correctly
+ * when the option overflows the boundary and another option
+ * is created to hold the resta of the data.
+ * Tested against Wireshark-4.4.1 */
+#define RFC3396_BOUNDARY 255UL
+static ssize_t
+rfc3396_write(struct rfc3396_ctx *ctx, void *data, size_t len)
+{
+	uint8_t *datap = data;
+	size_t wlen, left, r = 0;
+
+	while (len != 0) {
+		if (ctx->len == NULL || *ctx->len == RFC3396_BOUNDARY) {
+			if (ctx->buflen < 2) {
+				errno = ENOMEM;
+				return -1;
+			}
+			*(*ctx->buf)++ = ctx->code;
+			ctx->len = (*ctx->buf)++;
+			*ctx->len = 0;
+			ctx->buflen -= 2;
+			r += 2;
+		}
+
+		wlen = len < RFC3396_BOUNDARY ? len : RFC3396_BOUNDARY;
+		left = RFC3396_BOUNDARY - *ctx->len;
+		if (left < wlen)
+			wlen = left;
+		if (ctx->buflen < wlen) {
+			errno = ENOMEM;
+			return -1;
+		}
+
+		memcpy(*ctx->buf, datap, wlen);
+		datap += wlen;
+		*ctx->buf += wlen;
+		ctx->buflen -= wlen;
+		*ctx->len = (uint8_t)(*ctx->len + wlen);
+		len -= wlen;
+		r += wlen;
+	}
+
+	return (ssize_t)r;
+}
+
+static ssize_t
+rfc3396_write_byte(struct rfc3396_ctx *ctx, uint8_t byte)
+{
+
+	return rfc3396_write(ctx, &byte, sizeof(byte));
+}
+
+static uint8_t *
+rfc3396_zero(struct rfc3396_ctx *ctx) {
+	uint8_t *zerop = *ctx->buf, zero = 0;
+
+	if (rfc3396_write(ctx, &zero, sizeof(zero)) == -1)
+		return NULL;
+	return zerop;
+}
+#endif
+
 static ssize_t
 make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
 {
@@ -734,25 +810,29 @@ make_message(struct bootp **bootpm, cons
 	const struct dhcp_lease *lease = &state->lease;
 	char hbuf[HOSTNAME_MAX_LEN + 1];
 	const char *hostname;
-	const struct vivco *vivco;
 	int mtu;
 #ifdef AUTH
 	uint8_t *auth, auth_len;
 #endif
 
-	if ((mtu = if_getmtu(ifp)) == -1)
+	/* We could take the DHCPv6 approach and work out the
+	 * message length up front rather than this big hammer approach. */
+	if ((mtu = if_getmtu(ifp)) == -1) {
 		logerr("%s: if_getmtu", ifp->name);
-	else if (mtu < MTU_MIN) {
-		if (if_setmtu(ifp, MTU_MIN) == -1)
-			logerr("%s: if_setmtu", ifp->name);
-		mtu = MTU_MIN;
+		return -1;
+	}
+	if ((size_t)mtu < BOOTP_MIN_MTU) {
+		logerr("%s: interface mtu is too small (%d<%zu)",
+		    ifp->name, mtu, BOOTP_MIN_MTU);
+		return -1;
 	}
 
-	if (ifo->options & DHCPCD_BOOTP)
-		bootp = calloc(1, sizeof (*bootp));
-	else
+	if (ifo->options & DHCPCD_BOOTP) {
+		bootp = calloc(1, sizeof(*bootp));
+	} else {
 		/* Make the maximal message we could send */
-		bootp = calloc(1, (size_t)(mtu - IP_UDP_SIZE));
+		bootp = calloc(1, (size_t)mtu - IP_UDP_SIZE);
+	}
 
 	if (bootp == NULL)
 		return -1;
@@ -774,7 +854,7 @@ make_message(struct bootp **bootpm, cons
 	}
 
 	if (ifo->options & DHCPCD_BROADCAST &&
-	    bootp->ciaddr == 0 &&
+	    bootp->ciaddr == INADDR_ANY &&
 	    type != DHCP_DECLINE &&
 	    type != DHCP_RELEASE)
 		bootp->flags = htons(BROADCAST_FLAG);
@@ -797,7 +877,7 @@ make_message(struct bootp **bootpm, cons
 		return sizeof(*bootp);
 
 	p = bootp->vend;
-	e = (uint8_t *)bootp + (mtu - IP_UDP_SIZE) - 1; /* -1 for DHO_END */
+	e = (uint8_t *)bootp + ((size_t)mtu - IP_UDP_SIZE - 1/* DHO_END */);
 
 	ul = htonl(MAGIC_COOKIE);
 	memcpy(p, &ul, sizeof(ul));
@@ -924,7 +1004,7 @@ make_message(struct bootp **bootpm, cons
 			AREA_CHECK(2);
 			*p++ = DHO_MAXMESSAGESIZE;
 			*p++ = 2;
-			sz = htons((uint16_t)(mtu - IP_UDP_SIZE));
+			sz = htons((uint16_t)((size_t)mtu - IP_UDP_SIZE));
 			memcpy(p, &sz, 2);
 			p += 2;
 		}
@@ -1058,33 +1138,77 @@ make_message(struct bootp **bootpm, cons
 		       p += ifo->mudurl[0] + 1;
 		}
 
+#ifndef SMALL
 		if (ifo->vivco_len &&
 		    !has_option_mask(ifo->nomask, DHO_VIVCO))
 		{
-			AREA_CHECK(sizeof(ul));
-			*p++ = DHO_VIVCO;
-			lp = p++;
-			*lp = sizeof(ul);
-			ul = htonl(ifo->vivco_en);
-			memcpy(p, &ul, sizeof(ul));
-			p += sizeof(ul);
-			for (i = 0, vivco = ifo->vivco;
-			    i < ifo->vivco_len;
-			    i++, vivco++)
-			{
-				AREA_FIT(vivco->len);
-				if (vivco->len + 2 + *lp > 255) {
-					logerrx("%s: VIVCO option too big",
-					    ifp->name);
-					free(bootp);
-					return -1;
-				}
-				*p++ = (uint8_t)vivco->len;
-				memcpy(p, vivco->data, vivco->len);
-				p += vivco->len;
+			struct vivco *vivco = ifo->vivco;
+			size_t vlen = ifo->vivco_len;
+			struct rfc3396_ctx rctx = {
+				.code = DHO_VIVCO,
+				.buf = &p,
+				.buflen = AREA_LEFT,
+			};
+
+			for (; vlen > 0; vivco++, vlen--) {
+				ul = htonl(vivco->en);
+				if (rfc3396_write(&rctx, &ul, sizeof(ul)) == -1)
+					goto toobig;
+				lp = rfc3396_zero(&rctx);
+				if (lp == NULL)
+					goto toobig;
+				if (rfc3396_write_byte(&rctx,
+				    (uint8_t)vivco->len) == -1)
+					goto toobig;
+				if (rfc3396_write(&rctx,
+				    vivco->data, vivco->len) == -1)
+					goto toobig;
 				*lp = (uint8_t)(*lp + vivco->len + 1);
 			}
 		}
+		
+		if (ifo->vsio_len &&
+		    !has_option_mask(ifo->nomask, DHO_VIVSO))
+		{
+			struct vsio *vso = ifo->vsio;
+			size_t vlen = ifo->vsio_len;
+			struct vsio_so *so;
+			size_t slen;
+			struct rfc3396_ctx rctx = {
+				.code = DHO_VIVSO,
+				.buf = &p,
+				.buflen = AREA_LEFT,
+			};
+
+			for (; vlen > 0; vso++, vlen--) {
+				if (vso->so_len == 0)
+					continue;
+
+				so = vso->so;
+				slen = vso->so_len;
+
+				ul = htonl(vso->en);
+				if (rfc3396_write(&rctx, &ul, sizeof(ul)) == -1)
+					goto toobig;
+				lp = rfc3396_zero(&rctx);
+				if (lp == NULL)
+					goto toobig;
+
+				for (; slen > 0; so++, slen--) {
+					if (rfc3396_write_byte(&rctx,
+					    (uint8_t)so->opt) == -1)
+						goto toobig;
+					if (rfc3396_write_byte(&rctx,
+					    (uint8_t)so->len) == -1)
+						goto toobig;
+					if (rfc3396_write(&rctx,
+					    so->data, so->len) == -1)
+						goto toobig;
+					*lp = (uint8_t)(*lp + so->len + 2);
+				}
+			}
+		}
+#endif
 
 #ifdef AUTH
 		if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
@@ -1409,9 +1533,15 @@ get_lease(struct interface *ifp,
 			const struct ipv4_addr *ia;
 
 			ia = ipv4_iffindaddr(ifp, &lease->addr, NULL);
-			assert(ia != NULL);
-			lease->mask = ia->mask;
-			lease->brd = ia->brd;
+			if (ia == NULL) {
+				lease->mask.s_addr =
+				    ipv4_getnetmask(lease->addr.s_addr);
+				lease->brd.s_addr =
+				    lease->addr.s_addr | ~lease->mask.s_addr;
+			} else {
+				lease->mask = ia->mask;
+				lease->brd = ia->brd;
+			}
 		}
 	} else {
 		if (get_option_addr(ctx, &lease->mask, bootp, len,
@@ -1645,7 +1775,7 @@ dhcp_makeudppacket(size_t *sz, const uin
 
 	ip->ip_p = IPPROTO_UDP;
 	ip->ip_src.s_addr = source.s_addr;
-	if (dest.s_addr == 0)
+	if (dest.s_addr == INADDR_ANY)
 		ip->ip_dst.s_addr = INADDR_BROADCAST;
 	else
 		ip->ip_dst.s_addr = dest.s_addr;
@@ -2233,6 +2363,46 @@ dhcp_arp_found(struct arp_state *astate,
 
 #endif /* ARP */
 
+static void
+dhcp_bound(struct interface *ifp, uint8_t old_state)
+{
+	struct dhcpcd_ctx *ctx = ifp->ctx;
+	struct dhcp_state *state = D_STATE(ifp);
+
+	/* Close the BPF filter as we can now receive DHCP messages
+	 * on a UDP socket. */
+	dhcp_closebpf(ifp);
+
+	/* If not in manager mode, open an address specific socket. */
+	if (ctx->options & DHCPCD_MANAGER ||
+	    ifp->options->options & DHCPCD_STATIC ||
+	    (state->old != NULL &&
+		state->old->yiaddr == state->new->yiaddr) ||
+	    (old_state & STATE_ADDED && !(old_state & STATE_FAKE)))
+		return;
+
+	dhcp_closeinet(ifp);
+#ifdef PRIVSEP
+	if (IN_PRIVSEP_SE(ctx)) {
+		if (ps_inet_openbootp(state->addr) == -1)
+		    logerr(__func__);
+		return;
+	}
+#endif
+
+	state->udp_rfd = dhcp_openudp(&state->addr->addr);
+	if (state->udp_rfd == -1) {
+		logerr(__func__);
+		/* We still need to work, so re-open BPF. */
+		dhcp_openbpf(ifp);
+		return;
+	}
+
+	if (eloop_event_add(ctx->eloop, state->udp_rfd, ELE_READ,
+	    dhcp_handleifudp, ifp) == -1)
+		logerr("%s: eloop_event_add", __func__);
+}
+
 void
 dhcp_bind(struct interface *ifp)
 {
@@ -2359,7 +2529,18 @@ dhcp_bind(struct interface *ifp)
 
 	old_state = state->added;
 
-	if (!(ifo->options & DHCPCD_CONFIGURE)) {
+	if (ifo->options & DHCPCD_CONFIGURE) {
+		/* Add the address */
+		if (ipv4_applyaddr(ifp) == NULL) {
+			/* There was an error adding the address.
+			 * If we are in oneshot, exit with a failure. */
+			if (ctx->options & DHCPCD_ONESHOT) {
+				loginfox("exiting due to oneshot");
+				eloop_exit(ctx->eloop, EXIT_FAILURE);
+			}
+			return;
+		}
+	} else {
 		struct ipv4_addr *ia;
 
 		script_runreason(ifp, state->reason);
@@ -2368,61 +2549,13 @@ dhcp_bind(struct interface *ifp)
 		/* We we are not configuring the address, we need to keep
 		 * the BPF socket open if the address does not exist. */
 		ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL);
-		if (ia != NULL) {
-			state->addr = ia;
-			state->added = STATE_ADDED;
-			dhcp_closebpf(ifp);
-			goto openudp;
-		}
-		return;
-	}
-
-	/* Add the address */
-	if (ipv4_applyaddr(ifp) == NULL) {
-		/* There was an error adding the address.
-		 * If we are in oneshot, exit with a failure. */
-		if (ctx->options & DHCPCD_ONESHOT) {
-			loginfox("exiting due to oneshot");
-			eloop_exit(ctx->eloop, EXIT_FAILURE);
-		}
-		return;
+		if (ia == NULL)
+			return;
+		state->addr = ia;
+		state->added = STATE_ADDED;
 	}
 
-	/* Close the BPF filter as we can now receive DHCP messages
-	 * on a UDP socket. */
-	dhcp_closebpf(ifp);
-
-openudp:
-	/* If not in manager mode, open an address specific socket. */
-	if (ctx->options & DHCPCD_MANAGER ||
-	    ifo->options & DHCPCD_STATIC ||
-	    (state->old != NULL &&
-	     state->old->yiaddr == state->new->yiaddr &&
-	     old_state & STATE_ADDED && !(old_state & STATE_FAKE)))
-		return;
-
-	dhcp_closeinet(ifp);
-#ifdef PRIVSEP
-	if (IN_PRIVSEP_SE(ctx)) {
-		if (ps_inet_openbootp(state->addr) == -1)
-		    logerr(__func__);
-		return;
-	}
-#endif
-
-	state->udp_rfd = dhcp_openudp(&state->addr->addr);
-	if (state->udp_rfd == -1) {
-		logerr(__func__);
-		/* Address sharing without manager mode is not supported.
-		 * It's also possible another DHCP client could be running,
-		 * which is even worse.
-		 * We still need to work, so re-open BPF. */
-		dhcp_openbpf(ifp);
-		return;
-	}
-	if (eloop_event_add(ctx->eloop, state->udp_rfd, ELE_READ,
-	    dhcp_handleifudp, ifp) == -1)
-		logerr("%s: eloop_event_add", __func__);
+	dhcp_bound(ifp, old_state);
 }
 
 static size_t
@@ -2655,43 +2788,11 @@ dhcp_reboot_newopts(struct interface *if
 	}
 }
 
-#ifdef ARP
-static int
-dhcp_activeaddr(const struct interface *ifp, const struct in_addr *addr)
-{
-	const struct interface *ifp1;
-	const struct dhcp_state *state;
-
-	TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) {
-		if (ifp1 == ifp)
-			continue;
-		if ((state = D_CSTATE(ifp1)) == NULL)
-			continue;
-		switch(state->state) {
-		case DHS_REBOOT:
-		case DHS_RENEW:
-		case DHS_REBIND:
-		case DHS_BOUND:
-		case DHS_INFORM:
-			break;
-		default:
-			continue;
-		}
-		if (state->lease.addr.s_addr == addr->s_addr)
-			return 1;
-	}
-	return 0;
-}
-#endif
-
 static void
 dhcp_reboot(struct interface *ifp)
 {
 	struct if_options *ifo;
 	struct dhcp_state *state = D_STATE(ifp);
-#ifdef ARP
-	struct ipv4_addr *ia;
-#endif
 
 	if (state == NULL || state->state == DHS_NONE)
 		return;
@@ -2723,25 +2824,11 @@ dhcp_reboot(struct interface *ifp)
 	loginfox("%s: rebinding lease of %s",
 	    ifp->name, inet_ntoa(state->lease.addr));
 
-#ifdef ARP
-#ifndef KERNEL_RFC5227
+#if defined(ARP) && !defined(KERNEL_RFC5227)
 	/* Create the DHCP ARP state so we can defend it. */
 	(void)dhcp_arp_new(ifp, &state->lease.addr);
 #endif
 
-	/* If the address exists on the interface and no other interface
-	 * is currently using it then announce it to ensure this
-	 * interface gets the reply. */
-	ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL);
-	if (ia != NULL &&
-	    !(ifp->ctx->options & DHCPCD_TEST) &&
-#ifdef IN_IFF_NOTUSEABLE
-	    !(ia->addr_flags & IN_IFF_NOTUSEABLE) &&
-#endif
-	    dhcp_activeaddr(ifp, &state->lease.addr) == 0)
-		arp_ifannounceaddr(ifp, &state->lease.addr);
-#endif
-
 	dhcp_new_xid(ifp);
 	state->lease.server.s_addr = INADDR_ANY;
 	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
@@ -2769,6 +2856,7 @@ void
 dhcp_drop(struct interface *ifp, const char *reason)
 {
 	struct dhcp_state *state = D_STATE(ifp);
+	struct if_options *ifo = ifp->options;
 
 	/* dhcp_start may just have been called and we don't yet have a state
 	 * but we do have a timeout, so punt it. */
@@ -2785,9 +2873,7 @@ dhcp_drop(struct interface *ifp, const c
 	state->arping_index = -1;
 #endif
 
-	if (ifp->options->options & DHCPCD_RELEASE &&
-	    !(ifp->options->options & DHCPCD_INFORM))
-	{
+	if (ifo->options & DHCPCD_RELEASE && !(ifo->options & DHCPCD_INFORM)) {
 		/* Failure to send the release may cause this function to
 		 * re-enter so guard by setting the state. */
 		if (state->state == DHS_RELEASE)
@@ -2833,7 +2919,7 @@ dhcp_drop(struct interface *ifp, const c
 	state->new = NULL;
 	state->new_len = 0;
 	state->reason = reason;
-	if (ifp->options->options & DHCPCD_CONFIGURE)
+	if (ifo->options & DHCPCD_CONFIGURE)
 		ipv4_applyaddr(ifp);
 	else {
 		state->addr = NULL;
@@ -2844,8 +2930,7 @@ dhcp_drop(struct interface *ifp, const c
 	state->old = NULL;
 	state->old_len = 0;
 	state->lease.addr.s_addr = 0;
-	ifp->options->options &= ~(DHCPCD_CSR_WARNED |
-	    DHCPCD_ROUTER_HOST_ROUTE_WARNED);
+	ifo->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED);
 
 	/* Close DHCP ports so a changed interface family is picked
 	 * up by a new BPF state. */
@@ -2989,7 +3074,7 @@ dhcp_handledhcp(struct interface *ifp, s
 	char *msg;
 	bool bootp_copied;
 	uint32_t v6only_time = 0;
-	bool use_v6only = false;
+	bool use_v6only = false, has_auto_conf = false;
 #ifdef AUTH
 	const uint8_t *auth;
 	size_t auth_len;
@@ -3143,8 +3228,7 @@ dhcp_handledhcp(struct interface *ifp, s
 	/* Ensure that no reject options are present */
 	for (i = 1; i < 255; i++) {
 		if (has_option_mask(ifo->rejectmask, i) &&
-		    get_option_uint8(ifp->ctx, &tmp,
-		    bootp, bootp_len, (uint8_t)i) == 0)
+		    get_option(ifp->ctx, bootp, bootp_len, (uint8_t)i, NULL))
 		{
 			LOGDHCP(LOG_WARNING, "reject DHCP");
 			return;
@@ -3192,8 +3276,7 @@ dhcp_handledhcp(struct interface *ifp, s
 	/* Ensure that all required options are present */
 	for (i = 1; i < 255; i++) {
 		if (has_option_mask(ifo->requiremask, i) &&
-		    get_option_uint8(ifp->ctx, &tmp,
-		    bootp, bootp_len, (uint8_t)i) != 0)
+		    !get_option(ifp->ctx, bootp, bootp_len, (uint8_t)i, NULL))
 		{
 			/* If we are BOOTP, then ignore the need for serverid.
 			 * To ignore BOOTP, require dhcp_message_type.
@@ -3209,7 +3292,8 @@ dhcp_handledhcp(struct interface *ifp, s
 
 	if (has_option_mask(ifo->requestmask, DHO_IPV6_PREFERRED_ONLY)) {
 		if (get_option_uint32(ifp->ctx, &v6only_time, bootp, bootp_len,
-		    DHO_IPV6_PREFERRED_ONLY) == 0 && (state->state == DHS_DISCOVER ||
+		    DHO_IPV6_PREFERRED_ONLY) == 0 &&
+		    (state->state == DHS_DISCOVER ||
 		    state->state == DHS_REBOOT || state->state == DHS_NONE))
 		{
 			char v6msg[128];
@@ -3225,8 +3309,8 @@ dhcp_handledhcp(struct interface *ifp, s
 	}
 
 	/* DHCP Auto-Configure, RFC 2563 */
-	if (type == DHCP_OFFER && bootp->yiaddr == 0) {
-		LOGDHCP(LOG_WARNING, "no address given");
+	if (type == DHCP_OFFER && bootp->yiaddr == INADDR_ANY) {
+		LOGDHCP(LOG_WARNING, "no address offered");
 		if ((msg = get_option_string(ifp->ctx,
 		    bootp, bootp_len, DHO_MESSAGE)))
 		{
@@ -3238,6 +3322,7 @@ dhcp_handledhcp(struct interface *ifp, s
 		    get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len,
 		    DHO_AUTOCONFIGURE) == 0)
 		{
+			has_auto_conf = true;
 			switch (tmp) {
 			case 0:
 				LOGDHCP(LOG_WARNING, "IPv4LL disabled from");
@@ -3256,24 +3341,27 @@ dhcp_handledhcp(struct interface *ifp, s
 				    ifp->name, tmp);
 				break;
 			}
-			eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
-			eloop_timeout_add_sec(ifp->ctx->eloop,
-			    use_v6only ? v6only_time : DHCP_MAX,
-			    dhcp_discover, ifp);
 		}
 #endif
-		return;
 	}
 
 	if (use_v6only) {
 		dhcp_drop(ifp, "EXPIRE");
 		dhcp_unlink(ifp->ctx, state->leasefile);
+	}
+	if (use_v6only || has_auto_conf) {
 		eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
-		eloop_timeout_add_sec(ifp->ctx->eloop, v6only_time,
+		eloop_timeout_add_sec(ifp->ctx->eloop,
+		    use_v6only ? v6only_time : DHCP_MAX,
 		    dhcp_discover, ifp);
 		return;
 	}
 
+	/* No hints as what to do with no address?
+	 * All we can do is continue. */
+	if (type == DHCP_OFFER && bootp->yiaddr == INADDR_ANY)
+		return;
+
 	/* Ensure that the address offered is valid */
 	if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) &&
 	    (bootp->ciaddr == INADDR_ANY || bootp->ciaddr == INADDR_BROADCAST)
@@ -3471,61 +3559,63 @@ is_packet_udp_bootp(void *packet, size_t
 	return true;
 }
 
+/* IPv4 pseudo header used for computing TCP and UDP checksums. */
+struct ip_pseudo {
+	struct in_addr ipp_src;
+	struct in_addr ipp_dst;
+	uint8_t ipp_pad; /* must be zero */
+	uint8_t ipp_p;
+	uint16_t ipp_len;
+};
+
 /* Lengths have already been checked. */
 static bool
-checksums_valid(void *packet,
+checksums_valid(const void *packet,
     struct in_addr *from, unsigned int flags)
 {
-	struct ip *ip = packet;
-	union pip {
-		struct ip ip;
-		uint16_t w[sizeof(struct ip) / 2];
-	} pip = {
-		.ip = {
-			.ip_p = IPPROTO_UDP,
-			.ip_src = ip->ip_src,
-			.ip_dst = ip->ip_dst,
-		}
-	};
+	const struct ip *ip = packet;
 	size_t ip_hlen;
 	struct udphdr udp;
-	char *udpp, *uh_sump;
+	const char *udpp;
 	uint32_t csum;
+	struct ip_pseudo ip_pseudo;
+	/* We create a buffer to copy ip_pseudo into and send that to
+	 * in_cksum() to avoid memory issues. */
+	uint8_t ip_pseudo_buf[sizeof(struct ip_pseudo)];
 
 	if (from != NULL)
 		from->s_addr = ip->ip_src.s_addr;
 
 	ip_hlen = (size_t)ip->ip_hl * 4;
+	/* RFC 1071 states that the check of the checksum is equal to 0. */
 	if (in_cksum(ip, ip_hlen, NULL) != 0)
 		return false;
 
 	if (flags & BPF_PARTIALCSUM)
 		return true;
 
-	udpp = (char *)ip + ip_hlen;
+	udpp = (const char *)ip + ip_hlen;
 	memcpy(&udp, udpp, sizeof(udp));
+	/* RFC 768 states that zero means no checksum to verify. */
 	if (udp.uh_sum == 0)
 		return true;
 
 	/* UDP checksum is based on a pseudo IP header alongside
 	 * the UDP header and payload. */
-	pip.ip.ip_len = udp.uh_ulen;
-	csum = 0;
-
-	/* Need to zero the UDP sum in the packet for the checksum to work. */
-	uh_sump = udpp + offsetof(struct udphdr, uh_sum);
-	memset(uh_sump, 0, sizeof(udp.uh_sum));
+	ip_pseudo.ipp_src = ip->ip_src;
+	ip_pseudo.ipp_dst = ip->ip_dst;
+	ip_pseudo.ipp_pad = 0;
+	ip_pseudo.ipp_p = ip->ip_p;
+	ip_pseudo.ipp_len = udp.uh_ulen;
+	memcpy(ip_pseudo_buf, &ip_pseudo, sizeof(ip_pseudo_buf));
 
 	/* Checksum pseudo header and then UDP + payload. */
-	in_cksum(pip.w, sizeof(pip.w), &csum);
+	csum = 0;
+	in_cksum(ip_pseudo_buf, sizeof(ip_pseudo_buf), &csum);
 	csum = in_cksum(udpp, ntohs(udp.uh_ulen), &csum);
 
-#if 0	/* Not needed, just here for completeness. */
-	/* Put the checksum back. */
-	memcpy(uh_sump, &udp.uh_sum, sizeof(udp.uh_sum));
-#endif
-
-	return csum == udp.uh_sum;
+	/* RFC 1071 states that the check of the checksum is equal to 0. */
+	return csum == 0;
 }
 
 static void
@@ -3626,7 +3716,8 @@ static void
 dhcp_readbpf(void *arg, unsigned short events)
 {
 	struct interface *ifp = arg;
-	uint8_t buf[FRAMELEN_MAX];
+	/* Sparc64 needs this buffer aligned */
+	alignas(sizeof(struct ip *)) uint8_t buf[FRAMELEN_MAX];
 	ssize_t bytes;
 	struct dhcp_state *state = D_STATE(ifp);
 	struct bpf *bpf = state->bpf;
@@ -4213,13 +4304,8 @@ dhcp_abort(struct interface *ifp)
 
 	eloop_timeout_delete(ifp->ctx->eloop, dhcp_start1, ifp);
 
-	if (state != NULL && state->added) {
+	if (state != NULL && state->added)
 		rt_build(ifp->ctx, AF_INET);
-#ifdef ARP
-		if (ifp->options->options & DHCPCD_ARP)
-			arp_announceaddr(ifp->ctx, &state->addr->addr);
-#endif
-	}
 }
 
 struct ipv4_addr *
@@ -4261,18 +4347,15 @@ dhcp_handleifa(int cmd, struct ipv4_addr
 
 	ifo = ifp->options;
 
-#ifdef PRIVSEP
-	if (IN_PRIVSEP_SE(ifp->ctx) &&
-	    !(ifp->ctx->options & (DHCPCD_MANAGER | DHCPCD_CONFIGURE)) &&
+	if (!(ifp->ctx->options & (DHCPCD_MANAGER | DHCPCD_CONFIGURE)) &&
 	    IN_ARE_ADDR_EQUAL(&state->lease.addr, &ia->addr))
 	{
+		uint8_t old_state = state->added;
+
 		state->addr = ia;
 		state->added = STATE_ADDED;
-		dhcp_closebpf(ifp);
-		if (ps_inet_openbootp(ia) == -1)
-		    logerr(__func__);
+		dhcp_bound(ifp, old_state);
 	}
-#endif
 
 	/* If we have requested a specific address, return now.
 	 * The below code is only for when inform or static has been

Index: src/external/bsd/dhcpcd/dist/src/dhcp6.c
diff -u src/external/bsd/dhcpcd/dist/src/dhcp6.c:1.34 src/external/bsd/dhcpcd/dist/src/dhcp6.c:1.35
--- src/external/bsd/dhcpcd/dist/src/dhcp6.c:1.34	Fri May 24 16:09:09 2024
+++ src/external/bsd/dhcpcd/dist/src/dhcp6.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -112,8 +112,9 @@ struct dhcp6_ia_addr {
 };
 __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? */
+/* Some compilers do not support packed structures.
+ * We manually decode this. */
+#if 0
 struct dhcp6_pd_addr {
 	uint32_t pltime;
 	uint32_t vltime;
@@ -121,6 +122,13 @@ struct dhcp6_pd_addr {
 	struct in6_addr prefix;
 } __packed;
 __CTASSERT(sizeof(struct dhcp6_pd_addr) == 8 + 1 + 16);
+#endif
+
+#define DHCP6_PD_ADDR_SIZE	(8 + 1 + 16)
+#define DHCP6_PD_ADDR_PLTIME	0
+#define DHCP6_PD_ADDR_VLTIME	4
+#define DHCP6_PD_ADDR_PLEN	8
+#define DHCP6_PD_ADDR_PREFIX	9
 
 struct dhcp6_op {
 	uint16_t type;
@@ -179,6 +187,7 @@ static const char * const dhcp6_statuses
 
 static void dhcp6_bind(struct interface *, const char *, const char *);
 static void dhcp6_failinform(void *);
+static void dhcp6_startrebind(void *arg);
 static void dhcp6_recvaddr(void *, unsigned short);
 static void dhcp6_startdecline(struct interface *);
 static void dhcp6_startrequest(struct interface *);
@@ -195,6 +204,11 @@ static int dhcp6_hasprefixdelegation(str
 	!((ia)->flags & IPV6_AF_STALE) && \
 	(ia)->prefix_vltime != 0)
 
+
+/* Gets a pointer to the length part of the option to fill it
+ * in later. */
+#define NEXTLEN(p) ((p) + offsetof(struct dhcp6_option, len))
+
 void
 dhcp6_printoptions(const struct dhcpcd_ctx *ctx,
     const struct dhcp_opt *opts, size_t opts_len)
@@ -262,29 +276,28 @@ dhcp6_makeuser(void *data, const struct 
 	return sizeof(o) + olen;
 }
 
+#ifndef SMALL
+/* DHCPv6 Option 16 (Vendor Class Option) */
 static size_t
 dhcp6_makevendor(void *data, const struct interface *ifp)
 {
 	const struct if_options *ifo;
-	size_t len, vlen, i;
+	size_t len = 0, optlen, vlen, i;
 	uint8_t *p;
 	const struct vivco *vivco;
 	struct dhcp6_option o;
 
 	ifo = ifp->options;
-	len = sizeof(uint32_t); /* IANA PEN */
-	if (ifo->vivco_en) {
-		vlen = 0;
+	if (ifo->vivco_len > 0) {
 		for (i = 0, vivco = ifo->vivco;
 		    i < ifo->vivco_len;
 		    i++, vivco++)
-			vlen += sizeof(uint16_t) + vivco->len;
-		len += vlen;
+			len += sizeof(o) + sizeof(uint32_t) + sizeof(uint16_t) + vivco->len;
 	} else if (ifo->vendorclassid[0] != '\0') {
 		/* dhcpcd owns DHCPCD_IANA_PEN.
 		 * If you need your own string, get your own IANA PEN. */
 		vlen = strlen(ifp->ctx->vendor);
-		len += sizeof(uint16_t) + vlen;
+		len += sizeof(o) + sizeof(uint32_t) + sizeof(uint16_t) + vlen;
 	} else
 		return 0;
 
@@ -298,19 +311,19 @@ dhcp6_makevendor(void *data, const struc
 		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) {
+		if (ifo->vivco_len > 0) {
 			for (i = 0, vivco = ifo->vivco;
 			    i < ifo->vivco_len;
-			    i++, vivco++)
-			{
+			    i++, vivco++) {
+				optlen = sizeof(uint32_t) + sizeof(uint16_t) + vivco->len;
+				o.code = htons(D6_OPTION_VENDOR_CLASS);
+				o.len = htons((uint16_t)optlen);
+				memcpy(p, &o, sizeof(o));
+				p += sizeof(o);
+				pen = htonl(vivco->en);
+				memcpy(p, &pen, sizeof(pen));
+				p += sizeof(pen);
 				hvlen = htons((uint16_t)vivco->len);
 				memcpy(p, &hvlen, sizeof(hvlen));
 				p += sizeof(hvlen);
@@ -318,15 +331,88 @@ dhcp6_makevendor(void *data, const struc
 				p += vivco->len;
 			}
 		} else if (ifo->vendorclassid[0] != '\0') {
+			o.code = htons(D6_OPTION_VENDOR_CLASS);
+			o.len = htons((uint16_t)len);
+			memcpy(p, &o, sizeof(o));
+			p += sizeof(o);
+			pen = htonl(DHCPCD_IANA_PEN);
+			memcpy(p, &pen, sizeof(pen));
+			p += sizeof(pen);
 			hvlen = htons((uint16_t)vlen);
 			memcpy(p, &hvlen, sizeof(hvlen));
 			p += sizeof(hvlen);
 			memcpy(p, ifp->ctx->vendor, vlen);
 		}
 	}
+	return len;
+}
 
-	return sizeof(o) + len;
+/* DHCPv6 Option 17 (Vendor-Specific Information Option) */
+static size_t
+dhcp6_makevendoropts(void *data, const struct interface *ifp)
+{
+	uint8_t *p = data, *olenp;
+	const struct if_options *ifo = ifp->options;
+	size_t len = 0, olen;
+	const struct vsio *vsio, *vsio_endp = ifo->vsio6 + ifo->vsio6_len;
+	const struct vsio_so *so, *so_endp;
+	struct dhcp6_option o;
+	uint32_t en;
+	uint16_t opt, slen;
+
+	for (vsio = ifo->vsio6; vsio != vsio_endp; ++vsio) {
+		if (vsio->so_len == 0)
+			continue;
+
+		if (p != NULL) {
+			olenp = NEXTLEN(p);
+			o.code = htons(D6_OPTION_VENDOR_OPTS);
+			o.len = 0;
+			memcpy(p, &o, sizeof(o));
+			p += sizeof(o);
+
+			en = htonl(vsio->en);
+			memcpy(p, &en, sizeof(en));
+			p += sizeof(en);
+		} else
+			olenp = NULL;
+
+		olen = sizeof(en);
+
+		so_endp = vsio->so + vsio->so_len;
+		for (so = vsio->so; so != so_endp; so++) {
+			if (olen + sizeof(opt) + sizeof(slen)
+			    + so->len > UINT16_MAX)
+			{
+				logerrx("%s: option too big", __func__);
+				break;
+			}
+
+			if (p != NULL) {
+				opt = htons(so->opt);
+				memcpy(p, &opt, sizeof(opt));
+				p += sizeof(opt);
+				slen = htons(so->len);
+				memcpy(p, &slen, sizeof(slen));
+				p += sizeof(slen);
+				memcpy(p, so->data, so->len);
+				p += so->len;
+			}
+
+			olen += sizeof(opt) + sizeof(slen) + so->len;
+		}
+
+		if (olenp != NULL) {
+			slen = htons((uint16_t)olen);
+			memcpy(olenp, &slen, sizeof(slen));
+		}
+
+		len += sizeof(o) + olen;
+	}
+
+	return len;
 }
+#endif
 
 static void *
 dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len)
@@ -793,8 +879,13 @@ dhcp6_makemessage(struct interface *ifp)
 
 	if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS))
 		len += dhcp6_makeuser(NULL, ifp);
+
+#ifndef SMALL
 	if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
 		len += dhcp6_makevendor(NULL, ifp);
+	if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS))
+		len += dhcp6_makevendoropts(NULL, ifp);
+#endif
 
 	/* IA */
 	m = NULL;
@@ -838,7 +929,7 @@ dhcp6_makemessage(struct interface *ifp)
 				continue;
 			if (ap->ia_type == D6_OPTION_IA_PD) {
 #ifndef SMALL
-				len += sizeof(o) + sizeof(struct dhcp6_pd_addr);
+				len += sizeof(o) + DHCP6_PD_ADDR_SIZE;
 				if (ap->prefix_exclude_len)
 					len += sizeof(o) + 1 +
 					    (uint8_t)((ap->prefix_exclude_len -
@@ -941,7 +1032,6 @@ dhcp6_makemessage(struct interface *ifp)
 		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. */
@@ -959,7 +1049,7 @@ dhcp6_makemessage(struct interface *ifp)
 
 	for (l = 0; IA && l < ifo->ia_len; l++) {
 		ifia = &ifo->ia[l];
-		o_lenp = NEXTLEN;
+		o_lenp = NEXTLEN(p);
 		/* 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,
@@ -989,19 +1079,14 @@ dhcp6_makemessage(struct interface *ifp)
 				continue;
 			if (ap->ia_type == D6_OPTION_IA_PD) {
 #ifndef SMALL
-				struct dhcp6_pd_addr pdp = {
-				    .prefix_len = ap->prefix_len,
-				    /*
-				     * RFC 8415 21.22 states that the
-				     * valid and preferred lifetimes sent by
-				     * the client SHOULD be zero and MUST
-				     * be ignored by the server.
-				     */
-				};
+				uint8_t pdp[DHCP6_PD_ADDR_SIZE];
+
+				memset(pdp, 0, DHCP6_PD_ADDR_PLEN);
+				pdp[DHCP6_PD_ADDR_PLEN] = (uint8_t)ap->prefix_len;
+				memcpy(pdp + DHCP6_PD_ADDR_PREFIX, &ap->prefix,
+				    DHCP6_PD_ADDR_SIZE - DHCP6_PD_ADDR_PREFIX);
+				COPYIN(D6_OPTION_IAPREFIX, pdp, sizeof(pdp));
 
-				/* 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));
 
@@ -1060,7 +1145,7 @@ dhcp6_makemessage(struct interface *ifp)
 	    state->send->type != DHCP6_DECLINE &&
 	    n_options)
 	{
-		o_lenp = NEXTLEN;
+		o_lenp = NEXTLEN(p);
 		o.len = 0;
 		COPYIN1(D6_OPTION_ORO, 0);
 		for (l = 0, opt = ifp->ctx->dhcp6_opts;
@@ -1118,14 +1203,19 @@ dhcp6_makemessage(struct interface *ifp)
 
 	if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS))
 		p += dhcp6_makeuser(p, ifp);
+
+#ifndef SMALL
 	if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
 		p += dhcp6_makevendor(p, ifp);
+	if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS))
+		p += dhcp6_makevendoropts(p, ifp);
+#endif
 
 	if (state->send->type != DHCP6_RELEASE &&
 	    state->send->type != DHCP6_DECLINE)
 	{
 		if (fqdn != FQDN_DISABLE) {
-			o_lenp = NEXTLEN;
+			o_lenp = NEXTLEN(p);
 			COPYIN1(D6_OPTION_FQDN, 0);
 			if (hl == 0)
 				*p = D6_FQDN_NONE;
@@ -1187,14 +1277,14 @@ dhcp6_get_op(uint16_t type)
 }
 
 static void
-dhcp6_freedrop_addrs(struct interface *ifp, int drop,
+dhcp6_freedrop_addrs(struct interface *ifp, int drop, unsigned int notflags,
     const struct interface *ifd)
 {
 	struct dhcp6_state *state;
 
 	state = D6_STATE(ifp);
 	if (state) {
-		ipv6_freedrop_addrs(&state->addrs, drop, ifd);
+		ipv6_freedrop_addrs(&state->addrs, drop, notflags, ifd);
 		if (drop)
 			rt_build(ifp->ctx, AF_INET6);
 	}
@@ -1208,7 +1298,7 @@ static void dhcp6_delete_delegates(struc
 	if (ifp->ctx->ifaces) {
 		TAILQ_FOREACH(ifp0, ifp->ctx->ifaces, next) {
 			if (ifp0 != ifp)
-				dhcp6_freedrop_addrs(ifp0, 1, ifp);
+				dhcp6_freedrop_addrs(ifp0, 1, 0, ifp);
 		}
 	}
 }
@@ -1408,7 +1498,7 @@ sent:
 		state->RT = RT * 2;
 		if (state->RT < RT) /* Check overflow */
 			state->RT = RT;
-		if (state->MRC == 0 || state->RTC < state->MRC)
+		if (state->MRC == 0 || state->RTC <= state->MRC)
 			eloop_timeout_add_msec(ctx->eloop,
 			    RT, callback, ifp);
 		else if (state->MRC != 0 && state->MRCcallback)
@@ -1570,10 +1660,6 @@ dhcp6_dadcallback(void *arg)
 	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;
 
@@ -1656,16 +1742,22 @@ static void
 dhcp6_startdiscover(void *arg)
 {
 	struct interface *ifp;
+	struct if_options *ifo;
 	struct dhcp6_state *state;
 	int llevel;
 	struct ipv6_addr *ia;
 
 	ifp = arg;
 	state = D6_STATE(ifp);
+	ifo = ifp->options;
 #ifndef SMALL
 	if (state->reason == NULL || strcmp(state->reason, "TIMEOUT6") != 0)
 		dhcp6_delete_delegates(ifp);
 #endif
+	/* Ensure we never request INFO_REFRESH_TIME,
+ 	 * this only belongs in Information-Request messages */
+	del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
+
 	if (state->new == NULL && !state->failed)
 		llevel = LOG_INFO;
 	else
@@ -1678,11 +1770,6 @@ dhcp6_startdiscover(void *arg)
 	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 we fail to renew or confirm, our requested addreses will
 	 * be marked as stale.
 	 To re-request them, just mark them as not stale. */
@@ -1703,9 +1790,11 @@ dhcp6_startinform(void *arg)
 	struct interface *ifp;
 	struct dhcp6_state *state;
 	int llevel;
+	struct if_options *ifo;
 
 	ifp = arg;
 	state = D6_STATE(ifp);
+	ifo = ifp->options;
 	llevel = state->failed ? LOG_DEBUG : LOG_INFO;
 	logmessage(llevel, "%s: requesting DHCPv6 information", ifp->name);
 	state->state = DH6S_INFORM;
@@ -1715,7 +1804,9 @@ dhcp6_startinform(void *arg)
 	state->MRT = state->inf_max_rt;
 	state->MRC = 0;
 
-	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+	/* Ensure we always request INFO_REFRESH_TIME as per rfc8415 */
+	add_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
+
 	if (dhcp6_makemessage(ifp) == -1) {
 		logerr("%s: %s", __func__, ifp->name);
 		return;
@@ -1745,41 +1836,15 @@ dhcp6_startdiscoinform(struct interface 
 }
 
 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)
+dhcp6_fail(struct interface *ifp, bool drop)
 {
 	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);
+	if (drop) {
+		dhcp6_freedrop_addrs(ifp, 1,
+		    IPV6_AF_DELEGATED | IPV6_AF_PFXDELEGATION, NULL);
 #ifndef SMALL
 		dhcp6_delete_delegates(ifp);
 #endif
@@ -1792,12 +1857,21 @@ dhcp6_fail(struct interface *ifp)
 			script_runreason(ifp, "EXPIRE6");
 		dhcp_unlink(ifp->ctx, state->leasefile);
 		dhcp6_addrequestedaddrs(ifp);
+		eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+	} else if ((state->state == DH6S_CONFIRM || state->state == DH6S_REBIND) &&
+	           ifp->options->options & DHCPCD_LASTLEASE) {
+		dhcp6_bind(ifp, NULL, NULL);
+		state->state = DH6S_REBIND;
+		dhcp6_startrebind(ifp);
+		return;
+	} else if (state->new) {
+		script_runreason(ifp, "TIMEOUT6");
+		// We need to keep the expire timeout alive
 	}
 
 	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);
 	}
 }
 
@@ -1817,7 +1891,10 @@ dhcp6_failconfirm(void *arg)
 
 	logmessage(llevel, "%s: failed to confirm prior DHCPv6 address",
 	    ifp->name);
-	dhcp6_fail(ifp);
+	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendconfirm, ifp);
+
+	/* RFC8415 18.2.3 says that prior addresses SHOULD be used on failure. */
+	dhcp6_fail(ifp, false);
 }
 
 static void
@@ -1827,7 +1904,7 @@ dhcp6_failrequest(void *arg)
 	int llevel = dhcp6_failloglevel(ifp);
 
 	logmessage(llevel, "%s: failed to request DHCPv6 address", ifp->name);
-	dhcp6_fail(ifp);
+	dhcp6_fail(ifp, true);
 }
 
 static void
@@ -1838,17 +1915,21 @@ dhcp6_failinform(void *arg)
 
 	logmessage(llevel, "%s: failed to request DHCPv6 information",
 	    ifp->name);
-	dhcp6_fail(ifp);
+	dhcp6_fail(ifp, true);
 }
 
 #ifndef SMALL
 static void
-dhcp6_failrebind(void *arg)
+dhcp6_failrebindpd(void *arg)
 {
 	struct interface *ifp = arg;
 
 	logerrx("%s: failed to rebind prior DHCPv6 delegation", ifp->name);
-	dhcp6_fail(ifp);
+	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp);
+
+	/* RFC8415 18.2.3 says that prior addresses SHOULD be used on failure.
+	 * 18.2 says REBIND rather than CONFIRM with PD but use CONFIRM timings. */
+	dhcp6_fail(ifp, false);
 }
 
 static int
@@ -1875,47 +1956,39 @@ 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);
+
+	state->IMD = REB_MAX_DELAY;
+	state->IRT = REB_TIMEOUT;
+	state->MRT = REB_MAX_RT;
+	state->RTC = 0;
+	state->MRC = 0;
+
 	if (state->state == DH6S_RENEW)
 		logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name);
-	else
+	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
+		/* RFC 8415 18.2.5 */
+		if (dhcp6_hasprefixdelegation(ifp)) {
+			state->IMD = CNF_MAX_DELAY;
+			state->IRT = CNF_TIMEOUT;
+			state->MRT = CNF_MAX_RT;
+			eloop_timeout_add_sec(ifp->ctx->eloop,
+			    CNF_MAX_RD, dhcp6_failrebindpd, ifp);
+		}
 #endif
-	{
-		state->IMD = REB_MAX_DELAY;
-		state->IRT = REB_TIMEOUT;
-		state->MRT = REB_MAX_RT;
 	}
 
+	state->state = DH6S_REBIND;
 	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
@@ -1984,7 +2057,7 @@ dhcp6_startexpire(void *arg)
 	eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp);
 
 	logerrx("%s: DHCPv6 lease expired", ifp->name);
-	dhcp6_fail(ifp);
+	dhcp6_fail(ifp, true);
 }
 
 static void
@@ -1993,7 +2066,7 @@ dhcp6_faildecline(void *arg)
 	struct interface *ifp = arg;
 
 	logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name);
-	dhcp6_fail(ifp);
+	dhcp6_fail(ifp, true);
 }
 
 static void
@@ -2129,6 +2202,12 @@ dhcp6_checkstatusok(const struct interfa
 	state->lerror = code;
 	errno = 0;
 
+	/* RFC 8415 18.2.10 */
+	if (code == D6_STATUS_USEMULTICAST) {
+		logdebugx("%s: server sent USEMULTICAST", ifp->name);
+		state->unicast = in6addr_any;
+	}
+
 	/* code cannot be D6_STATUS_OK, so there is a failure */
 	if (ifp->ctx->options & DHCPCD_TEST)
 		eloop_exit(ifp->ctx->eloop, EXIT_FAILURE);
@@ -2255,7 +2334,8 @@ dhcp6_findpd(struct interface *ifp, cons
 	int i;
 	uint8_t nb, *pw;
 	uint16_t ol;
-	struct dhcp6_pd_addr pdp;
+	uint32_t pdp_vltime, pdp_pltime;
+	uint8_t pdp_plen;
 	struct in6_addr pdp_prefix;
 
 	i = 0;
@@ -2265,37 +2345,43 @@ dhcp6_findpd(struct interface *ifp, cons
 		nd = o + ol;
 		l -= (size_t)(nd - d);
 		d = nd;
-		if (ol < sizeof(pdp)) {
+		if (ol < DHCP6_PD_ADDR_SIZE) {
 			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);
+		memcpy(&pdp_pltime, o, sizeof(pdp_pltime));
+		o += sizeof(pdp_pltime);
+		memcpy(&pdp_vltime, o, sizeof(pdp_vltime));
+		o += sizeof(pdp_vltime);
+		memcpy(&pdp_plen, o, sizeof(pdp_plen));
+		o += sizeof(pdp_plen);
+
+		pdp_pltime = ntohl(pdp_pltime);
+		pdp_vltime = ntohl(pdp_vltime);
 		/* RFC 3315 22.6 */
-		if (pdp.pltime > pdp.vltime) {
+		if (pdp_pltime > pdp_vltime) {
 			errno = EINVAL;
 			logerrx("%s: IA Prefix pltime %"PRIu32
 			    " > vltime %"PRIu32,
-			    ifp->name, pdp.pltime, pdp.vltime);
+			    ifp->name, pdp_pltime, pdp_vltime);
 			continue;
 		}
 
-		o += sizeof(pdp);
-		ol = (uint16_t)(ol - sizeof(pdp));
+		memcpy(&pdp_prefix, o, sizeof(pdp_prefix));
+		o += sizeof(pdp_prefix);
+		ol = (uint16_t)(ol - sizeof(pdp_pltime) - sizeof(pdp_vltime) -
+		    sizeof(pdp_plen) - sizeof(pdp_prefix));
 
-		/* 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);
+			a = ipv6_newaddr(ifp, &pdp_prefix, pdp_plen,
+			    IPV6_AF_PFXDELEGATION);
 			if (a == NULL)
 				break;
 			a->created = *acquired;
@@ -2304,16 +2390,16 @@ dhcp6_findpd(struct interface *ifp, cons
 			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;
+			if (!(a->flags & IPV6_AF_PFXDELEGATION))
+				a->flags |= IPV6_AF_NEW | IPV6_AF_PFXDELEGATION;
 			a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED);
-			if (a->prefix_vltime != pdp.vltime)
+			if (a->prefix_vltime != pdp_vltime)
 				a->flags |= IPV6_AF_NEW;
 		}
 
 		a->acquired = *acquired;
-		a->prefix_pltime = pdp.pltime;
-		a->prefix_vltime = pdp.vltime;
+		a->prefix_pltime = pdp_pltime;
+		a->prefix_vltime = pdp_vltime;
 
 		if (a->prefix_pltime && a->prefix_pltime < state->lowpl)
 			state->lowpl = a->prefix_pltime;
@@ -2394,6 +2480,8 @@ dhcp6_findia(struct interface *ifp, stru
 	i = e = 0;
 	state = D6_STATE(ifp);
 	TAILQ_FOREACH(ap, &state->addrs, next) {
+		/* Anything not from a lease for this interface should be
+		 * marked as stale. */
 		if (!(ap->flags & IPV6_AF_DELEGATED))
 			ap->flags |= IPV6_AF_STALE;
 	}
@@ -2571,7 +2659,7 @@ dhcp6_deprecateaddrs(struct ipv6_addrhea
 #ifndef SMALL
 		/* If we delegated from this prefix, deprecate or remove
 		 * the delegations. */
-		if (ia->flags & IPV6_AF_DELEGATEDPFX)
+		if (ia->flags & IPV6_AF_PFXDELEGATION)
 			dhcp6_deprecatedele(ia);
 #endif
 
@@ -2582,7 +2670,7 @@ dhcp6_deprecateaddrs(struct ipv6_addrhea
 			continue;
 		}
 		TAILQ_REMOVE(addrs, ia, next);
-		if (ia->flags & IPV6_AF_EXTENDED)
+		if (!(ia->flags & IPV6_AF_EXTENDED))
 			ipv6_deleteaddr(ia);
 		ipv6_freeaddr(ia);
 	}
@@ -2686,8 +2774,7 @@ dhcp6_readlease(struct interface *ifp, i
 	}
 
 	if (state->expire != ND6_INFINITE_LIFETIME &&
-	    (time_t)state->expire < now - mtime &&
-	    !(ifp->options->options & DHCPCD_LASTLEASE_EXTEND))
+	    (time_t)state->expire < now - mtime)
 	{
 		logdebugx("%s: discarding expired lease", ifp->name);
 		bytes = 0;
@@ -2732,7 +2819,7 @@ out:
 	return bytes;
 
 ex:
-	dhcp6_freedrop_addrs(ifp, 0, NULL);
+	dhcp6_freedrop_addrs(ifp, 0, IPV6_AF_DELEGATED, NULL);
 	dhcp_unlink(ifp->ctx, state->leasefile);
 	free(state->new);
 	state->new = NULL;
@@ -2745,19 +2832,20 @@ static void
 dhcp6_startinit(struct interface *ifp)
 {
 	struct dhcp6_state *state;
+	struct if_options *ifo;
 	ssize_t r;
 	uint8_t has_ta, has_non_ta;
 	size_t i;
 
 	state = D6_STATE(ifp);
-	state->state = DH6S_INIT;
+	ifo = ifp->options;
 	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) {
+	for (i = 0; i < ifo->ia_len; i++) {
+		switch (ifo->ia[i].ia_type) {
 		case D6_OPTION_IA_TA:
 			has_ta = 1;
 			break;
@@ -2768,18 +2856,19 @@ dhcp6_startinit(struct interface *ifp)
 
 	if (!(ifp->ctx->options & DHCPCD_TEST) &&
 	    !(has_ta && !has_non_ta) &&
-	    ifp->options->reboot != 0)
+	    ifo->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))
+		    !(ifo->options & DHCPCD_ANONYMOUS))
 		{
 			/* RFC 3633 section 12.1 */
 #ifndef SMALL
-			if (dhcp6_hasprefixdelegation(ifp))
+			if (state->state == DH6S_MANUALREBIND ||
+			    dhcp6_hasprefixdelegation(ifp))
 				dhcp6_startrebind(ifp);
 			else
 #endif
@@ -2947,7 +3036,7 @@ dhcp6_delegate_prefix(struct interface *
 		k = 0;
 		carrier_warned = false;
 		TAILQ_FOREACH(ap, &state->addrs, next) {
-			if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
+			if (!(ap->flags & IPV6_AF_PFXDELEGATION))
 				continue;
 			if (!(ap->flags & IPV6_AF_DELEGATEDLOG)) {
 				int loglevel;
@@ -3048,7 +3137,7 @@ dhcp6_find_delegates(struct interface *i
 		if (state == NULL || state->state != DH6S_BOUND)
 			continue;
 		TAILQ_FOREACH(ap, &state->addrs, next) {
-			if (!(ap->flags & IPV6_AF_DELEGATEDPFX))
+			if (!(ap->flags & IPV6_AF_PFXDELEGATION))
 				continue;
 			for (i = 0; i < ifo->ia_len; i++) {
 				ia = &ifo->ia[i];
@@ -3082,7 +3171,6 @@ dhcp6_find_delegates(struct interface *i
 	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);
@@ -3139,7 +3227,7 @@ dhcp6_bind(struct interface *ifp, const 
 
 		if (state->reason == NULL)
 			state->reason = "INFORM6";
-		o = dhcp6_findmoption(state->new, state->new_len,
+		o = dhcp6_findmoption(state->recv, state->recv_len,
 				      D6_OPTION_INFO_REFRESH_TIME, &ol);
 		if (o == NULL || ol != sizeof(uint32_t))
 			state->renew = IRT_DEFAULT;
@@ -3319,6 +3407,48 @@ dhcp6_bind(struct interface *ifp, const 
 }
 
 static void
+dhcp6_adjust_max_rt(struct interface *ifp,
+    struct dhcp6_message *r, size_t len)
+{
+	struct dhcp6_state *state = D6_STATE(ifp);
+	uint8_t *o;
+	uint16_t ol;
+
+	/* RFC 8415 */
+	o = dhcp6_findmoption(r, len, D6_OPTION_SOL_MAX_RT, &ol);
+	if (o != NULL && 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
+			logerrx("%s: invalid SOL_MAX_RT %u", ifp->name, max_rt);
+	}
+
+	o = dhcp6_findmoption(r, len, D6_OPTION_INF_MAX_RT, &ol);
+	if (o != NULL && 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);
+	}
+}
+
+static void
 dhcp6_recvif(struct interface *ifp, const char *sfrom,
     struct dhcp6_message *r, size_t len)
 {
@@ -3326,7 +3456,7 @@ dhcp6_recvif(struct interface *ifp, cons
 	size_t i;
 	const char *op;
 	struct dhcp6_state *state;
-	uint8_t *o;
+	uint8_t *o, preference = 0;
 	uint16_t ol;
 	const struct dhcp_opt *opt;
 	const struct if_options *ifo;
@@ -3438,6 +3568,7 @@ dhcp6_recvif(struct interface *ifp, cons
 		case DH6S_REQUEST: /* FALLTHROUGH */
 		case DH6S_RENEW: /* FALLTHROUGH */
 		case DH6S_REBIND:
+			dhcp6_adjust_max_rt(ifp, r, len);
 			if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
 			{
 				/*
@@ -3476,7 +3607,7 @@ dhcp6_recvif(struct interface *ifp, cons
 			 * acknowledgement of one. */
 			loginfox("%s: %s acknowledged DECLINE6",
 			    ifp->name, sfrom);
-			dhcp6_fail(ifp);
+			dhcp6_fail(ifp, true);
 			return;
 		default:
 			valid_op = false;
@@ -3488,49 +3619,25 @@ dhcp6_recvif(struct interface *ifp, cons
 			valid_op = false;
 			break;
 		}
+
+		o = dhcp6_findmoption(r, len, D6_OPTION_PREFERENCE, &ol);
+		if (o && ol == sizeof(uint8_t))
+			preference = *o;
+
+		/* If we already have an advertisement check that this one
+		 * has a higher preference value. */
 		if (state->recv_len && state->recv->type == DHCP6_ADVERTISE) {
-			/* We already have an advertismemnt.
-			 * RFC 8415 says we have to wait for the IRT to elapse.
-			 * To keep the same behaviour we won't do anything with
-			 * this. In the future we should make a lists of
-			 * ADVERTS and pick the "best" one. */
-			logdebugx("%s: discarding ADVERTISEMENT from %s",
-			    ifp->name, sfrom);
-			return;
-		}
-		/* 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);
+			o = dhcp6_findmoption(state->recv, state->recv_len,
+			    D6_OPTION_PREFERENCE, &ol);
+			if (o && ol == sizeof(uint8_t) && *o >= preference) {
+				logdebugx(
+				    "%s: discarding ADVERTISEMENT from %s (%u)",
+				    ifp->name, sfrom, preference);
+				return;
+			}
 		}
+
+		dhcp6_adjust_max_rt(ifp, r, len);
 		if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1)
 			return;
 		break;
@@ -3609,8 +3716,6 @@ dhcp6_recvif(struct interface *ifp, cons
 	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;
@@ -3618,16 +3723,22 @@ dhcp6_recvif(struct interface *ifp, cons
 		if (ia == NULL)
 			ia = TAILQ_FIRST(&state->addrs);
 		if (ia == NULL)
-			loginfox("%s: ADV (no address) from %s",
-			    ifp->name, sfrom);
+			loginfox("%s: ADV (no address) from %s (%u)",
+			    ifp->name, sfrom, preference);
 		else
-			loginfox("%s: ADV %s from %s",
-			    ifp->name, ia->saddr, sfrom);
-		// We will request when the IRT elapses
+			loginfox("%s: ADV %s from %s (%u)",
+			    ifp->name, ia->saddr, sfrom, preference);
+
+		/*
+		 * RFC 8415 18.2.1 says we must collect until ADVERTISEMENTs
+		 * until we get one with a preference of 255 or
+		 * the initial RT has elpased.
+		 */
+		if (preference == 255 || state->RTC > 1)
+			dhcp6_startrequest(ifp);
 		return;
 	}
 
-bind:
 	dhcp6_bind(ifp, op, sfrom);
 }
 
@@ -3978,13 +4089,10 @@ dhcp6_start1(void *arg)
 		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);
+	if (state->state == DH6S_INFORM)
 		dhcp6_startinform(ifp);
-	} else {
-		del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME);
+	else
 		dhcp6_startinit(ifp);
-	}
 
 #ifndef SMALL
 	dhcp6_activateinterfaces(ifp);
@@ -4026,7 +4134,17 @@ dhcp6_start(struct interface *ifp, enum 
 			}
 			break;
 		case DH6S_CONFIRM:
-			init_state = DH6S_INIT;
+			/*
+			 * CONFIRM a prior lease from a RA.
+			 * This could be triggered by a roaming interface.
+			 * We could also get here if we are delegated to.
+			 * Now that we don't remove delegated addresses when
+			 * reading the lease file this is the safe path.
+			 */
+			if (state->state == DH6S_MANUALREBIND)
+				init_state = DH6S_MANUALREBIND;
+			else
+				init_state = DH6S_INIT;
 			goto gogogo;
 		default:
 			/* Not possible, but sushes some compiler warnings. */
@@ -4060,8 +4178,8 @@ dhcp6_start(struct interface *ifp, enum 
 	TAILQ_INIT(&state->addrs);
 
 gogogo:
-	state->new_start = true;
 	state->state = init_state;
+	state->new_start = true;
 	state->lerror = 0;
 	state->failed = false;
 	dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile),
@@ -4085,15 +4203,17 @@ dhcp6_reboot(struct interface *ifp)
 	if (state == NULL)
 		return;
 
-	state->lerror = 0;
 	switch (state->state) {
-	case DH6S_BOUND:
-		dhcp6_startrebind(ifp);
+	case DH6S_RENEW: /* FALLTHROUGH */
+	case DH6S_BOUND: /* FALLTHROUGH */
+	case DH6S_REBIND:
+		state->state = DH6S_MANUALREBIND;
 		break;
-	default:
-		dhcp6_startdiscoinform(ifp);
+	default: /* Appease compilers */
 		break;
 	}
+
+	/* Do nothing. On confirming the next lease we will REBIND instead. */
 }
 
 static void
@@ -4153,7 +4273,7 @@ dhcp6_freedrop(struct interface *ifp, in
 		}
 #endif
 
-		dhcp6_freedrop_addrs(ifp, drop, NULL);
+		dhcp6_freedrop_addrs(ifp, drop, 0, NULL);
 		free(state->old);
 		state->old = state->new;
 		state->old_len = state->new_len;
@@ -4207,21 +4327,12 @@ 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);

Index: src/external/bsd/dhcpcd/dist/src/dhcpcd.c
diff -u src/external/bsd/dhcpcd/dist/src/dhcpcd.c:1.56 src/external/bsd/dhcpcd/dist/src/dhcpcd.c:1.57
--- src/external/bsd/dhcpcd/dist/src/dhcpcd.c:1.56	Fri May 24 16:09:09 2024
+++ src/external/bsd/dhcpcd/dist/src/dhcpcd.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -26,10 +26,9 @@
  * SUCH DAMAGE.
  */
 
-static const char dhcpcd_copyright[] = "Copyright (c) 2006-2023 Roy Marples";
+static const char dhcpcd_copyright[] = "Copyright (c) 2006-2024 Roy Marples";
 
 #include <sys/file.h>
-#include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/time.h>
@@ -115,7 +114,7 @@ printf("usage: "PACKAGE"\t[-146ABbDdEGgH
 	"\t\t[-O, --nooption option] [-o, --option option]\n"
 	"\t\t[-Q, --require option] [-r, --request address]\n"
 	"\t\t[-S, --static value]\n"
-	"\t\t[-s, --inform address[/cidr[/broadcast_address]]]\n [--inform6]"
+	"\t\t[-s, --inform address[/cidr[/broadcast_address]]] [--inform6]\n"
 	"\t\t[-t, --timeout seconds] [-u, --userclass class]\n"
 	"\t\t[-v, --vendor code, value] [-W, --whitelist address[/cidr]] [-w]\n"
 	"\t\t[--waitip [4 | 6]] [-y, --reboot seconds]\n"
@@ -724,6 +723,9 @@ dhcpcd_nocarrier_roaming(struct interfac
 #ifdef INET
 	dhcp_abort(ifp);
 #endif
+#ifdef INET6
+	ipv6nd_abort(ifp);
+#endif
 #ifdef DHCP6
 	dhcp6_abort(ifp);
 #endif
@@ -963,6 +965,7 @@ dhcpcd_startinterface(void *arg)
 				else if (ifo->options & DHCPCD_INFORM6)
 					d6_state = DH6S_INFORM;
 				else
+					/* CONFIRM lease triggered from RA */
 					d6_state = DH6S_CONFIRM;
 				if (dhcp6_start(ifp, d6_state) == -1)
 					logerr("%s: dhcp6_start", ifp->name);
@@ -2278,8 +2281,7 @@ printpidfile:
 
 #ifndef SMALL
 	if (ctx.options & DHCPCD_DUMPLEASE &&
-	    ioctl(fileno(stdin), FIONREAD, &i, sizeof(i)) == 0 &&
-	    i > 0)
+	    ctx.ifc == 1 && ctx.ifv[0][0] == '-' && ctx.ifv[0][1] == '\0')
 	{
 		ctx.options |= DHCPCD_FORKED; /* pretend child process */
 #ifdef PRIVSEP
@@ -2720,6 +2722,7 @@ exit1:
 	eloop_free(ctx.eloop);
 	logclose();
 	free(ctx.logfile);
+	fflush(stdout);
 	free(ctx.ctl_buf);
 #ifdef SETPROCTITLE_H
 	setproctitle_fini();

Index: src/external/bsd/dhcpcd/dist/src/if-bsd.c
diff -u src/external/bsd/dhcpcd/dist/src/if-bsd.c:1.31 src/external/bsd/dhcpcd/dist/src/if-bsd.c:1.32
--- src/external/bsd/dhcpcd/dist/src/if-bsd.c:1.31	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/if-bsd.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * BSD interface driver for dhcpcd
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -742,15 +742,12 @@ if_route(unsigned char cmd, const struct
 		{
 			rtm->rtm_index = (unsigned short)rt->rt_ifp->index;
 /*
- * OpenBSD rejects the message for on-link routes.
- * FreeBSD-12 kernel apparently panics.
- * I can't replicate the panic, but better safe than sorry!
- * https://roy.marples.name/archives/dhcpcd-discuss/0002286.html
- *
- * Neither OS currently allows IPv6 address sharing anyway, so let's
- * try to encourage someone to fix that by logging a waring during compile.
+ * OpenBSD rejects this for on-link routes when there is no default route
+ * OpenBSD does not allow the same IPv6 address on different
+ * interfaces on the same network, so let's try to encourage someone to
+ * fix that by logging a waring during compile.
  */
-#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#ifdef __OpenBSD__
 #warning kernel does not allow IPv6 address sharing
 			if (!gateway_unspec || rt->rt_dest.sa_family!=AF_INET6)
 #endif
@@ -1355,8 +1352,18 @@ if_ifa(struct dhcpcd_ctx *ctx, const str
 
 	/* All BSD's set IFF_UP on the interface when adding an address.
 	 * But not all BSD's emit this via RTM_IFINFO when they do this ... */
-	if (ifam->ifam_type == RTM_NEWADDR && !(ifp->flags & IFF_UP))
-		dhcpcd_handlecarrier(ifp, ifp->carrier, ifp->flags | IFF_UP);
+	if (ifam->ifam_type == RTM_NEWADDR && !(ifp->flags & IFF_UP)) {
+		struct ifreq ifr = { .ifr_flags = 0 };
+
+		/* Don't blindly assume the interface is up though.
+		 * We might get the address via a state change. */
+		strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+		if (ioctl(ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1)
+			return -1;
+		if (ifr.ifr_flags & IFF_UP)
+			dhcpcd_handlecarrier(ifp, ifp->carrier,
+			    ifp->flags | IFF_UP);
+	}
 
 	switch (rti_info[RTAX_IFA]->sa_family) {
 	case AF_LINK:
@@ -1671,8 +1678,7 @@ if_machinearch(char *str, size_t len)
 }
 
 #ifdef INET6
-#if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \
-    defined(IPV6CTL_FORWARDING)
+#if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV))
 #define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0)
 #define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1)
 static int
@@ -1744,39 +1750,6 @@ if_applyra(const struct ra *rap)
 #endif
 }
 
-#ifndef IPV6CTL_FORWARDING
-#define get_inet6_sysctlbyname(code) inet6_sysctlbyname(code, 0, 0)
-#define set_inet6_sysctlbyname(code, val) inet6_sysctlbyname(code, val, 1)
-static int
-inet6_sysctlbyname(const char *name, int val, int action)
-{
-	size_t size;
-
-	size = sizeof(val);
-	if (action) {
-		if (sysctlbyname(name, NULL, 0, &val, size) == -1)
-			return -1;
-		return 0;
-	}
-	if (sysctlbyname(name, &val, &size, NULL, 0) == -1)
-		return -1;
-	return val;
-}
-#endif
-
-int
-ip6_forwarding(__unused const char *ifname)
-{
-	int val;
-
-#ifdef IPV6CTL_FORWARDING
-	val = get_inet6_sysctl(IPV6CTL_FORWARDING);
-#else
-	val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding");
-#endif
-	return val < 0 ? 0 : val;
-}
-
 #ifdef SIOCIFAFATTACH
 static int
 if_af_attach(const struct interface *ifp, int af)
Index: src/external/bsd/dhcpcd/dist/src/ipv6nd.c
diff -u src/external/bsd/dhcpcd/dist/src/ipv6nd.c:1.31 src/external/bsd/dhcpcd/dist/src/ipv6nd.c:1.32
--- src/external/bsd/dhcpcd/dist/src/ipv6nd.c:1.31	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/ipv6nd.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - IPv6 ND handling
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -434,167 +434,6 @@ sent:
 		logwarnx("%s: no IPv6 Routers available", ifp->name);
 }
 
-#ifdef ND6_ADVERTISE
-static void
-ipv6nd_sendadvertisement(void *arg)
-{
-	struct ipv6_addr *ia = arg;
-	struct interface *ifp = ia->iface;
-	struct dhcpcd_ctx *ctx = ifp->ctx;
-	struct sockaddr_in6 dst = {
-	    .sin6_family = AF_INET6,
-	    .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT,
-	    .sin6_scope_id = ifp->index,
-	};
-	struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len };
-	union {
-		struct cmsghdr hdr;
-		uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
-	} cmsgbuf = { .buf = { 0 } };
-	struct msghdr msg = {
-	    .msg_name = &dst, .msg_namelen = sizeof(dst),
-	    .msg_iov = &iov, .msg_iovlen = 1,
-	    .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf),
-	};
-	struct cmsghdr *cm;
-	struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index };
-	const struct rs_state *state = RS_CSTATE(ifp);
-	int s;
-
-	if (state == NULL || !if_is_link_up(ifp))
-		goto freeit;
-
-#ifdef SIN6_LEN
-	dst.sin6_len = sizeof(dst);
-#endif
-
-	/* Set the outbound interface. */
-	cm = CMSG_FIRSTHDR(&msg);
-	assert(cm != NULL);
-	cm->cmsg_level = IPPROTO_IPV6;
-	cm->cmsg_type = IPV6_PKTINFO;
-	cm->cmsg_len = CMSG_LEN(sizeof(pi));
-	memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
-	logdebugx("%s: sending NA for %s", ifp->name, ia->saddr);
-
-#ifdef PRIVSEP
-	if (IN_PRIVSEP(ifp->ctx)) {
-		if (ps_inet_sendnd(ifp, &msg) == -1)
-			logerr(__func__);
-		goto sent;
-	}
-#endif
-#ifdef __sun
-	s = state->nd_fd;
-#else
-	s = ctx->nd_fd;
-#endif
-	if (sendmsg(s, &msg, 0) == -1)
-		logerr(__func__);
-
-#ifdef PRIVSEP
-sent:
-#endif
-	if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) {
-		eloop_timeout_add_sec(ctx->eloop,
-		    state->retrans / 1000, ipv6nd_sendadvertisement, ia);
-		return;
-	}
-
-freeit:
-	free(ia->na);
-	ia->na = NULL;
-	ia->na_count = 0;
-}
-
-void
-ipv6nd_advertise(struct ipv6_addr *ia)
-{
-	struct dhcpcd_ctx *ctx;
-	struct interface *ifp;
-	struct ipv6_state *state;
-	struct ipv6_addr *iap, *iaf;
-	struct nd_neighbor_advert *na;
-
-	if (IN6_IS_ADDR_MULTICAST(&ia->addr))
-		return;
-
-#ifdef __sun
-	if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX)
-		return;
-#endif
-
-	ctx = ia->iface->ctx;
-	/* Find the most preferred address to advertise. */
-	iaf = NULL;
-	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
-		state = IPV6_STATE(ifp);
-		if (state == NULL || !if_is_link_up(ifp))
-			continue;
-
-		TAILQ_FOREACH(iap, &state->addrs, next) {
-			if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr))
-				continue;
-
-			/* Cancel any current advertisement. */
-			eloop_timeout_delete(ctx->eloop,
-			    ipv6nd_sendadvertisement, iap);
-
-			/* Don't advertise what we can't use. */
-			if (iap->prefix_vltime == 0 ||
-			    iap->addr_flags & IN6_IFF_NOTUSEABLE)
-				continue;
-
-			if (iaf == NULL ||
-			    iaf->iface->metric > iap->iface->metric)
-				iaf = iap;
-		}
-	}
-	if (iaf == NULL)
-		return;
-
-	/* Make the packet. */
-	ifp = iaf->iface;
-	iaf->na_len = sizeof(*na);
-	if (ifp->hwlen != 0)
-		iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2);
-	na = calloc(1, iaf->na_len);
-	if (na == NULL) {
-		logerr(__func__);
-		return;
-	}
-
-	na->nd_na_type = ND_NEIGHBOR_ADVERT;
-	na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE;
-#if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE))
-	if (IN_PRIVSEP(ctx)) {
-		if (ps_root_ip6forwarding(ctx, ifp->name) != 0)
-			na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
-	} else
-#endif
-	if (ip6_forwarding(ifp->name) != 0)
-		na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
-	na->nd_na_target = ia->addr;
-
-	if (ifp->hwlen != 0) {
-		struct nd_opt_hdr *opt;
-
-		opt = (struct nd_opt_hdr *)(na + 1);
-		opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
-		opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
-		memcpy(opt + 1, ifp->hwaddr, ifp->hwlen);
-	}
-
-	iaf->na_count = 0;
-	free(iaf->na);
-	iaf->na = na;
-	eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf);
-	ipv6nd_sendadvertisement(iaf);
-}
-#elif !defined(SMALL)
-#warning kernel does not support userland sending ND6 advertisements
-#endif /* ND6_ADVERTISE */
-
 static void
 ipv6nd_expire(void *arg)
 {
@@ -842,7 +681,7 @@ ipv6nd_removefreedrop_ra(struct ra *rap,
 	eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap);
 	if (remove_ra)
 		TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next);
-	ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL);
+	ipv6_freedrop_addrs(&rap->addrs, drop_ra, 0, NULL);
 	routeinfohead_free(&rap->rinfos);
 	free(rap->data);
 	free(rap);
@@ -1065,9 +904,6 @@ try_script:
 				ipv6nd_scriptrun(rap);
 			}
 		}
-#ifdef ND6_ADVERTISE
-		ipv6nd_advertise(ia);
-#endif
 	}
 }
 
@@ -1138,6 +974,12 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
 	bool new_ia;
 #endif
 
+#define FREE_RAP(rap)					\
+	if (new_rap)					\
+		ipv6nd_removefreedrop_ra(rap, 0, 0);	\
+	else						\
+		ipv6nd_free_ra(rap);			\
+
 	if (ifp == NULL || RS_STATE(ifp) == NULL) {
 #ifdef DEBUG_RS
 		logdebugx("RA for unexpected interface from %s", sfrom);
@@ -1294,8 +1136,10 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
 		memcpy(&ndo, p, sizeof(ndo));
 		olen = (size_t)ndo.nd_opt_len * 8;
 		if (olen == 0) {
+			/* RFC4681 4.6 says we MUST discard this ND packet. */
 			logerrx("%s: zero length option", ifp->name);
-			break;
+			FREE_RAP(rap);
+			return;
 		}
 		if (olen > len) {
 			logerrx("%s: option length exceeds message",
@@ -1319,10 +1163,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
 			else
 				logwarnx("%s: reject RA (option %d) from %s",
 				    ifp->name, ndo.nd_opt_type, rap->sfrom);
-			if (new_rap)
-				ipv6nd_removefreedrop_ra(rap, 0, 0);
-			else
-				ipv6nd_free_ra(rap);
+			FREE_RAP(rap);
 			return;
 		}
 
@@ -1573,10 +1414,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
 		{
 			logwarnx("%s: reject RA (no option %s) from %s",
 			    ifp->name, dho->var, rap->sfrom);
-			if (new_rap)
-				ipv6nd_removefreedrop_ra(rap, 0, 0);
-			else
-				ipv6nd_free_ra(rap);
+			FREE_RAP(rap);
 			return;
 		}
 	}
@@ -1598,7 +1436,8 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
 		logwarnx("%s: %s: no longer a default router (lifetime = 0)",
 		    ifp->name, rap->sfrom);
 
-	if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp))
+	if (new_data && !has_address && rap->lifetime &&
+	    ifp->options->options & DHCPCD_GATEWAY && !ipv6_anyglobal(ifp))
 		logwarnx("%s: no global addresses for default route",
 		    ifp->name);
 
@@ -1657,6 +1496,7 @@ nodhcp6:
 
 	/* Expire should be called last as the rap object could be destroyed */
 	ipv6nd_expirera(ifp);
+#undef FREE_RAP
 }
 
 bool
@@ -1862,7 +1702,6 @@ ipv6nd_expirera(void *arg)
 	struct interface *ifp;
 	struct ra *rap, *ran;
 	struct timespec now;
-	uint32_t elapsed;
 	bool expired, valid;
 	struct ipv6_addr *ia;
 	struct routeinfo *rinfo, *rinfob;
@@ -1874,11 +1713,11 @@ ipv6nd_expirera(void *arg)
 #endif
 	struct nd_opt_dnssl dnssl;
 	struct nd_opt_rdnss rdnss;
-	unsigned int next = 0, ltime;
+	uint32_t next = 0, ltime, elapsed;
 	size_t nexpired = 0;
 
 	ifp = arg;
-	clock_gettime(CLOCK_MONOTONIC, &now);
+	timespecclear(&now);
 	expired = false;
 
 	TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) {
@@ -1886,10 +1725,10 @@ ipv6nd_expirera(void *arg)
 			continue;
 		valid = false;
 		/* lifetime may be set to infinite by rfc4191 route information */
-		if (rap->lifetime && rap->lifetime != ND6_INFINITE_LIFETIME) {
-			elapsed = (uint32_t)eloop_timespec_diff(&now,
-			    &rap->acquired, NULL);
-			if (elapsed >= rap->lifetime || rap->doexpire) {
+		if (rap->lifetime) {
+			ltime = lifetime_left(rap->lifetime,
+			    &rap->acquired, &now);
+			if (ltime == 0 || rap->doexpire) {
 				if (!rap->expired) {
 					logwarnx("%s: %s: router expired",
 					    ifp->name, rap->sfrom);
@@ -1898,7 +1737,6 @@ ipv6nd_expirera(void *arg)
 				}
 			} else {
 				valid = true;
-				ltime = rap->lifetime - elapsed;
 				if (next == 0 || ltime < next)
 					next = ltime;
 			}
@@ -1910,15 +1748,9 @@ ipv6nd_expirera(void *arg)
 		TAILQ_FOREACH(ia, &rap->addrs, next) {
 			if (ia->prefix_vltime == 0)
 				continue;
-			if (ia->prefix_vltime == ND6_INFINITE_LIFETIME &&
-			    !rap->doexpire)
-			{
-				valid = true;
-				continue;
-			}
-			elapsed = (uint32_t)eloop_timespec_diff(&now,
-			    &ia->acquired, NULL);
-			if (elapsed >= ia->prefix_vltime || rap->doexpire) {
+			ltime = lifetime_left(ia->prefix_vltime,
+			    &ia->acquired, &now);
+			if (ltime == 0 || rap->doexpire) {
 				if (ia->flags & IPV6_AF_ADDED) {
 					logwarnx("%s: expired %s %s",
 					    ia->iface->name,
@@ -1936,7 +1768,6 @@ ipv6nd_expirera(void *arg)
 				expired = true;
 			} else {
 				valid = true;
-				ltime = ia->prefix_vltime - elapsed;
 				if (next == 0 || ltime < next)
 					next = ltime;
 			}
@@ -1944,12 +1775,9 @@ ipv6nd_expirera(void *arg)
 
 		/* Expire route information */
 		TAILQ_FOREACH_SAFE(rinfo, &rap->rinfos, next, rinfob) {
-			if (rinfo->lifetime == ND6_INFINITE_LIFETIME &&
-			    !rap->doexpire)
-				continue;
-			elapsed = (uint32_t)eloop_timespec_diff(&now,
-			    &rinfo->acquired, NULL);
-			if (elapsed >= rinfo->lifetime || rap->doexpire) {
+			ltime = lifetime_left(rinfo->lifetime,
+			    &rinfo->acquired, &now);
+			if (ltime == 0 || rap->doexpire) {
 				logwarnx("%s: expired route %s",
 				    rap->iface->name, rinfo->sprefix);
 				TAILQ_REMOVE(&rap->rinfos, rinfo, next);
@@ -2163,7 +1991,7 @@ ipv6nd_handledata(void *arg, unsigned sh
 }
 
 static void
-ipv6nd_startrs1(void *arg)
+ipv6nd_startrs2(void *arg)
 {
 	struct interface *ifp = arg;
 	struct rs_state *state;
@@ -2195,22 +2023,43 @@ ipv6nd_startrs1(void *arg)
 	ipv6nd_sendrsprobe(ifp);
 }
 
-void
-ipv6nd_startrs(struct interface *ifp)
+static void
+ipv6nd_startrs1(void *arg)
 {
+	struct interface *ifp = arg;
 	unsigned int delay;
 
-	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
 	if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) {
-		ipv6nd_startrs1(ifp);
+		ipv6nd_startrs2(ifp);
 		return;
 	}
 
 	delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC);
 	logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds",
 	    ifp->name, (float)delay / MSEC_PER_SEC);
-	eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp);
-	return;
+	eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs2, ifp);
+}
+
+void
+ipv6nd_startrs(struct interface *ifp)
+{
+
+	if (ipv6_linklocal(ifp) == NULL) {
+		logdebugx("%s: "
+		    "delaying IPv6 Router Solicitation for LL address",
+		    ifp->name);
+		ipv6_addlinklocalcallback(ifp, ipv6nd_startrs1, ifp);
+	} else
+		ipv6nd_startrs1(ifp);
+}
+
+void
+ipv6nd_abort(struct interface *ifp)
+{
+
+	eloop_timeout_delete(ifp->ctx->eloop, ipv6nd_startrs1, ifp);
+	eloop_timeout_delete(ifp->ctx->eloop, ipv6nd_startrs2, ifp);
+	eloop_timeout_delete(ifp->ctx->eloop, ipv6nd_sendrsprobe, ifp);
 }
 
 static struct routeinfo *routeinfo_findalloc(struct ra *rap, const struct in6_addr *prefix, uint8_t prefix_len)

Index: src/external/bsd/dhcpcd/dist/src/if-options.c
diff -u src/external/bsd/dhcpcd/dist/src/if-options.c:1.37 src/external/bsd/dhcpcd/dist/src/if-options.c:1.38
--- src/external/bsd/dhcpcd/dist/src/if-options.c:1.37	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/if-options.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -87,6 +87,8 @@ const struct option cf_options[] = {
 #ifndef SMALL
 	{"msuserclass",     required_argument, NULL, O_MSUSERCLASS},
 #endif
+	{"vsio",            required_argument, NULL, O_VSIO},
+	{"vsio6",           required_argument, NULL, O_VSIO6},
 	{"vendor",          required_argument, NULL, 'v'},
 	{"waitip",          optional_argument, NULL, 'w'},
 	{"exit",            no_argument,       NULL, 'x'},
@@ -654,6 +656,7 @@ parse_option(struct dhcpcd_ctx *ctx, con
 	struct dhcp_opt **dop, *ndop;
 	size_t *dop_len, dl, odl;
 	struct vivco *vivco;
+	const struct vivco *vivco_endp = ifo->vivco + ifo->vivco_len;
 	struct group *grp;
 #ifdef AUTH
 	struct token *token;
@@ -666,7 +669,11 @@ parse_option(struct dhcpcd_ctx *ctx, con
 	struct if_ia *ia;
 	uint8_t iaid[4];
 #ifndef SMALL
+	struct in6_addr in6addr;
 	struct if_sla *sla, *slap;
+	struct vsio **vsiop = NULL, *vsio;
+	size_t *vsio_lenp = NULL, opt_max, opt_header;
+	struct vsio_so *vsio_so;
 #endif
 #endif
 
@@ -755,7 +762,7 @@ parse_option(struct dhcpcd_ctx *ctx, con
 	case 'i':
 		if (arg)
 			s = parse_string((char *)ifo->vendorclassid + 1,
-			    VENDORCLASSID_MAX_LEN, arg);
+			    sizeof(ifo->vendorclassid) - 1, arg);
 		else
 			s = 0;
 		if (s == -1) {
@@ -897,6 +904,139 @@ parse_option(struct dhcpcd_ctx *ctx, con
 		ifo->userclass[0] = (uint8_t)s;
 		break;
 #endif
+
+	case O_VSIO:
+#ifndef SMALL
+		vsiop = &ifo->vsio;
+		vsio_lenp = &ifo->vsio_len;
+		opt_max = UINT8_MAX;
+		opt_header = sizeof(uint8_t) + sizeof(uint8_t);
+#endif
+		/* FALLTHROUGH */
+	case O_VSIO6:
+#ifndef SMALL
+		if (vsiop == NULL) {
+			vsiop = &ifo->vsio6;
+			vsio_lenp = &ifo->vsio6_len;
+			opt_max = UINT16_MAX;
+			opt_header = sizeof(uint16_t) + sizeof(uint16_t);
+		}
+#endif
+		ARG_REQUIRED;
+#ifdef SMALL
+		logwarnx("%s: vendor options not compiled in", ifname);
+		return -1;
+#else
+		fp = strwhite(arg);
+		if (fp)
+			*fp++ = '\0';
+		u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
+		if (e) {
+			logerrx("invalid code: %s", arg);
+			return -1;
+		}
+
+		fp = strskipwhite(fp);
+		p = strchr(fp, ',');
+		if (!p || !p[1]) {
+			logerrx("invalid vendor format: %s", arg);
+			return -1;
+		}
+
+		/* Strip and preserve the comma */
+		*p = '\0';
+		i = (int)strtoi(fp, NULL, 0, 1, (intmax_t)opt_max, &e);
+		*p = ',';
+		if (e) {
+			logerrx("vendor option should be between"
+			    " 1 and %zu inclusive", opt_max);
+			return -1;
+		}
+
+		fp = p + 1;
+
+		if (fp) {
+			if (inet_pton(AF_INET, fp, &addr) == 1) {
+				s = sizeof(addr.s_addr);
+				dl = (size_t)s;
+				np = malloc(dl);
+				if (np == NULL) {
+					logerr(__func__);
+					return -1;
+				}
+				memcpy(np, &addr.s_addr, dl);
+			} else if (inet_pton(AF_INET6, fp, &in6addr) == 1) {
+				s = sizeof(in6addr.s6_addr);
+				dl = (size_t)s;
+				np = malloc(dl);
+				if (np == NULL) {
+					logerr(__func__);
+					return -1;
+				}
+				memcpy(np, &in6addr.s6_addr, dl);
+			} else {
+				s = parse_string(NULL, 0, fp);
+				if (s == -1) {
+					logerr(__func__);
+					return -1;
+				}
+				dl = (size_t)s;
+				np = malloc(dl);
+				if (np == NULL) {
+					logerr(__func__);
+					return -1;
+				}
+				parse_string(np, dl, fp);
+			}
+		} else {
+			dl = 0;
+			np = NULL;
+		}
+
+		for (sl = 0, vsio = *vsiop; sl < *vsio_lenp; sl++, vsio++) {
+			if (vsio->en == (uint32_t)u)
+				break;
+		}
+		if (sl == *vsio_lenp) {
+			vsio = reallocarray(*vsiop, *vsio_lenp + 1,
+			    sizeof(**vsiop));
+			if (vsio == NULL) {
+				logerr("%s: reallocarray vsio", __func__);
+				free(np);
+				return -1;
+			}
+			*vsiop = vsio;
+			vsio = &(*vsiop)[(*vsio_lenp)++];
+			vsio->en = (uint32_t)u;
+			vsio->so = NULL;
+			vsio->so_len = 0;
+		}
+
+		for (sl = 0, vsio_so = vsio->so;
+		    sl < vsio->so_len;
+		    sl++, vsio_so++)
+			opt_max -= opt_header + vsio_so->len;
+		if (opt_header + dl > opt_max) {
+			logerrx("vsio is too big: %s", fp);
+			free(np);
+			return -1;
+		}
+
+		vsio_so = reallocarray(vsio->so, vsio->so_len + 1,
+		    sizeof(*vsio_so));
+		if (vsio_so == NULL) {
+			logerr("%s: reallocarray vsio_so", __func__);
+			free(np);
+			return -1;
+		}
+
+		vsio->so = vsio_so;
+		vsio_so = &vsio->so[vsio->so_len++];
+		vsio_so->opt = (uint16_t)i;
+		vsio_so->len = (uint16_t)dl;
+		vsio_so->data = np;
+		break;
+#endif
 	case 'v':
 		ARG_REQUIRED;
 		p = strchr(arg, ',');
@@ -909,7 +1049,7 @@ parse_option(struct dhcpcd_ctx *ctx, con
 		if (p == arg) {
 			arg++;
 			s = parse_string((char *)ifo->vendor + 1,
-			    VENDOR_MAX_LEN, arg);
+			    sizeof(ifo->vendor) - 1, arg);
 			if (s == -1) {
 				logerr("vendor");
 				return -1;
@@ -936,7 +1076,7 @@ parse_option(struct dhcpcd_ctx *ctx, con
 		}
 
 		arg = p + 1;
-		s = VENDOR_MAX_LEN - ifo->vendor[0] - 2;
+		s = sizeof(ifo->vendor) - 1 - ifo->vendor[0] - 2;
 		if (inet_aton(arg, &addr) == 1) {
 			if (s < 6) {
 				s = -1;
@@ -1063,11 +1203,13 @@ parse_option(struct dhcpcd_ctx *ctx, con
 		ifo->options |= DHCPCD_XID_HWADDR;
 		break;
 	case 'I':
-		/* Strings have a type of 0 */;
-		ifo->clientid[1] = 0;
 		if (arg)
+			/* If parse_hwaddr cannot decoded arg as a
+			 * hardware address then the first byte
+			 * in the clientid will be zero to indicate
+			 * a string value. */
 			s = parse_hwaddr((char *)ifo->clientid + 1,
-			    CLIENTID_MAX_LEN, arg);
+			    sizeof(ifo->clientid) - 1, arg);
 		else
 			s = 0;
 		if (s == -1) {
@@ -1218,7 +1360,7 @@ parse_option(struct dhcpcd_ctx *ctx, con
 			if (p == NULL)
 				break;
 			ifo->mtu = (unsigned int)strtou(p, NULL, 0,
-			    MTU_MIN, MTU_MAX, &e);
+			    IPV4_MMTU, UINT_MAX, &e);
 			if (e) {
 				logerrx("invalid MTU %s", p);
 				return -1;
@@ -1972,6 +2114,10 @@ err_sla:
 		break;
 	case O_VENDCLASS:
 		ARG_REQUIRED;
+#ifdef SMALL
+			logwarnx("%s: vendor options not compiled in", ifname);
+			return -1;
+#else
 		fp = strwhite(arg);
 		if (fp)
 			*fp++ = '\0';
@@ -1980,6 +2126,12 @@ err_sla:
 			logerrx("invalid code: %s", arg);
 			return -1;
 		}
+		for (vivco = ifo->vivco; vivco != vivco_endp; vivco++) {
+			if (vivco->en == (uint32_t)u) {
+				logerrx("vendor class option for enterprise number %u already defined", vivco->en);
+				return -1;
+			}
+		}
 		fp = strskipwhite(fp);
 		if (fp) {
 			s = parse_string(NULL, 0, fp);
@@ -2010,11 +2162,12 @@ err_sla:
 			return -1;
 		}
 		ifo->vivco = vivco;
-		ifo->vivco_en = (uint32_t)u;
 		vivco = &ifo->vivco[ifo->vivco_len++];
+		vivco->en = (uint32_t)u;
 		vivco->len = dl;
 		vivco->data = (uint8_t *)np;
 		break;
+#endif
 	case O_AUTHPROTOCOL:
 		ARG_REQUIRED;
 #ifdef AUTH
@@ -2318,7 +2471,8 @@ invalid_token:
 		break;
 	case O_MUDURL:
 		ARG_REQUIRED;
-		s = parse_string((char *)ifo->mudurl + 1, MUDURL_MAX_LEN, arg);
+		s = parse_string((char *)ifo->mudurl + 1,
+		    sizeof(ifo->mudurl) - 1, arg);
 		if (s == -1) {
 			logerr("mudurl");
 			return -1;
@@ -2801,6 +2955,10 @@ free_options(struct dhcpcd_ctx *ctx, str
 #ifdef AUTH
 	struct token *token;
 #endif
+#ifndef SMALL
+	struct vsio *vsio;
+	struct vsio_so *vsio_so;
+#endif
 
 	if (ifo == NULL)
 		return;
@@ -2851,11 +3009,35 @@ free_options(struct dhcpcd_ctx *ctx, str
 	    opt++, ifo->dhcp6_override_len--)
 		free_dhcp_opt_embenc(opt);
 	free(ifo->dhcp6_override);
+#ifndef SMALL
 	for (vo = ifo->vivco;
 	    ifo->vivco_len > 0;
 	    vo++, ifo->vivco_len--)
 		free(vo->data);
 	free(ifo->vivco);
+	for (vsio = ifo->vsio;
+	    ifo->vsio_len > 0;
+	    vsio++, ifo->vsio_len--)
+	{
+		for (vsio_so = vsio->so;
+		    vsio->so_len > 0;
+		    vsio_so++, vsio->so_len--)
+			free(vsio_so->data);
+		free(vsio->so);
+	}
+	free(ifo->vsio);
+	for (vsio = ifo->vsio6;
+	    ifo->vsio6_len > 0;
+	    vsio++, ifo->vsio6_len--)
+	{
+		for (vsio_so = vsio->so;
+		    vsio->so_len > 0;
+		    vsio_so++, vsio->so_len--)
+			free(vsio_so->data);
+		free(vsio->so);
+	}
+	free(ifo->vsio6);
+#endif
 	for (opt = ifo->vivso_override;
 	    ifo->vivso_override_len > 0;
 	    opt++, ifo->vivso_override_len--)

Index: src/external/bsd/dhcpcd/dist/src/ipv6.c
diff -u src/external/bsd/dhcpcd/dist/src/ipv6.c:1.19 src/external/bsd/dhcpcd/dist/src/ipv6.c:1.20
--- src/external/bsd/dhcpcd/dist/src/ipv6.c:1.19	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/ipv6.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -89,7 +89,6 @@
 
 #ifdef IPV6_POLLADDRFLAG
 #  warning kernel does not report IPv6 address flag changes
-#  warning polling tentative address flags periodically
 #endif
 
 /* Hackery at it's finest. */
@@ -615,9 +614,8 @@ ipv6_deletedaddr(struct ipv6_addr *ia)
 #ifdef PRIVSEP
 	if (!(ia->iface->ctx->options & DHCPCD_MANAGER))
 		ps_inet_closedhcp6(ia);
-#elif defined(SMALL)
-	UNUSED(ia);
-#else
+#endif
+#ifndef SMALL
 	/* NOREJECT is set if we delegated exactly the prefix to another
 	 * address.
 	 * This can only be one address, so just clear the flag.
@@ -625,8 +623,10 @@ ipv6_deletedaddr(struct ipv6_addr *ia)
 	if (ia->delegating_prefix != NULL)
 		ia->delegating_prefix->flags &= ~IPV6_AF_NOREJECT;
 #endif
-#else
-	UNUSED(ia);
+#endif
+
+#if !defined(DHCP6) || (!defined(PRIVSEP) && defined(SMALL))
+	UNUSED(ia)
 #endif
 }
 
@@ -652,45 +652,52 @@ ipv6_deleteaddr(struct ipv6_addr *ia)
 			break;
 		}
 	}
+}
 
-#ifdef ND6_ADVERTISE
-	/* Advertise the address if it exists on another interface. */
-	ipv6nd_advertise(ia);
-#endif
+static struct ipv6_state *
+ipv6_getstate(struct interface *ifp)
+{
+	struct ipv6_state *state;
+
+	state = IPV6_STATE(ifp);
+	if (state == NULL) {
+	        ifp->if_data[IF_DATA_IPV6] = calloc(1, sizeof(*state));
+		state = IPV6_STATE(ifp);
+		if (state == NULL) {
+			logerr(__func__);
+			return NULL;
+		}
+		TAILQ_INIT(&state->addrs);
+		TAILQ_INIT(&state->ll_callbacks);
+	}
+	return state;
 }
 
 static int
-ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
+ipv6_addaddr1(struct ipv6_addr *ia, struct timespec *now)
 {
 	struct interface *ifp;
 	uint32_t pltime, vltime;
 	int loglevel;
-#ifdef ND6_ADVERTISE
-	bool vltime_was_zero = ia->prefix_vltime == 0;
-#endif
-#ifdef __sun
-	struct ipv6_state *state;
-	struct ipv6_addr *ia2;
+	struct ipv6_addr *iaf;
 
+#ifdef __sun
 	/* If we re-add then address on Solaris then the prefix
 	 * route will be scrubbed and re-added. Something might
 	 * be using it, so let's avoid it. */
 	if (ia->flags & IPV6_AF_DADCOMPLETED) {
 		logdebugx("%s: IP address %s already exists",
 		    ia->iface->name, ia->saddr);
-#ifdef ND6_ADVERTISE
-		goto advertise;
-#else
 		return 0;
-#endif
 	}
 #endif
 
 	/* Remember the interface of the address. */
 	ifp = ia->iface;
 
-	if (!(ia->flags & IPV6_AF_DADCOMPLETED) &&
-	    ipv6_iffindaddr(ifp, &ia->addr, IN6_IFF_NOTUSEABLE))
+	/* Find any existing address. */
+	iaf = ipv6_iffindaddr(ifp, &ia->addr, 0);
+	if (iaf != NULL && !(iaf->addr_flags & IN6_IFF_NOTUSEABLE))
 		ia->flags |= IPV6_AF_DADCOMPLETED;
 
 	/* Adjust plftime and vltime based on acquired time */
@@ -704,31 +711,11 @@ ipv6_addaddr1(struct ipv6_addr *ia, cons
 		ia->prefix_vltime = ia->prefix_pltime = ND6_INFINITE_LIFETIME;
 	}
 
-	if (timespecisset(&ia->acquired) &&
-	    (ia->prefix_pltime != ND6_INFINITE_LIFETIME ||
-	    ia->prefix_vltime != ND6_INFINITE_LIFETIME))
-	{
-		uint32_t elapsed;
-		struct timespec n;
-
-		if (now == NULL) {
-			clock_gettime(CLOCK_MONOTONIC, &n);
-			now = &n;
-		}
-		elapsed = (uint32_t)eloop_timespec_diff(now, &ia->acquired,
-		    NULL);
-		if (ia->prefix_pltime != ND6_INFINITE_LIFETIME) {
-			if (elapsed > ia->prefix_pltime)
-				ia->prefix_pltime = 0;
-			else
-				ia->prefix_pltime -= elapsed;
-		}
-		if (ia->prefix_vltime != ND6_INFINITE_LIFETIME) {
-			if (elapsed > ia->prefix_vltime)
-				ia->prefix_vltime = 0;
-			else
-				ia->prefix_vltime -= elapsed;
-		}
+	if (timespecisset(&ia->acquired)) {
+		ia->prefix_pltime = lifetime_left(ia->prefix_pltime,
+		    &ia->acquired, now);
+		ia->prefix_vltime = lifetime_left(ia->prefix_vltime,
+		    &ia->acquired, now);
 	}
 
 	loglevel = ia->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG;
@@ -793,35 +780,19 @@ ipv6_addaddr1(struct ipv6_addr *ia, cons
 	}
 #endif
 
-#ifdef __sun
-	/* Solaris does not announce new addresses which need DaD
-	 * so we need to take a copy and add it to our list.
-	 * Otherwise aliasing gets confused if we add another
-	 * address during DaD. */
+	/* Take a copy of the address and add it to our state if
+	 * it does not exist.
+	 * This is important if route overflow loses the message. */
+	if (iaf == NULL) {
+		struct ipv6_state *state = ipv6_getstate(ifp);
 
-	state = IPV6_STATE(ifp);
-	TAILQ_FOREACH(ia2, &state->addrs, next) {
-		if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr))
-			break;
-	}
-	if (ia2 == NULL) {
-		if ((ia2 = malloc(sizeof(*ia2))) == NULL) {
+		if ((iaf = malloc(sizeof(*iaf))) == NULL) {
 			logerr(__func__);
 			return 0; /* Well, we did add the address */
 		}
-		memcpy(ia2, ia, sizeof(*ia2));
-		TAILQ_INSERT_TAIL(&state->addrs, ia2, next);
+		memcpy(iaf, ia, sizeof(*iaf));
+		TAILQ_INSERT_TAIL(&state->addrs, iaf, next);
 	}
-#endif
-
-#ifdef ND6_ADVERTISE
-#ifdef __sun
-advertise:
-#endif
-	/* Re-advertise the preferred address to be safe. */
-	if (!vltime_was_zero)
-		ipv6nd_advertise(ia);
-#endif
 
 	return 0;
 }
@@ -889,7 +860,7 @@ find_unit:
 #endif
 
 int
-ipv6_addaddr(struct ipv6_addr *ia, const struct timespec *now)
+ipv6_addaddr(struct ipv6_addr *ia, struct timespec *now)
 {
 	int r;
 #ifdef ALIAS_ADDR
@@ -965,7 +936,7 @@ ipv6_doaddr(struct ipv6_addr *ia, struct
 {
 
 	/* A delegated prefix is not an address. */
-	if (ia->flags & IPV6_AF_DELEGATEDPFX)
+	if (ia->flags & IPV6_AF_PFXDELEGATION)
 		return 0;
 
 	if (ia->prefix_vltime == 0) {
@@ -984,8 +955,6 @@ ipv6_doaddr(struct ipv6_addr *ia, struct
 	    IN6_IS_ADDR_UNSPECIFIED(&ia->addr))
 		return 0;
 
-	if (!timespecisset(now))
-		clock_gettime(CLOCK_MONOTONIC, now);
 	ipv6_addaddr(ia, now);
 	return ia->flags & IPV6_AF_NEW ? 1 : 0;
 }
@@ -1019,7 +988,7 @@ ipv6_freeaddr(struct ipv6_addr *ia)
 	struct ipv6_addr *iad;
 
 	/* Forget the reference */
-	if (ia->flags & IPV6_AF_DELEGATEDPFX) {
+	if (ia->flags & IPV6_AF_PFXDELEGATION) {
 		TAILQ_FOREACH(iad, &ia->pd_pfxs, pd_next) {
 			iad->delegating_prefix = NULL;
 		}
@@ -1040,7 +1009,7 @@ ipv6_freeaddr(struct ipv6_addr *ia)
 
 void
 ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop,
-    const struct interface *ifd)
+    unsigned int notflags, const struct interface *ifd)
 {
 	struct ipv6_addr *ap, *apn, *apf;
 	struct timespec now;
@@ -1050,6 +1019,8 @@ ipv6_freedrop_addrs(struct ipv6_addrhead
 #endif
 	timespecclear(&now);
 	TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
+		if (ap->flags & notflags)
+			continue;
 #ifndef SMALL
 		if (ifd != NULL &&
 		    (ap->delegating_prefix == NULL ||
@@ -1077,12 +1048,7 @@ ipv6_freedrop_addrs(struct ipv6_addrhead
 					ipv6_deleteaddr(ap);
 				if (!(ap->iface->options->options &
 				    DHCPCD_EXITING) && apf)
-				{
-					if (!timespecisset(&now))
-						clock_gettime(CLOCK_MONOTONIC,
-						    &now);
 					ipv6_addaddr(apf, &now);
-				}
 				if (drop == 2)
 					ipv6_freeaddr(ap);
 			}
@@ -1092,61 +1058,51 @@ ipv6_freedrop_addrs(struct ipv6_addrhead
 	}
 }
 
-static struct ipv6_state *
-ipv6_getstate(struct interface *ifp)
+static struct ipv6_addr *
+ipv6_ifanyglobal(struct interface *ifp)
 {
 	struct ipv6_state *state;
+	struct ipv6_addr *ia;
 
 	state = IPV6_STATE(ifp);
-	if (state == NULL) {
-	        ifp->if_data[IF_DATA_IPV6] = calloc(1, sizeof(*state));
-		state = IPV6_STATE(ifp);
-		if (state == NULL) {
-			logerr(__func__);
-			return NULL;
-		}
-		TAILQ_INIT(&state->addrs);
-		TAILQ_INIT(&state->ll_callbacks);
+	if (state == NULL)
+		return NULL;
+
+	TAILQ_FOREACH(ia, &state->addrs, next) {
+		if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) ||
+		    IN6_IS_ADDR_LOOPBACK(&ia->addr))
+			continue;
+		/* Let's be optimistic.
+		 * Any decent OS won't forward or accept traffic
+		 * from/to tentative or detached addresses. */
+		if (!(ia->addr_flags & IN6_IFF_DUPLICATED))
+			return ia;
 	}
-	return state;
+
+	return NULL;
 }
 
 struct ipv6_addr *
 ipv6_anyglobal(struct interface *sifp)
 {
-	struct interface *ifp;
-	struct ipv6_state *state;
 	struct ipv6_addr *ia;
-	bool forwarding;
-
-	/* BSD forwarding is either on or off.
-	 * Linux forwarding is technically the same as it's
-	 * configured by the "all" interface.
-	 * Per interface only affects IsRouter of NA messages. */
-#if defined(PRIVSEP) && (defined(HAVE_PLEDGE) || defined(__linux__))
-	if (IN_PRIVSEP(sifp->ctx))
-		forwarding = ps_root_ip6forwarding(sifp->ctx, NULL) != 0;
-	else
-#endif
-		forwarding = ip6_forwarding(NULL) != 0;
+	struct interface *ifp;
 
+	/*
+	 * IPv6 source address selection will prefer the outgoing interface,
+	 * but will also use any other interface if it things the address is
+	 * a better fit for the destination.
+	 * This logic is pretty much baked into all kernels and you
+	 * don't need to be a router either.
+	 * We only have this logic to work around badly configured IPv6
+	 * setups where there is a default router, but you're not handed
+	 * a reachable address. This results in network timeouts which we
+	 * want to actively avoid.
+	 */
 	TAILQ_FOREACH(ifp, sifp->ctx->ifaces, next) {
-		if (ifp != sifp && !forwarding)
-			continue;
-
-		state = IPV6_STATE(ifp);
-		if (state == NULL)
-			continue;
-
-		TAILQ_FOREACH(ia, &state->addrs, next) {
-			if (IN6_IS_ADDR_LINKLOCAL(&ia->addr))
-				continue;
-			/* Let's be optimistic.
-			 * Any decent OS won't forward or accept traffic
-			 * from/to tentative or detached addresses. */
-			if (!(ia->addr_flags & IN6_IFF_DUPLICATED))
-				return ia;
-		}
+		ia = ipv6_ifanyglobal(ifp);
+		if (ia != NULL)
+			return ia;
 	}
 	return NULL;
 }
@@ -1198,22 +1154,20 @@ ipv6_handleifa(struct dhcpcd_ctx *ctx,
 	anyglobal = ipv6_anyglobal(ifp) != NULL;
 
 	TAILQ_FOREACH(ia, &state->addrs, next) {
-		if (IN6_ARE_ADDR_EQUAL(&ia->addr, addr))
+		if (IN6_ARE_ADDR_EQUAL(&ia->addr, addr)) {
+			ia->addr_flags = addrflags;
 			break;
+		}
 	}
 
 	switch (cmd) {
 	case RTM_DELADDR:
 		if (ia != NULL) {
 			TAILQ_REMOVE(&state->addrs, ia, next);
-#ifdef ND6_ADVERTISE
-			/* Advertise the address if it exists on
-			 * another interface. */
-			ipv6nd_advertise(ia);
-#endif
 			/* We'll free it at the end of the function. */
 		}
 		break;
+
 	case RTM_NEWADDR:
 		if (ia == NULL) {
 			ia = ipv6_newaddr(ifp, addr, prefix_len, 0);
@@ -1244,49 +1198,52 @@ ipv6_handleifa(struct dhcpcd_ctx *ctx,
 			 * generate a new temporary address on
 			 * restart. */
 			ia->acquired = ia->created;
+			ia->addr_flags = addrflags;
 			TAILQ_INSERT_TAIL(&state->addrs, ia, next);
 		}
-		ia->addr_flags = addrflags;
 		ia->flags &= ~IPV6_AF_STALE;
 #ifdef IPV6_MANAGETEMPADDR
 		if (ia->addr_flags & IN6_IFF_TEMPORARY)
 			ia->flags |= IPV6_AF_TEMPORARY;
 #endif
-		if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) {
+
 #ifdef IPV6_POLLADDRFLAG
-			if (ia->addr_flags & IN6_IFF_TENTATIVE) {
-				eloop_timeout_add_msec(
-				    ia->iface->ctx->eloop,
-				    RETRANS_TIMER / 2, ipv6_checkaddrflags, ia);
-				break;
-			}
+		if ((IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) &&
+		    ia->addr_flags & IN6_IFF_TENTATIVE)
+		{
+			eloop_timeout_add_msec(
+			    ia->iface->ctx->eloop,
+			    RETRANS_TIMER / 2, ipv6_checkaddrflags, ia);
+		}
 #endif
 
-			if (ia->dadcallback)
-				ia->dadcallback(ia);
-
-			if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) &&
-			    !(ia->addr_flags & IN6_IFF_NOTUSEABLE))
-			{
-				/* Now run any callbacks.
-				 * Typically IPv6RS or DHCPv6 */
-				while ((cb =
-				    TAILQ_FIRST(&state->ll_callbacks)))
-				{
-					TAILQ_REMOVE(
-					    &state->ll_callbacks,
-					    cb, next);
-					cb->callback(cb->arg);
-					free(cb);
-				}
-			}
-		}
 		break;
+
+	default:
+		return;
 	}
 
 	if (ia == NULL)
 		return;
 
+	if (ia->dadcallback && ((ia->addr_flags &
+	    (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) == 0 ||
+	    ia->addr_flags & IN6_IFF_DUPLICATED))
+		ia->dadcallback(ia);
+
+	if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) &&
+	    !(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+	{
+		/* Now run any callbacks.
+		 * Typically IPv6RS or DHCPv6 */
+		while ((cb = TAILQ_FIRST(&state->ll_callbacks)))
+		{
+			TAILQ_REMOVE(&state->ll_callbacks, cb, next);
+			cb->callback(cb->arg);
+			free(cb);
+		}
+	}
+
 	ctx->options &= ~DHCPCD_RTBUILD;
 	ipv6nd_handleifa(cmd, ia, pid);
 #ifdef DHCP6
@@ -1332,17 +1289,19 @@ ipv6_iffindaddr(struct interface *ifp, c
 	struct ipv6_addr *ap;
 
 	state = IPV6_STATE(ifp);
-	if (state) {
-		TAILQ_FOREACH(ap, &state->addrs, next) {
-			if (addr == NULL) {
-				if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) &&
-				    (!revflags || !(ap->addr_flags & revflags)))
-					return ap;
-			} else {
-				if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr) &&
-				    (!revflags || !(ap->addr_flags & revflags)))
-					return ap;
-			}
+	if (state == NULL)
+		return NULL;
+
+	TAILQ_FOREACH(ap, &state->addrs, next) {
+		if (addr == NULL) {
+			if (IN6_IS_ADDR_LINKLOCAL(&ap->addr) &&
+			    (!revflags || !(ap->addr_flags & revflags)))
+				return ap;
+		} else if (IN6_ARE_ADDR_EQUAL(&ap->addr, addr)) {
+			/* This is our address so we will return now */
+			if (!revflags || !(ap->addr_flags & revflags))
+				return ap;
+			return NULL;
 		}
 	}
 	return NULL;
@@ -1391,18 +1350,17 @@ ipv6_addlinklocalcallback(struct interfa
 	state = ipv6_getstate(ifp);
 	TAILQ_FOREACH(cb, &state->ll_callbacks, next) {
 		if (cb->callback == callback && cb->arg == arg)
-			break;
+			return 0;
 	}
+
+	cb = malloc(sizeof(*cb));
 	if (cb == NULL) {
-		cb = malloc(sizeof(*cb));
-		if (cb == NULL) {
-			logerr(__func__);
-			return -1;
-		}
-		cb->callback = callback;
-		cb->arg = arg;
-		TAILQ_INSERT_TAIL(&state->ll_callbacks, cb, next);
+		logerr(__func__);
+		return -1;
 	}
+	cb->callback = callback;
+	cb->arg = arg;
+	TAILQ_INSERT_TAIL(&state->ll_callbacks, cb, next);
 	return 0;
 }
 
@@ -1609,38 +1567,13 @@ ipv6_newaddr(struct interface *ifp, cons
 	struct ipv6_addr *ia, *iaf;
 	char buf[INET6_ADDRSTRLEN];
 	const char *cbp;
-	bool tempaddr;
-	int addr_flags;
-
-#ifdef IPV6_AF_TEMPORARY
-	tempaddr = flags & IPV6_AF_TEMPORARY;
-#else
-	tempaddr = false;
-#endif
-
-	/* If adding a new DHCP / RA derived address, check current flags
-	 * from an existing address. */
-	if (tempaddr)
-		iaf = NULL;
-	else if (flags & IPV6_AF_AUTOCONF)
-		iaf = ipv6nd_iffindprefix(ifp, addr, prefix_len);
-	else
-		iaf = ipv6_iffindaddr(ifp, addr, 0);
-	if (iaf != NULL) {
-		addr_flags = iaf->addr_flags;
-		flags |= IPV6_AF_ADDED;
-	} else
-		addr_flags = IN6_IFF_TENTATIVE;
 
 	ia = calloc(1, sizeof(*ia));
 	if (ia == NULL)
 		goto err;
 
 	ia->iface = ifp;
-	ia->addr_flags = addr_flags;
 	ia->flags = IPV6_AF_NEW | flags;
-	if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE))
-		ia->flags |= IPV6_AF_DADCOMPLETED;
 	ia->prefix_len = prefix_len;
 	ia->dhcp6_fd = -1;
 
@@ -1652,6 +1585,7 @@ ipv6_newaddr(struct interface *ifp, cons
 		goto makepfx;
 	else if (ia->flags & IPV6_AF_AUTOCONF) {
 		ia->prefix = *addr;
+		iaf = ipv6nd_iffindprefix(ifp, addr, prefix_len);
 		if (iaf != NULL)
 			memcpy(&ia->addr, &iaf->addr, sizeof(ia->addr));
 		else {
@@ -1669,9 +1603,9 @@ ipv6_newaddr(struct interface *ifp, cons
 		cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
 		goto paddr;
 #else
-		return ia;
+		goto flags;
 #endif
-	} else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX)) {
+	} else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_PFXDELEGATION)) {
 		ia->prefix = *addr;
 		cbp = inet_ntop(AF_INET6, &ia->prefix, buf, sizeof(buf));
 		goto paddr;
@@ -1689,6 +1623,21 @@ paddr:
 		goto err;
 	snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", cbp, ia->prefix_len);
 
+#ifndef __sun
+flags:
+#endif
+	/* If adding a new DHCP / RA derived address, check current flags
+	 * from an existing address. */
+	iaf = ipv6_iffindaddr(ifp, &ia->addr, 0);
+	if (iaf != NULL) {
+		ia->addr_flags = iaf->addr_flags;
+		ia->flags |= IPV6_AF_ADDED;
+	} else
+		ia->addr_flags |= IN6_IFF_TENTATIVE;
+
+	if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+		ia->flags |= IPV6_AF_DADCOMPLETED;
+
 	return ia;
 
 err:
@@ -1857,7 +1806,7 @@ ipv6_freedrop(struct interface *ifp, int
 		free(cb);
 	}
 
-	ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL);
+	ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, 0, NULL);
 	if (drop) {
 		if (ifp->ctx->ra_routers != NULL)
 			rt_build(ifp->ctx, AF_INET6);
@@ -1883,10 +1832,13 @@ ipv6_handleifa_addrs(int cmd,
     struct ipv6_addrhead *addrs, const struct ipv6_addr *addr, pid_t pid)
 {
 	struct ipv6_addr *ia, *ian;
-	uint8_t found, alldadcompleted;
+	int found = 0, alldadcompleted = 1;
+
+	if (cmd != RTM_NEWADDR && cmd != RTM_DELADDR) {
+		errno = EINVAL;
+		return -1;
+	}
 
-	alldadcompleted = 1;
-	found = 0;
 	TAILQ_FOREACH_SAFE(ia, addrs, next, ian) {
 		if (!IN6_ARE_ADDR_EQUAL(&addr->addr, &ia->addr)) {
 			if (ia->flags & IPV6_AF_ADDED &&
@@ -1894,34 +1846,35 @@ ipv6_handleifa_addrs(int cmd,
 				alldadcompleted = 0;
 			continue;
 		}
-		switch (cmd) {
-		case RTM_DELADDR:
-			if (ia->flags & IPV6_AF_ADDED) {
-				logwarnx("%s: pid %d deleted address %s",
-				    ia->iface->name, pid, ia->saddr);
-				ia->flags &= ~IPV6_AF_ADDED;
-			}
+
+		ia->addr_flags = addr->addr_flags;
+
+		if (cmd == RTM_DELADDR && ia->flags & IPV6_AF_ADDED)
+			logwarnx("%s: pid %d deleted address %s",
+			    ia->iface->name, pid, ia->saddr);
+
+		/* Check DAD.
+		 * On Linux we can get IN6_IFF_DUPLICATED via RTM_DELADDR. */
+		if (((ia->addr_flags &
+		    (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) == 0 ||
+		    ia->addr_flags & IN6_IFF_DUPLICATED) &&
+		    (ia->flags & IPV6_AF_DADCOMPLETED) == 0)
+		{
+			found++;
+			if (ia->dadcallback)
+				ia->dadcallback(ia);
+			/* We need to set this here in-case the
+			 * dadcallback function checks it */
+			ia->flags |= IPV6_AF_DADCOMPLETED;
+		}
+
+		if (cmd == RTM_DELADDR) {
+			ia->flags &= ~IPV6_AF_ADDED;
 			ipv6_deletedaddr(ia);
 			if (ia->flags & IPV6_AF_DELEGATED) {
 				TAILQ_REMOVE(addrs, ia, next);
 				ipv6_freeaddr(ia);
 			}
-			break;
-		case RTM_NEWADDR:
-			ia->addr_flags = addr->addr_flags;
-			/* Safety - ignore tentative announcements */
-			if (ia->addr_flags &
-			    (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE))
-				break;
-			if ((ia->flags & IPV6_AF_DADCOMPLETED) == 0) {
-				found++;
-				if (ia->dadcallback)
-					ia->dadcallback(ia);
-				/* We need to set this here in-case the
-				 * dadcallback function checks it */
-				ia->flags |= IPV6_AF_DADCOMPLETED;
-			}
-			break;
 		}
 	}
 
@@ -2093,7 +2046,7 @@ valid:
 }
 
 void
-ipv6_addtempaddrs(struct interface *ifp, const struct timespec *now)
+ipv6_addtempaddrs(struct interface *ifp, struct timespec *now)
 {
 	struct ipv6_state *state;
 	struct ipv6_addr *ia;
@@ -2238,7 +2191,7 @@ inet6_makeprefix(struct interface *ifp, 
 
 	/* This address is the delegated prefix, so add a reject route for
 	 * it via the loopback interface. */
-	if (addr->flags & IPV6_AF_DELEGATEDPFX) {
+	if (addr->flags & IPV6_AF_PFXDELEGATION) {
 		struct interface *lo0;
 
 		TAILQ_FOREACH(lo0, ifp->ctx->ifaces, next) {
@@ -2258,7 +2211,7 @@ inet6_makeprefix(struct interface *ifp, 
 	sa_in6_init(&rt->rt_dest, &addr->prefix);
 	ipv6_mask(&netmask, addr->prefix_len);
 	sa_in6_init(&rt->rt_netmask, &netmask);
-	if (addr->flags & IPV6_AF_DELEGATEDPFX) {
+	if (addr->flags & IPV6_AF_PFXDELEGATION) {
 		rt->rt_flags |= RTF_REJECT;
 		/* Linux does not like a gateway for a reject route. */
 #ifndef __linux__
@@ -2321,10 +2274,13 @@ inet6_raroutes(rb_tree_t *routes, struct
 	const struct routeinfo *rinfo;
 	const struct ipv6_addr *addr;
 	struct in6_addr netmask;
+	struct timespec now;
 
 	if (ctx->ra_routers == NULL)
 		return 0;
 
+	timespecclear(&now);
+
 	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
 		if (rap->expired)
 			continue;
@@ -2341,10 +2297,14 @@ inet6_raroutes(rb_tree_t *routes, struct
 			sa_in6_init(&rt->rt_dest, &rinfo->prefix);
 			sa_in6_init(&rt->rt_netmask, &netmask);
 			sa_in6_init(&rt->rt_gateway, &rap->from);
+			rt->rt_dflags |= RTDF_RA;
 #ifdef HAVE_ROUTE_PREF
 			rt->rt_pref = ipv6nd_rtpref(rinfo->flags);
 #endif
-
+#ifdef HAVE_ROUTE_LIFETIME
+			rt->rt_lifetime = lifetime_left(rinfo->lifetime,
+			    &rinfo->acquired, &now);
+#endif
 			rt_proto_add(routes, rt);
 		}
 
@@ -2358,6 +2318,12 @@ inet6_raroutes(rb_tree_t *routes, struct
 #ifdef HAVE_ROUTE_PREF
 				rt->rt_pref = ipv6nd_rtpref(rap->flags);
 #endif
+#ifdef HAVE_ROUTE_LIFETIME
+				rt->rt_lifetime =
+				    lifetime_left(addr->prefix_vltime,
+				    &addr->acquired, &now);
+#endif
+
 				rt_proto_add(routes, rt);
 			}
 		}
@@ -2365,6 +2331,21 @@ inet6_raroutes(rb_tree_t *routes, struct
 		/* add default route */
 		if (rap->lifetime == 0)
 			continue;
+		/*
+		 * We only want to install a default route if we have
+		 * an address that we can use over it.
+		 * If we don't have any global addresses then the link-local
+		 * address would be used instead and we wouldn't reach
+		 * our destination and even if we could, they wouldn't
+		 * be able to reply back to us.
+		 * This avoids timeouts on badly configured IPv6 setups
+		 * where there is a default router but it or a DHCPv6 server
+		 * doesn't hand out an address.
+		 * If an address appears from anywhere, dhcpcd will spot this
+		 * and then add the default like.
+		 * Likewise, if all global addresses are removed then dhcpcd
+		 * will remove the default route.
+		 */
 		if (ipv6_anyglobal(rap->iface) == NULL)
 			continue;
 		rt = inet6_makerouter(rap);
@@ -2374,6 +2355,11 @@ inet6_raroutes(rb_tree_t *routes, struct
 #ifdef HAVE_ROUTE_PREF
 		rt->rt_pref = ipv6nd_rtpref(rap->flags);
 #endif
+#ifdef HAVE_ROUTE_LIFETIME
+		rt->rt_lifetime = lifetime_left(rap->lifetime,
+		    &rap->acquired, &now);
+#endif
+
 		rt_proto_add(routes, rt);
 	}
 	return 0;
@@ -2386,19 +2372,30 @@ inet6_dhcproutes(rb_tree_t *routes, stru
 {
 	struct interface *ifp;
 	const struct dhcp6_state *d6_state;
-	const struct ipv6_addr *addr;
+	const struct ipv6_addr *ia;
 	struct rt *rt;
 
 	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
 		d6_state = D6_CSTATE(ifp);
-		if (d6_state && d6_state->state == dstate) {
-			TAILQ_FOREACH(addr, &d6_state->addrs, next) {
-				rt = inet6_makeprefix(ifp, NULL, addr);
-				if (rt == NULL)
+		if (d6_state == NULL)
+			continue;
+
+		// Don't test the actual state as we could
+		// be between states with still valid routes
+
+		TAILQ_FOREACH(ia, &d6_state->addrs, next) {
+			if (dstate == DH6S_DELEGATED) {
+				// Reject route won't have IPV6_AF_ADDED
+				if (!(ia->flags & IPV6_AF_PFXDELEGATION))
 					continue;
-				rt->rt_dflags |= RTDF_DHCP;
-				rt_proto_add(routes, rt);
-			}
+			} else if (!(ia->flags & IPV6_AF_ADDED))
+				continue;
+
+			rt = inet6_makeprefix(ifp, NULL, ia);
+			if (rt == NULL)
+				continue;
+			rt->rt_dflags |= RTDF_DHCP;
+			rt_proto_add(routes, rt);
 		}
 	}
 	return 0;
Index: src/external/bsd/dhcpcd/dist/src/privsep.c
diff -u src/external/bsd/dhcpcd/dist/src/privsep.c:1.19 src/external/bsd/dhcpcd/dist/src/privsep.c:1.20
--- src/external/bsd/dhcpcd/dist/src/privsep.c:1.19	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/privsep.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * Privilege Separation for dhcpcd
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -125,12 +125,20 @@ static int
 ps_dropprivs(struct dhcpcd_ctx *ctx)
 {
 	struct passwd *pw = ctx->ps_user;
+	int fd_out = ctx->options & DHCPCD_DUMPLEASE ?
+	   STDOUT_FILENO : STDERR_FILENO;
 
 	if (ctx->options & DHCPCD_LAUNCHER)
+#ifdef ASAN
+		logwarnx("not chrooting as compiled for ASAN");
+#else
 		logdebugx("chrooting as %s to %s", pw->pw_name, pw->pw_dir);
+
 	if (chroot(pw->pw_dir) == -1 &&
 	    (errno != EPERM || ctx->options & DHCPCD_FORKED))
 		logerr("%s: chroot: %s", __func__, pw->pw_dir);
+#endif
+
 	if (chdir("/") == -1)
 		logerr("%s: chdir: /", __func__);
 
@@ -172,7 +180,7 @@ ps_dropprivs(struct dhcpcd_ctx *ctx)
 	 * Obviously this won't work if we are using a logfile
 	 * or redirecting stderr to a file. */
 	if ((ctx->options & DHC_NOCHKIO) == DHC_NOCHKIO ||
-	    (ctx->logfile == NULL && isatty(STDERR_FILENO) == 1))
+	    (ctx->logfile == NULL && isatty(fd_out) == 1))
 	{
 		if (setrlimit(RLIMIT_FSIZE, &rzero) == -1)
 			logerr("setrlimit RLIMIT_FSIZE");
@@ -1131,7 +1139,7 @@ ps_recvpsmsg(struct dhcpcd_ctx *ctx, int
 
 	len = read(fd, &psm, sizeof(psm));
 #ifdef PRIVSEP_DEBUG
-	logdebugx("%s: %zd", __func__, len);
+	logdebugx("%s: fd=%d %zd", __func__, fd, len);
 #endif
 
 	if (len == -1 || len == 0)

Index: src/external/bsd/dhcpcd/dist/src/ipv6.h
diff -u src/external/bsd/dhcpcd/dist/src/ipv6.h:1.14 src/external/bsd/dhcpcd/dist/src/ipv6.h:1.15
--- src/external/bsd/dhcpcd/dist/src/ipv6.h:1.14	Fri Apr 21 16:54:26 2023
+++ src/external/bsd/dhcpcd/dist/src/ipv6.h	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -158,19 +158,6 @@
 #  define IN6_IFF_DETACHED	0
 #endif
 
-/*
- * ND6 Advertising is only used for IP address sharing to prefer
- * the address on a specific interface.
- * This just fails to work on OpenBSD and causes erroneous duplicate
- * address messages on BSD's other then DragonFly and NetBSD.
- */
-#if !defined(SMALL) && \
-    ((defined(__DragonFly_version) && __DragonFly_version >= 500703) || \
-    (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 899002800) || \
-    defined(__linux__) || defined(__sun))
-#  define ND6_ADVERTISE
-#endif
-
 #ifdef INET6
 TAILQ_HEAD(ipv6_addrhead, ipv6_addr);
 struct ipv6_addr {
@@ -217,8 +204,8 @@ struct ipv6_addr {
 #define	IPV6_AF_ADDED		(1U << 3)
 #define	IPV6_AF_AUTOCONF	(1U << 4)
 #define	IPV6_AF_DADCOMPLETED	(1U << 5)
-#define	IPV6_AF_DELEGATED	(1U << 6)
-#define	IPV6_AF_DELEGATEDPFX	(1U << 7)
+#define	IPV6_AF_PFXDELEGATION	(1U << 6)
+#define	IPV6_AF_DELEGATED	(1U << 7)
 #define	IPV6_AF_NOREJECT	(1U << 8)
 #define	IPV6_AF_REQUEST		(1U << 9)
 #define	IPV6_AF_STATIC		(1U << 10)
@@ -227,8 +214,9 @@ struct ipv6_addr {
 #define	IPV6_AF_EXTENDED	(1U << 13)
 #define	IPV6_AF_REGEN		(1U << 14)
 #define	IPV6_AF_ROUTER		(1U << 15)
+#define	IPV6_AF_ADVERTISED	(1U << 16)
 #ifdef IPV6_MANAGETEMPADDR
-#define	IPV6_AF_TEMPORARY	(1U << 16)
+#define	IPV6_AF_TEMPORARY	(1U << 17)
 #endif
 
 struct ll_callback {
@@ -266,11 +254,11 @@ int ipv6_userprefix( const struct in6_ad
 void ipv6_checkaddrflags(void *);
 void ipv6_markaddrsstale(struct interface *, unsigned int);
 void ipv6_deletestaleaddrs(struct interface *);
-int ipv6_addaddr(struct ipv6_addr *, const struct timespec *);
+int ipv6_addaddr(struct ipv6_addr *, struct timespec *);
 int ipv6_doaddr(struct ipv6_addr *, struct timespec *);
 ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs);
 void ipv6_deleteaddr(struct ipv6_addr *);
-void ipv6_freedrop_addrs(struct ipv6_addrhead *, int,
+void ipv6_freedrop_addrs(struct ipv6_addrhead *, int, unsigned int,
     const struct interface *);
 void ipv6_handleifa(struct dhcpcd_ctx *ctx, int, struct if_head *,
     const char *, const struct in6_addr *, uint8_t, int, pid_t);
@@ -301,7 +289,7 @@ void ipv6_freedrop(struct interface *, i
 struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *,
     const struct timespec *);
 struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int);
-void ipv6_addtempaddrs(struct interface *, const struct timespec *);
+void ipv6_addtempaddrs(struct interface *, struct timespec *);
 void ipv6_regentempaddrs(void *);
 #endif
 
Index: src/external/bsd/dhcpcd/dist/src/logerr.c
diff -u src/external/bsd/dhcpcd/dist/src/logerr.c:1.14 src/external/bsd/dhcpcd/dist/src/logerr.c:1.15
--- src/external/bsd/dhcpcd/dist/src/logerr.c:1.14	Fri May 24 11:30:29 2024
+++ src/external/bsd/dhcpcd/dist/src/logerr.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * logerr: errx with logging
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -447,7 +447,7 @@ int
 logopen(const char *path)
 {
 	struct logctx *ctx = &_logctx;
-	int opts = 0;
+	int opts = LOG_NDELAY; /* Ensure openlog gets a fd */
 
 	/* Cache timezone */
 	tzset();

Index: src/external/bsd/dhcpcd/dist/src/script.c
diff -u src/external/bsd/dhcpcd/dist/src/script.c:1.17 src/external/bsd/dhcpcd/dist/src/script.c:1.18
--- src/external/bsd/dhcpcd/dist/src/script.c:1.17	Thu Oct 19 11:26:52 2023
+++ src/external/bsd/dhcpcd/dist/src/script.c	Wed Feb 12 19:23:13 2025
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2023 Roy Marples <r...@marples.name>
+ * Copyright (c) 2006-2024 Roy Marples <r...@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -738,6 +738,7 @@ script_dump(const char *env, size_t len)
 			env += 4;
 		printf("%s\n", env);
 	}
+	fflush(stdout);
 	return 0;
 }
 

Reply via email to