diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c --- a/sbin/ifconfig/af_inet6.c +++ b/sbin/ifconfig/af_inet6.c @@ -721,6 +721,8 @@ 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 --- a/sbin/ifconfig/af_nd6.c +++ b/sbin/ifconfig/af_nd6.c @@ -66,6 +66,7 @@ [9] = "IPV6_ONLY", [10] = "IPV6_ONLY_MANUAL", #endif + [11] = "STABLEADDR", [15] = "DEFAULTIF", }; diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -1005,6 +1005,23 @@ .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. +.It Cm -stableaddr +Clear a 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 --- a/sys/netinet6/in6.h +++ b/sys/netinet6/in6.h @@ -609,6 +609,7 @@ /* IPV6CTL_RTMINEXPIRE 26 deprecated */ /* IPV6CTL_RTMAXCACHE 27 deprecated */ +#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 +618,7 @@ #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_USE_STABLEADDR 40 /* use semantically opaque addresses (RFC7217) */ #define IPV6CTL_MAXFRAGS 41 /* max fragments */ #if 0 diff --git a/sys/netinet6/in6_ifattach.h b/sys/netinet6/in6_ifattach.h --- a/sys/netinet6/in6_ifattach.h +++ b/sys/netinet6/in6_ifattach.h @@ -40,6 +40,7 @@ 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_nigroup(struct ifnet *, const char *, int, struct in6_addr *); diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c --- a/sys/netinet6/in6_ifattach.c +++ b/sys/netinet6/in6_ifattach.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include @@ -345,6 +347,106 @@ 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 + * + * in6 - upper 64bits are preserved + */ +bool +in6_get_stableifid(struct ifnet *ifp, struct in6_addr *in6, int prefixlen) +{ + const uint8_t *netiface; + size_t netiface_len, hostuuid_len; + uint8_t hostuuid[HOSTUUIDLEN + 1]; + u_int64_t dad_failures; + + netiface = (const uint8_t *)if_name(ifp); + netiface_len = strlen(netiface); + + /* 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); + + /* + * 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 bbe invalid we + * want to force the has function to generate a different one, otherwise we would end up + * in an infinite loop trying the same invalid interface id oover and over. + * + * 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++) { + SHA1_CTX ctxt; + uint8_t digest[SHA1_RESULTLEN]; + + SHA1Init(&ctxt); + SHA1Update(&ctxt, in6->s6_addr, prefixlen / 8); + SHA1Update(&ctxt, netiface, netiface_len); + SHA1Update(&ctxt, (uint8_t *)&dad_failures, 8); + SHA1Update(&ctxt, hostuuid, hostuuid_len); + if (offset > 0) + SHA1Update(&ctxt, &offset, 1); + SHA1Final(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 @@ -360,7 +462,14 @@ 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_proto.c b/sys/netinet6/in6_proto.c --- a/sys/netinet6/in6_proto.c +++ b/sys/netinet6/in6_proto.c @@ -167,6 +167,7 @@ * 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; @@ -309,6 +310,12 @@ 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_USE_STABLEADDR, use_stableaddr, + CTLFLAG_VNET | CTLFLAG_RW, &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_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 --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -230,6 +230,7 @@ &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 --- a/sys/netinet6/ip6_var.h +++ b/sys/netinet6/ip6_var.h @@ -338,8 +338,14 @@ 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) VNET_DECLARE(int, ip6_use_defzone); /* Whether to use the default scope * zone when unspecified */ diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h --- a/sys/netinet6/nd6.h +++ b/sys/netinet6/nd6.h @@ -76,6 +76,7 @@ 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 @@ #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.c b/sys/netinet6/nd6.c --- a/sys/netinet6/nd6.c +++ b/sys/netinet6/nd6.c @@ -324,6 +324,13 @@ /* 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 @@ } NET_EPOCH_EXIT(et); + counter_u64_free(nd->dad_failures); + free(nd, M_IP6NDP); } diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c --- a/sys/netinet6/nd6_nbr.c +++ b/sys/netinet6/nd6_nbr.c @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -1466,9 +1467,14 @@ * 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) + counter_u64_zero(ND_IFINFO(ifp)->dad_failures); + } nd6log((LOG_DEBUG, "%s: DAD complete for %s - no duplicates found\n", @@ -1497,20 +1503,39 @@ 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 hade retries. + * More addresses will be generated as long as tries are not exhausted. + */ + if (ND_IFINFO(ifp)->flags & ND6_IFF_STABLEADDR) { + 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 (dad_failures == V_ip6_stableaddr_maxretries) + /* retries exhausted, output an error message */ + 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 --- a/sys/netinet6/nd6_rtr.c +++ b/sys/netinet6/nd6_rtr.c @@ -90,6 +90,7 @@ #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(u_int32_t, ip6_temp_preferred_lifetime) = DEF_TEMP_PREFERRED_LIFETIME; @@ -1186,7 +1187,7 @@ struct in6_aliasreq ifra; struct in6_ifaddr *ia, *ib; int error, plen0; - struct in6_addr mask; + struct in6_addr mask, newaddr; int prefixlen = pr->ndpr_plen; int updateflags; char ip6buf[INET6_ADDRSTRLEN]; @@ -1212,37 +1213,46 @@ * (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; - else - return NULL; - - /* prefixlen + ifidlen must be equal to 128 */ - plen0 = in6_mask2len(&ib->ia_prefixmask.sin6_addr, NULL); - if (prefixlen != plen0) { - ifa_free(ifa); - nd6log((LOG_INFO, - "%s: wrong prefixlen for %s (prefix=%d ifid=%d)\n", - __func__, if_name(ifp), prefixlen, 128 - plen0)); - return NULL; - } /* 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; + else + return NULL; + + /* prefixlen + ifidlen must be equal to 128 */ + plen0 = in6_mask2len(&ib->ia_prefixmask.sin6_addr, NULL); + if (prefixlen != plen0) { + ifa_free(ifa); + nd6log((LOG_INFO, + "%s: wrong prefixlen for %s (prefix=%d ifid=%d)\n", + __func__, if_name(ifp), prefixlen, 128 - plen0)); + 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] |= - (ib->ia_addr.sin6_addr.s6_addr32[0] & ~mask.s6_addr32[0]); - ifra.ifra_addr.sin6_addr.s6_addr32[1] |= - (ib->ia_addr.sin6_addr.s6_addr32[1] & ~mask.s6_addr32[1]); - ifra.ifra_addr.sin6_addr.s6_addr32[2] |= - (ib->ia_addr.sin6_addr.s6_addr32[2] & ~mask.s6_addr32[2]); - ifra.ifra_addr.sin6_addr.s6_addr32[3] |= - (ib->ia_addr.sin6_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; @@ -1590,6 +1600,7 @@ CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct in6_ifaddr *ifa6; u_int32_t remaininglifetime; + uint64_t dad_failures; if (ifa->ifa_addr->sa_family != AF_INET6) continue; @@ -1618,6 +1629,20 @@ if (ifa6->ia6_ndpr != pr) continue; + /* + * 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. + * + * 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) { + dad_failures = counter_u64_fetch(ND_IFINFO(ifp)->dad_failures); + if (dad_failures <= V_ip6_stableaddr_maxretries && (ifa6->ia6_flags & IN6_IFF_DUPLICATED)) + continue; + } + if (ia6_match == NULL) /* remember the first one */ ia6_match = ifa6; diff --git a/usr.sbin/ndp/ndp.c b/usr.sbin/ndp/ndp.c --- a/usr.sbin/ndp/ndp.c +++ b/usr.sbin/ndp/ndp.c @@ -1058,6 +1058,9 @@ #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 @@ 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");