Index: sys/netinet/icmp6.h =================================================================== --- sys/netinet/icmp6.h +++ sys/netinet/icmp6.h @@ -297,9 +297,11 @@ #define ND_OPT_PREFIX_INFORMATION 3 #define ND_OPT_REDIRECTED_HEADER 4 #define ND_OPT_MTU 5 +#define ND_OPT_NONCE 14 /* RFC 3971 */ #define ND_OPT_ROUTE_INFO 24 /* RFC 4191 */ #define ND_OPT_RDNSS 25 /* RFC 6106 */ #define ND_OPT_DNSSL 31 /* RFC 6106 */ +#define ND_OPT_MAX 31 struct nd_opt_prefix_info { /* prefix information */ u_int8_t nd_opt_pi_type; @@ -330,6 +332,16 @@ u_int32_t nd_opt_mtu_mtu; } __packed; +#define ND_OPT_NONCE_LEN ((8 * 2) - 2) +#if ((ND_OPT_NONCE_LEN + 2) % 8) != 0 +#error "(ND_OPT_NONCE_LEN + 2) must be a multiple of 8." +#endif +struct nd_opt_nonce { /* nonce option */ + u_int8_t nd_opt_nonce_type; + u_int8_t nd_opt_nonce_len; + u_int8_t nd_opt_nonce[ND_OPT_NONCE_LEN]; +} __packed; + struct nd_opt_route_info { /* route info */ u_int8_t nd_opt_rti_type; u_int8_t nd_opt_rti_len; Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -359,7 +359,7 @@ #define V_ip6_temp_regen_advance VNET(ip6_temp_regen_advance) union nd_opts { - struct nd_opt_hdr *nd_opt_array[8]; /* max = target address list */ + struct nd_opt_hdr *nd_opt_array[16]; /* max = ND_OPT_NONCE */ struct { struct nd_opt_hdr *zero; struct nd_opt_hdr *src_lladdr; @@ -367,6 +367,16 @@ struct nd_opt_prefix_info *pi_beg; /* multiple opts, start */ struct nd_opt_rd_hdr *rh; struct nd_opt_mtu *mtu; + struct nd_opt_hdr *__res6; + struct nd_opt_hdr *__res7; + struct nd_opt_hdr *__res8; + struct nd_opt_hdr *__res9; + struct nd_opt_hdr *__res10; + struct nd_opt_hdr *__res11; + struct nd_opt_hdr *__res12; + struct nd_opt_hdr *__res13; + struct nd_opt_nonce *nonce; + struct nd_opt_hdr *__res15; struct nd_opt_hdr *search; /* multiple opts */ struct nd_opt_hdr *last; /* multiple opts */ int done; @@ -379,6 +389,7 @@ #define nd_opts_pi_end nd_opt_each.pi_end #define nd_opts_rh nd_opt_each.rh #define nd_opts_mtu nd_opt_each.mtu +#define nd_opts_nonce nd_opt_each.nonce #define nd_opts_search nd_opt_each.search #define nd_opts_last nd_opt_each.last #define nd_opts_done nd_opt_each.done @@ -425,7 +436,7 @@ const struct in6_addr *, u_long, int, struct sockaddr *); void nd6_ns_input(struct mbuf *, int, int); void nd6_ns_output(struct ifnet *, const struct in6_addr *, - const struct in6_addr *, struct llentry *, int); + const struct in6_addr *, struct llentry *, uint8_t *); caddr_t nd6_ifptomac(struct ifnet *); void nd6_dad_init(void); void nd6_dad_start(struct ifaddr *, int); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -372,6 +372,7 @@ case ND_OPT_TARGET_LINKADDR: case ND_OPT_MTU: case ND_OPT_REDIRECTED_HEADER: + case ND_OPT_NONCE: if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) { nd6log((LOG_INFO, "duplicated ND6 option found (type=%d)\n", @@ -507,7 +508,7 @@ ln->la_asked++; nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); LLE_WUNLOCK(ln); - nd6_ns_output(ifp, NULL, dst, ln, 0); + nd6_ns_output(ifp, NULL, dst, ln, NULL); LLE_WLOCK(ln); } else { struct mbuf *m = ln->la_hold; @@ -554,7 +555,7 @@ ln->ln_state = ND6_LLINFO_PROBE; nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); LLE_WUNLOCK(ln); - nd6_ns_output(ifp, dst, dst, ln, 0); + nd6_ns_output(ifp, dst, dst, ln, NULL); LLE_WLOCK(ln); } else { ln->ln_state = ND6_LLINFO_STALE; /* XXX */ @@ -566,7 +567,7 @@ ln->la_asked++; nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); LLE_WUNLOCK(ln); - nd6_ns_output(ifp, dst, dst, ln, 0); + nd6_ns_output(ifp, dst, dst, ln, NULL); LLE_WLOCK(ln); } else { EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED); @@ -2065,7 +2066,7 @@ nd6_llinfo_settimer_locked(lle, (long)ND_IFINFO(ifp)->retrans * hz / 1000); LLE_WUNLOCK(lle); - nd6_ns_output(ifp, NULL, &dst->sin6_addr, lle, 0); + nd6_ns_output(ifp, NULL, &dst->sin6_addr, lle, NULL); } else { /* We did the lookup so we need to do the unlock here. */ LLE_WUNLOCK(lle); Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +63,7 @@ #ifdef RADIX_MPATH #include #endif +#include #include #include @@ -79,7 +82,7 @@ #define SDL(s) ((struct sockaddr_dl *)s) struct dadq; -static struct dadq *nd6_dad_find(struct ifaddr *); +static struct dadq *nd6_dad_find(struct ifaddr *, struct nd_opt_nonce *); static void nd6_dad_add(struct dadq *dp); static void nd6_dad_del(struct dadq *dp); static void nd6_dad_rele(struct dadq *); @@ -88,16 +91,21 @@ static void nd6_dad_timer(struct dadq *); static void nd6_dad_duplicated(struct ifaddr *, struct dadq *); static void nd6_dad_ns_output(struct dadq *, struct ifaddr *); -static void nd6_dad_ns_input(struct ifaddr *); +static void nd6_dad_ns_input(struct ifaddr *, struct nd_opt_nonce *); static void nd6_dad_na_input(struct ifaddr *); static void nd6_na_output_fib(struct ifnet *, const struct in6_addr *, const struct in6_addr *, u_long, int, struct sockaddr *, u_int); -static VNET_DEFINE(int, dad_ignore_ns) = 0; /* ignore NS in DAD - - specwise incorrect */ +static VNET_DEFINE(int, dad_enhanced) = 1; +#define V_dad_enhanced VNET(dad_enhanced) + +SYSCTL_DECL(_net_inet6_ip6); +SYSCTL_INT(_net_inet6_ip6, OID_AUTO, dad_enhanced, CTLFLAG_VNET | CTLFLAG_RW, + &VNET_NAME(dad_enhanced), 0, + "Enable Enhanced DAD, which adds a random nonce to NS messages for DAD."); + static VNET_DEFINE(int, dad_maxtry) = 15; /* max # of *tries* to transmit DAD packet */ -#define V_dad_ignore_ns VNET(dad_ignore_ns) #define V_dad_maxtry VNET(dad_maxtry) /* @@ -321,7 +329,7 @@ * silently ignore it. */ if (IN6_IS_ADDR_UNSPECIFIED(&saddr6)) - nd6_dad_ns_input(ifa); + nd6_dad_ns_input(ifa, ndopts.nd_opts_nonce); goto freeit; } @@ -382,12 +390,13 @@ * Based on RFC 2461 * Based on RFC 2462 (duplicate address detection) * - * ln - for source address determination - * dad - duplicate address detection + * ln - for source address determination + * nonce - If non-NULL, NS is used for duplicate address detection and + * the value (length is ND_OPT_NONCE_LEN) is used as a random nonce. */ void nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6, - const struct in6_addr *taddr6, struct llentry *ln, int dad) + const struct in6_addr *taddr6, struct llentry *ln, uint8_t *nonce) { struct mbuf *m; struct m_tag *mtag; @@ -453,7 +462,7 @@ if (in6_setscope(&ip6->ip6_dst, ifp, NULL) != 0) goto bad; } - if (!dad) { + if (nonce == NULL) { struct ifaddr *ifa; /* @@ -550,7 +559,7 @@ * Multicast NS MUST add one add the option * Unicast NS SHOULD add one add the option */ - if (!dad && (mac = nd6_ifptomac(ifp))) { + if (nonce == NULL && (mac = nd6_ifptomac(ifp))) { int optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen; struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_ns + 1); /* 8 byte alignments... */ @@ -564,7 +573,26 @@ nd_opt->nd_opt_len = optlen >> 3; bcopy(mac, (caddr_t)(nd_opt + 1), ifp->if_addrlen); } + /* + * Add a Nonce option (RFC 3971) to detect looped back NS messages. + * This behavior is documented as Enhanced Duplicate Address + * Detection in draft-ietf-6man-enhanced-dad-13. + * net.inet6.ip6.dad_enhanced=0 disables this. + */ + if (V_dad_enhanced != 0 && nonce != NULL) { + int optlen = sizeof(struct nd_opt_hdr) + ND_OPT_NONCE_LEN; + struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_ns + 1); + /* 8-byte alignment is required. */ + optlen = (optlen + 7) & ~7; + m->m_pkthdr.len += optlen; + m->m_len += optlen; + icmp6len += optlen; + bzero((caddr_t)nd_opt, optlen); + nd_opt->nd_opt_type = ND_OPT_NONCE; + nd_opt->nd_opt_len = optlen >> 3; + bcopy(nonce, (caddr_t)(nd_opt + 1), ND_OPT_NONCE_LEN); + } ip6->ip6_plen = htons((u_short)icmp6len); nd_ns->nd_ns_cksum = 0; nd_ns->nd_ns_cksum = @@ -579,7 +607,8 @@ m_tag_prepend(m, mtag); } - ip6_output(m, NULL, &ro, dad ? IPV6_UNSPECSRC : 0, &im6o, NULL, NULL); + ip6_output(m, NULL, &ro, (nonce != NULL) ? IPV6_UNSPECSRC : 0, + &im6o, NULL, NULL); icmp6_ifstat_inc(ifp, ifs6_out_msg); icmp6_ifstat_inc(ifp, ifs6_out_neighborsolicit); ICMP6STAT_INC(icp6s_outhist[ND_NEIGHBOR_SOLICIT]); @@ -1139,9 +1168,13 @@ int dad_ns_ocount; /* NS sent so far */ int dad_ns_icount; int dad_na_icount; + int dad_ns_lcount; /* looped back NS */ struct callout dad_timer_ch; struct vnet *dad_vnet; u_int dad_refcnt; +#define ND_OPT_NONCE_LEN32 \ + ((ND_OPT_NONCE_LEN + sizeof(uint32_t) - 1)/sizeof(uint32_t)) + uint32_t dad_nonce[ND_OPT_NONCE_LEN32]; }; static VNET_DEFINE(TAILQ_HEAD(, dadq), dadq); @@ -1174,16 +1207,34 @@ } static struct dadq * -nd6_dad_find(struct ifaddr *ifa) +nd6_dad_find(struct ifaddr *ifa, struct nd_opt_nonce *n) { struct dadq *dp; + char ip6buf[INET6_ADDRSTRLEN]; DADQ_RLOCK(); - TAILQ_FOREACH(dp, &V_dadq, dad_list) - if (dp->dad_ifa == ifa) { - refcount_acquire(&dp->dad_refcnt); - break; + TAILQ_FOREACH(dp, &V_dadq, dad_list) { + if (dp->dad_ifa != ifa) + continue; + /* + * Skip if the nonce matches the received one. + * +2 in the length is required because of type and + * length fields are included in a header. + */ + if (n != NULL && + n->nd_opt_nonce_len == (ND_OPT_NONCE_LEN + 2) / 8 && + memcmp(&n->nd_opt_nonce[0], &dp->dad_nonce[0], + ND_OPT_NONCE_LEN) == 0) { + log(LOG_ERR, "%s: a looped back NS message is " + "detected during DAD for %s.\n", + if_name(ifa->ifa_ifp), + ip6_sprintf(ip6buf, IFA_IN6(ifa))); + dp->dad_ns_lcount++; + continue; } + refcount_acquire(&dp->dad_refcnt); + break; + } DADQ_RUNLOCK(); return (dp); @@ -1261,7 +1312,7 @@ } if (ND_IFINFO(ifa->ifa_ifp)->flags & ND6_IFF_IFDISABLED) return; - if ((dp = nd6_dad_find(ifa)) != NULL) { + if ((dp = nd6_dad_find(ifa, NULL)) != NULL) { /* DAD already in progress */ nd6_dad_rele(dp); return; @@ -1293,6 +1344,7 @@ dp->dad_count = V_ip6_dad_count; dp->dad_ns_icount = dp->dad_na_icount = 0; dp->dad_ns_ocount = dp->dad_ns_tcount = 0; + dp->dad_ns_lcount = 0; refcount_init(&dp->dad_refcnt, 1); nd6_dad_add(dp); if (delay == 0) { @@ -1312,7 +1364,7 @@ { struct dadq *dp; - dp = nd6_dad_find(ifa); + dp = nd6_dad_find(ifa, NULL); if (!dp) { /* DAD wasn't started yet */ return; @@ -1325,7 +1377,7 @@ * we were waiting for it to stop, so re-do the lookup. */ nd6_dad_rele(dp); - if (nd6_dad_find(ifa) == NULL) + if (nd6_dad_find(ifa, NULL) == NULL) return; nd6_dad_del(dp); @@ -1421,9 +1473,10 @@ char ip6buf[INET6_ADDRSTRLEN]; log(LOG_ERR, "%s: DAD detected duplicate IPv6 address %s: " - "NS in/out=%d/%d, NA in=%d\n", + "NS in/out/loopback=%d/%d/%d, NA in=%d\n", if_name(ifa->ifa_ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr), - dp->dad_ns_icount, dp->dad_ns_ocount, dp->dad_na_icount); + 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; @@ -1475,6 +1528,8 @@ { struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; struct ifnet *ifp = ifa->ifa_ifp; + uint8_t *nonce; + int i; dp->dad_ns_tcount++; if ((ifp->if_flags & IFF_UP) == 0) { @@ -1485,11 +1540,25 @@ } dp->dad_ns_ocount++; - nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL, 1); + if (V_dad_enhanced != 0) { + for (i = 0; i < ND_OPT_NONCE_LEN32; i++) + dp->dad_nonce[i] = arc4random(); + nonce = (uint8_t *)&dp->dad_nonce[0]; + /* + * XXXHRS: Note that in the case that + * DupAddrDetectTransmits > 1, multiple NS messages with + * different nonces can be looped back in an unexpected + * order. The current implementation recognizes only + * the latest nonce on the sender side. Practically it + * should work well in almost all cases. + */ + } else + nonce = NULL; + nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL, nonce); } static void -nd6_dad_ns_input(struct ifaddr *ifa) +nd6_dad_ns_input(struct ifaddr *ifa, struct nd_opt_nonce *ndopt_nonce) { struct in6_ifaddr *ia; struct ifnet *ifp; @@ -1502,22 +1571,13 @@ ia = (struct in6_ifaddr *)ifa; ifp = ifa->ifa_ifp; taddr6 = &ia->ia_addr.sin6_addr; - dp = nd6_dad_find(ifa); + /* Ignore Nonce option when Enhanced DAD is disabled. */ + if (V_dad_enhanced == 0) + ndopt_nonce = NULL; + dp = nd6_dad_find(ifa, ndopt_nonce); if (dp == NULL) return; - /* Quickhack - completely ignore DAD NS packets */ - if (V_dad_ignore_ns) { - char ip6buf[INET6_ADDRSTRLEN]; - nd6log((LOG_INFO, - "nd6_dad_ns_input: ignoring DAD NS packet for " - "address %s(%s)\n", ip6_sprintf(ip6buf, taddr6), - if_name(ifa->ifa_ifp))); - return; - } - - /* XXX more checks for loopback situation - see nd6_dad_timer too */ - dp->dad_ns_icount++; nd6_dad_rele(dp); } @@ -1530,7 +1590,7 @@ if (ifa == NULL) panic("ifa == NULL in nd6_dad_na_input"); - dp = nd6_dad_find(ifa); + dp = nd6_dad_find(ifa, NULL); if (dp != NULL) { dp->dad_na_icount++; nd6_dad_rele(dp);