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");