diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -151,6 +151,8 @@ static int in6_notify_ifa(struct ifnet *, struct in6_ifaddr *, struct in6_aliasreq *, int); +static int in6_ifaddprefix(struct in6_ifaddr *); +static int in6_ifremprefix(struct in6_ifaddr *); static void in6_unlink_ifa(struct in6_ifaddr *, struct ifnet *); static int in6_validate_ifra(struct ifnet *, struct in6_aliasreq *, @@ -161,6 +163,8 @@ struct in6_ifaddr *, int, int); static int in6_broadcast_ifa(struct ifnet *, struct in6_aliasreq *, struct in6_ifaddr *, int); +static int in6_handle_rtrequest(int cmd, struct in6_ifaddr *ia, + int flags); #define ifa2ia6(ifa) ((struct in6_ifaddr *)(ifa)) #define ia62ifa(ia6) (&((ia6)->ia_ifa)) @@ -198,6 +202,87 @@ } } +/* Add prefix route for the network. */ +static int +in6_ifaddprefix(struct in6_ifaddr *ia) +{ + int error, flags = 0; + + if (in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL) == 128) { + if (ia->ia_dstaddr.sin6_family != AF_INET6) + /* We don't need to install a host route. */ + return 0; + flags |= RTF_HOST; + } + + if ((error = in6_handle_rtrequest(RTM_ADD, ia, flags)) == 0) + ia->ia_flags |= IFA_ROUTE; + else if (error == EEXIST) + /* Existance of the route is not an error. */ + error = 0; + + return error; +} + +/* + * Delete network prefix route if present. + * Re-add it to another address if the prefix matches. + */ +static int +in6_ifremprefix(struct in6_ifaddr *target) +{ + int error, flags = 0; + struct rm_priotracker in6_ifa_tracker; + struct in6_ifaddr *ia; + + if ((target->ia_flags & IFA_ROUTE) == 0) + return 0; + + if (in6_mask2len(&target->ia_prefixmask.sin6_addr, NULL) == 128 && + target->ia_dstaddr.sin6_family == AF_INET6) + flags |= RTF_HOST; + + IN6_IFADDR_RLOCK(&in6_ifa_tracker); + CK_STAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) { + if (target->ia_dstaddr.sin6_len) { + if (ia->ia_dstaddr.sin6_len == 0 || + !IN6_ARE_ADDR_EQUAL(&ia->ia_dstaddr.sin6_addr, + &target->ia_dstaddr.sin6_addr)) + continue; + } else { + if (!IN6_ARE_MASKED_ADDR_EQUAL(&ia->ia_addr.sin6_addr, + &target->ia_addr.sin6_addr, + &target->ia_prefixmask.sin6_addr)) + continue; + } + + /* + * if we got a matching prefix route, move IFA_ROUTE to him + */ + if ((ia->ia_flags & IFA_ROUTE) == 0) { + ifa_ref(&ia->ia_ifa); + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); + + in6_handle_rtrequest(RTM_DELETE, target, flags); + target->ia_flags &= ~IFA_ROUTE; + + error = in6_ifaddprefix(ia); + + ifa_free(&ia->ia_ifa); + + return error; + } + } + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); + + /* + * noone seem to have prefix route. remove it. + */ + in6_handle_rtrequest(RTM_DELETE, target, flags); + target->ia_flags &= ~IFA_ROUTE; + return 0; +} + int in6_mask2len(struct in6_addr *mask, u_char *lim0) { @@ -559,11 +644,8 @@ case SIOCAIFADDR_IN6: { - struct nd_prefixctl pr0; - struct nd_prefix *pr; - /* - * first, make or update the interface address structure, + * make or update the interface address structure, * and link it to the list. */ if ((error = in6_update_ifa(ifp, ifra, ia, 0)) != 0) @@ -594,85 +676,6 @@ carp_attached = 1; } - /* - * then, make the prefix on-link on the interface. - * XXX: we'd rather create the prefix before the address, but - * we need at least one address to install the corresponding - * interface route, so we configure the address first. - */ - - /* - * convert mask to prefix length (prefixmask has already - * been validated in in6_update_ifa(). - */ - bzero(&pr0, sizeof(pr0)); - pr0.ndpr_ifp = ifp; - pr0.ndpr_plen = in6_mask2len(&ifra->ifra_prefixmask.sin6_addr, - NULL); - if (pr0.ndpr_plen == 128) { - /* we don't need to install a host route. */ - goto aifaddr_out; - } - pr0.ndpr_prefix = ifra->ifra_addr; - /* apply the mask for safety. */ - IN6_MASK_ADDR(&pr0.ndpr_prefix.sin6_addr, - &ifra->ifra_prefixmask.sin6_addr); - - /* - * XXX: since we don't have an API to set prefix (not address) - * lifetimes, we just use the same lifetimes as addresses. - * The (temporarily) installed lifetimes can be overridden by - * later advertised RAs (when accept_rtadv is non 0), which is - * an intended behavior. - */ - pr0.ndpr_raf_onlink = 1; /* should be configurable? */ - pr0.ndpr_raf_auto = - ((ifra->ifra_flags & IN6_IFF_AUTOCONF) != 0); - pr0.ndpr_vltime = ifra->ifra_lifetime.ia6t_vltime; - pr0.ndpr_pltime = ifra->ifra_lifetime.ia6t_pltime; - - /* add the prefix if not yet. */ - if ((pr = nd6_prefix_lookup(&pr0)) == NULL) { - /* - * nd6_prelist_add will install the corresponding - * interface route. - */ - if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) { - if (carp_attached) - (*carp_detach_p)(&ia->ia_ifa, false); - goto out; - } - } - - /* relate the address to the prefix */ - if (ia->ia6_ndpr == NULL) { - ia->ia6_ndpr = pr; - pr->ndpr_addrcnt++; - - /* - * If this is the first autoconf address from the - * prefix, create a temporary address as well - * (when required). - */ - if ((ia->ia6_flags & IN6_IFF_AUTOCONF) && - V_ip6_use_tempaddr && pr->ndpr_addrcnt == 1) { - int e; - if ((e = in6_tmpifadd(ia, 1, 0)) != 0) { - log(LOG_NOTICE, "in6_control: failed " - "to create a temporary address, " - "errno=%d\n", e); - } - } - } - nd6_prefix_rele(pr); - - /* - * this might affect the status of autoconfigured addresses, - * that is, this address might make other addresses detached. - */ - pfxlist_onlink_check(); - -aifaddr_out: /* * Try to clear the flag when a new IPv6 address is added * onto an IFDISABLED interface and it succeeds. @@ -1037,19 +1040,23 @@ /* Check prefix mask */ if (ia != NULL && ifra->ifra_prefixmask.sin6_len != 0) { - /* - * We prohibit changing the prefix length of an existing - * address, because - * + such an operation should be rare in IPv6, and - * + the operation would confuse prefix management. - */ - if (ia->ia_prefixmask.sin6_len != 0 && - in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL) != plen) { - nd6log((LOG_INFO, "in6_validate_ifa: the prefix length " - "of an existing %s address should not be changed\n", - ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr))); + if (ia->ia_prefixmask.sin6_len != 0) { + /* + * We prohibit changing the prefix length of an + * existing address, because + * + such an operation should be rare in IPv6, and + * + the operation would confuse prefix management. + */ + if (ia->ia6_ndpr != NULL && + in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL) != + plen) { + nd6log((LOG_INFO, "in6_validate_ifa: the " + "prefix length of an existing %s address " + "should not be changed\n", + ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr))); - return (EINVAL); + return (EINVAL); + } } } @@ -1094,6 +1101,11 @@ /* set prefix mask if any */ ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; if (ifra->ifra_prefixmask.sin6_len != 0) { + if (ia->ia_prefixmask.sin6_len != 0) { + if (!IN6_ARE_ADDR_EQUAL(&ia->ia_prefixmask.sin6_addr, + &ifra->ifra_prefixmask.sin6_addr)) + in6_ifremprefix(ia); + } ia->ia_prefixmask.sin6_family = AF_INET6; ia->ia_prefixmask.sin6_len = ifra->ifra_prefixmask.sin6_len; ia->ia_prefixmask.sin6_addr = ifra->ifra_prefixmask.sin6_addr; @@ -1253,11 +1265,11 @@ } /* - * Adds or deletes interface route for p2p ifa. + * Adds or deletes interface route for ifa. * Returns 0 on success or errno. */ static int -in6_handle_dstaddr_rtrequest(int cmd, struct in6_ifaddr *ia) +in6_handle_rtrequest(int cmd, struct in6_ifaddr *ia, int flags) { struct epoch_tracker et; struct ifaddr *ifa = &ia->ia_ifa; @@ -1274,14 +1286,23 @@ struct sockaddr_in6 dst = { .sin6_family = AF_INET6, .sin6_len = sizeof(struct sockaddr_in6), - .sin6_addr = ia->ia_dstaddr.sin6_addr, + .sin6_addr = (flags & RTF_HOST) ? + ia->ia_dstaddr.sin6_addr : ia->ia_addr.sin6_addr, }; + struct sockaddr_in6 *netmask6 = (flags & RTF_HOST) ? + NULL : &ia->ia_prefixmask; + + if (netmask6) + IN6_MASK_ADDR(&dst.sin6_addr, &netmask6->sin6_addr); + struct rt_addrinfo info = { .rti_ifa = ifa, - .rti_flags = RTF_PINNED | RTF_HOST, + .rti_flags = flags | + (ifa->ifa_flags & ~IFA_RTSELF) | RTF_PINNED, .rti_info = { [RTAX_DST] = (struct sockaddr *)&dst, + [RTAX_NETMASK] = (struct sockaddr *)netmask6, [RTAX_GATEWAY] = (struct sockaddr *)&sdl, }, }; @@ -1294,20 +1315,6 @@ return (error); } -static bool -ifa_is_p2p(struct in6_ifaddr *ia) -{ - int plen; - - plen = in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL); /* XXX */ - - if ((plen == 128) && (ia->ia_dstaddr.sin6_family == AF_INET6) && - !IN6_ARE_ADDR_EQUAL(&ia->ia_addr.sin6_addr, &ia->ia_dstaddr.sin6_addr)) - return (true); - - return (false); -} - void in6_purgeaddr(struct ifaddr *ifa) { @@ -1341,14 +1348,9 @@ in6_leavegroup(imm->i6mm_maddr, NULL); free(imm, M_IP6MADDR); } - /* Check if we need to remove p2p route */ - if ((ia->ia_flags & IFA_ROUTE) && ifa_is_p2p(ia)) { - error = in6_handle_dstaddr_rtrequest(RTM_DELETE, ia); - if (error != 0) - log(LOG_INFO, "%s: err=%d, destination address delete " - "failed\n", __func__, error); - ia->ia_flags &= ~IFA_ROUTE; - } + + /* Delete any network route. */ + in6_ifremprefix(ia); in6_newaddrmsg(ia, RTM_DELETE); in6_unlink_ifa(ia, ifp); @@ -1366,11 +1368,6 @@ /* * If the address being deleted is the only one that owns * the corresponding prefix, expire the prefix as well. - * XXX: theoretically, we don't have to worry about such - * relationship, since we separate the address management - * and the prefix management. We do this, however, to provide - * as much backward compatibility as possible in terms of - * the ioctl operation. * Note that in6_purgeaddr() will decrement ndpr_addrcnt. */ pr = ia->ia6_ndpr; @@ -1387,7 +1384,6 @@ static void in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) { - char ip6buf[INET6_ADDRSTRLEN]; int remove_lle; IF_ADDR_WLOCK(ifp); @@ -1406,15 +1402,10 @@ IN6_IFADDR_WUNLOCK(); /* - * Release the reference to the base prefix. There should be a - * positive reference. + * Release the reference to the ND prefix. */ remove_lle = 0; - if (ia->ia6_ndpr == NULL) { - nd6log((LOG_NOTICE, - "in6_unlink_ifa: autoconf'ed address " - "%s has no prefix\n", ip6_sprintf(ip6buf, IA6_IN6(ia)))); - } else { + if (ia->ia6_ndpr != NULL) { ia->ia6_ndpr->ndpr_addrcnt--; /* Do not delete lles within prefix if refcont != 0 */ if (ia->ia6_ndpr->ndpr_addrcnt == 0) @@ -1448,7 +1439,6 @@ int error = 0, ifacount = 0; struct ifaddr *ifa; struct sockaddr_in6 *pdst; - char ip6buf[INET6_ADDRSTRLEN]; /* * Give the interface a chance to initialize @@ -1472,47 +1462,33 @@ goto done; } - /* - * If a new destination address is specified, scrub the old one and - * install the new destination. Note that the interface must be - * p2p or loopback. - */ + /* Set destination address. */ pdst = &ifra->ifra_dstaddr; - if (pdst->sin6_family == AF_INET6 && - !IN6_ARE_ADDR_EQUAL(&pdst->sin6_addr, &ia->ia_dstaddr.sin6_addr)) { - if ((ia->ia_flags & IFA_ROUTE) != 0 && - (in6_handle_dstaddr_rtrequest(RTM_DELETE, ia) != 0)) { - nd6log((LOG_ERR, "in6_update_ifa_internal: failed to " - "remove a route to the old destination: %s\n", - ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr))); - /* proceed anyway... */ - } else - ia->ia_flags &= ~IFA_ROUTE; + if (pdst->sin6_family == AF_INET6) { + if (!IN6_ARE_ADDR_EQUAL(&pdst->sin6_addr, + &ia->ia_dstaddr.sin6_addr)) + in6_ifremprefix(ia); ia->ia_dstaddr = *pdst; } - /* - * If a new destination address is specified for a point-to-point - * interface, install a route to the destination as an interface - * direct route. - * XXX: the logic below rejects assigning multiple addresses on a p2p - * interface that share the same destination. - */ - if (!(ia->ia_flags & IFA_ROUTE) && ifa_is_p2p(ia)) { - error = in6_handle_dstaddr_rtrequest(RTM_ADD, ia); - if (error) - goto done; - ia->ia_flags |= IFA_ROUTE; - } - /* * add a loopback route to self if not exists */ if (!(ia->ia_flags & IFA_RTSELF) && V_nd6_useloopback) { error = ifa_add_loopback_route((struct ifaddr *)ia, (struct sockaddr *)&ia->ia_addr); - if (error == 0) - ia->ia_flags |= IFA_RTSELF; + if (error) + goto done; + ia->ia_flags |= IFA_RTSELF; + } + + /* Add the network prefix route. */ + if ((error = in6_ifaddprefix(ia)) != 0) { + if (hostIsNew != 0) { + if (ifa_del_loopback_route((struct ifaddr *)ia, + (struct sockaddr *)&ia->ia_addr) == 0) + ia->ia_flags &= ~IFA_RTSELF; + } } done: WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, 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 @@ -422,9 +422,7 @@ { struct in6_ifaddr *ia; struct in6_aliasreq ifra; - struct nd_prefixctl pr0; struct epoch_tracker et; - struct nd_prefix *pr; int error; /* @@ -490,44 +488,6 @@ } ifa_free(&ia->ia_ifa); - /* - * Make the link-local prefix (fe80::%link/64) as on-link. - * Since we'd like to manage prefixes separately from addresses, - * we make an ND6 prefix structure for the link-local prefix, - * and add it to the prefix list as a never-expire prefix. - * XXX: this change might affect some existing code base... - */ - bzero(&pr0, sizeof(pr0)); - pr0.ndpr_ifp = ifp; - /* this should be 64 at this moment. */ - pr0.ndpr_plen = in6_mask2len(&ifra.ifra_prefixmask.sin6_addr, NULL); - pr0.ndpr_prefix = ifra.ifra_addr; - /* apply the mask for safety. (nd6_prelist_add will apply it again) */ - IN6_MASK_ADDR(&pr0.ndpr_prefix.sin6_addr, &in6mask64); - /* - * Initialize parameters. The link-local prefix must always be - * on-link, and its lifetimes never expire. - */ - pr0.ndpr_raf_onlink = 1; - pr0.ndpr_raf_auto = 1; /* probably meaningless */ - pr0.ndpr_vltime = ND6_INFINITE_LIFETIME; - pr0.ndpr_pltime = ND6_INFINITE_LIFETIME; - /* - * Since there is no other link-local addresses, nd6_prefix_lookup() - * probably returns NULL. However, we cannot always expect the result. - * For example, if we first remove the (only) existing link-local - * address, and then reconfigure another one, the prefix is still - * valid with referring to the old link-local address. - */ - if ((pr = nd6_prefix_lookup(&pr0)) == NULL) { - if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) - return (error); - /* Reference prefix */ - ia->ia6_ndpr = pr; - pr->ndpr_addrcnt++; - } else - nd6_prefix_rele(pr); - return 0; } 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 @@ -1757,6 +1757,13 @@ pr->ndpr_addrcnt++; ia6->ia6_ndpr = pr; + /* + * toggle onlink state if the address was assigned + * a prefix route. + */ + if (ia6->ia_flags & IFA_ROUTE) + pr->ndpr_stateflags |= NDPRF_ONLINK; + /* * RFC 3041 3.3 (2). * When a new public address is created as described