The branch main has been updated by madpilot:

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

commit 31ec8b6407fdd5a87d70265762457c67ce618283
Author:     Guido Falsi <madpi...@freebsd.org>
AuthorDate: 2025-09-20 12:26:41 +0000
Commit:     Guido Falsi <madpi...@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:              zarych...@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");

Reply via email to