/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include typedef boolean_t napplyfn_t(neti_stack_t *, void *); static void *neti_stack_init(netstackid_t stackid, netstack_t *ns); static void neti_stack_fini(netstackid_t stackid, void *arg); static net_instance_int_t *net_instance_int_create(net_instance_t *nin, net_instance_int_t *parent); static void neti_stack_shutdown(netstackid_t stackid, void *arg); static void net_instance_int_free(net_instance_int_t *nini); static boolean_t neti_stack_apply_create(neti_stack_t *, void *); static boolean_t neti_stack_apply_destroy(neti_stack_t *, void *); static boolean_t neti_stack_apply_shutdown(neti_stack_t *, void *); static void neti_apply_all_instances(neti_stack_t *, napplyfn_t *); static void neti_apply_all_stacks(void *, napplyfn_t *); static boolean_t wait_for_nini_inprogress(neti_stack_t *, net_instance_int_t *, uint32_t); static nini_head_t neti_instance_list; static neti_stack_head_t neti_stack_list; static kmutex_t neti_stack_lock; void neti_init() { mutex_init(&neti_stack_lock, NULL, MUTEX_DRIVER, NULL); LIST_INIT(&neti_instance_list); LIST_INIT(&neti_stack_list); /* * We want to be informed each time a netstack is created or * destroyed in the kernel. */ netstack_register(NS_NETI, neti_stack_init, neti_stack_shutdown, neti_stack_fini); } void neti_fini() { ASSERT(LIST_EMPTY(&neti_instance_list)); ASSERT(LIST_EMPTY(&neti_stack_list)); netstack_unregister(NS_NETI); mutex_destroy(&neti_stack_lock); } /* * Initialize the neti stack instance. Because this is called out of the * netstack framework, it is not possible for it to be called twice with * the same values for (stackid,ns). The same also applies to the other * two functions used with netstack_register: neti_stack_shutdown and * neti_stack_fini. */ static void * neti_stack_init(netstackid_t stackid, netstack_t *ns) { net_instance_int_t *dup; net_instance_int_t *n; neti_stack_t *nts; nts = kmem_zalloc(sizeof (*nts), KM_SLEEP); LIST_INIT(&nts->nts_instances); nts->nts_id = (netid_t)stackid; nts->nts_stackid = stackid; nts->nts_netstack = ns; nts->nts_zoneid = netstackid_to_zoneid(stackid); nts->nts_flags = NSF_ZONE_CREATE; cv_init(&nts->nts_cv, NULL, CV_DRIVER, NULL); mutex_init(&nts->nts_lock, NULL, MUTEX_DRIVER, NULL); mutex_enter(&neti_stack_lock); LIST_INSERT_HEAD(&neti_stack_list, nts, nts_next); LIST_FOREACH(n, &neti_instance_list, nini_next) { /* * This function returns with the NSS_CREATE_NEEDED flag * set in "dup", so it is adequately prepared for the * upcoming apply. */ dup = net_instance_int_create(n->nini_instance, n); mutex_enter(&nts->nts_lock); LIST_INSERT_HEAD(&nts->nts_instances, dup, nini_next); mutex_exit(&nts->nts_lock); } neti_apply_all_instances(nts, neti_stack_apply_create); mutex_enter(&nts->nts_lock); nts->nts_flags &= ~NSF_ZONE_CREATE; mutex_exit(&nts->nts_lock); mutex_exit(&neti_stack_lock); return (nts); } /* * Run the shutdown for all of the hooks. */ /*ARGSUSED*/ static void neti_stack_shutdown(netstackid_t stackid, void *arg) { neti_stack_t *nts = arg; net_instance_int_t *n; struct net_data *nd; ASSERT(nts != NULL); mutex_enter(&neti_stack_lock); mutex_enter(&nts->nts_lock); /* * Walk through all of the protocol stacks and mark them as shutting * down. */ LIST_FOREACH(nd, &nts->nts_netd_head, netd_list) { nd->netd_condemned = 1; } /* * Now proceed to see which callbacks are waiting to hear about the * impending shutdown... */ LIST_FOREACH(n, &nts->nts_instances, nini_next) { if (n->nini_instance->nin_shutdown == NULL) { /* * If there is no shutdown function registered, * fake that we have completed it. */ n->nini_flags |= NSS_SHUTDOWN_COMPLETED; continue; } /* * We need to ensure that we don't try and shutdown something * that is already in the process of being shutdown or * destroyed. If it is still being created, that's ok, the * shtudown flag is added to the mix of things to do. */ if ((n->nini_flags & (NSS_DESTROY_ALL|NSS_SHUTDOWN_ALL)) == 0) n->nini_flags |= NSS_SHUTDOWN_NEEDED; } nts->nts_flags |= NSF_ZONE_SHUTDOWN; mutex_exit(&nts->nts_lock); neti_apply_all_instances(nts, neti_stack_apply_shutdown); mutex_enter(&nts->nts_lock); nts->nts_netstack = NULL; nts->nts_flags &= ~NSF_ZONE_SHUTDOWN; mutex_exit(&nts->nts_lock); mutex_exit(&neti_stack_lock); ASSERT(nts != NULL); } /* * Free the neti stack instance. * This function relies on the netstack framework only calling the _destroy * callback once for each stackid. The netstack framework also provides us * with assurance that nobody else will be doing any work (_create, _shutdown) * on it, so there is no need to set and use flags to guard against * simultaneous execution (ie. no need to set NSF_CLOSING.) * What is required, however, is to make sure that we don't corrupt the * list of neti_stack_t's for other code that walks it. */ /*ARGSUSED*/ static void neti_stack_fini(netstackid_t stackid, void *arg) { neti_stack_t *nts = arg; net_instance_int_t *n; struct net_data *nd; mutex_enter(&neti_stack_lock); mutex_enter(&nts->nts_lock); LIST_REMOVE(nts, nts_next); /* * Walk through all of the protocol stacks and mark them as being * destroyed. */ LIST_FOREACH(nd, &nts->nts_netd_head, netd_list) { nd->netd_condemned = 2; } LIST_FOREACH(n, &nts->nts_instances, nini_next) { ASSERT((n->nini_flags & NSS_SHUTDOWN_ALL) != 0); if ((n->nini_flags & NSS_DESTROY_ALL) == 0) n->nini_flags |= NSS_DESTROY_NEEDED; } mutex_exit(&nts->nts_lock); neti_apply_all_instances(nts, neti_stack_apply_destroy); while (!LIST_EMPTY(&nts->nts_instances)) { n = LIST_FIRST(&nts->nts_instances); LIST_REMOVE(n, nini_next); net_instance_int_free(n); } mutex_exit(&neti_stack_lock); ASSERT(LIST_EMPTY(&nts->nts_netd_head)); mutex_destroy(&nts->nts_lock); cv_destroy(&nts->nts_cv); kmem_free(nts, sizeof (*nts)); } static net_instance_int_t * net_instance_int_create(net_instance_t *nin, net_instance_int_t *parent) { net_instance_int_t *nini; nini = kmem_zalloc(sizeof (net_instance_int_t), KM_SLEEP); nini->nini_instance = nin; nini->nini_parent = parent; if (parent != NULL) { /* * If the parent pointer is non-NULL then we take that as * an indication that the net_instance_int_t is being * created for an active instance and there will expect * the create function to be called. In contrast, if * parent is NULL then this code assumes the object is * being prepared for insertion onto the master list of * callbacks to be called when an instance is created, etc. */ parent->nini_ref++; nini->nini_flags |= NSS_CREATE_NEEDED; } cv_init(&nini->nini_cv, NULL, CV_DRIVER, NULL); return (nini); } /* * Free'ing of a net_instance_int_t is only to be done when we know nobody * else has is using it. For both parents and clones, this is indicated by * nini_ref being greater than 0, however, nini_ref is managed differently * for its two uses. For parents, nini_ref is increased when a new clone is * created and it is decremented here. For clones, nini_ref is adjusted by * code elsewhere (e.g. in neti_stack_apply_*) and is not changed here. */ static void net_instance_int_free(net_instance_int_t *nini) { /* * This mutex guards the use of nini_ref. */ ASSERT(mutex_owned(&neti_stack_lock)); /* * For 'parent' structures, nini_ref will drop to 0 when * the last clone has been free'd... but for clones, it * is possible for nini_ref to be non-zero if we get in * here when all the locks have been given up to execute * a callback or wait_for_nini_inprogress. In that case, * we do not want to free the structure and just indicate * that it is on the "doomed" list, thus we set the * condemned flag. */ if (nini->nini_parent != NULL) { if (nini->nini_ref > 0) nini->nini_condemned = B_TRUE; nini->nini_parent->nini_ref--; if (nini->nini_parent->nini_ref == 0) net_instance_int_free(nini->nini_parent); nini->nini_parent = NULL; } if (nini->nini_ref == 0) { cv_destroy(&nini->nini_cv); kmem_free(nini, sizeof (*nini)); } } net_instance_t * net_instance_alloc(const int version) { net_instance_t *nin; if (version != NETINFO_VERSION) return (NULL); nin = kmem_zalloc(sizeof (net_instance_t), KM_SLEEP); nin->nin_version = version; return (nin); } void net_instance_free(net_instance_t *nin) { kmem_free(nin, sizeof (*nin)); } int net_instance_register(net_instance_t *nin) { net_instance_int_t *parent; net_instance_int_t *tmp; neti_stack_t *nts; ASSERT(nin->nin_name != NULL); if (nin->nin_create == NULL || nin->nin_destroy == NULL) return (DDI_FAILURE); mutex_enter(&neti_stack_lock); /* * Search for duplicate, either on the global list or on any * of the known instances. */ LIST_FOREACH(tmp, &neti_instance_list, nini_next) { if (strcmp(nin->nin_name, tmp->nini_instance->nin_name) == 0) { mutex_exit(&neti_stack_lock); return (DDI_FAILURE); } } /* * Now insert and activate. */ parent = net_instance_int_create(nin, NULL); ASSERT(parent != NULL); LIST_INSERT_HEAD(&neti_instance_list, parent, nini_next); LIST_FOREACH(nts, &neti_stack_list, nts_next) { mutex_enter(&nts->nts_lock); /* * If shutdown of the zone has begun then do not add a new * instance of the object being registered. */ if ((nts->nts_flags & NSF_ZONE_SHUTDOWN) || (nts->nts_netstack == NULL)) { mutex_exit(&nts->nts_lock); continue; } /* * This function returns with the NSS_CREATE_NEEDED flag * set in "dup", so it is adequately prepared for the * upcoming apply. */ tmp = net_instance_int_create(nin, parent); ASSERT(tmp != NULL); LIST_INSERT_HEAD(&nts->nts_instances, tmp, nini_next); mutex_exit(&nts->nts_lock); } neti_apply_all_stacks(parent, neti_stack_apply_create); mutex_exit(&neti_stack_lock); return (DDI_SUCCESS); } /* * While net_instance_register() isn't likely to be racing against itself, * net_instance_unregister() can be entered from various directions that * can compete: shutdown of a zone, unloading of a module (and it calling * _unregister() as part of that) and the module doing an _unregister() * anyway. */ int net_instance_unregister(net_instance_t *nin) { net_instance_int_t *parent; net_instance_int_t *tmp; neti_stack_t *nts; mutex_enter(&neti_stack_lock); LIST_FOREACH(tmp, &neti_instance_list, nini_next) { if (strcmp(tmp->nini_instance->nin_name, nin->nin_name) == 0) { LIST_REMOVE(tmp, nini_next); break; } } if (tmp == NULL) { mutex_exit(&neti_stack_lock); return (DDI_FAILURE); } parent = tmp; LIST_FOREACH(nts, &neti_stack_list, nts_next) { mutex_enter(&nts->nts_lock); LIST_FOREACH(tmp, &nts->nts_instances, nini_next) { if (tmp->nini_parent != parent) continue; /* * Netstack difference: * In netstack.c, there is a check for * NSS_CREATE_COMPLETED before setting the other * _NEEDED flags. If we consider that a list * member must always have at least the _CREATE_NEEDED * flag set and that wait_for_nini_inprogress will * also wait for that flag to be cleared in both of * the shutdown and destroy apply functions. * * It is possible to optimize out the case where * all three _NEEDED flags are set to being able * to pretend everything has been done and just * set all three _COMPLETE flags. This makes a * special case that we then need to consider in * other locations, so for the sake of simplicity, * we leave it as it is. */ if ((tmp->nini_flags & NSS_SHUTDOWN_ALL) == 0) tmp->nini_flags |= NSS_SHUTDOWN_NEEDED; if ((tmp->nini_flags & NSS_DESTROY_ALL) == 0) tmp->nini_flags |= NSS_DESTROY_NEEDED; break; } mutex_exit(&nts->nts_lock); } /* * Each of these functions ensures that the requisite _COMPLETED * flag is present before calling the apply function. So we are * guaranteed to have NSS_CREATE_COMPLETED|NSS_SHUTDOWN_COMPLETED * both set after the first call here and when the second completes, * NSS_DESTROY_COMPLETED is also set. */ neti_apply_all_stacks(parent, neti_stack_apply_shutdown); neti_apply_all_stacks(parent, neti_stack_apply_destroy); /* * Remove the instance callback information from each stack. */ LIST_FOREACH(nts, &neti_stack_list, nts_next) { mutex_enter(&nts->nts_lock); LIST_FOREACH(tmp, &nts->nts_instances, nini_next) { if ((tmp->nini_parent == parent) && (tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) && (tmp->nini_flags & NSS_DESTROY_COMPLETED)) { /* * There should only be one entry that has a * matching nini_parent so there is no need to * worry about continuing a loop where we are * free'ing the structure holding the 'next' * pointer. */ LIST_REMOVE(tmp, nini_next); net_instance_int_free(tmp); break; } } mutex_exit(&nts->nts_lock); } mutex_exit(&neti_stack_lock); return (DDI_SUCCESS); } static void neti_apply_all_instances(neti_stack_t *nts, napplyfn_t *applyfn) { net_instance_int_t *n; ASSERT(mutex_owned(&neti_stack_lock)); n = LIST_FIRST(&nts->nts_instances); while (n != NULL) { if ((applyfn)(nts, n->nini_parent)) { /* Lock dropped - restart at head */ n = LIST_FIRST(&nts->nts_instances); } else { n = LIST_NEXT(n, nini_next); } } } static void neti_apply_all_stacks(void *parent, napplyfn_t *applyfn) { neti_stack_t *nts; ASSERT(mutex_owned(&neti_stack_lock)); nts = LIST_FIRST(&neti_stack_list); while (nts != NULL) { /* * This function differs, in that it doesn't have a call to * a "wait_creator" call, from the zsd/netstack code. The * waiting is pushed into the apply functions which cause * the waiting to be done in wait_for_nini_progress with * the passing in of cmask. */ if ((applyfn)(nts, parent)) { /* Lock dropped - restart at head */ nts = LIST_FIRST(&neti_stack_list); } else { nts = LIST_NEXT(nts, nts_next); } } } static boolean_t neti_stack_apply_create(neti_stack_t *nts, void *parent) { void *result; boolean_t dropped = B_FALSE; net_instance_int_t *tmp; net_instance_t *nin; ASSERT(parent != NULL); ASSERT(mutex_owned(&neti_stack_lock)); mutex_enter(&nts->nts_lock); LIST_FOREACH(tmp, &nts->nts_instances, nini_next) { if (tmp->nini_parent == parent) break; } if (tmp == NULL) { mutex_exit(&nts->nts_lock); return (dropped); } tmp->nini_ref++; if (wait_for_nini_inprogress(nts, tmp, 0)) dropped = B_TRUE; if ((tmp->nini_flags & NSS_CREATE_NEEDED) && !tmp->nini_condemned) { nin = tmp->nini_instance; tmp->nini_flags &= ~NSS_CREATE_NEEDED; tmp->nini_flags |= NSS_CREATE_INPROGRESS; DTRACE_PROBE2(neti__stack__create__inprogress, neti_stack_t *, nts, net_instance_int_t *, tmp); mutex_exit(&nts->nts_lock); mutex_exit(&neti_stack_lock); dropped = B_TRUE; ASSERT(tmp->nini_created == NULL); ASSERT(nin->nin_create != NULL); DTRACE_PROBE2(neti__stack__create__start, netstackid_t, nts->nts_id, neti_stack_t *, nts); result = (nin->nin_create)(nts->nts_id); DTRACE_PROBE2(neti__stack__create__end, void *, result, neti_stack_t *, nts); ASSERT(result != NULL); mutex_enter(&neti_stack_lock); mutex_enter(&nts->nts_lock); tmp->nini_created = result; tmp->nini_flags &= ~NSS_CREATE_INPROGRESS; tmp->nini_flags |= NSS_CREATE_COMPLETED; cv_broadcast(&tmp->nini_cv); DTRACE_PROBE2(neti__stack__create__completed, neti_stack_t *, nts, net_instance_int_t *, tmp); } tmp->nini_ref--; if (tmp->nini_condemned) { net_instance_int_free(tmp); dropped = B_TRUE; } mutex_exit(&nts->nts_lock); return (dropped); } static boolean_t neti_stack_apply_shutdown(neti_stack_t *nts, void *parent) { boolean_t dropped = B_FALSE; net_instance_int_t *tmp; net_instance_t *nin; ASSERT(parent != NULL); ASSERT(mutex_owned(&neti_stack_lock)); mutex_enter(&nts->nts_lock); LIST_FOREACH(tmp, &nts->nts_instances, nini_next) { if (tmp->nini_parent == parent) break; } if (tmp == NULL) { mutex_exit(&nts->nts_lock); return (dropped); } ASSERT((tmp->nini_flags & NSS_SHUTDOWN_ALL) != 0); tmp->nini_ref++; if (wait_for_nini_inprogress(nts, tmp, NSS_CREATE_NEEDED)) dropped = B_TRUE; nin = tmp->nini_instance; if (nin->nin_shutdown == NULL) { /* * If there is no shutdown function, fake having completed it. */ if (tmp->nini_flags & NSS_SHUTDOWN_NEEDED) { tmp->nini_flags &= ~NSS_SHUTDOWN_NEEDED; tmp->nini_flags |= NSS_SHUTDOWN_COMPLETED; } tmp->nini_ref--; if (tmp->nini_condemned) { net_instance_int_free(tmp); dropped = B_TRUE; } mutex_exit(&nts->nts_lock); return (dropped); } if ((tmp->nini_flags & NSS_SHUTDOWN_NEEDED) && !tmp->nini_condemned) { ASSERT((tmp->nini_flags & NSS_CREATE_COMPLETED) != 0); tmp->nini_flags &= ~NSS_SHUTDOWN_NEEDED; tmp->nini_flags |= NSS_SHUTDOWN_INPROGRESS; DTRACE_PROBE2(neti__stack__shutdown__inprogress, neti_stack_t *, nts, net_instance_int_t *, tmp); mutex_exit(&nts->nts_lock); mutex_exit(&neti_stack_lock); dropped = B_TRUE; ASSERT(nin->nin_shutdown != NULL); DTRACE_PROBE2(neti__stack__shutdown__start, netstackid_t, nts->nts_id, neti_stack_t *, nts); (nin->nin_shutdown)(nts->nts_id, tmp->nini_created); DTRACE_PROBE1(neti__stack__shutdown__end, neti_stack_t *, nts); mutex_enter(&neti_stack_lock); mutex_enter(&nts->nts_lock); tmp->nini_flags &= ~NSS_SHUTDOWN_INPROGRESS; tmp->nini_flags |= NSS_SHUTDOWN_COMPLETED; cv_broadcast(&tmp->nini_cv); DTRACE_PROBE2(neti__stack__shutdown__completed, neti_stack_t *, nts, net_instance_int_t *, tmp); } ASSERT((tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) != 0); tmp->nini_ref--; if (tmp->nini_condemned) { net_instance_int_free(tmp); dropped = B_TRUE; } mutex_exit(&nts->nts_lock); return (dropped); } static boolean_t neti_stack_apply_destroy(neti_stack_t *nts, void *parent) { boolean_t dropped = B_FALSE; net_instance_int_t *tmp; net_instance_t *nin; ASSERT(parent != NULL); ASSERT(mutex_owned(&neti_stack_lock)); mutex_enter(&nts->nts_lock); LIST_FOREACH(tmp, &nts->nts_instances, nini_next) { if (tmp->nini_parent == parent) break; } if (tmp == NULL) { mutex_exit(&nts->nts_lock); return (dropped); } tmp->nini_ref++; /* * We pause here so that when we continue we know that we're the * only one doing anything active with this node. */ if (wait_for_nini_inprogress(nts, tmp, NSS_CREATE_NEEDED|NSS_SHUTDOWN_NEEDED)) dropped = B_TRUE; if ((tmp->nini_flags & NSS_DESTROY_NEEDED) && !tmp->nini_condemned) { ASSERT((tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) != 0); nin = tmp->nini_instance; tmp->nini_flags &= ~NSS_DESTROY_NEEDED; tmp->nini_flags |= NSS_DESTROY_INPROGRESS; DTRACE_PROBE2(neti__stack__destroy__inprogress, neti_stack_t *, nts, net_instance_int_t *, tmp); mutex_exit(&nts->nts_lock); mutex_exit(&neti_stack_lock); dropped = B_TRUE; ASSERT(nin->nin_destroy != NULL); DTRACE_PROBE2(neti__stack__destroy__start, netstackid_t, nts->nts_id, neti_stack_t *, nts); (nin->nin_destroy)(nts->nts_id, tmp->nini_created); DTRACE_PROBE1(neti__stack__destroy__end, neti_stack_t *, nts); mutex_enter(&neti_stack_lock); mutex_enter(&nts->nts_lock); tmp->nini_flags &= ~NSS_DESTROY_INPROGRESS; tmp->nini_flags |= NSS_DESTROY_COMPLETED; cv_broadcast(&tmp->nini_cv); DTRACE_PROBE2(neti__stack__destroy__completed, neti_stack_t *, nts, net_instance_int_t *, tmp); } tmp->nini_ref--; if (tmp->nini_condemned) { net_instance_int_free(tmp); dropped = B_TRUE; } mutex_exit(&nts->nts_lock); return (dropped); } static boolean_t wait_for_nini_inprogress(neti_stack_t *nts, net_instance_int_t *nini, uint32_t cmask) { boolean_t dropped = B_FALSE; ASSERT(mutex_owned(&neti_stack_lock)); while (nini->nini_flags & (NSS_ALL_INPROGRESS|cmask)) { DTRACE_PROBE2(neti__wait__nini__inprogress, neti_stack_t *, nts, net_instance_int_t *, nini); dropped = B_TRUE; mutex_exit(&neti_stack_lock); cv_wait(&nini->nini_cv, &nts->nts_lock); /* First drop netstack_lock to preserve order */ mutex_exit(&nts->nts_lock); DTRACE_PROBE2(wait__nini__inprogress__pause, neti_stack_t *, nts, net_instance_int_t *, nini); mutex_enter(&neti_stack_lock); mutex_enter(&nts->nts_lock); } DTRACE_PROBE2(neti__wait__nini__inprogress__complete, neti_stack_t *, nts, net_instance_int_t *, nini); return (dropped); } /* ======================================================================= */ netid_t net_zoneidtonetid(zoneid_t zoneid) { neti_stack_t *nts; mutex_enter(&neti_stack_lock); LIST_FOREACH(nts, &neti_stack_list, nts_next) { if (nts->nts_zoneid == zoneid) { mutex_exit(&neti_stack_lock); return (nts->nts_id); } } mutex_exit(&neti_stack_lock); return (-1); } zoneid_t net_getzoneidbynetid(netid_t netid) { neti_stack_t *nts; mutex_enter(&neti_stack_lock); LIST_FOREACH(nts, &neti_stack_list, nts_next) { if (nts->nts_id == netid) { mutex_exit(&neti_stack_lock); return (nts->nts_zoneid); } } mutex_exit(&neti_stack_lock); return (-1); } netstackid_t net_getnetstackidbynetid(netid_t netid) { neti_stack_t *nts; mutex_enter(&neti_stack_lock); LIST_FOREACH(nts, &neti_stack_list, nts_next) { if (nts->nts_id == netid) { mutex_exit(&neti_stack_lock); return (nts->nts_stackid); } } mutex_exit(&neti_stack_lock); return (-1); } netid_t net_getnetidbynetstackid(netstackid_t netstackid) { neti_stack_t *nts; mutex_enter(&neti_stack_lock); LIST_FOREACH(nts, &neti_stack_list, nts_next) { if (nts->nts_stackid == netstackid) { mutex_exit(&neti_stack_lock); return (nts->nts_id); } } mutex_exit(&neti_stack_lock); return (-1); } neti_stack_t * net_getnetistackbyid(netid_t netid) { neti_stack_t *nts; mutex_enter(&neti_stack_lock); LIST_FOREACH(nts, &neti_stack_list, nts_next) { if (nts->nts_id == netid) { mutex_exit(&neti_stack_lock); return (nts); } } mutex_exit(&neti_stack_lock); return (NULL); } int net_instance_notify_register(netid_t netid, hook_notify_fn_t callback, void *arg) { return (hook_stack_notify_register(net_getnetstackidbynetid(netid), callback, arg)); } int net_instance_notify_unregister(netid_t netid, hook_notify_fn_t callback) { return (hook_stack_notify_unregister(net_getnetstackidbynetid(netid), callback)); }