This diff fixes address assignment and related issues in IPv6CP. The changes are:
- Move address assignment to process context. This makes use of workq because sppp hasn't been converted to taskq yet. I intend to convert sppp to taskq but didn't want to clutter this diff even more. If taskq is a prerequisite for commit, I'm happy to do taskq first. - Actually deal with IFID collisions instead of ignoring them. See RFC 5072 for details. - Use netinet6 APIs to configure addresses to account for side effects implemented in the netinet6 stack. This pulls in in6_var.h again which was removed a short while back, sorry :-) Disable duplicate address detection for IPv6CP link-local as per RFC 5072. - Use arc4random() for IFID generation (supersedes the diff I sent to tech@ yesterday). - Assign the destination's address to the point-to-point link if the prefix length of the link-local address is 128. Usually a /64 is used for point-to-point IPv6, to keep neighbour discovery and autoconf working on the link. But a /128 prefix with no destaddr looks very odd in ifconfig output. Tested with pppoe(4) over a native-IPv6-enabled ADSL connection from QSC (a German ISP). Tests in other configurations welcome, of course. The first chunk below is a related bug fix for the IPv6 stack to not set the TENTATIVE bit on an address that doesn't want duplicate address detection. Index: netinet6/in6.c =================================================================== RCS file: /cvs/src/sys/netinet6/in6.c,v retrieving revision 1.122 diff -u -p -r1.122 in6.c --- netinet6/in6.c 24 Oct 2013 11:20:18 -0000 1.122 +++ netinet6/in6.c 6 Nov 2013 23:41:46 -0000 @@ -984,7 +984,8 @@ in6_update_ifa(struct ifnet *ifp, struct * source address. */ ia->ia6_flags &= ~IN6_IFF_DUPLICATED; /* safety */ - if (hostIsNew && in6if_do_dad(ifp)) + if (hostIsNew && in6if_do_dad(ifp) && + (ifra->ifra_flags & IN6_IFF_NODAD) == 0) ia->ia6_flags |= IN6_IFF_TENTATIVE; /* Index: net/if_sppp.h =================================================================== RCS file: /cvs/src/sys/net/if_sppp.h,v retrieving revision 1.18 diff -u -p -r1.18 if_sppp.h --- net/if_sppp.h 10 Jul 2013 07:46:10 -0000 1.18 +++ net/if_sppp.h 7 Nov 2013 01:15:20 -0000 @@ -120,8 +120,8 @@ struct sipcp { #define IPCP_MYADDR_DYN 2 /* my address is dynamically assigned */ #define IPCP_MYADDR_SEEN 4 /* have seen my address already */ #define IPCP_HISADDR_DYN 8 /* his address is dynamically assigned */ -#define IPV6CP_MYIFID_DYN 2 -#define IPV6CP_MYIFID_SEEN 4 +#define IPV6CP_MYIFID_DYN 1 /* my ifid is dynamically assigned */ +#define IPV6CP_MYIFID_SEEN 2 /* have seen my suggested ifid */ u_int32_t saved_hisaddr; /* if hisaddr (IPv4) is dynamic, save * original one here, in network byte order */ u_int32_t req_hisaddr; /* remote address requested */ Index: net/if_spppsubr.c =================================================================== RCS file: /cvs/src/sys/net/if_spppsubr.c,v retrieving revision 1.110 diff -u -p -r1.110 if_spppsubr.c --- net/if_spppsubr.c 5 Nov 2013 15:32:48 -0000 1.110 +++ net/if_spppsubr.c 7 Nov 2013 15:18:40 -0000 @@ -72,6 +72,10 @@ #include <net/if_sppp.h> +#ifdef INET6 +#include <netinet6/in6_var.h> +#endif + # define UNTIMEOUT(fun, arg, handle) \ timeout_del(&(handle)) @@ -318,8 +322,8 @@ HIDE void sppp_ipv6cp_scr(struct sppp *s HIDE const char *sppp_ipv6cp_opt_name(u_char opt); HIDE void sppp_get_ip6_addrs(struct sppp *sp, struct in6_addr *src, struct in6_addr *dst, struct in6_addr *srcmask); -HIDE void sppp_set_ip6_addr(struct sppp *sp, const struct in6_addr *src); -HIDE void sppp_gen_ip6_addr(struct sppp *sp, struct in6_addr *addr); +HIDE void sppp_set_ip6_addr(struct sppp *sp, const struct in6_addr *src, const struct in6_addr *dst); +HIDE void sppp_update_ip6_addr(void *arg1, void *arg2); HIDE void sppp_suggest_ip6_addr(struct sppp *sp, struct in6_addr *suggest); HIDE void sppp_pap_input(struct sppp *sp, struct mbuf *m); @@ -3126,17 +3130,13 @@ sppp_ipv6cp_open(struct sppp *sp) STDDCL; struct in6_addr myaddr, hisaddr; -#ifdef IPV6CP_MYIFID_DYN sp->ipv6cp.flags &= ~(IPV6CP_MYIFID_SEEN|IPV6CP_MYIFID_DYN); -#else - sp->ipv6cp.flags &= ~IPV6CP_MYIFID_SEEN; -#endif - sppp_get_ip6_addrs(sp, &myaddr, &hisaddr, 0); + sppp_get_ip6_addrs(sp, &myaddr, &hisaddr, NULL); /* * If we don't have our address, this probably means our * interface doesn't want to talk IPv6 at all. (This could - * be the case if somebody wants to speak only IPX, for + * be the case if the IFXF_NOINET6 flag is set, for * example.) Don't open IPv6CP in this case. */ if (IN6_IS_ADDR_UNSPECIFIED(&myaddr)) { @@ -3146,7 +3146,6 @@ sppp_ipv6cp_open(struct sppp *sp) SPP_ARGS(ifp)); return; } - sp->ipv6cp.flags |= IPV6CP_MYIFID_SEEN; sp->ipv6cp.opts |= (1 << IPV6CP_OPT_IFID); sppp_open_event(&ipv6cp, sp); } @@ -3238,7 +3237,7 @@ sppp_ipv6cp_RCR(struct sppp *sp, struct addlog("\n"); /* pass 2: parse option values */ - sppp_get_ip6_addrs(sp, &myaddr, 0, 0); + sppp_get_ip6_addrs(sp, &myaddr, NULL, NULL); if (debug) log(LOG_DEBUG, "%s: ipv6cp parse opt values: ", SPP_ARGS(ifp)); @@ -3271,6 +3270,7 @@ sppp_ipv6cp_RCR(struct sppp *sp, struct ip6_sprintf(&desiredaddr), sppp_cp_type_name(type)); } + sppp_set_ip6_addr(sp, &myaddr, &desiredaddr); continue; } @@ -3390,52 +3390,32 @@ sppp_ipv6cp_RCN_nak(struct sppp *sp, str */ if (len < 10 || p[1] != 10) break; + sp->ipv6cp.flags |= IPV6CP_MYIFID_DYN; memset(&suggestaddr, 0, sizeof(suggestaddr)); - suggestaddr.s6_addr16[0] = htons(0xfe80); bcopy(&p[2], &suggestaddr.s6_addr[8], 8); - - sp->ipv6cp.opts |= (1 << IPV6CP_OPT_IFID); - if (debug) - addlog(" [suggestaddr %s]", - ip6_sprintf(&suggestaddr)); -#ifdef IPV6CP_MYIFID_DYN - /* - * When doing dynamic address assignment, - * we accept his offer. - */ - if (sp->ipv6cp.flags & IPV6CP_MYIFID_DYN) { - struct in6_addr lastsuggest; + if (IN6_IS_ADDR_UNSPECIFIED(&suggestaddr) || + (sp->ipv6cp.flags & IPV6CP_MYIFID_SEEN)) { /* - * If <suggested myaddr from peer> equals to - * <hisaddr we have suggested last time>, - * we have a collision. generate new random - * ifid. + * The peer didn't suggest anything, + * or wants us to change a previously + * suggested address. + * Configure a new address for us. */ - sppp_suggest_ip6_addr(sp,&lastsuggest); - if (IN6_ARE_ADDR_EQUAL(&suggestaddr, - &lastsuggest)) { - if (debug) - addlog(" [random]"); - sppp_gen_ip6_addr(sp, &suggestaddr); - } - sppp_set_ip6_addr(sp, &suggestaddr); + sppp_suggest_ip6_addr(sp, &suggestaddr); + sppp_set_ip6_addr(sp, &suggestaddr, NULL); + sp->ipv6cp.flags &= ~IPV6CP_MYIFID_SEEN; + } else { + /* Configure address suggested by peer. */ + suggestaddr.s6_addr16[0] = htons(0xfe80); + sp->ipv6cp.opts |= (1 << IPV6CP_OPT_IFID); + if (debug) + addlog(" [suggestaddr %s]", + ip6_sprintf(&suggestaddr)); + sppp_set_ip6_addr(sp, &suggestaddr, NULL); if (debug) addlog(" [agree]"); sp->ipv6cp.flags |= IPV6CP_MYIFID_SEEN; } -#else - /* - * Since we do not do dynamic address assignment, - * we ignore it and thus continue to negotiate - * our already existing value. This can possibly - * go into infinite request-reject loop. - * - * This is not likely because we normally use - * ifid based on MAC-address. - * If you have no ethernet card on the node, too bad. - * XXX should we use fail_counter? - */ -#endif break; #ifdef notyet case IPV6CP_OPT_COMPRESS: @@ -3483,7 +3463,7 @@ sppp_ipv6cp_scr(struct sppp *sp) int i = 0; if (sp->ipv6cp.opts & (1 << IPV6CP_OPT_IFID)) { - sppp_get_ip6_addrs(sp, &ouraddr, 0, 0); + sppp_get_ip6_addrs(sp, &ouraddr, NULL, NULL); opt[i++] = IPV6CP_OPT_IFID; opt[i++] = 10; bcopy(&ouraddr.s6_addr[8], &opt[i], 8); @@ -4738,39 +4718,27 @@ sppp_get_ip6_addrs(struct sppp *sp, stru struct in6_addr *srcmask) { struct ifnet *ifp = &sp->pp_if; - struct ifaddr *ifa; - struct sockaddr_in6 *si, *sm; + struct in6_ifaddr *ia; struct in6_addr ssrc, ddst; - sm = NULL; bzero(&ssrc, sizeof(ssrc)); bzero(&ddst, sizeof(ddst)); /* * Pick the first link-local AF_INET6 address from the list, * aliases don't make any sense on a p2p link anyway. */ - si = 0; - TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { - if (ifa->ifa_addr->sa_family == AF_INET6) { - si = (struct sockaddr_in6 *)ifa->ifa_addr; - sm = (struct sockaddr_in6 *)ifa->ifa_netmask; - if (si && IN6_IS_ADDR_LINKLOCAL(&si->sin6_addr)) - break; - } - } - - if (ifa) { - if (si && !IN6_IS_ADDR_UNSPECIFIED(&si->sin6_addr)) { - bcopy(&si->sin6_addr, &ssrc, sizeof(ssrc)); + ia = in6ifa_ifpforlinklocal(ifp, 0); + if (ia) { + if (!IN6_IS_ADDR_UNSPECIFIED(&ia->ia_addr.sin6_addr)) { + bcopy(&ia->ia_addr.sin6_addr, &ssrc, sizeof(ssrc)); if (srcmask) { - bcopy(&sm->sin6_addr, srcmask, + bcopy(&ia->ia_prefixmask.sin6_addr, srcmask, sizeof(*srcmask)); } } - si = (struct sockaddr_in6 *)ifa->ifa_dstaddr; - if (si && !IN6_IS_ADDR_UNSPECIFIED(&si->sin6_addr)) - bcopy(&si->sin6_addr, &ddst, sizeof(ddst)); + if (!IN6_IS_ADDR_UNSPECIFIED(&ia->ia_dstaddr.sin6_addr)) + bcopy(&ia->ia_dstaddr.sin6_addr, &ddst, sizeof(ddst)); } if (dst) @@ -4779,70 +4747,111 @@ sppp_get_ip6_addrs(struct sppp *sp, stru bcopy(&ssrc, src, sizeof(*src)); } -#ifdef IPV6CP_MYIFID_DYN -/* - * Generate random ifid. - */ +/* Task to update my IPv6 address from process context. */ HIDE void -sppp_gen_ip6_addr(struct sppp *sp, struct in6_addr *addr) +sppp_update_ip6_addr(void *arg1, void *arg2) { - /* TBD */ + struct sppp *sp = arg1; + struct ifnet *ifp = &sp->pp_if; + struct in6_aliasreq *ifra = arg2; + struct in6_addr mask = in6mask128; + struct in6_ifaddr *ia; + int s, error; + + s = splnet(); + + ia = in6ifa_ifpforlinklocal(ifp, 0); + if (ia == NULL) { + /* IPv6 disabled? */ + splx(s); + free(ifra, M_TEMP); + return; + } + + /* Destination address can only be set for /128. */ + if (!in6_are_prefix_equal(&ia->ia_prefixmask.sin6_addr, &mask, 128)) { + ifra->ifra_dstaddr.sin6_len = 0; + ifra->ifra_dstaddr.sin6_family = AF_UNSPEC; + } + + ifra->ifra_lifetime = ia->ia6_lifetime; + + error = in6_update_ifa(ifp, ifra, ia); + if (error) { + log(LOG_ERR, SPP_FMT + "could not update IPv6 address (error %d)\n", + SPP_ARGS(ifp), error); + } + splx(s); + free(ifra, M_TEMP); } /* - * Set my IPv6 address. Must be called at splnet. + * Configure my link-local address. */ HIDE void -sppp_set_ip6_addr(struct sppp *sp, const struct in6_addr *src) +sppp_set_ip6_addr(struct sppp *sp, const struct in6_addr *src, + const struct in6_addr *dst) { struct ifnet *ifp = &sp->pp_if; - struct ifaddr *ifa; - struct sockaddr_in6 *sin6; + struct in6_aliasreq *ifra; - /* - * Pick the first link-local AF_INET6 address from the list, - * aliases don't make any sense on a p2p link anyway. + ifra = malloc(sizeof(*ifra), M_TEMP, M_NOWAIT|M_ZERO); + if (ifra == NULL) + return; + + bcopy(ifp->if_xname, ifra->ifra_name, sizeof(ifra->ifra_name)); + + ifra->ifra_addr.sin6_len = sizeof(struct sockaddr_in6); + ifra->ifra_addr.sin6_family = AF_INET6; + ifra->ifra_addr.sin6_addr = *src; + if (dst) { + ifra->ifra_dstaddr.sin6_len = sizeof(struct sockaddr_in6); + ifra->ifra_dstaddr.sin6_family = AF_INET6; + ifra->ifra_dstaddr.sin6_addr = *dst; + } else + ifra->ifra_dstaddr.sin6_family = AF_UNSPEC; + + /* + * Don't change the existing prefixlen. + * It is common to use a /64 for IPv6 over point-to-point links + * to allow e.g. neighbour discovery and autoconf to work. + * But it is legal to use other values. */ + ifra->ifra_prefixmask.sin6_family = AF_UNSPEC; - sin6 = NULL; - TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { - if (ifa->ifa_addr->sa_family == AF_INET6) { - sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; - if (sin6 && IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) - break; - } - } + /* DAD is redundant after an IPv6CP exchange. */ + ifra->ifra_flags |= IN6_IFF_NODAD; - if (ifa && sin6) { - struct sockaddr_in6 new_sin6 = *sin6; - bcopy(src, &new_sin6.sin6_addr, sizeof(new_sin6.sin6_addr)); - dohooks(ifp->if_addrhooks, 0); + if (workq_add_task(NULL, 0, sppp_update_ip6_addr, sp, ifra)) { + free(ifra, M_TEMP); + printf("%s: workq_add_task failed, cannot set IPv6 " + "addresses\n", ifp->if_xname); } } -#endif /* - * Suggest a candidate address to be used by peer. + * Generate an address that differs from our existing address. */ HIDE void sppp_suggest_ip6_addr(struct sppp *sp, struct in6_addr *suggest) { struct in6_addr myaddr; - struct timeval tv; + u_int32_t random; - sppp_get_ip6_addrs(sp, &myaddr, 0, 0); + sppp_get_ip6_addrs(sp, &myaddr, NULL, NULL); myaddr.s6_addr[8] &= ~0x02; /* u bit to "local" */ - getmicrouptime(&tv); - if ((tv.tv_usec & 0xff) == 0 && (tv.tv_sec & 0xff) == 0) { + + random = arc4random(); + if ((random & 0xff) == 0 && (random & 0xff00) == 0) { myaddr.s6_addr[14] ^= 0xff; myaddr.s6_addr[15] ^= 0xff; } else { - myaddr.s6_addr[14] ^= (tv.tv_usec & 0xff); - myaddr.s6_addr[15] ^= (tv.tv_sec & 0xff); + myaddr.s6_addr[14] ^= (random & 0xff); + myaddr.s6_addr[15] ^= ((random & 0xff00) >> 8); } - if (suggest) - bcopy(&myaddr, suggest, sizeof(myaddr)); + bcopy(&myaddr, suggest, sizeof(myaddr)); } #endif /*INET6*/