Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 20 Sep 2025 12:31:52 GMT
From:      Guido Falsi <madpilot@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 31ec8b6407fd - main - sys/netinet6: Implement RFC 7217
Message-ID:  <202509201231.58KCVqBC047480@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by madpilot:

URL: https://cgit.FreeBSD.org/src/commit/?id=31ec8b6407fdd5a87d70265762457c67ce618283

commit 31ec8b6407fdd5a87d70265762457c67ce618283
Author:     Guido Falsi <madpilot@FreeBSD.org>
AuthorDate: 2025-09-20 12:26:41 +0000
Commit:     Guido Falsi <madpilot@FreeBSD.org>
CommitDate: 2025-09-20 12:31:44 +0000

    sys/netinet6: Implement RFC 7217
    
    Implement RFC 7217 (A Method for Generating Semantically Opaque
    Interface Identifiers with IPv6 Stateless Address Autoconfiguration
    (SLAAC)) in our IPv6 stack.
    
    A new ifconfig `stableaddr` flag is added to enable the feature on
    interfaces, which defaults to on or off for new interfaces based
    on the sysctl `net.inet6.ip6.use_stableaddr` (off by default, so
    this commit causes no change in behavior with default settings).
    
    The algorithm follows the RFC in its logic, using SHA256-HMAC as
    the algorithm to derive addresses so as to provide code that can
    be leveraged by future implentations of RFC 8981, leveraging the
    `hostuuid` as the secret.
    
    The source of the hostidentifier can be configured using the sysctl
    `net.inet6.ip6.stableaddr_netifsource`, while the number of retries
    generating a new address in case of collision can be configured
    using the `net.inet6.ip6.stableaddr_maxretries` sysctl (default 3).
    
    Documentation about all these flags is added to the ifconfig(8) man
    page.
    
    Reviewed by:            cognet, glebius, hrs
    Tested by:              zarychtam@plan-b.pwste.edu.pl
    Approved by:            cognet, glebius
    Relnotes:               yes
    Differential Revision:  https://reviews.freebsd.org/D49681
---
 sbin/ifconfig/af_inet6.c    |   2 +
 sbin/ifconfig/af_nd6.c      |   1 +
 sbin/ifconfig/ifconfig.8    |  30 +++++
 sys/netinet6/in6.h          |   3 +
 sys/netinet6/in6_ifattach.c | 275 +++++++++++++++++++++++++++++++++++++-------
 sys/netinet6/in6_ifattach.h |   2 +
 sys/netinet6/in6_proto.c    |  10 ++
 sys/netinet6/ip6_input.c    |   1 +
 sys/netinet6/ip6_var.h      |  12 ++
 sys/netinet6/nd6.c          |   9 ++
 sys/netinet6/nd6.h          |   2 +
 sys/netinet6/nd6_nbr.c      |  35 +++++-
 sys/netinet6/nd6_rtr.c      | 128 +++++++++++++--------
 usr.sbin/ndp/ndp.c          |   7 ++
 14 files changed, 423 insertions(+), 94 deletions(-)

diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c
index e0f34f0c4d82..9386f5eaf513 100644
--- a/sbin/ifconfig/af_inet6.c
+++ b/sbin/ifconfig/af_inet6.c
@@ -726,6 +726,8 @@ static struct cmd inet6_cmds[] = {
 	DEF_CMD_ARG("pltime",        			setip6pltime),
 	DEF_CMD_ARG("vltime",        			setip6vltime),
 	DEF_CMD("eui64",	0,			setip6eui64),
+	DEF_CMD("stableaddr",	ND6_IFF_STABLEADDR,	setnd6flags),
+	DEF_CMD("-stableaddr",	-ND6_IFF_STABLEADDR,	setnd6flags),
 #ifdef EXPERIMENTAL
 	DEF_CMD("ipv6_only",	ND6_IFF_IPV6_ONLY_MANUAL,setnd6flags),
 	DEF_CMD("-ipv6_only",	-ND6_IFF_IPV6_ONLY_MANUAL,setnd6flags),
diff --git a/sbin/ifconfig/af_nd6.c b/sbin/ifconfig/af_nd6.c
index 2899ad6a0778..fb7e72028e2e 100644
--- a/sbin/ifconfig/af_nd6.c
+++ b/sbin/ifconfig/af_nd6.c
@@ -66,6 +66,7 @@ static const char *ND6BITS[] = {
 	[9]  = "IPV6_ONLY",
 	[10] = "IPV6_ONLY_MANUAL",
 #endif
+	[11]  = "STABLEADDR",
 	[15] = "DEFAULTIF",
 };
 
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
index c4184ba61ee4..fafb77e1ca6c 100644
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -1004,6 +1004,36 @@ Set a flag to disable Duplicate Address Detection.
 .It Cm -no_dad
 Clear a flag
 .Cm no_dad .
+.It Cm stableaddr
+Set a flag to create SLAAC addresses using a stable algorithm according to RFC 7217
+The
+.Xr sysctl 8
+variable
+.Va net.inet6.ip6.use_stableaddr
+controls whether this flag is set by default or not for newly created interfaces.
+To get consistent defaults for interfaces created at boot it should be set as a tunable via loader.conf(8).
+The
+.Xr sysctl 8
+variable
+.Va net.inet6.ip6.stableaddr_maxretries
+sets the maximum number of retries to generate a unique IPv6 address to be performed in case of DAD failures.
+This defaults to 3 which is also the reccommended minimum value.
+The interface ID source can be configured using the
+.Xr sysctl 8
+variable
+.Va net.inet6.ip6.stableaddr_netifsource:
+.Bl -tag -compact
+.It Cm 0
+uses the interface name string (the default)
+.It Cm 1
+uses the interface ID
+.It Cm 2
+uses the MAC address of the interface (if one can be obtained for it)
+.El
+.Pp
+.It Cm -stableaddr
+Clear the flag
+.Cm stableaddr .
 .El
 .Ss IPv6 Parameters
 The following parameters are specific for IPv6 addresses.
diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h
index 67c3ccbb1be8..a7fe03b9c3d7 100644
--- a/sys/netinet6/in6.h
+++ b/sys/netinet6/in6.h
@@ -609,6 +609,8 @@ struct ip6_mtuinfo {
 /*	IPV6CTL_RTMINEXPIRE	26	deprecated */
 /*	IPV6CTL_RTMAXCACHE	27	deprecated */
 
+#define IPV6CTL_STABLEADDR_NETIFSRC	30	/* semantically opaque addresses (RFC7217) hash algo netif parameter src */
+#define IPV6CTL_STABLEADDR_MAXRETRIES	31	/* semantically opaque addresses (RFC7217) max DAD retries */
 #define IPV6CTL_USETEMPADDR	32	/* use temporary addresses (RFC3041) */
 #define IPV6CTL_TEMPPLTIME	33	/* preferred lifetime for tmpaddrs */
 #define IPV6CTL_TEMPVLTIME	34	/* valid lifetime for tmpaddrs */
@@ -617,6 +619,7 @@ struct ip6_mtuinfo {
 #define IPV6CTL_PREFER_TEMPADDR	37	/* prefer temporary addr as src */
 #define IPV6CTL_ADDRCTLPOLICY	38	/* get/set address selection policy */
 #define IPV6CTL_USE_DEFAULTZONE	39	/* use default scope zone */
+#define IPV6CTL_USESTABLEADDR	40	/* use semantically opaque addresses (RFC7217) */
 
 #define IPV6CTL_MAXFRAGS	41	/* max fragments */
 #if 0
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c
index cc149616006e..57fe12a1c93b 100644
--- a/sys/netinet6/in6_ifattach.c
+++ b/sys/netinet6/in6_ifattach.c
@@ -33,6 +33,7 @@
 
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/counter.h>
 #include <sys/malloc.h>
 #include <sys/socket.h>
 #include <sys/sockio.h>
@@ -43,6 +44,7 @@
 #include <sys/rmlock.h>
 #include <sys/syslog.h>
 #include <sys/md5.h>
+#include <crypto/sha2/sha256.h>
 
 #include <net/if.h>
 #include <net/if_var.h>
@@ -79,6 +81,8 @@ VNET_DEFINE(int, ip6_auto_linklocal) = 1;	/* enabled by default */
 VNET_DEFINE(struct callout, in6_tmpaddrtimer_ch);
 #define	V_in6_tmpaddrtimer_ch		VNET(in6_tmpaddrtimer_ch)
 
+VNET_DEFINE(int, ip6_stableaddr_netifsource) = IP6_STABLEADDR_NETIFSRC_NAME; /* Use interface name by default */
+
 VNET_DECLARE(struct inpcbinfo, ripcbinfo);
 #define	V_ripcbinfo			VNET(ripcbinfo)
 
@@ -98,6 +102,9 @@ static void in6_purgemaddrs(struct ifnet *);
 #define IFID_LOCAL(in6)		(!EUI64_LOCAL(in6))
 #define IFID_UNIVERSAL(in6)	(!EUI64_UNIVERSAL(in6))
 
+#define HMAC_IPAD	0x36
+#define HMAC_OPAD	0x5C
+
 /*
  * Generate a last-resort interface identifier, when the machine has no
  * IEEE802/EUI64 address sources.
@@ -147,22 +154,14 @@ get_rand_ifid(struct ifnet *ifp, struct in6_addr *in6)
 }
 
 
-/*
- * Get interface identifier for the specified interface.
- * XXX assumes single sockaddr_dl (AF_LINK address) per an interface
- *
- * in6 - upper 64bits are preserved
+/**
+ * Get interface link level sockaddr
  */
-int
-in6_get_hw_ifid(struct ifnet *ifp, struct in6_addr *in6)
+static struct sockaddr_dl *
+get_interface_link_level(struct ifnet *ifp)
 {
 	struct ifaddr *ifa;
 	struct sockaddr_dl *sdl;
-	u_int8_t *addr;
-	size_t addrlen;
-	static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
-	static u_int8_t allone[8] =
-		{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
 
 	NET_EPOCH_ASSERT();
 
@@ -175,14 +174,30 @@ in6_get_hw_ifid(struct ifnet *ifp, struct in6_addr *in6)
 		if (sdl->sdl_alen == 0)
 			continue;
 
-		goto found;
+		return sdl;
 	}
 
-	return -1;
+	return NULL;
+}
+
+/*
+ * Get hwaddr from link interface
+ */
+static uint8_t *
+in6_get_interface_hwaddr(struct ifnet *ifp, size_t *len)
+{
+	struct sockaddr_dl *sdl;
+	u_int8_t *addr;
+	static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+	static u_int8_t allone[8] =
+		{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+	sdl = get_interface_link_level(ifp);
+	if (sdl == NULL)
+		return (NULL);
 
-found:
 	addr = LLADDR(sdl);
-	addrlen = sdl->sdl_alen;
+	*len = sdl->sdl_alen;
 
 	/* get EUI64 */
 	switch (ifp->if_type) {
@@ -193,36 +208,21 @@ found:
 	case IFT_IEEE1394:
 		/* IEEE802/EUI64 cases - what others? */
 		/* IEEE1394 uses 16byte length address starting with EUI64 */
-		if (addrlen > 8)
-			addrlen = 8;
+		if (*len > 8)
+			*len = 8;
 
 		/* look at IEEE802/EUI64 only */
-		if (addrlen != 8 && addrlen != 6)
-			return -1;
+		if (*len != 8 && *len != 6)
+			return (NULL);
 
 		/*
 		 * check for invalid MAC address - on bsdi, we see it a lot
 		 * since wildboar configures all-zero MAC on pccard before
 		 * card insertion.
 		 */
-		if (bcmp(addr, allzero, addrlen) == 0)
-			return -1;
-		if (bcmp(addr, allone, addrlen) == 0)
-			return -1;
-
-		/* make EUI64 address */
-		if (addrlen == 8)
-			bcopy(addr, &in6->s6_addr[8], 8);
-		else if (addrlen == 6) {
-			in6->s6_addr[8] = addr[0];
-			in6->s6_addr[9] = addr[1];
-			in6->s6_addr[10] = addr[2];
-			in6->s6_addr[11] = 0xff;
-			in6->s6_addr[12] = 0xfe;
-			in6->s6_addr[13] = addr[3];
-			in6->s6_addr[14] = addr[4];
-			in6->s6_addr[15] = addr[5];
-		}
+		if (memcmp(addr, allzero, *len) == 0 || memcmp(addr, allone, *len) == 0)
+			return (NULL);
+
 		break;
 
 	case IFT_GIF:
@@ -233,16 +233,51 @@ found:
 		 * identifier source (can be renumbered).
 		 * we don't do this.
 		 */
-		return -1;
+		return (NULL);
 
 	case IFT_INFINIBAND:
-		if (addrlen != 20)
-			return -1;
-		bcopy(addr + 12, &in6->s6_addr[8], 8);
+		if (*len != 20)
+			return (NULL);
+		*len = 8;
+		addr += 12;
 		break;
 
 	default:
+		return (NULL);
+	}
+
+	return addr;
+}
+
+ /*
+ * Get interface identifier for the specified interface.
+ * XXX assumes single sockaddr_dl (AF_LINK address) per an interface
+ *
+ * in6 - upper 64bits are preserved
+ */
+int
+in6_get_hw_ifid(struct ifnet *ifp, struct in6_addr *in6)
+{
+	size_t hwaddr_len;
+	uint8_t *hwaddr;
+	static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+	hwaddr = in6_get_interface_hwaddr(ifp, &hwaddr_len);
+	if (hwaddr == NULL || (hwaddr_len != 6 && hwaddr_len != 8))
 		return -1;
+
+	/* make EUI64 address */
+	if (hwaddr_len == 8)
+		memcpy(&in6->s6_addr[8], hwaddr, 8);
+	else if (hwaddr_len == 6) {
+		in6->s6_addr[8] = hwaddr[0];
+		in6->s6_addr[9] = hwaddr[1];
+		in6->s6_addr[10] = hwaddr[2];
+		in6->s6_addr[11] = 0xff;
+		in6->s6_addr[12] = 0xfe;
+		in6->s6_addr[13] = hwaddr[3];
+		in6->s6_addr[14] = hwaddr[4];
+		in6->s6_addr[15] = hwaddr[5];
 	}
 
 	/* sanity check: g bit must not indicate "group" */
@@ -263,6 +298,153 @@ found:
 	return 0;
 }
 
+/*
+ * Validate generated interface id to make sure it does not fall in any reserved range:
+ *
+ * https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml
+ */
+static bool
+validate_ifid(uint8_t *iid)
+{
+	static uint8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+	static uint8_t reserved_eth[5] = { 0x02, 0x00, 0x5E, 0xFF, 0xFE };
+	static uint8_t reserved_anycast[7] = { 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+	/* Subnet-Router Anycast (RFC 4291)*/
+	if (memcmp(iid, allzero, 8) == 0)
+		return (false);
+
+	/*
+	 * Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block (RFC 4291)
+	 * and
+	 * Proxy Mobile IPv6 (RFC 6543)
+	 */
+	if (memcmp(iid, reserved_eth, 5) == 0)
+		return (false);
+
+	/* Reserved Subnet Anycast Addresses (RFC 2526) */
+	if (memcmp(iid, reserved_anycast, 7) == 0 && iid[7] >= 0x80)
+		return (false);
+
+	return (true);
+}
+
+/*
+ * Get interface identifier for the specified interface, according to
+ * RFC 7217 Stable and Opaque IDs with SLAAC, using HMAC-SHA256 digest.
+ *
+ * in6 - upper 64bits are preserved
+ */
+bool
+in6_get_stableifid(struct ifnet *ifp, struct in6_addr *in6, int prefixlen)
+{
+	struct sockaddr_dl *sdl;
+	const uint8_t *netiface;
+	size_t netiface_len, hostuuid_len;
+	uint8_t hostuuid[HOSTUUIDLEN + 1], hmac_key[SHA256_BLOCK_LENGTH],
+		hk_ipad[SHA256_BLOCK_LENGTH], hk_opad[SHA256_BLOCK_LENGTH];
+	uint64_t dad_failures;
+	SHA256_CTX ctxt;
+
+	switch (V_ip6_stableaddr_netifsource) {
+		case IP6_STABLEADDR_NETIFSRC_ID:
+			sdl = get_interface_link_level(ifp);
+			if (sdl == NULL)
+				return (false);
+			netiface = (uint8_t *)&LLINDEX(sdl);
+			netiface_len = sizeof(u_short); /* real return type of LLINDEX */
+			break;
+
+		case IP6_STABLEADDR_NETIFSRC_MAC:
+			netiface = in6_get_interface_hwaddr(ifp, &netiface_len);
+			if (netiface == NULL)
+				return (false);
+			break;
+
+		case IP6_STABLEADDR_NETIFSRC_NAME:
+		default:
+			netiface = (const uint8_t *)if_name(ifp);
+			netiface_len = strlen(netiface);
+			break;
+	}
+
+	/* Use hostuuid as constant "secret" key */
+	getcredhostuuid(curthread->td_ucred, hostuuid, sizeof(hostuuid));
+	if (strncmp(hostuuid, DEFAULT_HOSTUUID, sizeof(hostuuid)) == 0) {
+		// If hostuuid is not set, use a random value
+		arc4rand(hostuuid, HOSTUUIDLEN, 0);
+		hostuuid[HOSTUUIDLEN] = '\0';
+	}
+	hostuuid_len = strlen(hostuuid);
+
+	dad_failures = counter_u64_fetch(ND_IFINFO(ifp)->dad_failures);
+
+	/*
+	 * RFC 7217 section 7
+	 *
+	 * default max retries
+	 */
+	if (dad_failures > V_ip6_stableaddr_maxretries)
+		return (false);
+
+	/*
+	 * Use hostuuid as basis for HMAC key
+	 */
+	memset(hmac_key, 0, sizeof(hmac_key));
+	if (hostuuid_len <= SHA256_BLOCK_LENGTH) {
+		/* copy to hmac key variable, zero padded */
+		memcpy(hmac_key, hostuuid, hostuuid_len);
+	} else {
+		/* if longer than block length, use hash of the value, zero padded */
+		SHA256_Init(&ctxt);
+		SHA256_Update(&ctxt, hostuuid, hostuuid_len);
+		SHA256_Final(hmac_key, &ctxt);
+	}
+	/* XOR key with ipad and opad values */
+	for (uint16_t i = 0; i < sizeof(hmac_key); i++) {
+		hk_ipad[i] = hmac_key[i] ^ HMAC_IPAD;
+		hk_opad[i] = hmac_key[i] ^ HMAC_OPAD;
+	}
+
+	/*
+	 * Generate interface id in a loop, adding an offset to be factored in the hash function.
+	 * This is necessary, because if the generated interface id happens to be invalid we
+	 * want to force the hash function to generate a different one, otherwise we would end up
+	 * in an infinite loop trying the same invalid interface id over and over again.
+	 *
+	 * Using an uint8 counter for the offset, so limit iteration at UINT8_MAX. This is a safety
+	 * measure, this will never iterate more than once or twice in practice.
+	 */
+	for(uint8_t offset = 0; offset < UINT8_MAX; offset++) {
+		uint8_t digest[SHA256_DIGEST_LENGTH];
+
+		/* Calculate inner hash */
+		SHA256_Init(&ctxt);
+		SHA256_Update(&ctxt, hk_ipad, sizeof(hk_ipad));
+		SHA256_Update(&ctxt, in6->s6_addr, prefixlen / 8);
+		SHA256_Update(&ctxt, netiface, netiface_len);
+		SHA256_Update(&ctxt, (uint8_t *)&dad_failures, 8);
+		SHA256_Update(&ctxt, hostuuid, hostuuid_len);
+		SHA256_Update(&ctxt, &offset, 1);
+		SHA256_Final(digest, &ctxt);
+
+		/* Calculate outer hash */
+		SHA256_Init(&ctxt);
+		SHA256_Update(&ctxt, hk_opad, sizeof(hk_opad));
+		SHA256_Update(&ctxt, digest, sizeof(digest));
+		SHA256_Final(digest, &ctxt);
+
+		if (validate_ifid(digest)) {
+			/* assumes sizeof(digest) > sizeof(ifid) */
+			memcpy(&in6->s6_addr[8], digest, 8);
+
+			return (true);
+		}
+	}
+
+	return (false);
+}
+
 /*
  * Get interface identifier for the specified interface.  If it is not
  * available on ifp0, borrow interface identifier from other information
@@ -278,7 +460,14 @@ in6_get_ifid(struct ifnet *ifp0, struct ifnet *altifp,
 
 	NET_EPOCH_ASSERT();
 
-	/* first, try to get it from the interface itself */
+	/* first, try to get it from the interface itself, with stable algorithm, if configured */
+	if ((ND_IFINFO(ifp0)->flags & ND6_IFF_STABLEADDR) && in6_get_stableifid(ifp0, in6, 64) == 0) {
+		nd6log((LOG_DEBUG, "%s: got interface identifier from itself (stable private)\n",
+		    if_name(ifp0)));
+		goto success;
+	}
+
+	/* then/otherwise try to get it from the interface itself */
 	if (in6_get_hw_ifid(ifp0, in6) == 0) {
 		nd6log((LOG_DEBUG, "%s: got interface identifier from itself\n",
 		    if_name(ifp0)));
diff --git a/sys/netinet6/in6_ifattach.h b/sys/netinet6/in6_ifattach.h
index fd52422b10be..75b2ca4fa018 100644
--- a/sys/netinet6/in6_ifattach.h
+++ b/sys/netinet6/in6_ifattach.h
@@ -39,6 +39,8 @@ void in6_ifattach(struct ifnet *, struct ifnet *);
 void in6_ifattach_destroy(void);
 void in6_ifdetach(struct ifnet *);
 void in6_ifdetach_destroy(struct ifnet *);
+int in6_get_tmpifid(struct ifnet *, u_int8_t *, const u_int8_t *, int);
+bool in6_get_stableifid(struct ifnet *, struct in6_addr *, int);
 void in6_tmpaddrtimer(void *);
 int in6_get_hw_ifid(struct ifnet *, struct in6_addr *);
 int in6_get_ifid(struct ifnet *, struct ifnet *, struct in6_addr *);
diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c
index b289d4eeb0a2..6669a2ba56ce 100644
--- a/sys/netinet6/in6_proto.c
+++ b/sys/netinet6/in6_proto.c
@@ -167,6 +167,7 @@ VNET_DEFINE(int, ip6_rr_prune) = 5;	/* router renumbering prefix
 					 * walk list every 5 sec. */
 VNET_DEFINE(int, ip6_mcast_pmtu) = 0;	/* enable pMTU discovery for multicast? */
 VNET_DEFINE(int, ip6_v6only) = 1;
+VNET_DEFINE(int, ip6_stableaddr_maxretries) = IP6_IDGEN_RETRIES;
 
 #ifdef IPSTEALTH
 VNET_DEFINE(int, ip6stealth) = 0;
@@ -313,6 +314,15 @@ SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RR_PRUNE, rr_prune,
 SYSCTL_INT(_net_inet6_ip6, IPV6CTL_USETEMPADDR, use_tempaddr,
 	CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_use_tempaddr), 0,
 	"Create RFC3041 temporary addresses for autoconfigured addresses");
+SYSCTL_BOOL(_net_inet6_ip6, IPV6CTL_USESTABLEADDR, use_stableaddr,
+	CTLFLAG_VNET | CTLFLAG_RWTUN, &VNET_NAME(ip6_use_stableaddr), 0,
+	"Create RFC7217 semantically opaque address for autoconfigured addresses (default for new interfaces)");
+SYSCTL_INT(_net_inet6_ip6, IPV6CTL_STABLEADDR_MAXRETRIES, stableaddr_maxretries,
+	CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_stableaddr_maxretries), IP6_IDGEN_RETRIES,
+	"RFC7217 semantically opaque address DAD max retries");
+SYSCTL_INT(_net_inet6_ip6, IPV6CTL_STABLEADDR_NETIFSRC, stableaddr_netifsource,
+	CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_stableaddr_netifsource), IP6_STABLEADDR_NETIFSRC_NAME,
+	"RFC7217 semantically opaque address Net_Iface source (0 - name, 1 - ID, 2 - MAC addr)");
 SYSCTL_PROC(_net_inet6_ip6, IPV6CTL_TEMPPLTIME, temppltime,
 	CTLFLAG_VNET | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
 	NULL, 0, sysctl_ip6_temppltime, "I",
diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c
index b22491a6007f..99dad1e7c309 100644
--- a/sys/netinet6/ip6_input.c
+++ b/sys/netinet6/ip6_input.c
@@ -235,6 +235,7 @@ ip6_vnet_init(void *arg __unused)
 	    &V_ip6_auto_linklocal);
 	TUNABLE_INT_FETCH("net.inet6.ip6.accept_rtadv", &V_ip6_accept_rtadv);
 	TUNABLE_INT_FETCH("net.inet6.ip6.no_radr", &V_ip6_no_radr);
+	TUNABLE_BOOL_FETCH("net.inet6.ip6.use_stableaddr", &V_ip6_use_stableaddr);
 
 	CK_STAILQ_INIT(&V_in6_ifaddrhead);
 	V_in6_ifaddrhashtbl = hashinit(IN6ADDR_NHASH, M_IFADDR,
diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h
index 12b00d4f9934..e1a4e8678ebb 100644
--- a/sys/netinet6/ip6_var.h
+++ b/sys/netinet6/ip6_var.h
@@ -338,8 +338,20 @@ VNET_DECLARE(int, ip6_use_tempaddr);	/* Whether to use temporary addresses */
 VNET_DECLARE(int, ip6_prefer_tempaddr);	/* Whether to prefer temporary
 					 * addresses in the source address
 					 * selection */
+VNET_DECLARE(bool, ip6_use_stableaddr);	/* Whether to use stable address generation (RFC 7217) */
 #define	V_ip6_use_tempaddr		VNET(ip6_use_tempaddr)
 #define	V_ip6_prefer_tempaddr		VNET(ip6_prefer_tempaddr)
+#define	V_ip6_use_stableaddr		VNET(ip6_use_stableaddr)
+
+#define IP6_IDGEN_RETRIES		3 /* RFC 7217 section 7 default max retries */
+VNET_DECLARE(int, ip6_stableaddr_maxretries);
+#define	V_ip6_stableaddr_maxretries	VNET(ip6_stableaddr_maxretries)
+
+#define IP6_STABLEADDR_NETIFSRC_NAME	0
+#define IP6_STABLEADDR_NETIFSRC_ID	1
+#define IP6_STABLEADDR_NETIFSRC_MAC	2
+VNET_DECLARE(int, ip6_stableaddr_netifsource);
+#define	V_ip6_stableaddr_netifsource	VNET(ip6_stableaddr_netifsource)
 
 VNET_DECLARE(int, ip6_use_defzone);	/* Whether to use the default scope
 					 * zone when unspecified */
diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c
index 8480e7fc90e3..938d411711f0 100644
--- a/sys/netinet6/nd6.c
+++ b/sys/netinet6/nd6.c
@@ -324,6 +324,13 @@ nd6_ifattach(struct ifnet *ifp)
 	/* XXX: we cannot call nd6_setmtu since ifp is not fully initialized */
 	nd6_setmtu0(ifp, nd);
 
+	/* Configure default value for stable addresses algorithm, skip loopback interface */
+	if (V_ip6_use_stableaddr && !(ifp->if_flags & IFF_LOOPBACK)) {
+		nd->flags |= ND6_IFF_STABLEADDR;
+	}
+
+	nd->dad_failures = counter_u64_alloc(M_WAITOK);
+
 	return nd;
 }
 
@@ -343,6 +350,8 @@ nd6_ifdetach(struct ifnet *ifp, struct nd_ifinfo *nd)
 	}
 	NET_EPOCH_EXIT(et);
 
+	counter_u64_free(nd->dad_failures);
+
 	free(nd, M_IP6NDP);
 }
 
diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h
index 9cb2571da58b..1de2a77ddf6d 100644
--- a/sys/netinet6/nd6.h
+++ b/sys/netinet6/nd6.h
@@ -76,6 +76,7 @@ struct nd_ifinfo {
 	u_int8_t randomseed0[8]; /* upper 64 bits of MD5 digest */
 	u_int8_t randomseed1[8]; /* lower 64 bits (usually the EUI64 IFID) */
 	u_int8_t randomid[8];	/* current random ID */
+	counter_u64_t dad_failures;	/* DAD failures when using RFC 7217 stable addresses */
 };
 
 #define ND6_IFF_PERFORMNUD	0x1
@@ -89,6 +90,7 @@ struct nd_ifinfo {
 #define	ND6_IFF_NO_RADR		0x40
 #define ND6_IFF_NO_PREFER_IFACE	0x80 /* XXX: not related to ND. */
 #define ND6_IFF_NO_DAD		0x100
+#define ND6_IFF_STABLEADDR	0x800
 #ifdef EXPERIMENTAL
 /* XXX: not related to ND. */
 #define	ND6_IFF_IPV6_ONLY	0x200 /* draft-ietf-6man-ipv6only-flag */
diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c
index 640348a1d198..76b1fd86ee08 100644
--- a/sys/netinet6/nd6_nbr.c
+++ b/sys/netinet6/nd6_nbr.c
@@ -38,6 +38,7 @@
 
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/counter.h>
 #include <sys/eventhandler.h>
 #include <sys/malloc.h>
 #include <sys/libkern.h>
@@ -1466,9 +1467,14 @@ nd6_dad_timer(void *arg)
 			 * No duplicate address found.  Check IFDISABLED flag
 			 * again in case that it is changed between the
 			 * beginning of this function and here.
+			 *
+			 * Reset DAD failures counter if using stable addresses.
 			 */
-			if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) == 0)
+			if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) == 0) {
 				ia->ia6_flags &= ~IN6_IFF_TENTATIVE;
+				if ((ND_IFINFO(ifp)->flags & ND6_IFF_STABLEADDR) && !(ia->ia6_flags & IN6_IFF_TEMPORARY))
+					counter_u64_zero(ND_IFINFO(ifp)->dad_failures);
+			}
 
 			nd6log((LOG_DEBUG,
 			    "%s: DAD complete for %s - no duplicates found\n",
@@ -1497,20 +1503,39 @@ nd6_dad_duplicated(struct ifaddr *ifa, struct dadq *dp)
 	struct ifnet *ifp;
 	char ip6buf[INET6_ADDRSTRLEN];
 
+	ifp = ifa->ifa_ifp;
+
 	log(LOG_ERR, "%s: DAD detected duplicate IPv6 address %s: "
 	    "NS in/out/loopback=%d/%d/%d, NA in=%d\n",
-	    if_name(ifa->ifa_ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr),
+	    if_name(ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr),
 	    dp->dad_ns_icount, dp->dad_ns_ocount, dp->dad_ns_lcount,
 	    dp->dad_na_icount);
 
 	ia->ia6_flags &= ~IN6_IFF_TENTATIVE;
 	ia->ia6_flags |= IN6_IFF_DUPLICATED;
 
-	ifp = ifa->ifa_ifp;
 	log(LOG_ERR, "%s: DAD complete for %s - duplicate found\n",
 	    if_name(ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr));
-	log(LOG_ERR, "%s: manual intervention required\n",
-	    if_name(ifp));
+
+	/*
+	 * For RFC 7217 stable addresses, increment failure counter here if we still have retries.
+	 * More addresses will be generated as long as retries are not exhausted.
+	 */
+	if ((ND_IFINFO(ifp)->flags & ND6_IFF_STABLEADDR) && !(ia->ia6_flags & IN6_IFF_TEMPORARY)) {
+		uint64_t dad_failures = counter_u64_fetch(ND_IFINFO(ifp)->dad_failures);
+
+		if (dad_failures <= V_ip6_stableaddr_maxretries) {
+			counter_u64_add(ND_IFINFO(ifp)->dad_failures, 1);
+			/* if retries exhausted, output an informative error message */
+			if (dad_failures == V_ip6_stableaddr_maxretries)
+				log(LOG_ERR, "%s: manual intervention required, consider disabling \"stableaddr\" on the interface"
+				    " or checking hostuuid for uniqueness\n",
+				    if_name(ifp));
+		}
+	} else {
+		log(LOG_ERR, "%s: manual intervention required\n",
+		    if_name(ifp));
+	}
 
 	/*
 	 * If the address is a link-local address formed from an interface
diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c
index 6fe78083df23..01623a4506be 100644
--- a/sys/netinet6/nd6_rtr.c
+++ b/sys/netinet6/nd6_rtr.c
@@ -92,6 +92,7 @@ VNET_DEFINE(int, nd6_defifindex);
 #define	V_nd6_defifp			VNET(nd6_defifp)
 
 VNET_DEFINE(int, ip6_use_tempaddr) = 0;
+VNET_DEFINE(bool, ip6_use_stableaddr) = 0;
 
 VNET_DEFINE(int, ip6_desync_factor);
 VNET_DEFINE(uint32_t, ip6_temp_max_desync_factor) = TEMP_MAX_DESYNC_FACTOR_BASE;
@@ -1184,7 +1185,7 @@ in6_ifadd(struct nd_prefixctl *pr, int mcast)
 	struct in6_aliasreq ifra;
 	struct in6_ifaddr *ia = NULL, *ib = NULL;
 	int error, plen0;
-	struct in6_addr *ifid_addr = NULL, mask;
+	struct in6_addr *ifid_addr = NULL, mask, newaddr;
 	int prefixlen = pr->ndpr_plen;
 	int updateflags;
 	char ip6buf[INET6_ADDRSTRLEN];
@@ -1210,61 +1211,70 @@ in6_ifadd(struct nd_prefixctl *pr, int mcast)
 	 * (4) it is easier to manage when an interface has addresses
 	 * with the same interface identifier, than to have multiple addresses
 	 * with different interface identifiers.
+	 *
+	 * If using stable privacy generation, generate a new address with
+	 * the algorithm specified in RFC 7217 section 5
 	 */
-	ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, 0); /* 0 is OK? */
-	if (ifa) {
-		ib = (struct in6_ifaddr *)ifa;
-		ifid_addr = &ib->ia_addr.sin6_addr;
-
-		/* prefixlen + ifidlen must be equal to 128 */
-		plen0 = in6_mask2len(&ib->ia_prefixmask.sin6_addr, NULL);
-		if (prefixlen != plen0) {
-			ifa_free(ifa);
-			ifid_addr = NULL;
-			nd6log((LOG_DEBUG,
-			    "%s: wrong prefixlen for %s (prefix=%d ifid=%d)\n",
-			    __func__, if_name(ifp), prefixlen, 128 - plen0));
-		}
-	}
 
-	/* No suitable LL address, get the ifid directly */
-	if (ifid_addr == NULL) {
-		struct in6_addr taddr;
-		ifa = ifa_alloc(sizeof(taddr), M_WAITOK);
+	/* make ifaddr */
+	in6_prepare_ifra(&ifra, &pr->ndpr_prefix.sin6_addr, &mask);
+
+	if (ND_IFINFO(ifp)->flags & ND6_IFF_STABLEADDR) {
+		memcpy(&newaddr, &pr->ndpr_prefix.sin6_addr,  sizeof(pr->ndpr_prefix.sin6_addr));
+
+		if(!in6_get_stableifid(ifp, &newaddr, prefixlen))
+			return NULL;
+	} else {
+		ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, 0); /* 0 is OK? */
 		if (ifa) {
 			ib = (struct in6_ifaddr *)ifa;
 			ifid_addr = &ib->ia_addr.sin6_addr;
-			if(in6_get_ifid(ifp, NULL, ifid_addr) != 0) {
-				nd6log((LOG_DEBUG,
-				    "%s: failed to get ifid for %s\n",
-				    __func__, if_name(ifp)));
+
+			/* prefixlen + ifidlen must be equal to 128 */
+			plen0 = in6_mask2len(&ib->ia_prefixmask.sin6_addr, NULL);
+			if (prefixlen != plen0) {
 				ifa_free(ifa);
 				ifid_addr = NULL;
+				nd6log((LOG_DEBUG,
+				    "%s: wrong prefixlen for %s (prefix=%d ifid=%d)\n",
+				    __func__, if_name(ifp), prefixlen, 128 - plen0));
 			}
 		}
-	}
 
-	if (ifid_addr == NULL) {
-		nd6log((LOG_INFO,
-		    "%s: could not determine ifid for %s\n",
-		    __func__, if_name(ifp)));
-		return NULL;
-	}
+		/* No suitable LL address, get the ifid directly */
+		if (ifid_addr == NULL) {
+			struct in6_addr taddr;
+			ifa = ifa_alloc(sizeof(taddr), M_WAITOK);
+			if (ifa) {
+				ib = (struct in6_ifaddr *)ifa;
+				ifid_addr = &ib->ia_addr.sin6_addr;
+				if(in6_get_ifid(ifp, NULL, ifid_addr) != 0) {
+					nd6log((LOG_DEBUG,
+					    "%s: failed to get ifid for %s\n",
+					    __func__, if_name(ifp)));
+					ifa_free(ifa);
+					ifid_addr = NULL;
+				}
+			}
+		}
 
-	/* make ifaddr */
-	in6_prepare_ifra(&ifra, &pr->ndpr_prefix.sin6_addr, &mask);
+		if (ifid_addr == NULL) {
+			nd6log((LOG_INFO,
+			    "%s: could not determine ifid for %s\n",
+			    __func__, if_name(ifp)));
+			return NULL;
+		}
+
+		memcpy(&newaddr, &ib->ia_addr.sin6_addr, sizeof(ib->ia_addr.sin6_addr));
+		ifa_free(ifa);
+	}
 
 	IN6_MASK_ADDR(&ifra.ifra_addr.sin6_addr, &mask);
 	/* interface ID */
-	ifra.ifra_addr.sin6_addr.s6_addr32[0] |=
-	    (ifid_addr->s6_addr32[0] & ~mask.s6_addr32[0]);
-	ifra.ifra_addr.sin6_addr.s6_addr32[1] |=
-	    (ifid_addr->s6_addr32[1] & ~mask.s6_addr32[1]);
-	ifra.ifra_addr.sin6_addr.s6_addr32[2] |=
-	    (ifid_addr->s6_addr32[2] & ~mask.s6_addr32[2]);
-	ifra.ifra_addr.sin6_addr.s6_addr32[3] |=
-	    (ifid_addr->s6_addr32[3] & ~mask.s6_addr32[3]);
-	ifa_free(ifa);
+	ifra.ifra_addr.sin6_addr.s6_addr32[0] |= (newaddr.s6_addr32[0] & ~mask.s6_addr32[0]);
+	ifra.ifra_addr.sin6_addr.s6_addr32[1] |= (newaddr.s6_addr32[1] & ~mask.s6_addr32[1]);
+	ifra.ifra_addr.sin6_addr.s6_addr32[2] |= (newaddr.s6_addr32[2] & ~mask.s6_addr32[2]);
+	ifra.ifra_addr.sin6_addr.s6_addr32[3] |= (newaddr.s6_addr32[3] & ~mask.s6_addr32[3]);
 
 	/* lifetimes. */
 	ifra.ifra_lifetime.ia6t_vltime = pr->ndpr_vltime;
@@ -1495,6 +1505,7 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr,
 	int auth;
 	struct in6_addrlifetime lt6_tmp;
 	char ip6buf[INET6_ADDRSTRLEN];
+	bool has_temporary = false;
 
 	NET_EPOCH_ASSERT();
 
@@ -1640,9 +1651,6 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr,
 		if (ifa6->ia6_ndpr != pr)
 			continue;
 
-		if (ia6_match == NULL) /* remember the first one */
-			ia6_match = ifa6;
-
 		/*
 		 * An already autoconfigured address matched.  Now that we
 		 * are sure there is at least one matched address, we can
@@ -1702,6 +1710,13 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr,
 		if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0) {
 			u_int32_t maxvltime, maxpltime;
 
+			/*
+			 * if stable addresses (RFC 7217) are enabled, mark that a temporary address has been found
+			 * to avoid generating uneeded extra ones.
+			 */
+			if (ND_IFINFO(ifp)->flags & ND6_IFF_STABLEADDR)
+				has_temporary = true;
+
 			if (V_ip6_temp_valid_lifetime >
 			    (u_int32_t)((time_uptime - ifa6->ia6_createtime) +
 			    V_ip6_desync_factor)) {
@@ -1730,6 +1745,24 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr,
 		}
 		ifa6->ia6_lifetime = lt6_tmp;
 		ifa6->ia6_updatetime = time_uptime;
+
+		/*
+		 * If using stable addresses (RFC 7217) and we still have retries to perform, ignore
+		 * addresses already marked as duplicated, since a new one will be generated.
+		 * Also ignore addresses marked as temporary, since their generation is orthogonal to
+		 * opaque stable ones.
+		 *
+		 * There is a small race condition, in that the dad_counter could be incremented
+		 * between here and when a new address is generated, but this will cause that generation
+		 * to fail and no further retries should happen.
+		 */
+		if (ND_IFINFO(ifp)->flags & ND6_IFF_STABLEADDR &&
+		    counter_u64_fetch(ND_IFINFO(ifp)->dad_failures) <= V_ip6_stableaddr_maxretries &&
+		    ifa6->ia6_flags & (IN6_IFF_DUPLICATED | IN6_IFF_TEMPORARY))
+			continue;
+
+		if (ia6_match == NULL) /* remember the first one */
+			ia6_match = ifa6;
 	}
 	if (ia6_match == NULL && new->ndpr_vltime) {
 		int ifidlen;
@@ -1780,8 +1813,11 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr,
 			 * immediately together with a new set of temporary
 			 * addresses.  Thus, we specifiy 1 as the 2nd arg of
 			 * in6_tmpifadd().
+			 *
+			 * Skip this if a temporary address has been marked as
+			 * found (happens only if stable addresses (RFC 7217) is in use)
 			 */
-			if (V_ip6_use_tempaddr) {
+			if (V_ip6_use_tempaddr && !has_temporary) {
 				int e;
 				if ((e = in6_tmpifadd(ia6, 1, 1)) != 0) {
 					nd6log((LOG_NOTICE, "%s: failed to "
diff --git a/usr.sbin/ndp/ndp.c b/usr.sbin/ndp/ndp.c
index 6e6f40c3ff64..cbca8ec20941 100644
--- a/usr.sbin/ndp/ndp.c
+++ b/usr.sbin/ndp/ndp.c
@@ -1058,6 +1058,9 @@ ifinfo(char *ifname, int argc, char **argv)
 #endif
 #ifdef ND6_IFF_NO_PREFER_IFACE
 		SETFLAG("no_prefer_iface", ND6_IFF_NO_PREFER_IFACE);
+#endif
+#ifdef ND6_IFF_STABLEADDR
+		SETFLAG("stableaddr", ND6_IFF_STABLEADDR);
 #endif
 		SETVALUE("basereachable", ND.basereachable);
 		SETVALUE("retrans", ND.retrans);
@@ -1144,6 +1147,10 @@ ifinfo(char *ifname, int argc, char **argv)
 		if ((ND.flags & ND6_IFF_AUTO_LINKLOCAL))
 			xo_emit("{l:%s} ", "auto_linklocal");
 #endif
+#ifdef ND6_IFF_STABLEADDR
+		if ((ND.flags & ND6_IFF_STABLEADDR))
+			xo_emit("{l:%s} ", "stableaddr");
+#endif
 #ifdef ND6_IFF_NO_PREFER_IFACE
 		if ((ND.flags & ND6_IFF_NO_PREFER_IFACE))
 			xo_emit("{l:%s} ", "no_prefer_iface");



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202509201231.58KCVqBC047480>