Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -3213,6 +3213,7 @@ net/bpf_zerocopy.c optional bpf net/bridgestp.c optional bridge | if_bridge net/flowtable.c optional flowtable inet | flowtable inet6 +net/delayed_dispatch.c standard net/ieee8023ad_lacp.c optional lagg net/if.c standard net/if_arcsubr.c optional arcnet Index: sys/net/delayed_dispatch.h =================================================================== --- /dev/null +++ sys/net/delayed_dispatch.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2014 Alexander V. Chernikov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _NET_DELAYED_DISPATCH_H_ +#define _NET_DELAYED_DISPATCH_H_ + +struct dly_item { + int type; + int subtype; + struct ifnet *ifp; + uintptr_t data; +}; + +typedef int dly_dispatch_t(struct mbuf *, int, uintptr_t, struct ifnet *); +typedef int dly_pdispatch_t(struct mbuf *, struct dly_item *); +typedef int dly_free_t(struct mbuf *, int, uintptr_t, struct ifnet *); + +struct dly_dispatcher { + const char *name; + dly_dispatch_t *dly_dispatch; + dly_pdispatch_t *dly_pdispatch; + dly_free_t *dly_free; +}; + + +int dly_register(struct dly_dispatcher *); +void dly_unregister(int); +int dly_queue(int, struct mbuf *, int, uintptr_t, struct ifnet *); +int dly_pqueue(int, struct mbuf *, struct dly_item *, size_t); + +#endif + Index: sys/net/delayed_dispatch.c =================================================================== --- /dev/null +++ sys/net/delayed_dispatch.c @@ -0,0 +1,364 @@ +/*- + * Copyright (c) 2014 Alexander V. Chernikov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: head/sys/net/delayed_dispatch.c$"); + +/* + * delayed dispatch is so-called "slowpath" packet path which permits you + * to enqueue mbufs requiring complex dispath (and/or possibly complex locking) + * into separate netisr queue instead of trying to deal with it in "fast" code path. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +struct dly_info { + struct dly_dispatcher *index; + int alloc; + int count; + struct rmlock lock; +}; +#define DLY_ALLOC_ITEMS 16 + +static struct dly_info dly; + +#define DLY_LOCK_INIT() rm_init(&dly.lock, "dly_lock") +#define DLY_RLOCK() rm_rlock(&dly.lock, &tracker) +#define DLY_RUNLOCK() rm_runlock(&dly.lock, &tracker) +#define DLY_WLOCK() rm_wlock(&dly.lock) +#define DLY_WUNLOCK() rm_wunlock(&dly.lock) +#define DLY_READER struct rm_priotracker tracker + +static eventhandler_tag ifdetach_tag; + +/* + * Adds mbuf to slowpath queue. Additional information + * is stored in PACKET_TAG_DISPATCH_INFO mbuf tag. + * Returns 0 if successfull, error code overwise. + */ +int +dly_queue(int dtype, struct mbuf *m, int dsubtype, uintptr_t data, + struct ifnet *ifp) +{ + struct dly_item *item; + struct m_tag *dtag; + DLY_READER; + + /* Ensure we're not going to cycle packet */ + if ((dtag = m_tag_find(m, PACKET_TAG_DISPATCH_INFO, NULL)) != NULL) { + printf("tag found: %p\n", dtag); + return (EINVAL); + } + + DLY_RLOCK(); + if (dtype < 0 || dtype >= dly.alloc || dly.index[dtype].name == NULL) { + DLY_RUNLOCK(); + printf("invalid dtype: 0..%d..%d\n", dtype, dly.alloc); + return (EINVAL); + } + DLY_RUNLOCK(); + + VNET_ASSERT(m->m_pkthdr.rcvif != NULL, + ("%s:%d rcvif == NULL: m=%p", __func__, __LINE__, m)); + + /* + * Do not allocate tag for basic IPv4/IPv6 output + */ + if (dtype != 0) { + dtag = m_tag_get(PACKET_TAG_DISPATCH_INFO, + sizeof(struct dly_item), M_NOWAIT); + + if (dtag == NULL) + return (ENOBUFS); + + item = (struct dly_item *)(dtag + 1); + + item->type = dtype; + item->subtype = dsubtype; + item->data = data; + item->ifp = ifp; + + m_tag_prepend(m, dtag); + } + + netisr_queue(NETISR_SLOWPATH, m); + + return (0); +} + +/* + * Adds mbuf to slowpath queue. User-provided buffer + * of size @size is stored inside PACKET_TAG_DISPATCH_INFO + * mbuf tag. Buffer structure needs to embed properly filled + * dly_item structure at the beginning of buffer. Such buffers + * needs to be dispatched by dly_pdispatch() handler. + * + * Returns 0 if successfull, error code overwise. + */ +int +dly_pqueue(int dtype, struct mbuf *m, struct dly_item *item, size_t size) +{ + struct m_tag *dtag; + DLY_READER; + + /* Ensure we're not going to cycle packet */ + if ((dtag = m_tag_find(m, PACKET_TAG_DISPATCH_INFO, NULL)) != NULL) { + return (EINVAL); + } + + DLY_RLOCK(); + if (dtype < 0 || dtype >= dly.alloc || dly.index[dtype].name == NULL) { + DLY_RUNLOCK(); + return (EINVAL); + } + DLY_RUNLOCK(); + + VNET_ASSERT(m->m_pkthdr.rcvif != NULL, + ("%s:%d rcvif == NULL: m=%p", __func__, __LINE__, m)); + + dtag = m_tag_get(PACKET_TAG_DISPATCH_INFO, size, M_NOWAIT); + + if (dtag == NULL) + return (ENOBUFS); + + memcpy(dtag + 1, item, size); + m_tag_prepend(m, dtag); + netisr_queue(NETISR_SLOWPATH, m); + + return (0); +} + +/* + * Base netisr handler for slowpath + */ +static void +dly_dispatch_item(struct mbuf *m) +{ + struct m_tag *dtag; + struct dly_item *item; + int dtype; + struct dly_dispatcher *dld; + DLY_READER; + + item = NULL; + dtype = 0; + + if ((dtag = m_tag_find(m, PACKET_TAG_DISPATCH_INFO, NULL)) != NULL) { + item = (struct dly_item *)(dtag + 1); + dtype = item->type; + } + + DLY_RLOCK(); + if (dtype < 0 || dtype >= dly.alloc || dly.index[dtype].name == NULL) { + DLY_RUNLOCK(); + return; + } + + dld = &dly.index[dtype]; + + if (dld->dly_dispatch != NULL) + dld->dly_dispatch(m, item->subtype, item->data, item->ifp); + else + dld->dly_pdispatch(m, item); + + DLY_RUNLOCK(); + + return; +} + + +/* + * Check if queue items is received or going to be transmitted + * via destroying interface. + */ +static int +dly_scan_ifp(struct mbuf *m, void *_data) +{ + struct m_tag *dtag; + struct dly_item *item; + struct ifnet *difp; + + difp = (struct ifnet *)_data; + + if (m->m_pkthdr.rcvif == difp) + return (1); + + if ((dtag = m_tag_find(m, PACKET_TAG_DISPATCH_INFO, NULL)) != NULL) { + item = (struct dly_item *)(dtag + 1); + if (item->ifp == difp) + return (1); + } + + return (0); +} + +/* + * Registers new slowpath handler. + * Returns handler id to use in dly_queue() or + * dly_pqueue() functions/ + */ +int +dly_register(struct dly_dispatcher *dld) +{ + int i, alloc; + struct dly_dispatcher *dd, *tmp; + +again: + DLY_WLOCK(); + + if (dly.count < dly.alloc) { + i = dly.count++; + dly.index[i] = *dld; + DLY_WUNLOCK(); + return (i); + } + + alloc = dly.alloc + DLY_ALLOC_ITEMS; + + DLY_WUNLOCK(); + + /* No spare room, need to increase */ + dd = malloc(sizeof(struct dly_dispatcher) * alloc, M_TEMP, + M_ZERO|M_WAITOK); + + DLY_WLOCK(); + if (dly.alloc >= alloc) { + /* Lost the race, try again */ + DLY_WUNLOCK(); + free(dd, M_TEMP); + goto again; + } + + memcpy(dly.index, dd, sizeof(struct dly_dispatcher) * dly.alloc); + tmp = dly.index; + dly.index = dd; + dly.alloc = alloc; + i = dly.count++; + dly.index[i] = *dld; + DLY_WUNLOCK(); + + free(tmp, M_TEMP); + + return (i); +} + +/* + * Checks if given netisr queue item is of type which + * needs to be unregistered. + */ +static int +dly_scan_unregistered(struct mbuf *m, void *_data) +{ + struct m_tag *dtag; + struct dly_item *item; + int i; + + i = *((int *)(intptr_t)_data); + + if ((dtag = m_tag_find(m, PACKET_TAG_DISPATCH_INFO, NULL)) != NULL) { + item = (struct dly_item *)(dtag + 1); + if (item->type == i) + return (1); + } + + return (0); +} + +/* + * Unregisters slow handler registered previously by dly_register(). + * Caller needs to ensure that no new items of given type can be queued + * prior calling this function. + */ +void +dly_unregister(int dtype) +{ + + netisr_scan(NETISR_SLOWPATH, dly_scan_unregistered, &dtype); + + DLY_WLOCK(); + if (dtype < 0 || dtype >= dly.alloc || dly.index[dtype].name == NULL) { + DLY_WUNLOCK(); + return; + } + + KASSERT(dly.index[dtype].name != NULL, + ("%s: unresigstering non-existend protocol %d", __func__, dtype)); + + memset(&dly.index[dtype], 0, sizeof(struct dly_dispatcher)); + DLY_WUNLOCK(); +} + + +static void +dly_ifdetach(void *arg __unused, struct ifnet *ifp) +{ + + netisr_scan(NETISR_SLOWPATH, dly_scan_ifp, ifp); +} + +static struct netisr_handler dly_nh = { + .nh_name = "slow", + .nh_handler = dly_dispatch_item, + .nh_proto = NETISR_SLOWPATH, + .nh_policy = NETISR_POLICY_SOURCE, +}; + +static void +dly_init(__unused void *arg) +{ + + memset(&dly, 0, sizeof(dly)); + dly.index = malloc(sizeof(struct dly_dispatcher) * DLY_ALLOC_ITEMS, + M_TEMP, M_ZERO | M_WAITOK); + dly.alloc = DLY_ALLOC_ITEMS; + dly.count = 1; + + DLY_LOCK_INIT(); + + netisr_register(&dly_nh); + ifdetach_tag = EVENTHANDLER_REGISTER(ifnet_departure_event, + dly_ifdetach, NULL, EVENTHANDLER_PRI_ANY); +} + +/* Exactly after netisr */ +SYSINIT(dly_init, SI_SUB_SOFTINTR, SI_ORDER_SECOND, dly_init, NULL); + Index: sys/net/netisr.h =================================================================== --- sys/net/netisr.h +++ sys/net/netisr.h @@ -59,6 +59,7 @@ #define NETISR_EPAIR 8 /* if_epair(4) */ #define NETISR_IP_DIRECT 9 /* direct-dispatch IPv4 */ #define NETISR_IPV6_DIRECT 10 /* direct-dispatch IPv6 */ +#define NETISR_SLOWPATH 11 /* delayed dispatch */ /* * Protocol ordering and affinity policy constants. See the detailed @@ -176,6 +177,7 @@ */ struct mbuf; typedef void netisr_handler_t(struct mbuf *m); +typedef int netisr_scan_t(struct mbuf *m, void *); typedef struct mbuf *netisr_m2cpuid_t(struct mbuf *m, uintptr_t source, u_int *cpuid); typedef struct mbuf *netisr_m2flow_t(struct mbuf *m, uintptr_t source); @@ -210,6 +212,7 @@ void netisr_register(const struct netisr_handler *nhp); int netisr_setqlimit(const struct netisr_handler *nhp, u_int qlimit); void netisr_unregister(const struct netisr_handler *nhp); +int netisr_scan(u_int proto, netisr_scan_t *, void *); /* * Process a packet destined for a protocol, and attempt direct dispatch. Index: sys/net/netisr.c =================================================================== --- sys/net/netisr.c +++ sys/net/netisr.c @@ -551,6 +551,85 @@ } /* + * Scan workqueue and delete mbufs pointed by handler. + */ +static int +netisr_scan_workqueue(struct netisr_work *npwp, netisr_scan_t *scan_f, + void *data) +{ + struct mbuf *m, *m_prev; + int deleted; + + deleted = 0; + m_prev = NULL; + m = npwp->nw_head; + while (m != NULL) { + if (scan_f(m, data) == 0) { + m_prev = m; + m = m->m_nextpkt; + continue; + } + + /* Handler requested item deletion */ + if (m_prev == NULL) + npwp->nw_head = m->m_nextpkt; + else + m_prev->m_nextpkt = m->m_nextpkt; + + if (m->m_nextpkt == NULL) + npwp->nw_tail = m_prev; + + npwp->nw_len--; + m_freem(m); + deleted++; + + if (m_prev == NULL) + m = npwp->nw_head; + else + m = m_prev->m_nextpkt; + } + + return (deleted); +} + +int +netisr_scan(unsigned int proto, netisr_scan_t *scan_f, void *data) +{ +#ifdef NETISR_LOCKING + struct rm_priotracker tracker; +#endif + struct netisr_proto *np; + struct netisr_workstream *nwsp; + struct netisr_work *npwp; + unsigned int i; + int deleted; + +#ifdef NETISR_LOCKING + NETISR_RLOCK(&tracker); +#endif + + deleted = 0; + + KASSERT(scan_f != NULL, ("%s: scan function is NULL", __func__)); + + np = &netisr_proto[proto]; + + CPU_FOREACH(i) { + nwsp = DPCPU_ID_PTR(i, nws); + npwp = &nwsp->nws_work[proto]; + NWS_LOCK(nwsp); + deleted += netisr_scan_workqueue(npwp, scan_f, data); + NWS_UNLOCK(nwsp); + } + +#ifdef NETISR_LOCKING + NETISR_RUNLOCK(&tracker); +#endif + + return (deleted); +} + +/* * Drain all packets currently held in a particular protocol work queue. */ static void Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -421,6 +421,8 @@ const struct sockaddr *, u_char *, struct llentry **); /* nd6_nbr.c */ +void nd6_nbr_init(void); +void nd6_nbr_destroy(void); void nd6_na_input(struct mbuf *, int, int); void nd6_na_output(struct ifnet *, const struct in6_addr *, const struct in6_addr *, u_long, int, struct sockaddr *); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -153,6 +153,8 @@ callout_init(&V_nd6_slowtimo_ch, 0); callout_reset(&V_nd6_slowtimo_ch, ND6_SLOWTIMER_INTERVAL * hz, nd6_slowtimo, curvnet); + + nd6_nbr_init(); } #ifdef VIMAGE @@ -160,6 +162,7 @@ nd6_destroy() { + nd6_nbr_destroy(); callout_drain(&V_nd6_slowtimo_ch); callout_drain(&V_nd6_timer_ch); } @@ -500,9 +503,7 @@ if (ln->la_asked < V_nd6_mmaxtries) { ln->la_asked++; nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); - LLE_WUNLOCK(ln); nd6_ns_output(ifp, NULL, dst, ln, 0); - LLE_WLOCK(ln); } else { struct mbuf *m = ln->la_hold; if (m) { @@ -547,9 +548,7 @@ ln->la_asked = 1; ln->ln_state = ND6_LLINFO_PROBE; nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); - LLE_WUNLOCK(ln); nd6_ns_output(ifp, dst, dst, ln, 0); - LLE_WLOCK(ln); } else { ln->ln_state = ND6_LLINFO_STALE; /* XXX */ nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz); @@ -559,9 +558,7 @@ if (ln->la_asked < V_nd6_umaxtries) { ln->la_asked++; nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); - LLE_WUNLOCK(ln); nd6_ns_output(ifp, dst, dst, ln, 0); - LLE_WLOCK(ln); } else { EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED); (void)nd6_free(ln, 0); Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -74,6 +74,7 @@ #include #include #include +#include #define SDL(s) ((struct sockaddr_dl *)s) @@ -87,6 +88,7 @@ static void nd6_dad_na_input(struct ifaddr *); static void nd6_na_output_fib(struct ifnet *, const struct in6_addr *, const struct in6_addr *, u_long, int, struct sockaddr *, u_int); +static int nd6_ns_output2(struct mbuf *, int, uintptr_t, struct ifnet *); static VNET_DEFINE(int, dad_ignore_ns) = 0; /* ignore NS in DAD - specwise incorrect */ @@ -95,6 +97,29 @@ #define V_dad_ignore_ns VNET(dad_ignore_ns) #define V_dad_maxtry VNET(dad_maxtry) +static struct dly_dispatcher dly_d = { + .name = "nd6_ns", + .dly_dispatch = nd6_ns_output2, +}; + +static int nd6_dlyid; + +void +nd6_nbr_init() +{ + + if (IS_DEFAULT_VNET(curvnet)) + nd6_dlyid = dly_register(&dly_d); +} + +void +nd6_nbr_destroy() +{ + + if (IS_DEFAULT_VNET(curvnet)) + dly_unregister(nd6_dlyid); +} + /* * Input a Neighbor Solicitation Message. * @@ -368,11 +393,34 @@ m_freem(m); } +static int +nd6_ns_output2(struct mbuf *m, int dad, uintptr_t _data, struct ifnet *ifp) +{ + struct ip6_moptions im6o; + + if (m->m_flags & M_MCAST) { + im6o.im6o_multicast_ifp = ifp; + im6o.im6o_multicast_hlim = 255; + im6o.im6o_multicast_loop = 0; + } + + /* Zero ingress interface not to fool PFIL consumers */ + m->m_pkthdr.rcvif = NULL; + + ip6_output(m, NULL, NULL, dad ? IPV6_UNSPECSRC : 0, &im6o, NULL, NULL); + icmp6_ifstat_inc(ifp, ifs6_out_msg); + icmp6_ifstat_inc(ifp, ifs6_out_neighborsolicit); + ICMP6STAT_INC(icp6s_outhist[ND_NEIGHBOR_SOLICIT]); + + return (0); +} + /* * Output a Neighbor Solicitation Message. Caller specifies: * - ICMP6 header source IP6 address * - ND6 header target IP6 address * - ND6 header source datalink address + * Note llentry has to be locked if specified * * Based on RFC 2461 * Based on RFC 2462 (duplicate address detection) @@ -388,11 +436,9 @@ struct m_tag *mtag; struct ip6_hdr *ip6; struct nd_neighbor_solicit *nd_ns; - struct ip6_moptions im6o; int icmp6len; int maxlen; caddr_t mac; - struct route_in6 ro; if (IN6_IS_ADDR_MULTICAST(taddr6)) return; @@ -415,13 +461,8 @@ if (m == NULL) return; - bzero(&ro, sizeof(ro)); - if (daddr6 == NULL || IN6_IS_ADDR_MULTICAST(daddr6)) { m->m_flags |= M_MCAST; - im6o.im6o_multicast_ifp = ifp; - im6o.im6o_multicast_hlim = 255; - im6o.im6o_multicast_loop = 0; } icmp6len = sizeof(*nd_ns); @@ -470,7 +511,6 @@ hsrc = NULL; if (ln != NULL) { - LLE_RLOCK(ln); if (ln->la_hold != NULL) { struct ip6_hdr *hip6; /* hold ip6 */ @@ -485,7 +525,6 @@ hsrc = &hip6->ip6_src; } } - LLE_RUNLOCK(ln); } if (hsrc && (ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp, hsrc)) != NULL) { @@ -504,7 +543,7 @@ oifp = ifp; error = in6_selectsrc(&dst_sa, NULL, - NULL, &ro, NULL, &oifp, &src_in); + NULL, NULL, NULL, &oifp, &src_in); if (error) { char ip6buf[INET6_ADDRSTRLEN]; nd6log((LOG_DEBUG, @@ -574,20 +613,16 @@ m_tag_prepend(m, mtag); } - ip6_output(m, NULL, &ro, dad ? IPV6_UNSPECSRC : 0, &im6o, NULL, NULL); - icmp6_ifstat_inc(ifp, ifs6_out_msg); - icmp6_ifstat_inc(ifp, ifs6_out_neighborsolicit); - ICMP6STAT_INC(icp6s_outhist[ND_NEIGHBOR_SOLICIT]); - - /* We don't cache this route. */ - RO_RTFREE(&ro); + if (ln == NULL) + nd6_ns_output2(m, dad, 0, ifp); + else { + m->m_pkthdr.rcvif = ifp; /* Save VNET */ + dly_queue(nd6_dlyid, m, dad, 0, ifp); + } return; bad: - if (ro.ro_rt) { - RTFREE(ro.ro_rt); - } m_freem(m); return; } Index: sys/sys/mbuf.h =================================================================== --- sys/sys/mbuf.h +++ sys/sys/mbuf.h @@ -1051,6 +1051,7 @@ #define PACKET_TAG_CARP 28 /* CARP info */ #define PACKET_TAG_IPSEC_NAT_T_PORTS 29 /* two uint16_t */ #define PACKET_TAG_ND_OUTGOING 30 /* ND outgoing */ +#define PACKET_TAG_DISPATCH_INFO 31 /* Netist slow dispatch */ /* Specific cookies and tags. */