diff options
Diffstat (limited to 'freebsd/sys/netinet6/in6.c')
-rw-r--r-- | freebsd/sys/netinet6/in6.c | 1836 |
1 files changed, 752 insertions, 1084 deletions
diff --git a/freebsd/sys/netinet6/in6.c b/freebsd/sys/netinet6/in6.c index 66888fa8..f5d82524 100644 --- a/freebsd/sys/netinet6/in6.c +++ b/freebsd/sys/netinet6/in6.c @@ -70,6 +70,7 @@ __FBSDID("$FreeBSD$"); #include <rtems/bsd/local/opt_inet6.h> #include <rtems/bsd/sys/param.h> +#include <sys/eventhandler.h> #include <rtems/bsd/sys/errno.h> #include <sys/jail.h> #include <sys/malloc.h> @@ -81,6 +82,8 @@ __FBSDID("$FreeBSD$"); #include <sys/proc.h> #include <sys/time.h> #include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/rmlock.h> #include <sys/syslog.h> #include <net/if.h> @@ -97,6 +100,7 @@ __FBSDID("$FreeBSD$"); #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/in_pcb.h> +#include <netinet/ip_carp.h> #include <netinet/ip6.h> #include <netinet6/ip6_var.h> @@ -105,6 +109,7 @@ __FBSDID("$FreeBSD$"); #include <netinet6/ip6_mroute.h> #include <netinet6/in6_ifattach.h> #include <netinet6/scope6_var.h> +#include <netinet6/in6_fib.h> #include <netinet6/in6_pcb.h> VNET_DECLARE(int, icmp6_nodeinfo_oldmcprefix); @@ -135,49 +140,36 @@ const struct in6_addr in6mask128 = IN6MASK128; const struct sockaddr_in6 sa6_any = { sizeof(sa6_any), AF_INET6, 0, 0, IN6ADDR_ANY_INIT, 0 }; -static int in6_lifaddr_ioctl(struct socket *, u_long, caddr_t, - struct ifnet *, struct thread *); -static int in6_ifinit(struct ifnet *, struct in6_ifaddr *, - struct sockaddr_in6 *, int); +static int in6_notify_ifa(struct ifnet *, struct in6_ifaddr *, + struct in6_aliasreq *, int); static void in6_unlink_ifa(struct in6_ifaddr *, struct ifnet *); -int (*faithprefix_p)(struct in6_addr *); +static int in6_validate_ifra(struct ifnet *, struct in6_aliasreq *, + struct in6_ifaddr *, int); +static struct in6_ifaddr *in6_alloc_ifa(struct ifnet *, + struct in6_aliasreq *, int flags); +static int in6_update_ifa_internal(struct ifnet *, struct in6_aliasreq *, + struct in6_ifaddr *, int, int); +static int in6_broadcast_ifa(struct ifnet *, struct in6_aliasreq *, + struct in6_ifaddr *, int); #define ifa2ia6(ifa) ((struct in6_ifaddr *)(ifa)) #define ia62ifa(ia6) (&((ia6)->ia_ifa)) + void -in6_ifaddloop(struct ifaddr *ifa) +in6_newaddrmsg(struct in6_ifaddr *ia, int cmd) { struct sockaddr_dl gateway; struct sockaddr_in6 mask, addr; struct rtentry rt; - struct in6_ifaddr *ia; - struct ifnet *ifp; - struct llentry *ln; - - ia = ifa2ia6(ifa); - ifp = ifa->ifa_ifp; - IF_AFDATA_LOCK(ifp); - ifa->ifa_rtrequest = nd6_rtrequest; - ln = lla_lookup(LLTABLE6(ifp), (LLE_CREATE | LLE_IFADDR | - LLE_EXCLUSIVE), (struct sockaddr *)&ia->ia_addr); - IF_AFDATA_UNLOCK(ifp); - if (ln != NULL) { - ln->la_expire = 0; /* for IPv6 this means permanent */ - ln->ln_state = ND6_LLINFO_REACHABLE; - /* - * initialize for rtmsg generation - */ - bzero(&gateway, sizeof(gateway)); - gateway.sdl_len = sizeof(gateway); - gateway.sdl_family = AF_LINK; - gateway.sdl_nlen = 0; - gateway.sdl_alen = 6; - memcpy(gateway.sdl_data, &ln->ll_addr.mac_aligned, - sizeof(ln->ll_addr)); - LLE_WUNLOCK(ln); - } + + /* + * initialize for rtmsg generation + */ + bzero(&gateway, sizeof(gateway)); + gateway.sdl_len = sizeof(gateway); + gateway.sdl_family = AF_LINK; bzero(&rt, sizeof(rt)); rt.rt_gateway = (struct sockaddr *)&gateway; @@ -185,42 +177,11 @@ in6_ifaddloop(struct ifaddr *ifa) memcpy(&addr, &ia->ia_addr, sizeof(ia->ia_addr)); rt_mask(&rt) = (struct sockaddr *)&mask; rt_key(&rt) = (struct sockaddr *)&addr; - rt.rt_flags = RTF_UP | RTF_HOST | RTF_STATIC; + rt.rt_flags = RTF_HOST | RTF_STATIC; + if (cmd == RTM_ADD) + rt.rt_flags |= RTF_UP; /* Announce arrival of local address to all FIBs. */ - rt_newaddrmsg(RTM_ADD, ifa, 0, &rt); -} - -void -in6_ifremloop(struct ifaddr *ifa) -{ - struct sockaddr_dl gateway; - struct sockaddr_in6 mask, addr; - struct rtentry rt0; - struct in6_ifaddr *ia; - struct ifnet *ifp; - - ia = ifa2ia6(ifa); - ifp = ifa->ifa_ifp; - memcpy(&addr, &ia->ia_addr, sizeof(ia->ia_addr)); - memcpy(&mask, &ia->ia_prefixmask, sizeof(ia->ia_prefixmask)); - lltable_prefix_free(AF_INET6, (struct sockaddr *)&addr, - (struct sockaddr *)&mask, LLE_STATIC); - - /* - * initialize for rtmsg generation - */ - bzero(&gateway, sizeof(gateway)); - gateway.sdl_len = sizeof(gateway); - gateway.sdl_family = AF_LINK; - gateway.sdl_nlen = 0; - gateway.sdl_alen = ifp->if_addrlen; - bzero(&rt0, sizeof(rt0)); - rt0.rt_gateway = (struct sockaddr *)&gateway; - rt_mask(&rt0) = (struct sockaddr *)&mask; - rt_key(&rt0) = (struct sockaddr *)&addr; - rt0.rt_flags = RTF_HOST | RTF_STATIC; - /* Announce removal of local address to all FIBs. */ - rt_newaddrmsg(RTM_DELETE, ifa, 0, &rt0); + rt_newaddrmsg(cmd, &ia->ia_ifa, 0, &rt); } int @@ -275,7 +236,15 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, struct in6_ifaddr *ia = NULL; struct in6_aliasreq *ifra = (struct in6_aliasreq *)data; struct sockaddr_in6 *sa6; + int carp_attached = 0; int error; + u_long ocmd = cmd; + + /* + * Compat to make pre-10.x ifconfig(8) operable. + */ + if (cmd == OSIOCAIFADDR_IN6) + cmd = SIOCAIFADDR_IN6; switch (cmd) { case SIOCGETSGCNT_IN6: @@ -317,8 +286,6 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, /* FALLTHROUGH */ case OSIOCGIFINFO_IN6: case SIOCGIFINFO_IN6: - case SIOCGDRLST_IN6: - case SIOCGPRLST_IN6: case SIOCGNBRINFO_IN6: case SIOCGDEFIFACE_IN6: return (nd6_ioctl(cmd, data, ifp)); @@ -366,26 +333,6 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, return (scope6_ioctl(cmd, data, ifp)); } - switch (cmd) { - case SIOCALIFADDR: - if (td != NULL) { - error = priv_check(td, PRIV_NET_ADDIFADDR); - if (error) - return (error); - } - return in6_lifaddr_ioctl(so, cmd, data, ifp, td); - - case SIOCDLIFADDR: - if (td != NULL) { - error = priv_check(td, PRIV_NET_DELIFADDR); - if (error) - return (error); - } - /* FALLTHROUGH */ - case SIOCGLIFADDR: - return in6_lifaddr_ioctl(so, cmd, data, ifp, td); - } - /* * Find address for this interface, if it exists. * @@ -417,7 +364,6 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, case SIOCSPFXFLUSH_IN6: case SIOCSRTRFLUSH_IN6: case SIOCGIFALIFETIME_IN6: - case SIOCSIFALIFETIME_IN6: case SIOCGIFSTAT_IN6: case SIOCGIFSTAT_ICMP6: sa6 = &ifr->ifr_addr; @@ -516,34 +462,6 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, goto out; } break; - - case SIOCSIFALIFETIME_IN6: - { - struct in6_addrlifetime *lt; - - if (td != NULL) { - error = priv_check(td, PRIV_NETINET_ALIFETIME6); - if (error) - goto out; - } - if (ia == NULL) { - error = EADDRNOTAVAIL; - goto out; - } - /* sanity for overflow - beware unsigned */ - lt = &ifr->ifr_ifru.ifru_lifetime; - if (lt->ia6t_vltime != ND6_INFINITE_LIFETIME && - lt->ia6t_vltime + time_second < time_second) { - error = EINVAL; - goto out; - } - if (lt->ia6t_pltime != ND6_INFINITE_LIFETIME && - lt->ia6t_pltime + time_second < time_second) { - error = EINVAL; - goto out; - } - break; - } } switch (cmd) { @@ -576,17 +494,17 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, break; case SIOCGIFSTAT_IN6: - bzero(&ifr->ifr_ifru.ifru_stat, - sizeof(ifr->ifr_ifru.ifru_stat)); - ifr->ifr_ifru.ifru_stat = - *((struct in6_ifextra *)ifp->if_afdata[AF_INET6])->in6_ifstat; + COUNTER_ARRAY_COPY(((struct in6_ifextra *) + ifp->if_afdata[AF_INET6])->in6_ifstat, + &ifr->ifr_ifru.ifru_stat, + sizeof(struct in6_ifstat) / sizeof(uint64_t)); break; case SIOCGIFSTAT_ICMP6: - bzero(&ifr->ifr_ifru.ifru_icmp6stat, - sizeof(ifr->ifr_ifru.ifru_icmp6stat)); - ifr->ifr_ifru.ifru_icmp6stat = - *((struct in6_ifextra *)ifp->if_afdata[AF_INET6])->icmp6_ifstat; + COUNTER_ARRAY_COPY(((struct in6_ifextra *) + ifp->if_afdata[AF_INET6])->icmp6_ifstat, + &ifr->ifr_ifru.ifru_icmp6stat, + sizeof(struct icmp6_ifstat) / sizeof(uint64_t)); break; case SIOCGIFALIFETIME_IN6: @@ -629,24 +547,8 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, } break; - case SIOCSIFALIFETIME_IN6: - ia->ia6_lifetime = ifr->ifr_ifru.ifru_lifetime; - /* for sanity */ - if (ia->ia6_lifetime.ia6t_vltime != ND6_INFINITE_LIFETIME) { - ia->ia6_lifetime.ia6t_expire = - time_second + ia->ia6_lifetime.ia6t_vltime; - } else - ia->ia6_lifetime.ia6t_expire = 0; - if (ia->ia6_lifetime.ia6t_pltime != ND6_INFINITE_LIFETIME) { - ia->ia6_lifetime.ia6t_preferred = - time_second + ia->ia6_lifetime.ia6t_pltime; - } else - ia->ia6_lifetime.ia6t_preferred = 0; - break; - case SIOCAIFADDR_IN6: { - int i; struct nd_prefixctl pr0; struct nd_prefix *pr; @@ -667,6 +569,18 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, break; } + if (cmd == ocmd && ifra->ifra_vhid > 0) { + if (carp_attach_p != NULL) + error = (*carp_attach_p)(&ia->ia_ifa, + ifra->ifra_vhid); + else + error = EPROTONOSUPPORT; + if (error) + goto out; + else + carp_attached = 1; + } + /* * then, make the prefix on-link on the interface. * XXX: we'd rather create the prefix before the address, but @@ -683,14 +597,14 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, pr0.ndpr_plen = in6_mask2len(&ifra->ifra_prefixmask.sin6_addr, NULL); if (pr0.ndpr_plen == 128) { - break; /* we don't need to install a host route. */ + /* we don't need to install a host route. */ + goto aifaddr_out; } pr0.ndpr_prefix = ifra->ifra_addr; /* apply the mask for safety. */ - for (i = 0; i < 4; i++) { - pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &= - ifra->ifra_prefixmask.sin6_addr.s6_addr32[i]; - } + 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. @@ -710,12 +624,9 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, * nd6_prelist_add will install the corresponding * interface route. */ - if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) - goto out; - if (pr == NULL) { - log(LOG_ERR, "nd6_prelist_add succeeded but " - "no prefix\n"); - error = EINVAL; + if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) { + if (carp_attached) + (*carp_detach_p)(&ia->ia_ifa); goto out; } } @@ -746,32 +657,28 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, * that is, this address might make other addresses detached. */ pfxlist_onlink_check(); - if (error == 0 && ia) { - if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) { - /* - * Try to clear the flag when a new - * IPv6 address is added onto an - * IFDISABLED interface and it - * succeeds. - */ - struct in6_ndireq nd; - - memset(&nd, 0, sizeof(nd)); - nd.ndi.flags = ND_IFINFO(ifp)->flags; - nd.ndi.flags &= ~ND6_IFF_IFDISABLED; - if (nd6_ioctl(SIOCSIFINFO_FLAGS, - (caddr_t)&nd, ifp) < 0) - log(LOG_NOTICE, "SIOCAIFADDR_IN6: " - "SIOCSIFINFO_FLAGS for -ifdisabled " - "failed."); - /* - * Ignore failure of clearing the flag - * intentionally. The failure means - * address duplication was detected. - */ - } - EVENTHANDLER_INVOKE(ifaddr_event, ifp); + +aifaddr_out: + /* + * Try to clear the flag when a new IPv6 address is added + * onto an IFDISABLED interface and it succeeds. + */ + if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) { + struct in6_ndireq nd; + + memset(&nd, 0, sizeof(nd)); + nd.ndi.flags = ND_IFINFO(ifp)->flags; + nd.ndi.flags &= ~ND6_IFF_IFDISABLED; + if (nd6_ioctl(SIOCSIFINFO_FLAGS, (caddr_t)&nd, ifp) < 0) + log(LOG_NOTICE, "SIOCAIFADDR_IN6: " + "SIOCSIFINFO_FLAGS for -ifdisabled " + "failed."); + /* + * Ignore failure of clearing the flag intentionally. + * The failure means address duplication was detected. + */ } + EVENTHANDLER_INVOKE(ifaddr_event, ifp); break; } @@ -823,27 +730,24 @@ in6_update_ifa_join_mc(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *ia, int flags, struct in6_multi **in6m_sol) { char ip6buf[INET6_ADDRSTRLEN]; - struct sockaddr_in6 mltaddr, mltmask; - struct in6_addr llsol; + struct in6_addr mltaddr; struct in6_multi_mship *imm; - struct rtentry *rt; int delay, error; KASSERT(in6m_sol != NULL, ("%s: in6m_sol is NULL", __func__)); /* Join solicited multicast addr for new host id. */ - bzero(&llsol, sizeof(struct in6_addr)); - llsol.s6_addr32[0] = IPV6_ADDR_INT32_MLL; - llsol.s6_addr32[1] = 0; - llsol.s6_addr32[2] = htonl(1); - llsol.s6_addr32[3] = ifra->ifra_addr.sin6_addr.s6_addr32[3]; - llsol.s6_addr8[12] = 0xff; - if ((error = in6_setscope(&llsol, ifp, NULL)) != 0) { + bzero(&mltaddr, sizeof(struct in6_addr)); + mltaddr.s6_addr32[0] = IPV6_ADDR_INT32_MLL; + mltaddr.s6_addr32[2] = htonl(1); + mltaddr.s6_addr32[3] = ifra->ifra_addr.sin6_addr.s6_addr32[3]; + mltaddr.s6_addr8[12] = 0xff; + if ((error = in6_setscope(&mltaddr, ifp, NULL)) != 0) { /* XXX: should not happen */ log(LOG_ERR, "%s: in6_setscope failed\n", __func__); goto cleanup; } - delay = 0; + delay = error = 0; if ((flags & IN6_IFAUPDATE_DADDELAY)) { /* * We need a random delay for DAD on the address being @@ -853,62 +757,28 @@ in6_update_ifa_join_mc(struct ifnet *ifp, struct in6_aliasreq *ifra, */ delay = arc4random() % (MAX_RTR_SOLICITATION_DELAY * hz); } - imm = in6_joingroup(ifp, &llsol, &error, delay); + imm = in6_joingroup(ifp, &mltaddr, &error, delay); if (imm == NULL) { - nd6log((LOG_WARNING, "%s: addmulti failed for %s on %s " - "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, &llsol), + nd6log((LOG_WARNING, "%s: in6_joingroup failed for %s on %s " + "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, &mltaddr), if_name(ifp), error)); goto cleanup; } LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain); *in6m_sol = imm->i6mm_maddr; - bzero(&mltmask, sizeof(mltmask)); - mltmask.sin6_len = sizeof(struct sockaddr_in6); - mltmask.sin6_family = AF_INET6; - mltmask.sin6_addr = in6mask32; -#define MLTMASK_LEN 4 /* mltmask's masklen (=32bit=4octet) */ - /* * Join link-local all-nodes address. */ - bzero(&mltaddr, sizeof(mltaddr)); - mltaddr.sin6_len = sizeof(struct sockaddr_in6); - mltaddr.sin6_family = AF_INET6; - mltaddr.sin6_addr = in6addr_linklocal_allnodes; - if ((error = in6_setscope(&mltaddr.sin6_addr, ifp, NULL)) != 0) + mltaddr = in6addr_linklocal_allnodes; + if ((error = in6_setscope(&mltaddr, ifp, NULL)) != 0) goto cleanup; /* XXX: should not fail */ - /* - * XXX: do we really need this automatic routes? We should probably - * reconsider this stuff. Most applications actually do not need the - * routes, since they usually specify the outgoing interface. - */ - rt = in6_rtalloc1((struct sockaddr *)&mltaddr, 0, 0UL, RT_DEFAULT_FIB); - if (rt != NULL) { - /* XXX: only works in !SCOPEDROUTING case. */ - if (memcmp(&mltaddr.sin6_addr, - &((struct sockaddr_in6 *)rt_key(rt))->sin6_addr, - MLTMASK_LEN)) { - RTFREE_LOCKED(rt); - rt = NULL; - } - } - if (rt == NULL) { - error = in6_rtrequest(RTM_ADD, (struct sockaddr *)&mltaddr, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&mltmask, RTF_UP, - (struct rtentry **)0, RT_DEFAULT_FIB); - if (error) - goto cleanup; - } else - RTFREE_LOCKED(rt); - - imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, 0); + imm = in6_joingroup(ifp, &mltaddr, &error, 0); if (imm == NULL) { - nd6log((LOG_WARNING, "%s: addmulti failed for %s on %s " - "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, - &mltaddr.sin6_addr), if_name(ifp), error)); + nd6log((LOG_WARNING, "%s: in6_joingroup failed for %s on %s " + "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, &mltaddr), + if_name(ifp), error)); goto cleanup; } LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain); @@ -924,24 +794,26 @@ in6_update_ifa_join_mc(struct ifnet *ifp, struct in6_aliasreq *ifra, */ delay = arc4random() % (MAX_RTR_SOLICITATION_DELAY * hz); } - if (in6_nigroup(ifp, NULL, -1, &mltaddr.sin6_addr) == 0) { + if (in6_nigroup(ifp, NULL, -1, &mltaddr) == 0) { /* XXX jinmei */ - imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, delay); + imm = in6_joingroup(ifp, &mltaddr, &error, delay); if (imm == NULL) - nd6log((LOG_WARNING, "%s: addmulti failed for %s on %s " + nd6log((LOG_WARNING, + "%s: in6_joingroup failed for %s on %s " "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, - &mltaddr.sin6_addr), if_name(ifp), error)); + &mltaddr), if_name(ifp), error)); /* XXX not very fatal, go on... */ else LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain); } - if (V_icmp6_nodeinfo_oldmcprefix && - in6_nigroup_oldmcprefix(ifp, NULL, -1, &mltaddr.sin6_addr) == 0) { - imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, delay); + if (V_icmp6_nodeinfo_oldmcprefix && + in6_nigroup_oldmcprefix(ifp, NULL, -1, &mltaddr) == 0) { + imm = in6_joingroup(ifp, &mltaddr, &error, delay); if (imm == NULL) - nd6log((LOG_WARNING, "%s: addmulti failed for %s on %s " + nd6log((LOG_WARNING, + "%s: in6_joingroup failed for %s on %s " "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, - &mltaddr.sin6_addr), if_name(ifp), error)); + &mltaddr), if_name(ifp), error)); /* XXX not very fatal, go on... */ else LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain); @@ -951,38 +823,18 @@ in6_update_ifa_join_mc(struct ifnet *ifp, struct in6_aliasreq *ifra, * Join interface-local all-nodes address. * (ff01::1%ifN, and ff01::%ifN/32) */ - mltaddr.sin6_addr = in6addr_nodelocal_allnodes; - if ((error = in6_setscope(&mltaddr.sin6_addr, ifp, NULL)) != 0) + mltaddr = in6addr_nodelocal_allnodes; + if ((error = in6_setscope(&mltaddr, ifp, NULL)) != 0) goto cleanup; /* XXX: should not fail */ - /* XXX: again, do we really need the route? */ - rt = in6_rtalloc1((struct sockaddr *)&mltaddr, 0, 0UL, RT_DEFAULT_FIB); - if (rt != NULL) { - if (memcmp(&mltaddr.sin6_addr, - &((struct sockaddr_in6 *)rt_key(rt))->sin6_addr, - MLTMASK_LEN)) { - RTFREE_LOCKED(rt); - rt = NULL; - } - } - if (rt == NULL) { - error = in6_rtrequest(RTM_ADD, (struct sockaddr *)&mltaddr, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&mltmask, RTF_UP, - (struct rtentry **)0, RT_DEFAULT_FIB); - if (error) - goto cleanup; - } else - RTFREE_LOCKED(rt); - imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, 0); + imm = in6_joingroup(ifp, &mltaddr, &error, 0); if (imm == NULL) { - nd6log((LOG_WARNING, "%s: addmulti failed for %s on %s " + nd6log((LOG_WARNING, "%s: in6_joingroup failed for %s on %s " "(errno=%d)\n", __func__, ip6_sprintf(ip6buf, - &mltaddr.sin6_addr), if_name(ifp), error)); + &mltaddr), if_name(ifp), error)); goto cleanup; } LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain); -#undef MLTMASK_LEN cleanup: return (error); @@ -992,17 +844,65 @@ cleanup: * Update parameters of an IPv6 interface address. * If necessary, a new entry is created and linked into address chains. * This function is separated from in6_control(). - * XXX: should this be performed under splnet()? */ int in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *ia, int flags) { - int error = 0, hostIsNew = 0, plen = -1; + int error, hostIsNew = 0; + + if ((error = in6_validate_ifra(ifp, ifra, ia, flags)) != 0) + return (error); + + if (ia == NULL) { + hostIsNew = 1; + if ((ia = in6_alloc_ifa(ifp, ifra, flags)) == NULL) + return (ENOBUFS); + } + + error = in6_update_ifa_internal(ifp, ifra, ia, hostIsNew, flags); + if (error != 0) { + if (hostIsNew != 0) { + in6_unlink_ifa(ia, ifp); + ifa_free(&ia->ia_ifa); + } + return (error); + } + + if (hostIsNew) + error = in6_broadcast_ifa(ifp, ifra, ia, flags); + + return (error); +} + +/* + * Fill in basic IPv6 address request info. + */ +void +in6_prepare_ifra(struct in6_aliasreq *ifra, const struct in6_addr *addr, + const struct in6_addr *mask) +{ + + memset(ifra, 0, sizeof(struct in6_aliasreq)); + + ifra->ifra_addr.sin6_family = AF_INET6; + ifra->ifra_addr.sin6_len = sizeof(struct sockaddr_in6); + if (addr != NULL) + ifra->ifra_addr.sin6_addr = *addr; + + ifra->ifra_prefixmask.sin6_family = AF_INET6; + ifra->ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + if (mask != NULL) + ifra->ifra_prefixmask.sin6_addr = *mask; +} + +static int +in6_validate_ifra(struct ifnet *ifp, struct in6_aliasreq *ifra, + struct in6_ifaddr *ia, int flags) +{ + int plen = -1; struct sockaddr_in6 dst6; struct in6_addrlifetime *lt; - struct in6_multi *in6m_sol; - int delay; char ip6buf[INET6_ADDRSTRLEN]; /* Validate parameters */ @@ -1017,6 +917,14 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, ifra->ifra_dstaddr.sin6_family != AF_INET6 && ifra->ifra_dstaddr.sin6_family != AF_UNSPEC) return (EAFNOSUPPORT); + + /* + * Validate address + */ + if (ifra->ifra_addr.sin6_len != sizeof(struct sockaddr_in6) || + ifra->ifra_addr.sin6_family != AF_INET6) + return (EINVAL); + /* * validate ifra_prefixmask. don't check sin6_family, netmask * does not carry fields other than sin6_len. @@ -1069,6 +977,9 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, if (sa6_embedscope(&dst6, 0)) return (EINVAL); /* XXX: should be impossible */ } + /* Modify original ifra_dstaddr to reflect changes */ + ifra->ifra_dstaddr = dst6; + /* * The destination address can be specified only for a p2p or a * loopback interface. If specified, the corresponding prefix length @@ -1104,94 +1015,102 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, return (0); /* there's nothing to do */ } - /* - * If this is a new address, allocate a new ifaddr and link it - * into chains. - */ - if (ia == NULL) { - hostIsNew = 1; - /* - * When in6_update_ifa() is called in a process of a received - * RA, it is called under an interrupt context. So, we should - * call malloc with M_NOWAIT. - */ - ia = (struct in6_ifaddr *) malloc(sizeof(*ia), M_IFADDR, - M_NOWAIT); - if (ia == NULL) - return (ENOBUFS); - bzero((caddr_t)ia, sizeof(*ia)); - ifa_init(&ia->ia_ifa); - LIST_INIT(&ia->ia6_memberships); - /* Initialize the address and masks, and put time stamp */ - ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; - ia->ia_addr.sin6_family = AF_INET6; - ia->ia_addr.sin6_len = sizeof(ia->ia_addr); - ia->ia6_createtime = time_second; - if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) != 0) { - /* - * XXX: some functions expect that ifa_dstaddr is not - * NULL for p2p interfaces. - */ - ia->ia_ifa.ifa_dstaddr = - (struct sockaddr *)&ia->ia_dstaddr; - } else { - ia->ia_ifa.ifa_dstaddr = NULL; - } - ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; - ia->ia_ifp = ifp; - ifa_ref(&ia->ia_ifa); /* if_addrhead */ - IF_ADDR_WLOCK(ifp); - TAILQ_INSERT_TAIL(&ifp->if_addrhead, &ia->ia_ifa, ifa_link); - IF_ADDR_WUNLOCK(ifp); - - ifa_ref(&ia->ia_ifa); /* in6_ifaddrhead */ - IN6_IFADDR_WLOCK(); - TAILQ_INSERT_TAIL(&V_in6_ifaddrhead, ia, ia_link); - IN6_IFADDR_WUNLOCK(); - } - - /* update timestamp */ - ia->ia6_updatetime = time_second; - - /* set prefix mask */ - if (ifra->ifra_prefixmask.sin6_len) { + /* 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 && + if (ia->ia_prefixmask.sin6_len != 0 && in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL) != plen) { - nd6log((LOG_INFO, "in6_update_ifa: the prefix length of an" - " existing (%s) address should not be changed\n", + 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))); - error = EINVAL; - goto unlink; + + return (EINVAL); } - ia->ia_prefixmask = ifra->ifra_prefixmask; } + return (0); +} + + +/* + * Allocate a new ifaddr and link it into chains. + */ +static struct in6_ifaddr * +in6_alloc_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, int flags) +{ + struct in6_ifaddr *ia; + /* - * 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 (see the check above.) + * When in6_alloc_ifa() is called in a process of a received + * RA, it is called under an interrupt context. So, we should + * call malloc with M_NOWAIT. */ - if (dst6.sin6_family == AF_INET6 && - !IN6_ARE_ADDR_EQUAL(&dst6.sin6_addr, &ia->ia_dstaddr.sin6_addr)) { - int e; + ia = (struct in6_ifaddr *)ifa_alloc(sizeof(*ia), M_NOWAIT); + if (ia == NULL) + return (NULL); + LIST_INIT(&ia->ia6_memberships); + /* Initialize the address and masks, and put time stamp */ + ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; + ia->ia_addr.sin6_family = AF_INET6; + ia->ia_addr.sin6_len = sizeof(ia->ia_addr); + /* XXX: Can we assign ,sin6_addr and skip the rest? */ + ia->ia_addr = ifra->ifra_addr; + ia->ia6_createtime = time_uptime; + if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) != 0) { + /* + * Some functions expect that ifa_dstaddr is not + * NULL for p2p interfaces. + */ + ia->ia_ifa.ifa_dstaddr = + (struct sockaddr *)&ia->ia_dstaddr; + } else { + ia->ia_ifa.ifa_dstaddr = NULL; + } - if ((ia->ia_flags & IFA_ROUTE) != 0 && - (e = rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST)) != 0) { - nd6log((LOG_ERR, "in6_update_ifa: 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; - ia->ia_dstaddr = dst6; + /* set prefix mask if any */ + ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; + if (ifra->ifra_prefixmask.sin6_len != 0) { + 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; } + ia->ia_ifp = ifp; + ifa_ref(&ia->ia_ifa); /* if_addrhead */ + IF_ADDR_WLOCK(ifp); + TAILQ_INSERT_TAIL(&ifp->if_addrhead, &ia->ia_ifa, ifa_link); + IF_ADDR_WUNLOCK(ifp); + + ifa_ref(&ia->ia_ifa); /* in6_ifaddrhead */ + IN6_IFADDR_WLOCK(); + TAILQ_INSERT_TAIL(&V_in6_ifaddrhead, ia, ia_link); + LIST_INSERT_HEAD(IN6ADDR_HASH(&ia->ia_addr.sin6_addr), ia, ia6_hash); + IN6_IFADDR_WUNLOCK(); + + return (ia); +} + +/* + * Update/configure interface address parameters: + * + * 1) Update lifetime + * 2) Update interface metric ad flags + * 3) Notify other subsystems + */ +static int +in6_update_ifa_internal(struct ifnet *ifp, struct in6_aliasreq *ifra, + struct in6_ifaddr *ia, int hostIsNew, int flags) +{ + int error; + + /* update timestamp */ + ia->ia6_updatetime = time_uptime; + /* * Set lifetimes. We do not refer to ia6t_expire and ia6t_preferred * to see if the address is deprecated or invalidated, but initialize @@ -1200,71 +1119,85 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, ia->ia6_lifetime = ifra->ifra_lifetime; if (ia->ia6_lifetime.ia6t_vltime != ND6_INFINITE_LIFETIME) { ia->ia6_lifetime.ia6t_expire = - time_second + ia->ia6_lifetime.ia6t_vltime; + time_uptime + ia->ia6_lifetime.ia6t_vltime; } else ia->ia6_lifetime.ia6t_expire = 0; if (ia->ia6_lifetime.ia6t_pltime != ND6_INFINITE_LIFETIME) { ia->ia6_lifetime.ia6t_preferred = - time_second + ia->ia6_lifetime.ia6t_pltime; + time_uptime + ia->ia6_lifetime.ia6t_pltime; } else ia->ia6_lifetime.ia6t_preferred = 0; - /* reset the interface and routing table appropriately. */ - if ((error = in6_ifinit(ifp, ia, &ifra->ifra_addr, hostIsNew)) != 0) - goto unlink; - - /* - * configure address flags. - */ - ia->ia6_flags = ifra->ifra_flags; /* * backward compatibility - if IN6_IFF_DEPRECATED is set from the * userland, make it deprecated. */ if ((ifra->ifra_flags & IN6_IFF_DEPRECATED) != 0) { ia->ia6_lifetime.ia6t_pltime = 0; - ia->ia6_lifetime.ia6t_preferred = time_second; + ia->ia6_lifetime.ia6t_preferred = time_uptime; } + + /* + * configure address flags. + */ + ia->ia6_flags = ifra->ifra_flags; + /* * Make the address tentative before joining multicast addresses, * so that corresponding MLD responses would not have a tentative * source address. */ ia->ia6_flags &= ~IN6_IFF_DUPLICATED; /* safety */ - if (hostIsNew && in6if_do_dad(ifp)) - ia->ia6_flags |= IN6_IFF_TENTATIVE; - - /* DAD should be performed after ND6_IFF_IFDISABLED is cleared. */ - if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) - ia->ia6_flags |= IN6_IFF_TENTATIVE; /* - * We are done if we have simply modified an existing address. + * DAD should be performed for an new address or addresses on + * an interface with ND6_IFF_IFDISABLED. */ - if (!hostIsNew) - return (error); + if (in6if_do_dad(ifp) && + (hostIsNew || (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED))) + ia->ia6_flags |= IN6_IFF_TENTATIVE; - /* - * Beyond this point, we should call in6_purgeaddr upon an error, - * not just go to unlink. - */ + /* notify other subsystems */ + error = in6_notify_ifa(ifp, ia, ifra, hostIsNew); + + return (error); +} + +/* + * Do link-level ifa job: + * 1) Add lle entry for added address + * 2) Notifies routing socket users about new address + * 3) join appropriate multicast group + * 4) start DAD if enabled + */ +static int +in6_broadcast_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, + struct in6_ifaddr *ia, int flags) +{ + struct in6_multi *in6m_sol; + int error = 0; + + /* Add local address to lltable, if necessary (ex. on p2p link). */ + if ((error = nd6_add_ifa_lle(ia)) != 0) { + in6_purgeaddr(&ia->ia_ifa); + ifa_free(&ia->ia_ifa); + return (error); + } /* Join necessary multicast groups. */ in6m_sol = NULL; if ((ifp->if_flags & IFF_MULTICAST) != 0) { error = in6_update_ifa_join_mc(ifp, ifra, ia, flags, &in6m_sol); - if (error) - goto cleanup; + if (error != 0) { + in6_purgeaddr(&ia->ia_ifa); + ifa_free(&ia->ia_ifa); + return (error); + } } - /* - * Perform DAD, if needed. - * XXX It may be of use, if we can administratively disable DAD. - */ - if (in6if_do_dad(ifp) && ((ifra->ifra_flags & IN6_IFF_NODAD) == 0) && - (ia->ia6_flags & IN6_IFF_TENTATIVE)) - { - int mindelay, maxdelay; + /* Perform DAD, if the address is TENTATIVE. */ + if ((ia->ia6_flags & IN6_IFF_TENTATIVE)) { + int delay, mindelay, maxdelay; delay = 0; if ((flags & IN6_IFAUPDATE_DADDELAY)) { @@ -1295,159 +1228,9 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, nd6_dad_start((struct ifaddr *)ia, delay); } - KASSERT(hostIsNew, ("in6_update_ifa: !hostIsNew")); + in6_newaddrmsg(ia, RTM_ADD); ifa_free(&ia->ia_ifa); return (error); - - unlink: - /* - * XXX: if a change of an existing address failed, keep the entry - * anyway. - */ - if (hostIsNew) { - in6_unlink_ifa(ia, ifp); - ifa_free(&ia->ia_ifa); - } - return (error); - - cleanup: - KASSERT(hostIsNew, ("in6_update_ifa: cleanup: !hostIsNew")); - ifa_free(&ia->ia_ifa); - in6_purgeaddr(&ia->ia_ifa); - return error; -} - -/* - * Leave multicast groups. Factored out from in6_purgeaddr(). - * This entire work should only be done once, for the default FIB. - */ -static int -in6_purgeaddr_mc(struct ifnet *ifp, struct in6_ifaddr *ia, struct ifaddr *ifa0) -{ - struct sockaddr_in6 mltaddr, mltmask; - struct in6_multi_mship *imm; - struct rtentry *rt; - struct sockaddr_in6 sin6; - int error; - - /* - * Leave from multicast groups we have joined for the interface. - */ - while ((imm = LIST_FIRST(&ia->ia6_memberships)) != NULL) { - LIST_REMOVE(imm, i6mm_chain); - in6_leavegroup(imm); - } - - /* - * Remove the link-local all-nodes address. - */ - bzero(&mltmask, sizeof(mltmask)); - mltmask.sin6_len = sizeof(struct sockaddr_in6); - mltmask.sin6_family = AF_INET6; - mltmask.sin6_addr = in6mask32; - - bzero(&mltaddr, sizeof(mltaddr)); - mltaddr.sin6_len = sizeof(struct sockaddr_in6); - mltaddr.sin6_family = AF_INET6; - mltaddr.sin6_addr = in6addr_linklocal_allnodes; - - if ((error = in6_setscope(&mltaddr.sin6_addr, ifp, NULL)) != 0) - return (error); - - /* - * As for the mltaddr above, proactively prepare the sin6 to avoid - * rtentry un- and re-locking. - */ - if (ifa0 != NULL) { - bzero(&sin6, sizeof(sin6)); - sin6.sin6_len = sizeof(sin6); - sin6.sin6_family = AF_INET6; - memcpy(&sin6.sin6_addr, &satosin6(ifa0->ifa_addr)->sin6_addr, - sizeof(sin6.sin6_addr)); - error = in6_setscope(&sin6.sin6_addr, ifa0->ifa_ifp, NULL); - if (error != 0) - return (error); - } - - rt = in6_rtalloc1((struct sockaddr *)&mltaddr, 0, 0UL, RT_DEFAULT_FIB); - if (rt != NULL && rt->rt_gateway != NULL && - (memcmp(&satosin6(rt->rt_gateway)->sin6_addr, - &ia->ia_addr.sin6_addr, - sizeof(ia->ia_addr.sin6_addr)) == 0)) { - /* - * If no more IPv6 address exists on this interface then - * remove the multicast address route. - */ - if (ifa0 == NULL) { - memcpy(&mltaddr.sin6_addr, - &satosin6(rt_key(rt))->sin6_addr, - sizeof(mltaddr.sin6_addr)); - RTFREE_LOCKED(rt); - error = in6_rtrequest(RTM_DELETE, - (struct sockaddr *)&mltaddr, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&mltmask, RTF_UP, - (struct rtentry **)0, RT_DEFAULT_FIB); - if (error) - log(LOG_INFO, "%s: link-local all-nodes " - "multicast address deletion error\n", - __func__); - } else { - /* - * Replace the gateway of the route. - */ - memcpy(rt->rt_gateway, &sin6, sizeof(sin6)); - RTFREE_LOCKED(rt); - } - } else { - if (rt != NULL) - RTFREE_LOCKED(rt); - } - - /* - * Remove the node-local all-nodes address. - */ - mltaddr.sin6_addr = in6addr_nodelocal_allnodes; - if ((error = in6_setscope(&mltaddr.sin6_addr, ifp, NULL)) != 0) - return (error); - - rt = in6_rtalloc1((struct sockaddr *)&mltaddr, 0, 0UL, RT_DEFAULT_FIB); - if (rt != NULL && rt->rt_gateway != NULL && - (memcmp(&satosin6(rt->rt_gateway)->sin6_addr, - &ia->ia_addr.sin6_addr, - sizeof(ia->ia_addr.sin6_addr)) == 0)) { - /* - * If no more IPv6 address exists on this interface then - * remove the multicast address route. - */ - if (ifa0 == NULL) { - memcpy(&mltaddr.sin6_addr, - &satosin6(rt_key(rt))->sin6_addr, - sizeof(mltaddr.sin6_addr)); - - RTFREE_LOCKED(rt); - error = in6_rtrequest(RTM_DELETE, - (struct sockaddr *)&mltaddr, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&mltmask, RTF_UP, - (struct rtentry **)0, RT_DEFAULT_FIB); - if (error) - log(LOG_INFO, "%s: node-local all-nodes" - "multicast address deletion error\n", - __func__); - } else { - /* - * Replace the gateway of the route. - */ - memcpy(rt->rt_gateway, &sin6, sizeof(sin6)); - RTFREE_LOCKED(rt); - } - } else { - if (rt != NULL) - RTFREE_LOCKED(rt); - } - - return (0); } void @@ -1455,26 +1238,11 @@ in6_purgeaddr(struct ifaddr *ifa) { struct ifnet *ifp = ifa->ifa_ifp; struct in6_ifaddr *ia = (struct in6_ifaddr *) ifa; + struct in6_multi_mship *imm; int plen, error; - struct ifaddr *ifa0; - /* - * find another IPv6 address as the gateway for the - * link-local and node-local all-nodes multicast - * address routes - */ - IF_ADDR_RLOCK(ifp); - TAILQ_FOREACH(ifa0, &ifp->if_addrhead, ifa_link) { - if ((ifa0->ifa_addr->sa_family != AF_INET6) || - memcmp(&satosin6(ifa0->ifa_addr)->sin6_addr, - &ia->ia_addr.sin6_addr, sizeof(struct in6_addr)) == 0) - continue; - else - break; - } - if (ifa0 != NULL) - ifa_ref(ifa0); - IF_ADDR_RUNLOCK(ifp); + if (ifa->ifa_carp) + (*carp_detach_p)(ifa); /* * Remove the loopback route to the interface address. @@ -1491,32 +1259,30 @@ in6_purgeaddr(struct ifaddr *ifa) /* stop DAD processing */ nd6_dad_stop(ifa); - /* Remove local address entry from lltable. */ - in6_ifremloop(ifa); - /* Leave multicast groups. */ - error = in6_purgeaddr_mc(ifp, ia, ifa0); - - if (ifa0 != NULL) - ifa_free(ifa0); - + while ((imm = LIST_FIRST(&ia->ia6_memberships)) != NULL) { + LIST_REMOVE(imm, i6mm_chain); + in6_leavegroup(imm); + } plen = in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL); /* XXX */ if ((ia->ia_flags & IFA_ROUTE) && plen == 128) { error = rtinit(&(ia->ia_ifa), RTM_DELETE, ia->ia_flags | - (ia->ia_dstaddr.sin6_family == AF_INET6) ? RTF_HOST : 0); + (ia->ia_dstaddr.sin6_family == AF_INET6 ? RTF_HOST : 0)); if (error != 0) log(LOG_INFO, "%s: err=%d, destination address delete " "failed\n", __func__, error); ia->ia_flags &= ~IFA_ROUTE; } + in6_newaddrmsg(ia, RTM_DELETE); in6_unlink_ifa(ia, ifp); } static void in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) { - int s = splnet(); + char ip6buf[INET6_ADDRSTRLEN]; + int remove_lle; IF_ADDR_WLOCK(ifp); TAILQ_REMOVE(&ifp->if_addrhead, &ia->ia_ifa, ifa_link); @@ -1530,21 +1296,28 @@ in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) */ IN6_IFADDR_WLOCK(); TAILQ_REMOVE(&V_in6_ifaddrhead, ia, ia_link); + LIST_REMOVE(ia, ia6_hash); IN6_IFADDR_WUNLOCK(); /* * Release the reference to the base prefix. There should be a * positive reference. */ + remove_lle = 0; if (ia->ia6_ndpr == NULL) { nd6log((LOG_NOTICE, "in6_unlink_ifa: autoconf'ed address " - "%p has no prefix\n", ia)); + "%s has no prefix\n", ip6_sprintf(ip6buf, IA6_IN6(ia)))); } else { ia->ia6_ndpr->ndpr_refcnt--; + /* Do not delete lles within prefix if refcont != 0 */ + if (ia->ia6_ndpr->ndpr_refcnt == 0) + remove_lle = 1; ia->ia6_ndpr = NULL; } + nd6_rem_ifa_lle(ia, remove_lle); + /* * Also, if the address being removed is autoconf'ed, call * pfxlist_onlink_check() since the release might affect the status of @@ -1554,335 +1327,63 @@ in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) pfxlist_onlink_check(); } ifa_free(&ia->ia_ifa); /* in6_ifaddrhead */ - splx(s); -} - -void -in6_purgeif(struct ifnet *ifp) -{ - struct ifaddr *ifa, *nifa; - - TAILQ_FOREACH_SAFE(ifa, &ifp->if_addrhead, ifa_link, nifa) { - if (ifa->ifa_addr->sa_family != AF_INET6) - continue; - in6_purgeaddr(ifa); - } - - in6_ifdetach(ifp); } /* - * SIOC[GAD]LIFADDR. - * SIOCGLIFADDR: get first address. (?) - * SIOCGLIFADDR with IFLR_PREFIX: - * get first address that matches the specified prefix. - * SIOCALIFADDR: add the specified address. - * SIOCALIFADDR with IFLR_PREFIX: - * add the specified prefix, filling hostid part from - * the first link-local address. prefixlen must be <= 64. - * SIOCDLIFADDR: delete the specified address. - * SIOCDLIFADDR with IFLR_PREFIX: - * delete the first address that matches the specified prefix. - * return values: - * EINVAL on invalid parameters - * EADDRNOTAVAIL on prefix match failed/specified address not found - * other values may be returned from in6_ioctl() - * - * NOTE: SIOCALIFADDR(with IFLR_PREFIX set) allows prefixlen less than 64. - * this is to accomodate address naming scheme other than RFC2374, - * in the future. - * RFC2373 defines interface id to be 64bit, but it allows non-RFC2374 - * address encoding scheme. (see figure on page 8) + * Notifies other subsystems about address change/arrival: + * 1) Notifies device handler on the first IPv6 address assignment + * 2) Handle routing table changes for P2P links and route + * 3) Handle routing table changes for address host route */ static int -in6_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data, - struct ifnet *ifp, struct thread *td) +in6_notify_ifa(struct ifnet *ifp, struct in6_ifaddr *ia, + struct in6_aliasreq *ifra, int hostIsNew) { - struct if_laddrreq *iflr = (struct if_laddrreq *)data; + int error = 0, plen, ifacount = 0; struct ifaddr *ifa; - struct sockaddr *sa; - - /* sanity checks */ - if (!data || !ifp) { - panic("invalid argument to in6_lifaddr_ioctl"); - /* NOTREACHED */ - } - - switch (cmd) { - case SIOCGLIFADDR: - /* address must be specified on GET with IFLR_PREFIX */ - if ((iflr->flags & IFLR_PREFIX) == 0) - break; - /* FALLTHROUGH */ - case SIOCALIFADDR: - case SIOCDLIFADDR: - /* address must be specified on ADD and DELETE */ - sa = (struct sockaddr *)&iflr->addr; - if (sa->sa_family != AF_INET6) - return EINVAL; - if (sa->sa_len != sizeof(struct sockaddr_in6)) - return EINVAL; - /* XXX need improvement */ - sa = (struct sockaddr *)&iflr->dstaddr; - if (sa->sa_family && sa->sa_family != AF_INET6) - return EINVAL; - if (sa->sa_len && sa->sa_len != sizeof(struct sockaddr_in6)) - return EINVAL; - break; - default: /* shouldn't happen */ -#if 0 - panic("invalid cmd to in6_lifaddr_ioctl"); - /* NOTREACHED */ -#else - return EOPNOTSUPP; -#endif - } - if (sizeof(struct in6_addr) * 8 < iflr->prefixlen) - return EINVAL; - - switch (cmd) { - case SIOCALIFADDR: - { - struct in6_aliasreq ifra; - struct in6_addr *hostid = NULL; - int prefixlen; - - ifa = NULL; - if ((iflr->flags & IFLR_PREFIX) != 0) { - struct sockaddr_in6 *sin6; - - /* - * hostid is to fill in the hostid part of the - * address. hostid points to the first link-local - * address attached to the interface. - */ - ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, 0); - if (!ifa) - return EADDRNOTAVAIL; - hostid = IFA_IN6(ifa); - - /* prefixlen must be <= 64. */ - if (64 < iflr->prefixlen) { - if (ifa != NULL) - ifa_free(ifa); - return EINVAL; - } - prefixlen = iflr->prefixlen; - - /* hostid part must be zero. */ - sin6 = (struct sockaddr_in6 *)&iflr->addr; - if (sin6->sin6_addr.s6_addr32[2] != 0 || - sin6->sin6_addr.s6_addr32[3] != 0) { - if (ifa != NULL) - ifa_free(ifa); - return EINVAL; - } - } else - prefixlen = iflr->prefixlen; - - /* copy args to in6_aliasreq, perform ioctl(SIOCAIFADDR_IN6). */ - bzero(&ifra, sizeof(ifra)); - bcopy(iflr->iflr_name, ifra.ifra_name, sizeof(ifra.ifra_name)); - - bcopy(&iflr->addr, &ifra.ifra_addr, - ((struct sockaddr *)&iflr->addr)->sa_len); - if (hostid) { - /* fill in hostid part */ - ifra.ifra_addr.sin6_addr.s6_addr32[2] = - hostid->s6_addr32[2]; - ifra.ifra_addr.sin6_addr.s6_addr32[3] = - hostid->s6_addr32[3]; - } - - if (((struct sockaddr *)&iflr->dstaddr)->sa_family) { /* XXX */ - bcopy(&iflr->dstaddr, &ifra.ifra_dstaddr, - ((struct sockaddr *)&iflr->dstaddr)->sa_len); - if (hostid) { - ifra.ifra_dstaddr.sin6_addr.s6_addr32[2] = - hostid->s6_addr32[2]; - ifra.ifra_dstaddr.sin6_addr.s6_addr32[3] = - hostid->s6_addr32[3]; - } - } - if (ifa != NULL) - ifa_free(ifa); - - ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); - in6_prefixlen2mask(&ifra.ifra_prefixmask.sin6_addr, prefixlen); - - ifra.ifra_flags = iflr->flags & ~IFLR_PREFIX; - return in6_control(so, SIOCAIFADDR_IN6, (caddr_t)&ifra, ifp, td); - } - case SIOCGLIFADDR: - case SIOCDLIFADDR: - { - struct in6_ifaddr *ia; - struct in6_addr mask, candidate, match; - struct sockaddr_in6 *sin6; - int cmp; - - bzero(&mask, sizeof(mask)); - if (iflr->flags & IFLR_PREFIX) { - /* lookup a prefix rather than address. */ - in6_prefixlen2mask(&mask, iflr->prefixlen); - - sin6 = (struct sockaddr_in6 *)&iflr->addr; - bcopy(&sin6->sin6_addr, &match, sizeof(match)); - match.s6_addr32[0] &= mask.s6_addr32[0]; - match.s6_addr32[1] &= mask.s6_addr32[1]; - match.s6_addr32[2] &= mask.s6_addr32[2]; - match.s6_addr32[3] &= mask.s6_addr32[3]; - - /* if you set extra bits, that's wrong */ - if (bcmp(&match, &sin6->sin6_addr, sizeof(match))) - return EINVAL; - - cmp = 1; - } else { - if (cmd == SIOCGLIFADDR) { - /* on getting an address, take the 1st match */ - cmp = 0; /* XXX */ - } else { - /* on deleting an address, do exact match */ - in6_prefixlen2mask(&mask, 128); - sin6 = (struct sockaddr_in6 *)&iflr->addr; - bcopy(&sin6->sin6_addr, &match, sizeof(match)); - - cmp = 1; - } - } + struct sockaddr_in6 *pdst; + char ip6buf[INET6_ADDRSTRLEN]; + /* + * Give the interface a chance to initialize + * if this is its first address, + */ + if (hostIsNew != 0) { IF_ADDR_RLOCK(ifp); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; - if (!cmp) - break; - - /* - * XXX: this is adhoc, but is necessary to allow - * a user to specify fe80::/64 (not /10) for a - * link-local address. - */ - bcopy(IFA_IN6(ifa), &candidate, sizeof(candidate)); - in6_clearscope(&candidate); - candidate.s6_addr32[0] &= mask.s6_addr32[0]; - candidate.s6_addr32[1] &= mask.s6_addr32[1]; - candidate.s6_addr32[2] &= mask.s6_addr32[2]; - candidate.s6_addr32[3] &= mask.s6_addr32[3]; - if (IN6_ARE_ADDR_EQUAL(&candidate, &match)) - break; + ifacount++; } - if (ifa != NULL) - ifa_ref(ifa); IF_ADDR_RUNLOCK(ifp); - if (!ifa) - return EADDRNOTAVAIL; - ia = ifa2ia6(ifa); - - if (cmd == SIOCGLIFADDR) { - int error; - - /* fill in the if_laddrreq structure */ - bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin6_len); - error = sa6_recoverscope( - (struct sockaddr_in6 *)&iflr->addr); - if (error != 0) { - ifa_free(ifa); - return (error); - } - - if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { - bcopy(&ia->ia_dstaddr, &iflr->dstaddr, - ia->ia_dstaddr.sin6_len); - error = sa6_recoverscope( - (struct sockaddr_in6 *)&iflr->dstaddr); - if (error != 0) { - ifa_free(ifa); - return (error); - } - } else - bzero(&iflr->dstaddr, sizeof(iflr->dstaddr)); - - iflr->prefixlen = - in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL); - - iflr->flags = ia->ia6_flags; /* XXX */ - ifa_free(ifa); - - return 0; - } else { - struct in6_aliasreq ifra; - - /* fill in6_aliasreq and do ioctl(SIOCDIFADDR_IN6) */ - bzero(&ifra, sizeof(ifra)); - bcopy(iflr->iflr_name, ifra.ifra_name, - sizeof(ifra.ifra_name)); - - bcopy(&ia->ia_addr, &ifra.ifra_addr, - ia->ia_addr.sin6_len); - if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { - bcopy(&ia->ia_dstaddr, &ifra.ifra_dstaddr, - ia->ia_dstaddr.sin6_len); - } else { - bzero(&ifra.ifra_dstaddr, - sizeof(ifra.ifra_dstaddr)); - } - bcopy(&ia->ia_prefixmask, &ifra.ifra_dstaddr, - ia->ia_prefixmask.sin6_len); - - ifra.ifra_flags = ia->ia6_flags; - ifa_free(ifa); - return in6_control(so, SIOCDIFADDR_IN6, (caddr_t)&ifra, - ifp, td); - } - } } - return EOPNOTSUPP; /* just for safety */ -} - -/* - * Initialize an interface's IPv6 address and routing table entry. - */ -static int -in6_ifinit(struct ifnet *ifp, struct in6_ifaddr *ia, - struct sockaddr_in6 *sin6, int newhost) -{ - int error = 0, plen, ifacount = 0; - int s = splimp(); - struct ifaddr *ifa; - - /* - * Give the interface a chance to initialize - * if this is its first address, - * and to validate the address if necessary. - */ - IF_ADDR_RLOCK(ifp); - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { - if (ifa->ifa_addr->sa_family != AF_INET6) - continue; - ifacount++; - } - IF_ADDR_RUNLOCK(ifp); - - ia->ia_addr = *sin6; - if (ifacount <= 1 && ifp->if_ioctl) { error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (caddr_t)ia); - if (error) { - splx(s); + if (error) return (error); - } } - splx(s); - - ia->ia_ifa.ifa_metric = ifp->if_metric; - /* we could do in(6)_socktrim here, but just omit it at this moment. */ + /* + * 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. + */ + 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 && + (rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST) != 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; + ia->ia_dstaddr = *pdst; + } /* - * Special case: * If a new destination address is specified for a point-to-point * interface, install a route to the destination as an interface * direct route. @@ -1893,19 +1394,19 @@ in6_ifinit(struct ifnet *ifp, struct in6_ifaddr *ia, if (!(ia->ia_flags & IFA_ROUTE) && plen == 128 && ia->ia_dstaddr.sin6_family == AF_INET6) { int rtflags = RTF_UP | RTF_HOST; - error = rtinit(&ia->ia_ifa, RTM_ADD, ia->ia_flags | rtflags); - if (error) - return (error); - ia->ia_flags |= IFA_ROUTE; /* * Handle the case for ::1 . */ if (ifp->if_flags & IFF_LOOPBACK) ia->ia_flags |= IFA_RTSELF; + error = rtinit(&ia->ia_ifa, RTM_ADD, ia->ia_flags | rtflags); + if (error) + return (error); + ia->ia_flags |= IFA_ROUTE; } /* - * add a loopback route to self + * 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, @@ -1914,10 +1415,6 @@ in6_ifinit(struct ifnet *ifp, struct in6_ifaddr *ia, ia->ia_flags |= IFA_RTSELF; } - /* Add local address to lltable, if necessary (ex. on p2p link). */ - if (newhost) - in6_ifaddloop(&(ia->ia_ifa)); - return (error); } @@ -1949,11 +1446,35 @@ in6ifa_ifpforlinklocal(struct ifnet *ifp, int ignoreflags) /* + * find the internet address corresponding to a given address. + * ifaddr is returned referenced. + */ +struct in6_ifaddr * +in6ifa_ifwithaddr(const struct in6_addr *addr, uint32_t zoneid) +{ + struct rm_priotracker in6_ifa_tracker; + struct in6_ifaddr *ia; + + IN6_IFADDR_RLOCK(&in6_ifa_tracker); + LIST_FOREACH(ia, IN6ADDR_HASH(addr), ia6_hash) { + if (IN6_ARE_ADDR_EQUAL(IA6_IN6(ia), addr)) { + if (zoneid != 0 && + zoneid != ia->ia_addr.sin6_scope_id) + continue; + ifa_ref(&ia->ia_ifa); + break; + } + } + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); + return (ia); +} + +/* * find the internet address corresponding to a given interface and address. * ifaddr is returned referenced. */ struct in6_ifaddr * -in6ifa_ifpwithaddr(struct ifnet *ifp, struct in6_addr *addr) +in6ifa_ifpwithaddr(struct ifnet *ifp, const struct in6_addr *addr) { struct ifaddr *ifa; @@ -1982,7 +1503,7 @@ in6ifa_llaonifp(struct ifnet *ifp) if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) return (NULL); - if_addr_rlock(ifp); + IF_ADDR_RLOCK(ifp); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; @@ -1992,7 +1513,7 @@ in6ifa_llaonifp(struct ifnet *ifp) IN6_IS_ADDR_MC_NODELOCAL(&sin6->sin6_addr)) break; } - if_addr_runlock(ifp); + IF_ADDR_RUNLOCK(ifp); return ((struct in6_ifaddr *)ifa); } @@ -2082,20 +1603,21 @@ ip6_sprintf(char *ip6buf, const struct in6_addr *addr) int in6_localaddr(struct in6_addr *in6) { + struct rm_priotracker in6_ifa_tracker; struct in6_ifaddr *ia; if (IN6_IS_ADDR_LOOPBACK(in6) || IN6_IS_ADDR_LINKLOCAL(in6)) return 1; - IN6_IFADDR_RLOCK(); + IN6_IFADDR_RLOCK(&in6_ifa_tracker); TAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) { if (IN6_ARE_MASKED_ADDR_EQUAL(in6, &ia->ia_addr.sin6_addr, &ia->ia_prefixmask.sin6_addr)) { - IN6_IFADDR_RUNLOCK(); + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return 1; } } - IN6_IFADDR_RUNLOCK(); + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return (0); } @@ -2107,37 +1629,67 @@ in6_localaddr(struct in6_addr *in6) int in6_localip(struct in6_addr *in6) { + struct rm_priotracker in6_ifa_tracker; struct in6_ifaddr *ia; - IN6_IFADDR_RLOCK(); - TAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) { + IN6_IFADDR_RLOCK(&in6_ifa_tracker); + LIST_FOREACH(ia, IN6ADDR_HASH(in6), ia6_hash) { if (IN6_ARE_ADDR_EQUAL(in6, &ia->ia_addr.sin6_addr)) { - IN6_IFADDR_RUNLOCK(); + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return (1); } } - IN6_IFADDR_RUNLOCK(); + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return (0); } + +/* + * Return 1 if an internet address is configured on an interface. + */ +int +in6_ifhasaddr(struct ifnet *ifp, struct in6_addr *addr) +{ + struct in6_addr in6; + struct ifaddr *ifa; + struct in6_ifaddr *ia6; + in6 = *addr; + if (in6_clearscope(&in6)) + return (0); + in6_setscope(&in6, ifp, NULL); + + IF_ADDR_RLOCK(ifp); + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + ia6 = (struct in6_ifaddr *)ifa; + if (IN6_ARE_ADDR_EQUAL(&ia6->ia_addr.sin6_addr, &in6)) { + IF_ADDR_RUNLOCK(ifp); + return (1); + } + } + IF_ADDR_RUNLOCK(ifp); + + return (0); +} int in6_is_addr_deprecated(struct sockaddr_in6 *sa6) { + struct rm_priotracker in6_ifa_tracker; struct in6_ifaddr *ia; - IN6_IFADDR_RLOCK(); - TAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) { - if (IN6_ARE_ADDR_EQUAL(&ia->ia_addr.sin6_addr, - &sa6->sin6_addr) && - (ia->ia6_flags & IN6_IFF_DEPRECATED) != 0) { - IN6_IFADDR_RUNLOCK(); - return (1); /* true */ + IN6_IFADDR_RLOCK(&in6_ifa_tracker); + LIST_FOREACH(ia, IN6ADDR_HASH(&sa6->sin6_addr), ia6_hash) { + if (IN6_ARE_ADDR_EQUAL(IA6_IN6(ia), &sa6->sin6_addr)) { + if (ia->ia6_flags & IN6_IFF_DEPRECATED) { + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); + return (1); /* true */ + } + break; } - - /* XXX: do we still have to go thru the rest of the list? */ } - IN6_IFADDR_RUNLOCK(); + IN6_IFADDR_RUNLOCK(&in6_ifa_tracker); return (0); /* false */ } @@ -2222,7 +1774,7 @@ in6_ifawithifp(struct ifnet *ifp, struct in6_addr *dst) { int dst_scope = in6_addrscope(dst), blen = -1, tlen; struct ifaddr *ifa; - struct in6_ifaddr *besta = 0; + struct in6_ifaddr *besta = NULL; struct in6_ifaddr *dep[2]; /* last-resort: deprecated */ dep[0] = dep[1] = NULL; @@ -2347,37 +1899,24 @@ in6if_do_dad(struct ifnet *ifp) if ((ifp->if_flags & IFF_LOOPBACK) != 0) return (0); - if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) + if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) || + (ND_IFINFO(ifp)->flags & ND6_IFF_NO_DAD)) return (0); - switch (ifp->if_type) { -#ifdef IFT_DUMMY - case IFT_DUMMY: -#endif - case IFT_FAITH: - /* - * These interfaces do not have the IFF_LOOPBACK flag, - * but loop packets back. We do not have to do DAD on such - * interfaces. We should even omit it, because loop-backed - * NS would confuse the DAD procedure. - */ - return (0); - default: - /* - * Our DAD routine requires the interface up and running. - * However, some interfaces can be up before the RUNNING - * status. Additionaly, users may try to assign addresses - * before the interface becomes up (or running). - * We simply skip DAD in such a case as a work around. - * XXX: we should rather mark "tentative" on such addresses, - * and do DAD after the interface becomes ready. - */ - if (!((ifp->if_flags & IFF_UP) && - (ifp->if_drv_flags & IFF_DRV_RUNNING))) - return (0); + /* + * Our DAD routine requires the interface up and running. + * However, some interfaces can be up before the RUNNING + * status. Additionally, users may try to assign addresses + * before the interface becomes up (or running). + * This function returns EAGAIN in that case. + * The caller should mark "tentative" on the address instead of + * performing DAD immediately. + */ + if (!((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING))) + return (EAGAIN); - return (1); - } + return (1); } /* @@ -2391,7 +1930,7 @@ in6_setmaxmtu(void) struct ifnet *ifp; IFNET_RLOCK_NOSLEEP(); - TAILQ_FOREACH(ifp, &V_ifnet, if_list) { + TAILQ_FOREACH(ifp, &V_ifnet, if_link) { /* this function can be called during ifnet initialization */ if (!ifp->if_afdata[AF_INET6]) continue; @@ -2417,18 +1956,10 @@ in6_if2idlen(struct ifnet *ifp) { switch (ifp->if_type) { case IFT_ETHER: /* RFC2464 */ -#ifdef IFT_PROPVIRTUAL case IFT_PROPVIRTUAL: /* XXX: no RFC. treat it as ether */ -#endif -#ifdef IFT_L2VLAN case IFT_L2VLAN: /* ditto */ -#endif -#ifdef IFT_IEEE80211 case IFT_IEEE80211: /* ditto */ -#endif -#ifdef IFT_MIP - case IFT_MIP: /* ditto */ -#endif + case IFT_BRIDGE: /* bridge(4) only does Ethernet-like links */ case IFT_INFINIBAND: return (64); case IFT_FDDI: /* RFC2467 */ @@ -2468,25 +1999,38 @@ in6_if2idlen(struct ifnet *ifp) struct in6_llentry { struct llentry base; - struct sockaddr_in6 l3_addr6; }; +#define IN6_LLTBL_DEFAULT_HSIZE 32 +#define IN6_LLTBL_HASH(k, h) \ + (((((((k >> 8) ^ k) >> 8) ^ k) >> 8) ^ k) & ((h) - 1)) + /* - * Deletes an address from the address table. - * This function is called by the timer functions - * such as arptimer() and nd6_llinfo_timer(), and - * the caller does the locking. + * Do actual deallocation of @lle. */ static void -in6_lltable_free(struct lltable *llt, struct llentry *lle) +in6_lltable_destroy_lle_unlocked(struct llentry *lle) { - LLE_WUNLOCK(lle); + LLE_LOCK_DESTROY(lle); + LLE_REQ_DESTROY(lle); free(lle, M_LLTABLE); } +/* + * Called by LLE_FREE_LOCKED when number of references + * drops to zero. + */ +static void +in6_lltable_destroy_lle(struct llentry *lle) +{ + + LLE_WUNLOCK(lle); + in6_lltable_destroy_lle_unlocked(lle); +} + static struct llentry * -in6_lltable_new(const struct sockaddr *l3addr, u_int flags) +in6_lltable_new(const struct in6_addr *addr6, u_int flags) { struct in6_llentry *lle; @@ -2494,45 +2038,69 @@ in6_lltable_new(const struct sockaddr *l3addr, u_int flags) if (lle == NULL) /* NB: caller generates msg */ return NULL; - lle->l3_addr6 = *(const struct sockaddr_in6 *)l3addr; + lle->base.r_l3addr.addr6 = *addr6; lle->base.lle_refcnt = 1; - lle->base.lle_free = in6_lltable_free; + lle->base.lle_free = in6_lltable_destroy_lle; LLE_LOCK_INIT(&lle->base); - callout_init_rw(&lle->base.ln_timer_ch, &lle->base.lle_lock, - CALLOUT_RETURNUNLOCKED); + LLE_REQ_INIT(&lle->base); + callout_init(&lle->base.lle_timer, 1); return (&lle->base); } +static int +in6_lltable_match_prefix(const struct sockaddr *saddr, + const struct sockaddr *smask, u_int flags, struct llentry *lle) +{ + const struct in6_addr *addr, *mask, *lle_addr; + + addr = &((const struct sockaddr_in6 *)saddr)->sin6_addr; + mask = &((const struct sockaddr_in6 *)smask)->sin6_addr; + lle_addr = &lle->r_l3addr.addr6; + + if (IN6_ARE_MASKED_ADDR_EQUAL(lle_addr, addr, mask) == 0) + return (0); + + if (lle->la_flags & LLE_IFADDR) { + + /* + * Delete LLE_IFADDR records IFF address & flag matches. + * Note that addr is the interface address within prefix + * being matched. + */ + if (IN6_ARE_ADDR_EQUAL(addr, lle_addr) && + (flags & LLE_STATIC) != 0) + return (1); + return (0); + } + + /* flags & LLE_STATIC means deleting both dynamic and static entries */ + if ((flags & LLE_STATIC) || !(lle->la_flags & LLE_STATIC)) + return (1); + + return (0); +} + static void -in6_lltable_prefix_free(struct lltable *llt, const struct sockaddr *prefix, - const struct sockaddr *mask, u_int flags) +in6_lltable_free_entry(struct lltable *llt, struct llentry *lle) { - const struct sockaddr_in6 *pfx = (const struct sockaddr_in6 *)prefix; - const struct sockaddr_in6 *msk = (const struct sockaddr_in6 *)mask; - struct llentry *lle, *next; - int i; + struct ifnet *ifp; - /* - * (flags & LLE_STATIC) means deleting all entries - * including static ND6 entries. - */ - IF_AFDATA_WLOCK(llt->llt_ifp); - for (i = 0; i < LLTBL_HASHTBL_SIZE; i++) { - LIST_FOREACH_SAFE(lle, &llt->lle_head[i], lle_next, next) { - if (IN6_ARE_MASKED_ADDR_EQUAL( - &satosin6(L3_ADDR(lle))->sin6_addr, - &pfx->sin6_addr, &msk->sin6_addr) && - ((flags & LLE_STATIC) || - !(lle->la_flags & LLE_STATIC))) { - LLE_WLOCK(lle); - if (callout_stop(&lle->la_timer)) - LLE_REMREF(lle); - llentry_free(lle); - } - } + LLE_WLOCK_ASSERT(lle); + KASSERT(llt != NULL, ("lltable is NULL")); + + /* Unlink entry from table */ + if ((lle->la_flags & LLE_LINKED) != 0) { + + ifp = llt->llt_ifp; + IF_AFDATA_WLOCK_ASSERT(ifp); + lltable_unlink_entry(llt, lle); } - IF_AFDATA_WUNLOCK(llt->llt_ifp); + + if (callout_stop(&lle->lle_timer) > 0) + LLE_REMREF(lle); + + llentry_free(lle); } static int @@ -2540,122 +2108,178 @@ in6_lltable_rtcheck(struct ifnet *ifp, u_int flags, const struct sockaddr *l3addr) { - struct rtentry *rt; + const struct sockaddr_in6 *sin6; + struct nhop6_basic nh6; + struct in6_addr dst; + uint32_t scopeid; + int error; char ip6buf[INET6_ADDRSTRLEN]; KASSERT(l3addr->sa_family == AF_INET6, ("sin_family %d", l3addr->sa_family)); /* Our local addresses are always only installed on the default FIB. */ - /* XXX rtalloc1 should take a const param */ - rt = in6_rtalloc1(__DECONST(struct sockaddr *, l3addr), 0, 0, - RT_DEFAULT_FIB); - if (rt == NULL || (rt->rt_flags & RTF_GATEWAY) || rt->rt_ifp != ifp) { + + sin6 = (const struct sockaddr_in6 *)l3addr; + in6_splitscope(&sin6->sin6_addr, &dst, &scopeid); + error = fib6_lookup_nh_basic(RT_DEFAULT_FIB, &dst, scopeid, 0, 0, &nh6); + if (error != 0 || (nh6.nh_flags & NHF_GATEWAY) || nh6.nh_ifp != ifp) { struct ifaddr *ifa; /* * Create an ND6 cache for an IPv6 neighbor * that is not covered by our own prefix. */ - /* XXX ifaof_ifpforaddr should take a const param */ - ifa = ifaof_ifpforaddr(__DECONST(struct sockaddr *, l3addr), ifp); + ifa = ifaof_ifpforaddr(l3addr, ifp); if (ifa != NULL) { ifa_free(ifa); - if (rt != NULL) - RTFREE_LOCKED(rt); return 0; } log(LOG_INFO, "IPv6 address: \"%s\" is not on the network\n", - ip6_sprintf(ip6buf, &((const struct sockaddr_in6 *)l3addr)->sin6_addr)); - if (rt != NULL) - RTFREE_LOCKED(rt); + ip6_sprintf(ip6buf, &sin6->sin6_addr)); return EINVAL; } - RTFREE_LOCKED(rt); return 0; } -static struct llentry * -in6_lltable_lookup(struct lltable *llt, u_int flags, - const struct sockaddr *l3addr) +static inline uint32_t +in6_lltable_hash_dst(const struct in6_addr *dst, uint32_t hsize) +{ + + return (IN6_LLTBL_HASH(dst->s6_addr32[3], hsize)); +} + +static uint32_t +in6_lltable_hash(const struct llentry *lle, uint32_t hsize) +{ + + return (in6_lltable_hash_dst(&lle->r_l3addr.addr6, hsize)); +} + +static void +in6_lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa) +{ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)sa; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_addr = lle->r_l3addr.addr6; +} + +static inline struct llentry * +in6_lltable_find_dst(struct lltable *llt, const struct in6_addr *dst) { - const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr; - struct ifnet *ifp = llt->llt_ifp; struct llentry *lle; struct llentries *lleh; - u_int hashkey; - - IF_AFDATA_LOCK_ASSERT(ifp); - KASSERT(l3addr->sa_family == AF_INET6, - ("sin_family %d", l3addr->sa_family)); + u_int hashidx; - hashkey = sin6->sin6_addr.s6_addr32[3]; - lleh = &llt->lle_head[LLATBL_HASH(hashkey, LLTBL_HASHMASK)]; + hashidx = in6_lltable_hash_dst(dst, llt->llt_hsize); + lleh = &llt->lle_head[hashidx]; LIST_FOREACH(lle, lleh, lle_next) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)L3_ADDR(lle); if (lle->la_flags & LLE_DELETED) continue; - if (bcmp(&sa6->sin6_addr, &sin6->sin6_addr, - sizeof(struct in6_addr)) == 0) + if (IN6_ARE_ADDR_EQUAL(&lle->r_l3addr.addr6, dst)) break; } - if (lle == NULL) { - if (!(flags & LLE_CREATE)) - return (NULL); - IF_AFDATA_WLOCK_ASSERT(ifp); - /* - * A route that covers the given address must have - * been installed 1st because we are doing a resolution, - * verify this. - */ - if (!(flags & LLE_IFADDR) && - in6_lltable_rtcheck(ifp, flags, l3addr) != 0) - return NULL; - - lle = in6_lltable_new(l3addr, flags); - if (lle == NULL) { - log(LOG_INFO, "lla_lookup: new lle malloc failed\n"); - return NULL; - } - lle->la_flags = flags & ~LLE_CREATE; - if ((flags & (LLE_CREATE | LLE_IFADDR)) == (LLE_CREATE | LLE_IFADDR)) { - bcopy(IF_LLADDR(ifp), &lle->ll_addr, ifp->if_addrlen); - lle->la_flags |= (LLE_VALID | LLE_STATIC); - } + return (lle); +} - lle->lle_tbl = llt; - lle->lle_head = lleh; - lle->la_flags |= LLE_LINKED; - LIST_INSERT_HEAD(lleh, lle, lle_next); - } else if (flags & LLE_DELETE) { - if (!(lle->la_flags & LLE_IFADDR) || (flags & LLE_IFADDR)) { - LLE_WLOCK(lle); - lle->la_flags |= LLE_DELETED; +static void +in6_lltable_delete_entry(struct lltable *llt, struct llentry *lle) +{ + + lle->la_flags |= LLE_DELETED; + EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_DELETED); #ifdef DIAGNOSTIC - log(LOG_INFO, "ifaddr cache = %p is deleted\n", lle); + log(LOG_INFO, "ifaddr cache = %p is deleted\n", lle); #endif - if ((lle->la_flags & - (LLE_STATIC | LLE_IFADDR)) == LLE_STATIC) - llentry_free(lle); - else - LLE_WUNLOCK(lle); - } - lle = (void *)-1; + llentry_free(lle); +} + +static struct llentry * +in6_lltable_alloc(struct lltable *llt, u_int flags, + const struct sockaddr *l3addr) +{ + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr; + struct ifnet *ifp = llt->llt_ifp; + struct llentry *lle; + char linkhdr[LLE_MAX_LINKHDR]; + size_t linkhdrsize; + int lladdr_off; + + KASSERT(l3addr->sa_family == AF_INET6, + ("sin_family %d", l3addr->sa_family)); + + /* + * A route that covers the given address must have + * been installed 1st because we are doing a resolution, + * verify this. + */ + if (!(flags & LLE_IFADDR) && + in6_lltable_rtcheck(ifp, flags, l3addr) != 0) + return (NULL); + + lle = in6_lltable_new(&sin6->sin6_addr, flags); + if (lle == NULL) { + log(LOG_INFO, "lla_lookup: new lle malloc failed\n"); + return (NULL); } - if (LLE_IS_VALID(lle)) { - if (flags & LLE_EXCLUSIVE) - LLE_WLOCK(lle); - else - LLE_RLOCK(lle); + lle->la_flags = flags; + if ((flags & LLE_IFADDR) == LLE_IFADDR) { + linkhdrsize = LLE_MAX_LINKHDR; + if (lltable_calc_llheader(ifp, AF_INET6, IF_LLADDR(ifp), + linkhdr, &linkhdrsize, &lladdr_off) != 0) { + in6_lltable_destroy_lle_unlocked(lle); + return (NULL); + } + lltable_set_entry_addr(ifp, lle, linkhdr, linkhdrsize, + lladdr_off); + lle->la_flags |= LLE_STATIC; } + + if ((lle->la_flags & LLE_STATIC) != 0) + lle->ln_state = ND6_LLINFO_REACHABLE; + + return (lle); +} + +static struct llentry * +in6_lltable_lookup(struct lltable *llt, u_int flags, + const struct sockaddr *l3addr) +{ + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr; + struct llentry *lle; + + IF_AFDATA_LOCK_ASSERT(llt->llt_ifp); + KASSERT(l3addr->sa_family == AF_INET6, + ("sin_family %d", l3addr->sa_family)); + + lle = in6_lltable_find_dst(llt, &sin6->sin6_addr); + + if (lle == NULL) + return (NULL); + + KASSERT((flags & (LLE_UNLOCKED|LLE_EXCLUSIVE)) != + (LLE_UNLOCKED|LLE_EXCLUSIVE),("wrong lle request flags: 0x%X", + flags)); + + if (flags & LLE_UNLOCKED) + return (lle); + + if (flags & LLE_EXCLUSIVE) + LLE_WLOCK(lle); + else + LLE_RLOCK(lle); return (lle); } static int -in6_lltable_dump(struct lltable *llt, struct sysctl_req *wr) +in6_lltable_dump_entry(struct lltable *llt, struct llentry *lle, + struct sysctl_req *wr) { struct ifnet *ifp = llt->llt_ifp; - struct llentry *lle; /* XXX stack use */ struct { struct rt_msghdr rtm; @@ -2668,39 +2292,32 @@ in6_lltable_dump(struct lltable *llt, struct sysctl_req *wr) #endif struct sockaddr_dl sdl; } ndpc; - int i, error; - - if (ifp->if_flags & IFF_LOOPBACK) - return 0; - - LLTABLE_LOCK_ASSERT(); - - error = 0; - for (i = 0; i < LLTBL_HASHTBL_SIZE; i++) { - LIST_FOREACH(lle, &llt->lle_head[i], lle_next) { - struct sockaddr_dl *sdl; + struct sockaddr_dl *sdl; + int error; - /* skip deleted or invalid entries */ - if ((lle->la_flags & (LLE_DELETED|LLE_VALID)) != LLE_VALID) - continue; + bzero(&ndpc, sizeof(ndpc)); + /* skip deleted entries */ + if ((lle->la_flags & LLE_DELETED) == LLE_DELETED) + return (0); /* Skip if jailed and not a valid IP of the prison. */ - if (prison_if(wr->td->td_ucred, L3_ADDR(lle)) != 0) - continue; + lltable_fill_sa_entry(lle, + (struct sockaddr *)&ndpc.sin6); + if (prison_if(wr->td->td_ucred, + (struct sockaddr *)&ndpc.sin6) != 0) + return (0); /* * produce a msg made of: * struct rt_msghdr; * struct sockaddr_in6 (IPv6) * struct sockaddr_dl; */ - bzero(&ndpc, sizeof(ndpc)); ndpc.rtm.rtm_msglen = sizeof(ndpc); ndpc.rtm.rtm_version = RTM_VERSION; ndpc.rtm.rtm_type = RTM_GET; ndpc.rtm.rtm_flags = RTF_UP; ndpc.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY; - ndpc.sin6.sin6_family = AF_INET6; - ndpc.sin6.sin6_len = sizeof(ndpc.sin6); - bcopy(L3_ADDR(lle), &ndpc.sin6, L3_ADDR_LEN(lle)); + if (V_deembed_scopeid) + sa6_recoverscope(&ndpc.sin6); /* publish */ if (lle->la_flags & LLE_PUB) @@ -2709,22 +2326,56 @@ in6_lltable_dump(struct lltable *llt, struct sysctl_req *wr) sdl = &ndpc.sdl; sdl->sdl_family = AF_LINK; sdl->sdl_len = sizeof(*sdl); - sdl->sdl_alen = ifp->if_addrlen; sdl->sdl_index = ifp->if_index; sdl->sdl_type = ifp->if_type; - bcopy(&lle->ll_addr, LLADDR(sdl), ifp->if_addrlen); - ndpc.rtm.rtm_rmx.rmx_expire = - lle->la_flags & LLE_STATIC ? 0 : lle->la_expire; + if ((lle->la_flags & LLE_VALID) == LLE_VALID) { + sdl->sdl_alen = ifp->if_addrlen; + bcopy(lle->ll_addr, LLADDR(sdl), + ifp->if_addrlen); + } else { + sdl->sdl_alen = 0; + bzero(LLADDR(sdl), ifp->if_addrlen); + } + if (lle->la_expire != 0) + ndpc.rtm.rtm_rmx.rmx_expire = lle->la_expire + + lle->lle_remtime / hz + + time_second - time_uptime; ndpc.rtm.rtm_flags |= (RTF_HOST | RTF_LLDATA); if (lle->la_flags & LLE_STATIC) ndpc.rtm.rtm_flags |= RTF_STATIC; + if (lle->la_flags & LLE_IFADDR) + ndpc.rtm.rtm_flags |= RTF_PINNED; + if (lle->ln_router != 0) + ndpc.rtm.rtm_flags |= RTF_GATEWAY; + ndpc.rtm.rtm_rmx.rmx_pksent = lle->la_asked; + /* Store state in rmx_weight value */ + ndpc.rtm.rtm_rmx.rmx_state = lle->ln_state; ndpc.rtm.rtm_index = ifp->if_index; error = SYSCTL_OUT(wr, &ndpc, sizeof(ndpc)); - if (error) - break; - } - } - return error; + + return (error); +} + +static struct lltable * +in6_lltattach(struct ifnet *ifp) +{ + struct lltable *llt; + + llt = lltable_allocate_htbl(IN6_LLTBL_DEFAULT_HSIZE); + llt->llt_af = AF_INET6; + llt->llt_ifp = ifp; + + llt->llt_lookup = in6_lltable_lookup; + llt->llt_alloc_entry = in6_lltable_alloc; + llt->llt_delete_entry = in6_lltable_delete_entry; + llt->llt_dump_entry = in6_lltable_dump_entry; + llt->llt_hash = in6_lltable_hash; + llt->llt_fill_sa_entry = in6_lltable_fill_sa_entry; + llt->llt_free_entry = in6_lltable_free_entry; + llt->llt_match_prefix = in6_lltable_match_prefix; + lltable_link(llt); + + return (llt); } void * @@ -2732,32 +2383,45 @@ in6_domifattach(struct ifnet *ifp) { struct in6_ifextra *ext; + /* There are not IPv6-capable interfaces. */ + switch (ifp->if_type) { + case IFT_PFLOG: + case IFT_PFSYNC: + case IFT_USB: + return (NULL); + } ext = (struct in6_ifextra *)malloc(sizeof(*ext), M_IFADDR, M_WAITOK); bzero(ext, sizeof(*ext)); - ext->in6_ifstat = (struct in6_ifstat *)malloc(sizeof(struct in6_ifstat), - M_IFADDR, M_WAITOK); - bzero(ext->in6_ifstat, sizeof(*ext->in6_ifstat)); + ext->in6_ifstat = malloc(sizeof(counter_u64_t) * + sizeof(struct in6_ifstat) / sizeof(uint64_t), M_IFADDR, M_WAITOK); + COUNTER_ARRAY_ALLOC(ext->in6_ifstat, + sizeof(struct in6_ifstat) / sizeof(uint64_t), M_WAITOK); - ext->icmp6_ifstat = - (struct icmp6_ifstat *)malloc(sizeof(struct icmp6_ifstat), - M_IFADDR, M_WAITOK); - bzero(ext->icmp6_ifstat, sizeof(*ext->icmp6_ifstat)); + ext->icmp6_ifstat = malloc(sizeof(counter_u64_t) * + sizeof(struct icmp6_ifstat) / sizeof(uint64_t), M_IFADDR, + M_WAITOK); + COUNTER_ARRAY_ALLOC(ext->icmp6_ifstat, + sizeof(struct icmp6_ifstat) / sizeof(uint64_t), M_WAITOK); ext->nd_ifinfo = nd6_ifattach(ifp); ext->scope6_id = scope6_ifattach(ifp); - ext->lltable = lltable_init(ifp, AF_INET6); - if (ext->lltable != NULL) { - ext->lltable->llt_prefix_free = in6_lltable_prefix_free; - ext->lltable->llt_lookup = in6_lltable_lookup; - ext->lltable->llt_dump = in6_lltable_dump; - } + ext->lltable = in6_lltattach(ifp); ext->mld_ifinfo = mld_domifattach(ifp); return ext; } +int +in6_domifmtu(struct ifnet *ifp) +{ + if (ifp->if_afdata[AF_INET6] == NULL) + return ifp->if_mtu; + + return (IN6_LINKMTU(ifp)); +} + void in6_domifdetach(struct ifnet *ifp, void *aux) { @@ -2765,9 +2429,13 @@ in6_domifdetach(struct ifnet *ifp, void *aux) mld_domifdetach(ifp); scope6_ifdetach(ext->scope6_id); - nd6_ifdetach(ext->nd_ifinfo); + nd6_ifdetach(ifp, ext->nd_ifinfo); lltable_free(ext->lltable); + COUNTER_ARRAY_FREE(ext->in6_ifstat, + sizeof(struct in6_ifstat) / sizeof(uint64_t)); free(ext->in6_ifstat, M_IFADDR); + COUNTER_ARRAY_FREE(ext->icmp6_ifstat, + sizeof(struct icmp6_ifstat) / sizeof(uint64_t)); free(ext->icmp6_ifstat, M_IFADDR); free(ext, M_IFADDR); } |