Index: sbin/route/route.c =================================================================== --- sbin/route/route.c +++ sbin/route/route.c @@ -1149,6 +1149,14 @@ } #endif +static int +guess_af(char *str) +{ + if (strchr(str, ':')) + return (AF_INET6); + return (AF_INET); +} + /* * Interpret an argument as a network address of some kind, * returning 1 if a host address, 0 if a network address. @@ -1184,8 +1192,16 @@ #endif rtm_addrs |= (1 << idx); sa = (struct sockaddr *)&so[idx]; - sa->sa_family = af; - sa->sa_len = aflen; + switch (guess_af(str)) { + case AF_INET6: + sa->sa_family = AF_INET6; + sa->sa_len = sizeof(struct sockaddr_in6); + break; + case AF_INET: + sa->sa_family = AF_INET; + sa->sa_len = sizeof(struct sockaddr_in); + break; + } switch (idx) { case RTAX_GATEWAY: Index: sys/net/if_ethersubr.c =================================================================== --- sys/net/if_ethersubr.c +++ sys/net/if_ethersubr.c @@ -236,8 +236,8 @@ #ifdef INET6 case AF_INET6: if ((m->m_flags & M_MCAST) == 0) - error = nd6_resolve(ifp, ro_get_gw_type(ro), m, dst, - phdr, &lleflags, plle); + error = nd6_resolve(ifp, 0, m, dst, phdr, &lleflags, + plle); else { const struct in6_addr *a6; a6 = &(((const struct sockaddr_in6 *)dst)->sin6_addr); Index: sys/net/if_fwsubr.c =================================================================== --- sys/net/if_fwsubr.c +++ sys/net/if_fwsubr.c @@ -195,8 +195,8 @@ #ifdef INET6 case AF_INET6: if (unicast) { - error = nd6_resolve(fc->fc_ifp, ro_get_gw_type(ro), m, - dst, (u_char *) destfw, NULL, NULL); + error = nd6_resolve(fc->fc_ifp, 0, m, dst, + (u_char *) destfw, NULL, NULL); if (error) return (error == EWOULDBLOCK ? 0 : error); } Index: sys/net/if_infiniband.c =================================================================== --- sys/net/if_infiniband.c +++ sys/net/if_infiniband.c @@ -253,8 +253,8 @@ #ifdef INET6 case AF_INET6: if ((m->m_flags & M_MCAST) == 0) { - error = nd6_resolve(ifp, ro_get_gw_type(ro), m, dst, - phdr, &lleflags, plle); + error = nd6_resolve(ifp, 0, m, dst, phdr, &lleflags, + plle); } else { infiniband_ipv6_multicast_map( &((const struct sockaddr_in6 *)dst)->sin6_addr, Index: sys/net/if_llatbl.h =================================================================== --- sys/net/if_llatbl.h +++ sys/net/if_llatbl.h @@ -58,7 +58,8 @@ } r_l3addr; char r_linkdata[LLE_MAX_LINKHDR]; /* L2 data */ uint8_t r_hdrlen; /* length for LL header */ - uint8_t spare0[3]; + uint8_t r_family; /* Upper layer proto family */ + uint8_t spare0[2]; uint16_t r_flags; /* LLE runtime flags */ uint16_t r_skip_req; /* feedback from fast path */ @@ -78,6 +79,9 @@ time_t lle_hittime; /* Time when r_skip_req was unset */ int lle_refcnt; char *ll_addr; /* link-layer address */ + CK_SLIST_HEAD(llentry_children_head,llentry) lle_children; /* child encaps */ + CK_SLIST_ENTRY(llentry) lle_child_next; /* child encaps */ + struct llentry *lle_parent; /* parent for a child */ CK_LIST_ENTRY(llentry) lle_chain; /* chain of deleted items */ struct callout lle_timer; @@ -195,6 +199,7 @@ #define LLE_REDIRECT 0x0010 /* installed by redirect; has host rtentry */ #define LLE_PUB 0x0020 /* publish entry ??? */ #define LLE_LINKED 0x0040 /* linked to lookup structure */ +#define LLE_CHILD 0x0080 /* Child LLE storing different AF encap */ /* LLE request flags */ #define LLE_EXCLUSIVE 0x2000 /* return lle xlocked */ #define LLE_UNLOCKED 0x4000 /* return lle unlocked */ @@ -234,10 +239,14 @@ const struct sockaddr *l3addr); int lltable_link_entry(struct lltable *llt, struct llentry *lle); int lltable_unlink_entry(struct lltable *llt, struct llentry *lle); +void lltable_link_child_entry(struct llentry *parent_lle, struct llentry *child_lle); +void lltable_unlink_child_entry(struct llentry *child_lle); void lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa); struct ifnet *lltable_get_ifp(const struct lltable *llt); int lltable_get_af(const struct lltable *llt); +bool lltable_acquire_wlock(struct ifnet *ifp, struct llentry *lle); + int lltable_foreach_lle(struct lltable *llt, llt_foreach_cb_t *f, void *farg); /* @@ -263,6 +272,11 @@ lle->lle_tbl->llt_mark_used(lle); } +struct llentry *llentry_lookup_family(struct llentry *lle, int family); +void llentry_request_feedback(struct llentry *lle); +time_t llentry_get_hittime(struct llentry *lle); + + int lla_rt_output(struct rt_msghdr *, struct rt_addrinfo *); enum { Index: sys/net/if_llatbl.c =================================================================== --- sys/net/if_llatbl.c +++ sys/net/if_llatbl.c @@ -317,23 +317,13 @@ lle->r_flags |= RLLE_VALID; } -/* - * Tries to update @lle link-level address. - * Since update requires AFDATA WLOCK, function - * drops @lle lock, acquires AFDATA lock and then acquires - * @lle lock to maintain lock order. - * - * Returns 1 on success. - */ -int -lltable_try_set_entry_addr(struct ifnet *ifp, struct llentry *lle, - const char *linkhdr, size_t linkhdrsize, int lladdr_off) +bool +lltable_acquire_wlock(struct ifnet *ifp, struct llentry *lle) { + NET_EPOCH_ASSERT(); /* Perform real LLE update */ /* use afdata WLOCK to update fields */ - LLE_WLOCK_ASSERT(lle); - LLE_ADDREF(lle); LLE_WUNLOCK(lle); IF_AFDATA_WLOCK(ifp); LLE_WLOCK(lle); @@ -344,17 +334,33 @@ */ if ((lle->la_flags & LLE_DELETED) != 0) { IF_AFDATA_WUNLOCK(ifp); - LLE_FREE_LOCKED(lle); - return (0); + return (false); } + return (true); +} + +/* + * Tries to update @lle link-level address. + * Since update requires AFDATA WLOCK, function + * drops @lle lock, acquires AFDATA lock and then acquires + * @lle lock to maintain lock order. + * + * Returns 1 on success. + */ +int +lltable_try_set_entry_addr(struct ifnet *ifp, struct llentry *lle, + const char *linkhdr, size_t linkhdrsize, int lladdr_off) +{ + + if (!lltable_acquire_wlock(ifp, lle)) + return (0); + /* Update data */ lltable_set_entry_addr(ifp, lle, linkhdr, linkhdrsize, lladdr_off); IF_AFDATA_WUNLOCK(ifp); - LLE_REMREF(lle); - return (1); } @@ -386,6 +392,74 @@ return (error); } +/* + * Searches for the child entry matching @family inside @lle. + * Returns the entry or NULL. + */ +struct llentry * +llentry_lookup_family(struct llentry *lle, int family) +{ + struct llentry *child_lle; + + if (lle == NULL) + return (NULL); + + CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { + if (child_lle->r_family == family) + return (child_lle); + } + + return (NULL); +} + +void +llentry_request_feedback(struct llentry *lle) +{ + struct llentry *child_lle; + + LLE_REQ_LOCK(lle); + lle->r_skip_req = 1; + LLE_REQ_UNLOCK(lle); + + CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { + LLE_REQ_LOCK(child_lle); + child_lle->r_skip_req = 1; + LLE_REQ_UNLOCK(child_lle); + } + +} + +static time_t +llentry_get_hittime_raw(struct llentry *lle) +{ + time_t lle_hittime = 0; + + LLE_REQ_LOCK(lle); + if ((lle->r_skip_req == 0) && (lle_hittime < lle->lle_hittime)) + lle_hittime = lle->lle_hittime; + LLE_REQ_UNLOCK(lle); + + return (lle_hittime); +} + +time_t +llentry_get_hittime(struct llentry *lle) +{ + time_t lle_hittime = 0; + struct llentry *child_lle; + + lle_hittime = llentry_get_hittime_raw(lle); + + CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { + time_t hittime = llentry_get_hittime_raw(child_lle); + if (hittime > lle_hittime) + lle_hittime = hittime; + } + + return (lle_hittime); +} + + /* * Update link-layer header for given @lle after * interface lladdr was changed. @@ -642,6 +716,23 @@ return (llt->llt_link_entry(llt, lle)); } +void +lltable_link_child_entry(struct llentry *parent_lle, struct llentry *child_lle) +{ + child_lle->lle_parent = parent_lle; + child_lle->lle_tbl = parent_lle->lle_tbl; + child_lle->la_flags |= LLE_LINKED; + CK_SLIST_INSERT_HEAD(&parent_lle->lle_children, child_lle, lle_child_next); +} + +void +lltable_unlink_child_entry(struct llentry *child_lle) +{ + struct llentry *parent_lle = child_lle->lle_parent; + + CK_SLIST_REMOVE(&parent_lle->lle_children, child_lle, llentry, lle_child_next); +} + int lltable_unlink_entry(struct lltable *llt, struct llentry *lle) { Index: sys/net/route.h =================================================================== --- sys/net/route.h +++ sys/net/route.h @@ -230,38 +230,6 @@ #define NHR_COPY 0x100 /* Copy rte data */ #define NHR_UNLOCKED 0x200 /* Do not lock table */ -/* nd6 / arpresolve gateway type */ -/* FIXME need proper place */ -typedef enum { - GW_NONE = 0, -#ifdef INET - GW_IPV4_SRC, -#endif -#ifdef INET6 - GW_IPV6_SRC -#endif -} gw_type_t; - -/* FIXME need proper place */ -static __inline gw_type_t -ro_get_gw_type(struct route *ro) -{ - if (ro == NULL || (ro->ro_flags & RT_HAS_GW) == 0) - return GW_NONE; - switch(ro->ro_dst.sa_family) { -#ifdef INET - case AF_INET: - return GW_IPV4_SRC; -#endif -#ifdef INET6 - case AF_INET6: - return GW_IPV6_SRC; -#endif - default: - return GW_NONE; - } -} - /* * Routing statistics. */ Index: sys/netinet6/in6.c =================================================================== --- sys/netinet6/in6.c +++ sys/netinet6/in6.c @@ -2352,6 +2352,11 @@ ("wrong lle request flags: %#x", flags)); lle = in6_lltable_find_dst(llt, &sin6->sin6_addr); + + int family = flags >> 16; + if (__predict_false(family != 0)) + lle = llentry_lookup_family(lle, family); + if (lle == NULL) return (NULL); if (flags & LLE_UNLOCKED) Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -372,14 +372,14 @@ void nd6_purge(struct ifnet *); int nd6_resolve_addr(struct ifnet *ifp, int flags, const struct sockaddr *dst, char *desten, uint32_t *pflags); -int nd6_resolve(struct ifnet *, gw_type_t, struct mbuf *, +int nd6_resolve(struct ifnet *, int, struct mbuf *, const struct sockaddr *, u_char *, uint32_t *, struct llentry **); int nd6_ioctl(u_long, caddr_t, struct ifnet *); void nd6_cache_lladdr(struct ifnet *, struct in6_addr *, char *, int, int, int); void nd6_grab_holdchain(struct llentry *, struct mbuf **, struct sockaddr_in6 *); -int nd6_flush_holdchain(struct ifnet *, struct mbuf *, +int nd6_flush_holdchain(struct ifnet *, struct llentry *, struct mbuf *, struct sockaddr_in6 *); int nd6_add_ifa_lle(struct in6_ifaddr *); void nd6_rem_ifa_lle(struct in6_ifaddr *, int); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -140,11 +140,9 @@ static void nd6_llinfo_settimer_locked(struct llentry *, long); static void clear_llinfo_pqueue(struct llentry *); static int nd6_resolve_slow(struct ifnet *, int, struct mbuf *, - const struct sockaddr_in6 *, u_char *, uint32_t *, struct llentry **, - gw_type_t); + const struct sockaddr_in6 *, u_char *, uint32_t *, struct llentry **); static int nd6_need_cache(struct ifnet *); -static bool nd6_attach_encap_tag(struct mbuf *, gw_type_t); -static int output_ifp_one(struct ifnet *, struct mbuf *, struct sockaddr_in6 *); +static void nd6_flush_children_holdchain(struct ifnet *ifp, struct llentry *lle); VNET_DEFINE_STATIC(struct callout, nd6_slowtimo_ch); #define V_nd6_slowtimo_ch VNET(nd6_slowtimo_ch) @@ -620,7 +618,7 @@ static int nd6_is_stale(struct llentry *lle, long *pdelay, int *do_switch) { - int nd_delay, nd_gctimer, r_skip_req; + int nd_delay, nd_gctimer; time_t lle_hittime; long delay; @@ -628,12 +626,9 @@ nd_gctimer = V_nd6_gctimer; nd_delay = V_nd6_delay; - LLE_REQ_LOCK(lle); - r_skip_req = lle->r_skip_req; - lle_hittime = lle->lle_hittime; - LLE_REQ_UNLOCK(lle); + lle_hittime = llentry_get_hittime(lle); - if (r_skip_req > 0) { + if (lle_hittime == 0) { /* * Nonzero r_skip_req value was set upon entering * STALE state. Since value was not changed, no @@ -712,9 +707,7 @@ * Notify fast path that we want to know if any packet * is transmitted by setting r_skip_req. */ - LLE_REQ_LOCK(lle); - lle->r_skip_req = 1; - LLE_REQ_UNLOCK(lle); + llentry_request_feedback(lle); nd_delay = V_nd6_delay; nd_gctimer = V_nd6_gctimer; @@ -1395,6 +1388,94 @@ return (rc); } +static __noinline void +nd6_free_child_raw(struct llentry *ln) +{ + + NET_EPOCH_ASSERT(); + LLE_WLOCK_ASSERT(ln); + + /* Guard against race with other llentry_free(). */ + if (ln->la_flags & LLE_LINKED) { + + LLE_REMREF(ln); + ln->la_flags &= ~LLE_LINKED; + + /* Timer will be cancelled here */ + llentry_free(ln); + } +} + +static __noinline void +nd6_free_child(struct llentry *child_ln) +{ + + NET_EPOCH_ASSERT(); + LLE_WLOCK_ASSERT(child_ln); + + struct llentry *parent_ln = child_ln->lle_parent; + LLE_WUNLOCK(child_ln); + + /* Remove child_ln from the list */ + LLE_WLOCK(parent_ln); + lltable_unlink_child_entry(child_ln); + LLE_WUNLOCK(parent_ln); + + LLE_WLOCK(child_ln); + + nd6_free_child_raw(child_ln); +} + +static __noinline void +nd6_free_children(struct llentry_children_head *head) +{ + struct llentry *ln; + + NET_EPOCH_ASSERT(); + + CK_SLIST_FOREACH(ln, head, lle_child_next) { + LLE_WLOCK(ln); + nd6_free_child_raw(ln); + } +} + +static __noinline bool +nd6_try_set_entry_addr(struct ifnet *ifp, struct llentry *ln, char *lladdr) +{ + u_char buf[LLE_MAX_LINKHDR]; + size_t sz; + int fam, off; + + NET_EPOCH_ASSERT(); + + /* Assumes ln is WLOCKED or not yet linked */ + /* Assumes AFDATA lock is held IF updating linked lle */ + sz = sizeof(buf); + + if (lltable_calc_llheader(ifp, AF_INET6, lladdr, buf, &sz, &off) != 0) { + IF_AFDATA_WUNLOCK(ifp); + return (false); + } + + /* Update data */ + lltable_set_entry_addr(ifp, ln, buf, sz, off); + + struct llentry *child_ln; + CK_SLIST_FOREACH(child_ln, &ln->lle_children, lle_child_next) { + LLE_WLOCK(child_ln); + fam = AF_INET; + if (lltable_calc_llheader(ifp, fam, lladdr, buf, &sz, &off) == 0) { + /* success */ + lltable_set_entry_addr(ifp, child_ln, buf, sz, off); + child_ln->ln_state = ND6_LLINFO_REACHABLE; + } + LLE_WUNLOCK(child_ln); + } + + return (true); +} + + /* * Free an nd6 llinfo entry. * Since the function would cause significant changes in the kernel, DO NOT @@ -1416,6 +1497,12 @@ LLE_WLOCK_ASSERT(ln); ND6_RLOCK_ASSERT(); + if (ln->la_flags & LLE_CHILD) { + ND6_RUNLOCK(); + nd6_free_child(ln); + return; + } + ifp = lltable_get_ifp(ln->lle_tbl); if ((ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) != 0) dr = defrouter_lookup_locked(&ln->r_l3addr.addr6, ifp); @@ -1537,7 +1624,14 @@ } IF_AFDATA_UNLOCK(ifp); + /* Free children */ + struct llentry_children_head head = CK_SLIST_HEAD_INITIALIZER(head); + CK_SLIST_SWAP(&head, &ln->lle_children, llentry); + llentry_free(ln); + + nd6_free_children(&head); + if (dr != NULL) defrouter_rele(dr); } @@ -1938,9 +2032,6 @@ uint16_t router = 0; struct sockaddr_in6 sin6; struct mbuf *chain = NULL; - u_char linkhdr[LLE_MAX_LINKHDR]; - size_t linkhdrsize; - int lladdr_off; NET_EPOCH_ASSERT(); IF_AFDATA_UNLOCK_ASSERT(ifp); @@ -1975,12 +2066,8 @@ * fill it before insertion. */ if (lladdr != NULL) { - linkhdrsize = sizeof(linkhdr); - if (lltable_calc_llheader(ifp, AF_INET6, lladdr, - linkhdr, &linkhdrsize, &lladdr_off) != 0) + if (nd6_try_set_entry_addr(ifp, ln, lladdr)) return; - lltable_set_entry_addr(ifp, ln, linkhdr, linkhdrsize, - lladdr_off); } IF_AFDATA_WLOCK(ifp); @@ -2041,16 +2128,12 @@ * Record source link-layer address * XXX is it dependent to ifp->if_type? */ - linkhdrsize = sizeof(linkhdr); - if (lltable_calc_llheader(ifp, AF_INET6, lladdr, - linkhdr, &linkhdrsize, &lladdr_off) != 0) + if (!lltable_acquire_wlock(ifp, ln)) return; - - if (lltable_try_set_entry_addr(ifp, ln, linkhdr, linkhdrsize, - lladdr_off) == 0) { - /* Entry was deleted */ + bool ret = nd6_try_set_entry_addr(ifp, ln, lladdr); + IF_AFDATA_WUNLOCK(ifp); + if (!ret) return; - } nd6_llinfo_setstate(ln, ND6_LLINFO_STALE); @@ -2075,7 +2158,9 @@ LLE_RUNLOCK(ln); if (chain != NULL) - nd6_flush_holdchain(ifp, chain, &sin6); + nd6_flush_holdchain(ifp, ln, chain, &sin6); + if (do_update) + nd6_flush_children_holdchain(ifp, ln); /* * When the link-layer address of a router changes, select the @@ -2216,7 +2301,7 @@ * - other errors (alloc failure, etc) */ int -nd6_resolve(struct ifnet *ifp, gw_type_t gw_type, struct mbuf *m, +nd6_resolve(struct ifnet *ifp, int gw_flags, struct mbuf *m, const struct sockaddr *sa_dst, u_char *desten, uint32_t *pflags, struct llentry **plle) { @@ -2250,20 +2335,12 @@ } } - ln = nd6_lookup(&dst6->sin6_addr, plle ? LLE_EXCLUSIVE : LLE_UNLOCKED, - ifp); + int family = gw_flags >> 16; + int flags = (family << 16) | (plle ? LLE_EXCLUSIVE : LLE_UNLOCKED); + ln = nd6_lookup(&dst6->sin6_addr, flags, ifp); if (ln != NULL && (ln->r_flags & RLLE_VALID) != 0) { /* Entry found, let's copy lle info */ bcopy(ln->r_linkdata, desten, ln->r_hdrlen); -#ifdef INET - /* XXX fix ether type */ - if (gw_type == GW_IPV4_SRC) { - struct ether_header *eh; - uint16_t etype = htons(ETHERTYPE_IP); - eh = (struct ether_header *)desten; - bcopy(&etype, &eh->ether_type, sizeof(etype)); - } -#endif if (pflags != NULL) *pflags = LLE_VALID | (ln->r_flags & RLLE_IFADDR); /* Check if we have feedback request from nd6 timer */ @@ -2282,8 +2359,67 @@ } else if (plle && ln) LLE_WUNLOCK(ln); - return (nd6_resolve_slow(ifp, 0, m, dst6, desten, pflags, plle, - gw_type)); + flags = family << 16; + return (nd6_resolve_slow(ifp, flags, m, dst6, desten, pflags, plle)); +} + +static __noinline struct llentry * +nd6_get_llentry(struct ifnet *ifp, const struct in6_addr *addr, int family) +{ + struct llentry *parent_lle = NULL, *child_lle = NULL; + struct llentry *lle = NULL, *lle_tmp; + + parent_lle = nd6_alloc(addr, 0, ifp); + if (parent_lle != NULL && family != 0) { + child_lle = nd6_alloc(addr, 0, ifp); + if (child_lle == NULL) { + lltable_free_entry(LLTABLE6(ifp), parent_lle); + parent_lle = NULL; + } + child_lle->r_family = family; + child_lle->la_flags |= LLE_CHILD | LLE_STATIC; + child_lle->ln_state = ND6_LLINFO_INCOMPLETE; + } + + if (parent_lle == NULL) { + char ip6buf[INET6_ADDRSTRLEN]; + log(LOG_DEBUG, "nd6_output: can't allocate llinfo for %s " + "(ln=NULL)\n", ip6_sprintf(ip6buf, addr)); + return (NULL); + } + + IF_AFDATA_WLOCK(ifp); + LLE_WLOCK(parent_lle); + /* Prefer any existing entry over newly-created one */ + + /* Search for parent lle first */ + lle_tmp = nd6_lookup(addr, LLE_EXCLUSIVE, ifp); + if (lle_tmp == NULL) { + lltable_link_entry(LLTABLE6(ifp), parent_lle); + } else { + lltable_free_entry(LLTABLE6(ifp), parent_lle); + parent_lle = lle_tmp; + } + /* parent_lle is the needed lle and is WLOCKED */ + if (child_lle != NULL) { + /* Check if child lle for the same family exists */ + lle_tmp = llentry_lookup_family(parent_lle, child_lle->r_family); + if (lle_tmp == NULL) { + /* Attach */ + lltable_link_child_entry(parent_lle, child_lle); + } else { + lltable_free_entry(LLTABLE6(ifp), child_lle); + child_lle = lle_tmp; + } + LLE_WLOCK(child_lle); + LLE_WUNLOCK(parent_lle); + lle = child_lle; + } else + lle = parent_lle; + + IF_AFDATA_WUNLOCK(ifp); + + return (lle); } /* @@ -2300,9 +2436,9 @@ static __noinline int nd6_resolve_slow(struct ifnet *ifp, int flags, struct mbuf *m, const struct sockaddr_in6 *dst, u_char *desten, uint32_t *pflags, - struct llentry **plle, gw_type_t gw_type) + struct llentry **plle) { - struct llentry *lle = NULL, *lle_tmp; + struct llentry *lle = NULL; struct in6_addr *psrc, src; int send_ns, ll_len; char *lladdr; @@ -2316,36 +2452,16 @@ * or an anycast address(i.e. not a multicast). */ if (lle == NULL) { - lle = nd6_lookup(&dst->sin6_addr, LLE_EXCLUSIVE, ifp); + int family = flags >> 16; + int nd6_flags = (family << 16) | LLE_EXCLUSIVE; + lle = nd6_lookup(&dst->sin6_addr, nd6_flags, ifp); if ((lle == NULL) && nd6_is_addr_neighbor(dst, ifp)) { /* * Since nd6_is_addr_neighbor() internally calls nd6_lookup(), * the condition below is not very efficient. But we believe * it is tolerable, because this should be a rare case. */ - lle = nd6_alloc(&dst->sin6_addr, 0, ifp); - if (lle == NULL) { - char ip6buf[INET6_ADDRSTRLEN]; - log(LOG_DEBUG, - "nd6_output: can't allocate llinfo for %s " - "(ln=%p)\n", - ip6_sprintf(ip6buf, &dst->sin6_addr), lle); - m_freem(m); - return (ENOBUFS); - } - - IF_AFDATA_WLOCK(ifp); - LLE_WLOCK(lle); - /* Prefer any existing entry over newly-created one */ - lle_tmp = nd6_lookup(&dst->sin6_addr, LLE_EXCLUSIVE, ifp); - if (lle_tmp == NULL) - lltable_link_entry(LLTABLE6(ifp), lle); - IF_AFDATA_WUNLOCK(ifp); - if (lle_tmp != NULL) { - lltable_free_entry(LLTABLE6(ifp), lle); - lle = lle_tmp; - lle_tmp = NULL; - } + lle = nd6_get_llentry(ifp, &dst->sin6_addr, family); } } if (lle == NULL) { @@ -2362,7 +2478,7 @@ * neighbor unreachability detection on expiration. * (RFC 2461 7.3.3) */ - if (lle->ln_state == ND6_LLINFO_STALE) + if ((!(lle->la_flags & LLE_CHILD)) && (lle->ln_state == ND6_LLINFO_STALE)) nd6_llinfo_setstate(lle, ND6_LLINFO_DELAY); /* @@ -2379,15 +2495,6 @@ ll_len = lle->r_hdrlen; } bcopy(lladdr, desten, ll_len); -#ifdef INET - /* XXX fix ether type */ - if (gw_type == GW_IPV4_SRC) { - struct ether_header *eh; - uint16_t etype = htons(ETHERTYPE_IP); - eh = (struct ether_header *)desten; - bcopy(&etype, &eh->ether_type, sizeof(etype)); - } -#endif if (pflags != NULL) *pflags = lle->la_flags; if (plle) { @@ -2404,13 +2511,6 @@ * packet queue in the mbuf. When it exceeds nd6_maxqueuelen, * the oldest packet in the queue will be removed. */ - if (gw_type != GW_NONE && gw_type != GW_IPV6_SRC && - !nd6_attach_encap_tag(m, gw_type)) { - LLE_WUNLOCK(lle); - m_freem(m); - return (ENOMEM); - } - if (lle->la_hold != NULL) { struct mbuf *m_hold; int i; @@ -2442,6 +2542,13 @@ */ psrc = NULL; send_ns = 0; + + if (lle->la_flags & LLE_CHILD) { + struct llentry *lle_parent = lle->lle_parent; + LLE_WUNLOCK(lle); + lle = lle_parent; + LLE_WLOCK(lle); + } if (lle->la_asked == 0) { lle->la_asked++; send_ns = 1; @@ -2474,24 +2581,27 @@ flags |= LLE_ADDRONLY; error = nd6_resolve_slow(ifp, flags, NULL, - (const struct sockaddr_in6 *)dst, desten, pflags, NULL, GW_NONE); + (const struct sockaddr_in6 *)dst, desten, pflags, NULL); return (error); } int -nd6_flush_holdchain(struct ifnet *ifp, struct mbuf *chain, +nd6_flush_holdchain(struct ifnet *ifp, struct llentry *lle, struct mbuf *chain, struct sockaddr_in6 *dst) { struct mbuf *m, *m_head; int error = 0; + struct route_in6 ro = { + .ro_prepend = lle->r_linkdata, + .ro_plen = lle->r_hdrlen, + }; m_head = chain; - while (m_head) { m = m_head; m_head = m_head->m_nextpkt; m->m_nextpkt = NULL; - error = output_ifp_one(ifp, m, dst); + error = nd6_output_ifp(ifp, ifp, m, dst, (struct route *)&ro); } /* @@ -2501,54 +2611,24 @@ return (error); } -#define MTAG_ND6_ENCAP_TYPE 4237446679 - -static bool -nd6_attach_encap_tag(struct mbuf *m, gw_type_t type) -{ - struct m_tag *mtag; - sa_family_t af = AF_UNSPEC; - -#ifdef INET - if (type == GW_IPV4_SRC) - af = AF_INET; -#endif - if (af == AF_UNSPEC) - return (true); - mtag = m_tag_alloc(MTAG_ND6_ENCAP_TYPE, 0, sizeof(sa_family_t), - M_NOWAIT); - if (mtag == NULL) - return (false); - *(sa_family_t *)(mtag + 1) = af; - m_tag_prepend(m, mtag); - - return (true); -} - -static int -output_ifp_one(struct ifnet *ifp, struct mbuf *m, struct sockaddr_in6 *dst) +static __noinline void +nd6_flush_children_holdchain(struct ifnet *ifp, struct llentry *lle) { - int error; - struct m_tag *mtag; - sa_family_t af; - struct route ro; - - mtag = m_tag_locate(m, MTAG_ND6_ENCAP_TYPE, 0, NULL); - if (mtag != NULL) { - af = *(sa_family_t *)(mtag + 1); -#ifdef INET - KASSERT(af == AF_INET, ("Unexpected af %d", af)); -#endif - bzero(&ro, sizeof(struct route)); - ro.ro_flags = RT_HAS_GW; - ro.ro_dst.sa_family = af; /* XXX only restore af */ - /* XXX no ro.ro_dst.sa_len */ - error = (*ifp->if_output)(ifp, m, - (const struct sockaddr *)dst, &ro); - } else - error = nd6_output_ifp(ifp, ifp, m, dst, NULL); + struct llentry *child_lle; + struct sockaddr_in6 sin6; - return (error); + NET_EPOCH_ASSERT(); + /* Assume lle is locked */ + + CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { + struct mbuf *chain = NULL; + LLE_WLOCK(child_lle); + if (child_lle->la_hold != NULL) + nd6_grab_holdchain(child_lle, &chain, &sin6); + LLE_WUNLOCK(child_lle); + if (chain != NULL) + nd6_flush_holdchain(ifp, child_lle, chain, &sin6); + } } static int Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -908,7 +908,7 @@ LLE_WUNLOCK(ln); if (chain != NULL) - nd6_flush_holdchain(ifp, chain, &sin6); + nd6_flush_holdchain(ifp, ln, chain, &sin6); if (checklink) pfxlist_onlink_check();