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 @@ -204,6 +204,7 @@ struct ether_header *eh; uint32_t lleflags = 0; int error = 0; + int family = RO_GET_FAMILY(ro, dst); #if defined(INET) || defined(INET6) uint16_t etype; #endif @@ -235,10 +236,11 @@ #endif #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); - else { + if ((m->m_flags & M_MCAST) == 0) { + int flags = family << 16; + error = nd6_resolve(ifp, flags, m, dst, phdr, &lleflags, + plle); + } else { const struct in6_addr *a6; a6 = &(((const struct sockaddr_in6 *)dst)->sin6_addr); ETHER_MAP_IPV6_MULTICAST(a6, eh->ether_dhost); @@ -289,7 +291,6 @@ uint32_t pflags; struct llentry *lle = NULL; int addref = 0; - int af = RO_GET_FAMILY(ro, dst); phdr = NULL; pflags = 0; @@ -353,7 +354,7 @@ if ((pflags & RT_L2_ME) != 0) { update_mbuf_csumflags(m, m); - return (if_simloop(ifp, m, af, 0)); + return (if_simloop(ifp, m, RO_GET_FAMILY(ro, dst), 0)); } loop_copy = (pflags & RT_MAY_LOOP) != 0; @@ -371,19 +372,6 @@ if ((pflags & RT_HAS_HEADER) == 0) { eh = mtod(m, struct ether_header *); memcpy(eh, phdr, hlen); -#if defined(INET) && defined(INET6) - /* XXX phdr might be from lle cache, let's fix the ether_type */ - /* FIXME maybe we can cache phdr in route ??? */ - if (dst->sa_family != af) { - uint16_t etype = 0; - if (af == AF_INET) - etype = htons(ETHERTYPE_IP); - else if (af == AF_INET6) - etype = htons(ETHERTYPE_IPV6); - if (etype != 0) - memcpy(&eh->ether_type, &etype, sizeof(etype)); - } -#endif } /* @@ -413,7 +401,7 @@ */ if ((n = m_dup(m, M_NOWAIT)) != NULL) { update_mbuf_csumflags(m, n); - (void)if_simloop(ifp, n, af, hlen); + (void)if_simloop(ifp, n, RO_GET_FAMILY(ro, dst), hlen); } else if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); } 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; @@ -104,6 +108,8 @@ #define LLE_IS_VALID(lle) (((lle) != NULL) && ((lle) != (void *)-1)) +#define LLE_SF(_fl, _fam) (((_fl) & 0xFFFF) | ((_fam) << 16)) + #define LLE_ADDREF(lle) do { \ LLE_WLOCK_ASSERT(lle); \ KASSERT((lle)->lle_refcnt >= 0, \ @@ -195,6 +201,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 +241,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 +274,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. @@ -527,7 +601,7 @@ ifp = llt->llt_ifp; IF_AFDATA_WLOCK(ifp); - lle = lla_lookup(llt, LLE_EXCLUSIVE, l3addr); + lle = lla_lookup(llt, LLE_SF(LLE_EXCLUSIVE, l3addr->sa_family), l3addr); if (lle == NULL) { IF_AFDATA_WUNLOCK(ifp); @@ -642,6 +716,22 @@ 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) +{ + child_lle->la_flags &= ~LLE_LINKED; + child_lle->lle_parent = NULL; +} + 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/icmp6.c =================================================================== --- sys/netinet6/icmp6.c +++ sys/netinet6/icmp6.c @@ -2546,7 +2546,7 @@ struct nd_opt_hdr *nd_opt; char *lladdr; - ln = nd6_lookup(router_ll6, 0, ifp); + ln = nd6_lookup(router_ll6, ND6_SF6(0), ifp); if (ln == NULL) goto nolladdropt; 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 != AF_INET6)) + 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 @@ -353,6 +353,9 @@ #define nd_opts_last nd_opt_each.last #define nd_opts_done nd_opt_each.done +#define ND6_SF(_flags, _family) LLE_SF(_flags, _family) +#define ND6_SF6(_flags) ND6_SF(_flags, AF_INET6) + /* XXX: need nd6_var.h?? */ /* nd6.c */ void nd6_init(void); @@ -372,19 +375,21 @@ 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); int nd6_output_ifp(struct ifnet *, struct ifnet *, struct mbuf *, struct sockaddr_in6 *, struct route *); +bool nd6_try_set_entry_addr(struct ifnet *ifp, struct llentry *ln, char *lladdr); +void nd6_flush_children_holdchain(struct ifnet *ifp, struct llentry *lle); struct rib_head; struct rib_cmd_info; Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -140,11 +140,8 @@ 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 *); VNET_DEFINE_STATIC(struct callout, nd6_slowtimo_ch); #define V_nd6_slowtimo_ch VNET(nd6_slowtimo_ch) @@ -533,6 +530,10 @@ LLE_WLOCK_ASSERT(ln); + /* Do not schedule timers for child LLEs */ + if (ln->la_flags & LLE_CHILD) + return; + if (tick < 0) { ln->la_expire = 0; ln->ln_ntick = 0; @@ -620,7 +621,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 +629,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 +710,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; @@ -1388,13 +1384,81 @@ * Even if the address matches none of our addresses, it might be * in the neighbor cache. */ - if ((lle = nd6_lookup(&addr->sin6_addr, 0, ifp)) != NULL) { + if ((lle = nd6_lookup(&addr->sin6_addr, ND6_SF6(0), ifp)) != NULL) { LLE_RUNLOCK(lle); rc = 1; } return (rc); } +static __noinline void +nd6_free_children(struct llentry *lle) +{ + struct llentry *child_lle, *tmp; + struct llentry_children_head head = CK_SLIST_HEAD_INITIALIZER(head); + + NET_EPOCH_ASSERT(); + LLE_WLOCK_ASSERT(lle); + + CK_SLIST_SWAP(&head, &lle->lle_children, llentry); + CK_SLIST_FOREACH_SAFE(child_lle, &head, lle_child_next, tmp) { + LLE_WLOCK(child_lle); + lltable_unlink_child_entry(child_lle); + llentry_free(child_lle); + } +} + +static __noinline bool +nd6_try_set_entry_addr_unlocked(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) + 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 = child_ln->r_family; + sz = sizeof(buf); + 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); +} + +bool +nd6_try_set_entry_addr(struct ifnet *ifp, struct llentry *ln, char *lladdr) +{ + NET_EPOCH_ASSERT(); + LLE_WLOCK_ASSERT(ln); + + if (!lltable_acquire_wlock(ifp, ln)) + return (false); + + bool ret = nd6_try_set_entry_addr_unlocked(ifp, ln, lladdr); + IF_AFDATA_WUNLOCK(ifp); + + return (ret); +} + + /* * Free an nd6 llinfo entry. * Since the function would cause significant changes in the kernel, DO NOT @@ -1416,6 +1480,8 @@ LLE_WLOCK_ASSERT(ln); ND6_RLOCK_ASSERT(); + KASSERT((ln->la_flags & LLE_CHILD) == 0, ("child lle")); + 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 +1603,10 @@ } IF_AFDATA_UNLOCK(ifp); + nd6_free_children(ln); + llentry_free(ln); + if (dr != NULL) defrouter_rele(dr); } @@ -1811,7 +1880,7 @@ return (error); NET_EPOCH_ENTER(et); - ln = nd6_lookup(&nb_addr, 0, ifp); + ln = nd6_lookup(&nb_addr, ND6_SF6(0), ifp); NET_EPOCH_EXIT(et); if (ln == NULL) { @@ -1961,7 +2030,7 @@ * Spec says nothing in sections for RA, RS and NA. There's small * description on it in NS section (RFC 2461 7.2.3). */ - flags = lladdr ? LLE_EXCLUSIVE : 0; + flags = ND6_SF6(lladdr ? LLE_EXCLUSIVE : 0); ln = nd6_lookup(from, flags, ifp); is_newentry = 0; if (ln == NULL) { @@ -1986,7 +2055,7 @@ IF_AFDATA_WLOCK(ifp); LLE_WLOCK(ln); /* Prefer any existing lle over newly-created one */ - ln_tmp = nd6_lookup(from, LLE_EXCLUSIVE, ifp); + ln_tmp = nd6_lookup(from, ND6_SF6(LLE_EXCLUSIVE), ifp); if (ln_tmp == NULL) lltable_link_entry(LLTABLE6(ifp), ln); IF_AFDATA_WUNLOCK(ifp); @@ -2041,14 +2110,8 @@ * 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) - return; - - if (lltable_try_set_entry_addr(ifp, ln, linkhdr, linkhdrsize, - lladdr_off) == 0) { - /* Entry was deleted */ + if (!nd6_try_set_entry_addr(ifp, ln, lladdr)) { + LLE_WUNLOCK(ln); return; } @@ -2075,7 +2138,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 +2281,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 +2315,14 @@ } } - ln = nd6_lookup(&dst6->sin6_addr, plle ? LLE_EXCLUSIVE : LLE_UNLOCKED, - ifp); + int family = gw_flags >> 16; + if (family == 0) + family = AF_INET6; + int flags = ND6_SF(plle ? LLE_EXCLUSIVE : LLE_UNLOCKED, family); + 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 +2341,69 @@ } 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 != AF_INET6) { + 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 */ + int flags = ND6_SF6(LLE_EXCLUSIVE); + lle_tmp = nd6_lookup(addr, flags, 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); + LLE_WLOCK(child_lle); + if (lle_tmp == NULL) { + /* Attach */ + lltable_link_child_entry(parent_lle, child_lle); + } else { + /* child lle already exists, free newly-created one */ + lltable_free_entry(LLTABLE6(ifp), child_lle); + child_lle = lle_tmp; + } + LLE_WUNLOCK(parent_lle); + lle = child_lle; + } else + lle = parent_lle; + + IF_AFDATA_WUNLOCK(ifp); + + return (lle); } /* @@ -2300,9 +2420,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 +2436,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 = ND6_SF(LLE_EXCLUSIVE, family); + 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 +2462,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 +2479,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 +2495,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 +2526,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 +2565,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 +2595,23 @@ return (error); } -#define MTAG_ND6_ENCAP_TYPE 4237446679 - -static bool -nd6_attach_encap_tag(struct mbuf *m, gw_type_t type) +__noinline void +nd6_flush_children_holdchain(struct ifnet *ifp, struct llentry *lle) { - 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); -} + struct llentry *child_lle; + struct sockaddr_in6 sin6; -static int -output_ifp_one(struct ifnet *ifp, struct mbuf *m, struct sockaddr_in6 *dst) -{ - 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); + NET_EPOCH_ASSERT(); - return (error); + 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 @@ -2604,7 +2667,7 @@ IF_AFDATA_WLOCK(ifp); LLE_WLOCK(ln); /* Unlink any entry if exists */ - ln_tmp = lla_lookup(LLTABLE6(ifp), LLE_EXCLUSIVE, dst); + ln_tmp = lla_lookup(LLTABLE6(ifp), ND6_SF6(LLE_EXCLUSIVE), dst); if (ln_tmp != NULL) lltable_unlink_entry(LLTABLE6(ifp), ln_tmp); lltable_link_entry(LLTABLE6(ifp), ln); Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -625,12 +625,11 @@ struct in6_addr daddr6, taddr6; struct sockaddr_in6 sin6; union nd_opts ndopts; - u_char linkhdr[LLE_MAX_LINKHDR]; char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN]; char *lladdr; - size_t linkhdrsize; int flags, is_override, is_router, is_solicited; - int lladdr_off, lladdrlen, checklink; + int lladdrlen, checklink; + bool flush_chains = false; NET_EPOCH_ASSERT(); @@ -748,7 +747,7 @@ * If no neighbor cache entry is found, NA SHOULD silently be * discarded. */ - ln = nd6_lookup(&taddr6, LLE_EXCLUSIVE, ifp); + ln = nd6_lookup(&taddr6, ND6_SF6(LLE_EXCLUSIVE), ifp); if (ln == NULL) { goto freeit; } @@ -771,16 +770,10 @@ /* * Record link-layer address, and update the state. */ - linkhdrsize = sizeof(linkhdr); - if (lltable_calc_llheader(ifp, AF_INET6, lladdr, - linkhdr, &linkhdrsize, &lladdr_off) != 0) - return; - - if (lltable_try_set_entry_addr(ifp, ln, linkhdr, linkhdrsize, - lladdr_off) == 0) { - ln = NULL; + if (!nd6_try_set_entry_addr(ifp, ln, lladdr)) goto freeit; - } + + flush_chains = true; EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_RESOLVED); if (is_solicited) nd6_llinfo_setstate(ln, ND6_LLINFO_REACHABLE); @@ -846,15 +839,10 @@ * Update link-local address, if any. */ if (lladdr != NULL) { - linkhdrsize = sizeof(linkhdr); - if (lltable_calc_llheader(ifp, AF_INET6, lladdr, - linkhdr, &linkhdrsize, &lladdr_off) != 0) - goto freeit; - if (lltable_try_set_entry_addr(ifp, ln, linkhdr, - linkhdrsize, lladdr_off) == 0) { - ln = NULL; + if (!nd6_try_set_entry_addr(ifp, ln, lladdr)) goto freeit; - } + + flush_chains = true; EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_RESOLVED); } @@ -908,7 +896,9 @@ LLE_WUNLOCK(ln); if (chain != NULL) - nd6_flush_holdchain(ifp, chain, &sin6); + nd6_flush_holdchain(ifp, ln, chain, &sin6); + if (flush_chains) + nd6_flush_children_holdchain(ifp, ln); if (checklink) pfxlist_onlink_check(); Index: sys/netinet6/nd6_rtr.c =================================================================== --- sys/netinet6/nd6_rtr.c +++ sys/netinet6/nd6_rtr.c @@ -972,7 +972,7 @@ TAILQ_FOREACH(dr, &V_nd6_defrouter, dr_entry) { NET_EPOCH_ENTER(et); if (selected_dr == NULL && dr->ifp->if_fib == fibnum && - (ln = nd6_lookup(&dr->rtaddr, 0, dr->ifp)) && + (ln = nd6_lookup(&dr->rtaddr, ND6_SF6(0), dr->ifp)) && ND6_IS_LLINFO_PROBREACH(ln)) { selected_dr = dr; defrouter_ref(selected_dr); @@ -1022,7 +1022,7 @@ } } else if (installed_dr != NULL) { NET_EPOCH_ENTER(et); - if ((ln = nd6_lookup(&installed_dr->rtaddr, 0, + if ((ln = nd6_lookup(&installed_dr->rtaddr, ND6_SF6(0), installed_dr->ifp)) && ND6_IS_LLINFO_PROBREACH(ln) && installed_dr->ifp->if_fib == fibnum && @@ -1814,7 +1814,7 @@ NET_EPOCH_ENTER(et); LIST_FOREACH(pfxrtr, &pr->ndpr_advrtrs, pfr_entry) { - ln = nd6_lookup(&pfxrtr->router->rtaddr, 0, pfxrtr->router->ifp); + ln = nd6_lookup(&pfxrtr->router->rtaddr, ND6_SF6(0), pfxrtr->router->ifp); if (ln == NULL) continue; canreach = ND6_IS_LLINFO_PROBREACH(ln);