diff --git a/libexec/rc/rc.conf b/libexec/rc/rc.conf --- a/libexec/rc/rc.conf +++ b/libexec/rc/rc.conf @@ -526,6 +526,8 @@ # packets not explicitly addressed to itself. ipv6_privacy="NO" # Use privacy address on RA-receiving IFs # (RFC 4941) +ipv6_privacy_stable="NO" # Use stable algorithm to create SLAAC IPv6 addresses + # (RFC 7217) route6d_enable="NO" # Set to YES to enable an IPv6 routing daemon. route6d_program="/usr/sbin/route6d" # Name of IPv6 routing daemon. diff --git a/libexec/rc/rc.d/netoptions b/libexec/rc/rc.d/netoptions --- a/libexec/rc/rc.d/netoptions +++ b/libexec/rc/rc.d/netoptions @@ -107,6 +107,12 @@ ${SYSCTL} net.inet6.ip6.prefer_tempaddr=1 >/dev/null fi + if checkyesno ipv6_privacy_stable; then + netoptions_init + echo -n " IPv6 Privacy Stable Address" + ${SYSCTL} net.inet6.ip6.use_stableaddr=1 >/dev/null + fi + case $ipv6_cpe_wanif in ""|[Nn][Oo]|[Nn][Oo][Nn][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) ${SYSCTL} net.inet6.ip6.no_radr=0 >/dev/null diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h --- a/sys/netinet6/in6.h +++ b/sys/netinet6/in6.h @@ -617,6 +617,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_STABLEADDR 40 /* use semantically opaque addresses (RFC7217) */ #define IPV6CTL_MAXFRAGS 41 /* max fragments */ #if 0 diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -2618,6 +2618,8 @@ ext->mld_ifinfo = mld_domifattach(ifp); + ext->dad_failures = counter_u64_alloc(M_WAITOK); + return ext; } @@ -2635,6 +2637,7 @@ { struct in6_ifextra *ext = (struct in6_ifextra *)aux; + counter_u64_free(ext->dad_failures); mld_domifdetach(ifp); scope6_ifdetach(ext->scope6_id); nd6_ifdetach(ifp, ext->nd_ifinfo); 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); +int 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,64 @@ return 0; } +static int +generate_stable_ifid(u_int8_t *prefix, size_t prefix_len, const u_int8_t *netiface, size_t netiface_len, u_int64_t dad_failures, struct in6_addr *in6) +{ + static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + SHA1_CTX ctxt; + u_int8_t hostuuid[HOSTUUIDLEN + 1]; + u_int8_t digest[SHA1_RESULTLEN]; + + /* Use hostuuid as constant "secret" key */ + getcredhostuuid(curthread->td_ucred, hostuuid, sizeof(hostuuid)); + + SHA1Init(&ctxt); + SHA1Update(&ctxt, prefix, prefix_len); + SHA1Update(&ctxt, netiface, netiface_len); + SHA1Update(&ctxt, (u_int8_t *)&dad_failures, 8); + SHA1Update(&ctxt, hostuuid, strlen(hostuuid)); + SHA1Final(digest, &ctxt); + + /* assumes sizeof(digest) > sizeof(ifid) */ + bcopy(digest, &in6->s6_addr[8], 8); + + /* Make sure not to generate all zero interface identifier */ + if (bcmp(&in6->s6_addr[9], allzero, 7) == 0) + return -1; + + return 0; +} + +/* + * Get interface identifier for the specified interface, according to + * RFC 7217 Stable and Opaque IDs with SLAAC + * + * in6 - upper 64bits are preserved + */ +int +in6_get_stableifid(struct ifnet *ifp, struct in6_addr *in6, int prefixlen) +{ + const u_int8_t * netiface; + u_int64_t dad_failures; + + netiface = (const u_int8_t *)if_name(ifp); + + dad_failures = counter_u64_fetch(((struct in6_ifextra *)((ifp)->if_afdata[AF_INET6]))->dad_failures); + + /* + * RFC 7217 section 7 + * + * default max retries + */ + if (dad_failures > IP6_IDGEN_RETRIES) + return -1; + + if (generate_stable_ifid(in6->s6_addr, prefixlen / 8, netiface, strlen(netiface), dad_failures, in6) != 0) + return -1; + + return 0; +} + /* * Get interface identifier for the specified interface. If it is not * available on ifp0, borrow interface identifier from other information @@ -360,7 +420,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 (V_ip6_use_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 @@ -309,6 +309,9 @@ 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_INT(_net_inet6_ip6, IPV6CTL_STABLEADDR, use_stableaddr, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_use_stableaddr), 0, + "Create RFC7217 semantically opaque address for autoconfigured addresses"); 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/in6_var.h b/sys/netinet6/in6_var.h --- a/sys/netinet6/in6_var.h +++ b/sys/netinet6/in6_var.h @@ -106,6 +106,7 @@ struct scope6_id *scope6_id; struct lltable *lltable; struct mld_ifsoftc *mld_ifinfo; + counter_u64_t dad_failures; }; #define LLTABLE6(ifp) (((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->lltable) 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,12 @@ VNET_DECLARE(int, ip6_prefer_tempaddr); /* Whether to prefer temporary * addresses in the source address * selection */ +VNET_DECLARE(int, 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 max retries */ VNET_DECLARE(int, ip6_use_defzone); /* Whether to use the default scope * zone when unspecified */ 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 (V_ip6_use_stableaddr) + counter_u64_zero(((struct in6_ifextra *)((ifp)->if_afdata[AF_INET6]))->dad_failures); + } nd6log((LOG_DEBUG, "%s: DAD complete for %s - no duplicates found\n", @@ -1497,20 +1503,30 @@ 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, just increment counter here. A new one will be generated. + * + * Otherwise a new address could be generated here (I need to call in6_ifadd() with a faked struct nd_prefixctl argment?). + */ + if (V_ip6_use_stableaddr) + counter_u64_add(((struct in6_ifextra *)((ifp)->if_afdata[AF_INET6]))->dad_failures, 1); + 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(int, 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, new; int prefixlen = pr->ndpr_plen; int updateflags; char ip6buf[INET6_ADDRSTRLEN]; @@ -1212,37 +1213,55 @@ * (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); - 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); + if (V_ip6_use_stableaddr) { + bcopy(&pr->ndpr_prefix.sin6_addr, &new, sizeof(pr->ndpr_prefix.sin6_addr)); + + if(in6_get_stableifid(ifp, &new, prefixlen) != 0) + return NULL; + + IN6_MASK_ADDR(&ifra.ifra_addr.sin6_addr, &mask); + /* interface ID */ + ifra.ifra_addr.sin6_addr.s6_addr32[0] |= (new.s6_addr32[0] & ~mask.s6_addr32[0]); + ifra.ifra_addr.sin6_addr.s6_addr32[1] |= (new.s6_addr32[1] & ~mask.s6_addr32[1]); + ifra.ifra_addr.sin6_addr.s6_addr32[2] |= (new.s6_addr32[2] & ~mask.s6_addr32[2]); + ifra.ifra_addr.sin6_addr.s6_addr32[3] |= (new.s6_addr32[3] & ~mask.s6_addr32[3]); + } 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; + } + + 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); + } /* lifetimes. */ ifra.ifra_lifetime.ia6t_vltime = pr->ndpr_vltime; @@ -1590,6 +1609,7 @@ CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct in6_ifaddr *ifa6; u_int32_t remaininglifetime; + u_int64_t dad_failures; if (ifa->ifa_addr->sa_family != AF_INET6) continue; @@ -1618,6 +1638,20 @@ if (ifa6->ia6_ndpr != pr) continue; + /* + * If using stable addresses (RFC 7217) and we sill 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 (V_ip6_use_stableaddr) { + dad_failures = counter_u64_fetch(((struct in6_ifextra *)((ifp)->if_afdata[AF_INET6]))->dad_failures); + if (dad_failures <= IP6_IDGEN_RETRIES && (ifa6->ia6_flags & IN6_IFF_DUPLICATED)) + continue; + } + if (ia6_match == NULL) /* remember the first one */ ia6_match = ifa6;