/* * 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. * * Copyright 2018 Joyent, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include /* * This file provides kernel hook framework. */ static struct modldrv modlmisc = { &mod_miscops, /* drv_modops */ "Hooks Interface v1.0", /* drv_linkinfo */ }; static struct modlinkage modlinkage = { MODREV_1, /* ml_rev */ &modlmisc, /* ml_linkage */ NULL }; static const char *hook_hintvalue_none = ""; /* * How it works. * ============= * Use of the hook framework here is tied up with zones - when a new zone * is created, we create a new hook_stack_t and are open to business for * allowing new hook families and their events. * * A consumer of these hooks is expected to operate in this fashion: * 1) call hook_family_add() to create a new family of hooks. It is a * current requirement that this call must be made with the value * returned from hook_stack_init, by way of infrastructure elsewhere. * 2) add events to the registered family with calls to hook_event_add. * * At this point, the structures in place should be open to others to * add hooks to the event or add notifiers for when the contents of the * hook stack changes. * * The interesting stuff happens on teardown. * * It is a requirement that the provider of hook events work in the reverse * order to the above, so that the first step is: * 1) remove events from each hook family created earlier * 2) remove hook families from the hook stack. * * When doing teardown of both events and families, a check is made to see * if either structure is still "busy". If so then a boolean flag (FWF_DESTROY) * is set to say that the structure is condemned. The presence of this flag * being set must be checked for in _add()/_register()/ functions and a * failure returned if it is set. It is ignored by the _find() functions * because they're used by _remove()/_unregister(). * While setting the condemned flag when trying to delete a structure would * normally be keyed from the presence of a reference count being greater * than 1, in this implementation there are no reference counts required: * instead the presence of objects on linked lists is taken to mean * something is still "busy." * * ONLY the caller that adds the family and the events ever has a direct * reference to the internal structures and thus ONLY it should be doing * the removal of either the event or family. In practise, what this means * is that in ip_netinfo.c, we have calls to net_protocol_register(), followed * by net_event_register() (these interface to hook_family_add() and * hook_event_add(), respectively) that are made when we create an instance * of IP and when the IP instance is shutdown/destroyed, it calls * net_event_unregister() and net_protocol_unregister(), which in turn call * hook_event_remove() and hook_family_remove() respectively. Nobody else * is entitled to call the _unregister() functions. It is imperative that * there be only one _remove() call for every _add() call. * * It is possible that code which is interfacing with this hook framework * won't do all the cleaning up that it needs to at the right time. While * we can't prevent programmers from creating memory leaks, we can synchronise * when we clean up data structures to prevent code accessing free'd memory. * * A simple diagram showing the ownership is as follows: * * Owned +--------------+ * by | hook_stack_t | * the +--------------+ * Instance | * - - - - - - - -|- - - - - - - - - - - - - - - - - - * V * Owned +-------------------+ +-------------------+ * | hook_family_int_t |---->| hook_family_int_t | * by +-------------------+ +-------------------+ * | \+---------------+ \+---------------+ * network | | hook_family_t | | hook_family_t | * V +---------------+ +---------------+ * protocol +------------------+ +------------------+ * | hook_event_int_t |---->| hook_event_int_t | * (ipv4,ipv6) +------------------+ +------------------+ * | \+--------------+ \+--------------+ * | | hook_event_t | | hook_event_t | * | +--------------+ +--------------+ * - - - - - - - -|- - - - - - - - - - - - - - - - - - * V * Owned +------------+ * | hook_int_t | * by +------------+ * \+--------+ * the consumer | hook_t | * +--------+ * * The consumers, such as IPFilter, do not have any pointers or hold any * references to hook_int_t, hook_event_t or hook_event_int_t. By placing * a hook on an event through net_hook_register(), an implicit reference * to the hook_event_int_t is returned with a successful call. Additionally, * IPFilter does not see the hook_family_int_t or hook_family_t directly. * Rather it is returned a net_handle_t (from net_protocol_lookup()) that * contains a pointer to hook_family_int_t. The structure behind the * net_handle_t (struct net_data) *is* reference counted and managed * appropriately. * * A more detailed picture that describes how the family/event structures * are linked together can be found in * * Notification callbacks. * ======================= * For each of the hook stack, hook family and hook event, it is possible * to request notificatin of change to them. Why? * First, lets equate the hook stack to an IP instance, a hook family to * a network protocol and a hook event to IP packets on the input path. * If a kernel module wants to apply security from the very start of * things, it needs to know as soon as a new instance of networking * is initiated. Whilst for the global zone, it is taken for granted that * this instance will always exist before any interaction takes place, * that is not true for zones running with an exclusive networking instance. * Thus when a local zone is started and a new instance is created to support * that, parties that wish to monitor it and apply a security policy from * the onset need to be informed as early as possible - quite probably * before any networking is started by the zone's boot scripts. * Inside each instance, it is possible to have a number of network protocols * (hook families) in operation. Inside the context of the global zone, * it is possible to have code run before the kernel module providing the * IP networking is loaded. From here, to apply the appropriate security, * it is necessary to become informed of when IP is being configured into * the zone and this is done by registering a notification callback with * the hook stack for changes to it. The next step is to know when packets * can be received through the physical_in, etc, events. This is achieved * by registering a callback with the appropriate network protocol (or in * this file, the correct hook family.) Thus when IP finally attaches a * physical_in event to inet, the module looking to enforce a security * policy can become aware of it being present. Of course there's no * requirement for such a module to be present before all of the above * happens and in such a case, it is reasonable for the same module to * work after everything has been put in place. For this reason, when * a notification callback is added, a series of fake callback events * is generated to simulate the arrival of those entities. There is one * final series of callbacks that can be registered - those to monitor * actual hooks that are added or removed from an event. In practice, * this is useful when there are multiple kernel modules participating * in the processing of packets and there are behaviour dependencies * involved, such that one kernel module might only register its hook * if another is already present and also might want to remove its hook * when the other disappears. * * If you know a kernel module will not be loaded before the infrastructure * used in this file is present then it is not necessary to use this * notification callback mechanism. */ /* * Locking * ======= * The use of CVW_* macros to do locking is driven by the need to allow * recursive locking with read locks when we're processing packets. This * is necessary because various netinfo functions need to hold read locks, * by design, as they can be called in or out of packet context. */ /* * Hook internal functions */ static hook_int_t *hook_copy(hook_t *src); static hook_event_int_t *hook_event_checkdup(hook_event_t *he, hook_stack_t *hks); static hook_event_int_t *hook_event_copy(hook_event_t *src); static hook_event_int_t *hook_event_find(hook_family_int_t *hfi, char *event); static void hook_event_free(hook_event_int_t *hei, hook_family_int_t *hfi); static hook_family_int_t *hook_family_copy(hook_family_t *src); static hook_family_int_t *hook_family_find(char *family, hook_stack_t *hks); static void hook_family_free(hook_family_int_t *hfi, hook_stack_t *hks); static hook_int_t *hook_find(hook_event_int_t *hei, hook_t *h); static void hook_int_free(hook_int_t *hi, netstackid_t); static void hook_init(void); static void hook_fini(void); static void *hook_stack_init(netstackid_t stackid, netstack_t *ns); static void hook_stack_fini(netstackid_t stackid, void *arg); static void hook_stack_shutdown(netstackid_t stackid, void *arg); static int hook_insert(hook_int_head_t *head, hook_int_t *new); static void hook_insert_plain(hook_int_head_t *head, hook_int_t *new); static int hook_insert_afterbefore(hook_int_head_t *head, hook_int_t *new); static hook_int_t *hook_find_byname(hook_int_head_t *head, char *name); static void hook_event_init_kstats(hook_family_int_t *, hook_event_int_t *); static void hook_event_notify_run(hook_event_int_t *, hook_family_int_t *, char *event, char *name, hook_notify_cmd_t cmd); static void hook_init_kstats(hook_family_int_t *hfi, hook_event_int_t *hei, hook_int_t *hi); static int hook_notify_register(hook_notify_head_t *head, hook_notify_fn_t callback, void *arg); static int hook_notify_unregister(hook_notify_head_t *head, hook_notify_fn_t callback, void **); static void hook_notify_run(hook_notify_head_t *head, char *family, char *event, char *name, hook_notify_cmd_t cmd); static void hook_stack_notify_run(hook_stack_t *hks, char *name, hook_notify_cmd_t cmd); static void hook_stack_remove(hook_stack_t *hks); /* * A list of the hook stacks is kept here because we need to enable * net_instance_notify_register() to be called during the creation * of a new instance. Previously hook_stack_get() would just use * the netstack functions for this work but they will return NULL * until the zone has been fully initialised. */ static hook_stack_head_t hook_stacks; static kmutex_t hook_stack_lock; /* * Module entry points. */ int _init(void) { int error; hook_init(); error = mod_install(&modlinkage); if (error != 0) hook_fini(); return (error); } int _fini(void) { int error; error = mod_remove(&modlinkage); if (error == 0) hook_fini(); return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * Function: hook_init * Returns: None * Parameters: None * * Initialize hooks */ static void hook_init(void) { mutex_init(&hook_stack_lock, NULL, MUTEX_DRIVER, NULL); SLIST_INIT(&hook_stacks); /* * We want to be informed each time a stack is created or * destroyed in the kernel. */ netstack_register(NS_HOOK, hook_stack_init, hook_stack_shutdown, hook_stack_fini); } /* * Function: hook_fini * Returns: None * Parameters: None * * Deinitialize hooks */ static void hook_fini(void) { netstack_unregister(NS_HOOK); mutex_destroy(&hook_stack_lock); ASSERT(SLIST_EMPTY(&hook_stacks)); } /* * Function: hook_wait_setflag * Returns: -1 = setting flag is disallowed, 0 = flag set and did * not have to wait (ie no lock droped), 1 = flag set but * it was necessary to drop locks to set it. * Parameters: waiter(I) - control data structure * busyset(I) - set of flags that we don't want set while * we are active. * wanted(I) - flag associated with newflag to indicate * what we want to do. * newflag(I) - the new ACTIVE flag we want to set that * indicates what we are doing. * * The set of functions hook_wait_* implement an API that builds on top of * the kcondvar_t to provide controlled execution through a critical region. * For each flag that indicates work is being done (FWF_*_ACTIVE) there is * also a flag that we set to indicate that we want to do it (FWF_*_WANTED). * The combination of flags is required as when this function exits to do * the task, the structure is then free for another caller to use and * to indicate that it wants to do work. The flags used when a caller wants * to destroy an object take precedence over those that are used for making * changes to it (add/remove.) In this case, we don't try to secure the * ability to run and return with an error. * * "wantedset" is used here to determine who has the right to clear the * wanted bit from the fw_flags set: only whomever sets the flag has the * right to clear it at the bottom of the loop, even if someone else * wants to set it. * * wanted - the FWF_*_WANTED flag that describes the action being requested * busyset- the set of FWF_* flags we don't want set when we run * newflag- the FWF_*_ACTIVE flag we will set to indicate we are busy */ int hook_wait_setflag(flagwait_t *waiter, uint32_t busyset, fwflag_t wanted, fwflag_t newflag) { boolean_t wantedset; int waited = 0; mutex_enter(&waiter->fw_lock); if (waiter->fw_flags & FWF_DESTROY) { cv_signal(&waiter->fw_cv); mutex_exit(&waiter->fw_lock); return (-1); } while (waiter->fw_flags & busyset) { wantedset = ((waiter->fw_flags & wanted) == wanted); if (!wantedset) waiter->fw_flags |= wanted; CVW_EXIT_WRITE(waiter->fw_owner); cv_wait(&waiter->fw_cv, &waiter->fw_lock); /* * This lock needs to be dropped here to preserve the order * of acquisition that is fw_owner followed by fw_lock, else * we can deadlock. */ mutex_exit(&waiter->fw_lock); waited = 1; CVW_ENTER_WRITE(waiter->fw_owner); mutex_enter(&waiter->fw_lock); if (!wantedset) waiter->fw_flags &= ~wanted; if (waiter->fw_flags & FWF_DESTROY) { cv_signal(&waiter->fw_cv); mutex_exit(&waiter->fw_lock); return (-1); } } waiter->fw_flags &= ~wanted; ASSERT((waiter->fw_flags & wanted) == 0); ASSERT((waiter->fw_flags & newflag) == 0); waiter->fw_flags |= newflag; mutex_exit(&waiter->fw_lock); return (waited); } /* * Function: hook_wait_unsetflag * Returns: None * Parameters: waiter(I) - control data structure * oldflag(I) - flag to reset * * Turn off the bit that we had set to run and let others know that * they should now check to see if they can run. */ void hook_wait_unsetflag(flagwait_t *waiter, fwflag_t oldflag) { mutex_enter(&waiter->fw_lock); waiter->fw_flags &= ~oldflag; cv_signal(&waiter->fw_cv); mutex_exit(&waiter->fw_lock); } /* * Function: hook_wait_destroy * Returns: None * Parameters: waiter(I) - control data structure * * Since outer locking (on fw_owner) should ensure that only one function * at a time gets to call hook_wait_destroy() on a given object, there is * no need to guard against setting FWF_DESTROY_WANTED already being set. * It is, however, necessary to wait for all activity on the owning * structure to cease. */ int hook_wait_destroy(flagwait_t *waiter) { ASSERT((waiter->fw_flags & FWF_DESTROY_WANTED) == 0); mutex_enter(&waiter->fw_lock); if (waiter->fw_flags & FWF_DESTROY_WANTED) { cv_signal(&waiter->fw_cv); mutex_exit(&waiter->fw_lock); return (EINPROGRESS); } waiter->fw_flags |= FWF_DESTROY_WANTED; while (!FWF_DESTROY_OK(waiter)) { CVW_EXIT_WRITE(waiter->fw_owner); cv_wait(&waiter->fw_cv, &waiter->fw_lock); CVW_ENTER_WRITE(waiter->fw_owner); } /* * There should now be nothing else using "waiter" or its * owner, so we can safely assign here without risk of wiiping * out someone's bit. */ waiter->fw_flags = FWF_DESTROY_ACTIVE; cv_signal(&waiter->fw_cv); mutex_exit(&waiter->fw_lock); return (0); } /* * Function: hook_wait_init * Returns: None * Parameters: waiter(I) - control data structure * ownder(I) - pointer to lock that the owner of this * waiter uses * * "owner" gets passed in here so that when we need to call cv_wait, * for example in hook_wait_setflag(), we can drop the lock for the * next layer out, which is likely to be held in an exclusive manner. */ void hook_wait_init(flagwait_t *waiter, cvwaitlock_t *owner) { cv_init(&waiter->fw_cv, NULL, CV_DRIVER, NULL); mutex_init(&waiter->fw_lock, NULL, MUTEX_DRIVER, NULL); waiter->fw_flags = FWF_NONE; waiter->fw_owner = owner; } /* * Function: hook_stack_init * Returns: void * - pointer to new hook stack structure * Parameters: stackid(I) - identifier for the network instance that owns this * ns(I) - pointer to the network instance data structure * * Allocate and initialize the hook stack instance. This function is not * allowed to fail, so KM_SLEEP is used here when allocating memory. The * value returned is passed back into the shutdown and destroy hooks. */ /*ARGSUSED*/ static void * hook_stack_init(netstackid_t stackid, netstack_t *ns) { hook_stack_t *hks; #ifdef NS_DEBUG printf("hook_stack_init(stack %d)\n", stackid); #endif hks = (hook_stack_t *)kmem_zalloc(sizeof (*hks), KM_SLEEP); hks->hks_netstack = ns; hks->hks_netstackid = stackid; CVW_INIT(&hks->hks_lock); TAILQ_INIT(&hks->hks_nhead); SLIST_INIT(&hks->hks_familylist); hook_wait_init(&hks->hks_waiter, &hks->hks_lock); mutex_enter(&hook_stack_lock); SLIST_INSERT_HEAD(&hook_stacks, hks, hks_entry); mutex_exit(&hook_stack_lock); return (hks); } /* * Function: hook_stack_shutdown * Returns: void * Parameters: stackid(I) - identifier for the network instance that owns this * arg(I) - pointer returned by hook_stack_init * * Set the shutdown flag to indicate that we should stop accepting new * register calls as we're now in the cleanup process. The cleanup is a * two stage process and we're not required to free any memory here. * * The curious would wonder why isn't there any code that walks through * all of the data structures and sets the flag(s) there? The answer is * that it is expected that this will happen when the zone shutdown calls * the shutdown callbacks for other modules that they will initiate the * free'ing and shutdown of the hooks themselves. */ /*ARGSUSED*/ static void hook_stack_shutdown(netstackid_t stackid, void *arg) { hook_stack_t *hks = (hook_stack_t *)arg; mutex_enter(&hook_stack_lock); /* * Once this flag gets set to one, no more additions are allowed * to any of the structures that make up this stack. */ hks->hks_shutdown = 1; mutex_exit(&hook_stack_lock); } /* * Function: hook_stack_destroy * Returns: void * Parameters: stackid(I) - identifier for the network instance that owns this * arg(I) - pointer returned by hook_stack_init * * Free the hook stack instance. * * The rationale for the shutdown being lazy (see the comment above for * hook_stack_shutdown) also applies to the destroy being lazy. Only if * the hook_stack_t data structure is unused will it go away. Else it * is left up to the last user of a data structure to actually free it. */ /*ARGSUSED*/ static void hook_stack_fini(netstackid_t stackid, void *arg) { hook_stack_t *hks = (hook_stack_t *)arg; mutex_enter(&hook_stack_lock); hks->hks_shutdown = 2; hook_stack_remove(hks); mutex_exit(&hook_stack_lock); } /* * Function: hook_stack_remove * Returns: void * Parameters: hks(I) - pointer to an instance of a hook_stack_t * * This function assumes that it is called with hook_stack_lock held. * It functions differently to hook_family/event_remove in that it does * the checks to see if it can be removed. This difference exists * because this structure has nothing higher up that depends on it. */ static void hook_stack_remove(hook_stack_t *hks) { ASSERT(mutex_owned(&hook_stack_lock)); /* * Is the structure still in use? */ if (!SLIST_EMPTY(&hks->hks_familylist) || !TAILQ_EMPTY(&hks->hks_nhead)) return; SLIST_REMOVE(&hook_stacks, hks, hook_stack, hks_entry); VERIFY(hook_wait_destroy(&hks->hks_waiter) == 0); CVW_DESTROY(&hks->hks_lock); kmem_free(hks, sizeof (*hks)); } /* * Function: hook_stack_get * Returns: hook_stack_t * - NULL if not found, else matching instance * Parameters: stackid(I) - instance id to search for * * Search the list of currently active hook_stack_t structures for one that * has a matching netstackid_t to the value passed in. The linked list can * only ever have at most one match for this value. */ static hook_stack_t * hook_stack_get(netstackid_t stackid) { hook_stack_t *hks; SLIST_FOREACH(hks, &hook_stacks, hks_entry) { if (hks->hks_netstackid == stackid) break; } return (hks); } /* * Function: hook_stack_notify_register * Returns: int - 0 = success, else failure * Parameters: stackid(I) - netstack identifier * callback(I)- function to be called * arg(I) - arg to provide callback when it is called * * If we're not shutting down this instance, append a new function to the * list of those to call when a new family of hooks is added to this stack. * If the function can be successfully added to the list of callbacks * activated when there is a change to the stack (addition or removal of * a hook family) then generate a fake HN_REGISTER event by directly * calling the callback with the relevant information for each hook * family that currently exists (and isn't being shutdown.) */ int hook_stack_notify_register(netstackid_t stackid, hook_notify_fn_t callback, void *arg) { hook_family_int_t *hfi; hook_stack_t *hks; boolean_t canrun; char buffer[16]; int error; ASSERT(callback != NULL); canrun = B_FALSE; mutex_enter(&hook_stack_lock); hks = hook_stack_get(stackid); if (hks != NULL) { if (hks->hks_shutdown != 0) { error = ESHUTDOWN; } else { CVW_ENTER_WRITE(&hks->hks_lock); canrun = (hook_wait_setflag(&hks->hks_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) != -1); error = hook_notify_register(&hks->hks_nhead, callback, arg); CVW_EXIT_WRITE(&hks->hks_lock); } } else { error = ESRCH; } mutex_exit(&hook_stack_lock); if (error == 0 && canrun) { /* * Generate fake register event for callback that * is being added, letting it know everything that * already exists. */ (void) snprintf(buffer, sizeof (buffer), "%u", hks->hks_netstackid); SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) { if (hfi->hfi_condemned || hfi->hfi_shutdown) continue; callback(HN_REGISTER, arg, buffer, NULL, hfi->hfi_family.hf_name); } } if (canrun) hook_wait_unsetflag(&hks->hks_waiter, FWF_ADD_ACTIVE); return (error); } /* * Function: hook_stack_notify_unregister * Returns: int - 0 = success, else failure * Parameters: stackid(I) - netstack identifier * callback(I) - function to be called * * Attempt to remove a registered function from a hook stack's list of * callbacks to activiate when protocols are added/deleted. * As with hook_stack_notify_register, if all things are going well then * a fake unregister event is delivered to the callback being removed * for each hook family that presently exists. */ int hook_stack_notify_unregister(netstackid_t stackid, hook_notify_fn_t callback) { hook_family_int_t *hfi; hook_stack_t *hks; char buffer[16]; void *arg; int error; mutex_enter(&hook_stack_lock); hks = hook_stack_get(stackid); if (hks == NULL) { mutex_exit(&hook_stack_lock); return (ESRCH); } CVW_ENTER_WRITE(&hks->hks_lock); /* * If hook_wait_setflag returns -1, another thread has flagged that it * is attempting to destroy this hook stack. Before it can flag that * it's destroying the hook stack, it must first verify (with * hook_stack_lock held) that the hook stack is empty. If we * encounter this, it means we should have nothing to do and we * just snuck in. */ if (hook_wait_setflag(&hks->hks_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) { VERIFY(TAILQ_EMPTY(&hks->hks_nhead)); CVW_EXIT_WRITE(&hks->hks_lock); mutex_exit(&hook_stack_lock); return (ESRCH); } error = hook_notify_unregister(&hks->hks_nhead, callback, &arg); CVW_EXIT_WRITE(&hks->hks_lock); mutex_exit(&hook_stack_lock); if (error == 0) { /* * Generate fake unregister event for callback that * is being removed, letting it know everything that * currently exists is now "disappearing." */ (void) snprintf(buffer, sizeof (buffer), "%u", hks->hks_netstackid); SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) { callback(HN_UNREGISTER, arg, buffer, NULL, hfi->hfi_family.hf_name); } } else { /* * hook_notify_unregister() should only fail if the callback has * already been deleted (ESRCH). */ VERIFY3S(error, ==, ESRCH); } mutex_enter(&hook_stack_lock); hook_wait_unsetflag(&hks->hks_waiter, FWF_DEL_ACTIVE); if (hks->hks_shutdown == 2) hook_stack_remove(hks); mutex_exit(&hook_stack_lock); return (error); } /* * Function: hook_stack_notify_run * Returns: None * Parameters: hks(I) - hook stack pointer to execute callbacks for * name(I) - name of a hook family * cmd(I) - either HN_UNREGISTER or HN_REGISTER * * Run through the list of callbacks on the hook stack to be called when * a new hook family is added * * As hook_notify_run() expects 3 names, one for the family that is associated * with the cmd (HN_REGISTER or HN_UNREGISTER), one for the event and one * for the object being introduced and we really only have one name (that * of the new hook family), fake the hook stack's name by converting the * integer to a string and for the event just pass NULL. */ static void hook_stack_notify_run(hook_stack_t *hks, char *name, hook_notify_cmd_t cmd) { char buffer[16]; ASSERT(hks != NULL); ASSERT(name != NULL); (void) snprintf(buffer, sizeof (buffer), "%u", hks->hks_netstackid); hook_notify_run(&hks->hks_nhead, buffer, NULL, name, cmd); } /* * Function: hook_run * Returns: int - return value according to callback func * Parameters: token(I) - event pointer * info(I) - message * * Run hooks for specific provider. The hooks registered are stepped through * until either the end of the list is reached or a hook function returns a * non-zero value. If a non-zero value is returned from a hook function, we * return that value back to our caller. By design, a hook function can be * called more than once, simultaneously. */ int hook_run(hook_family_int_t *hfi, hook_event_token_t token, hook_data_t info) { hook_event_int_t *hei; hook_int_t *hi; int rval = 0; ASSERT(token != NULL); hei = (hook_event_int_t *)token; DTRACE_PROBE2(hook__run__start, hook_event_token_t, token, hook_data_t, info); /* * If we consider that this function is only called from within the * stack while an instance is currently active, */ CVW_ENTER_READ(&hfi->hfi_lock); TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) { ASSERT(hi->hi_hook.h_func != NULL); DTRACE_PROBE3(hook__func__start, hook_event_token_t, token, hook_data_t, info, hook_int_t *, hi); rval = (*hi->hi_hook.h_func)(token, info, hi->hi_hook.h_arg); DTRACE_PROBE4(hook__func__end, hook_event_token_t, token, hook_data_t, info, hook_int_t *, hi, int, rval); hi->hi_kstats.hook_hits.value.ui64++; if (rval != 0) break; } hei->hei_kstats.events.value.ui64++; CVW_EXIT_READ(&hfi->hfi_lock); DTRACE_PROBE3(hook__run__end, hook_event_token_t, token, hook_data_t, info, hook_int_t *, hi); return (rval); } /* * Function: hook_family_add * Returns: internal family pointer - NULL = Fail * Parameters: hf(I) - family pointer * hks(I) - pointer to an instance of a hook_stack_t * store(O) - where returned pointer will be stored * * Add new family to the family list. The requirements for the addition to * succeed are that the family name must not already be registered and that * the hook stack is not being shutdown. * If store is non-NULL, it is expected to be a pointer to the same variable * that is awaiting to be assigned the return value of this function. * In its current use, the returned value is assigned to netd_hooks in * net_family_register. The use of "store" allows the return value to be * used before this function returns. How can this happen? Through the * callbacks that can be activated at the bottom of this function, when * hook_stack_notify_run is called. */ hook_family_int_t * hook_family_add(hook_family_t *hf, hook_stack_t *hks, void **store) { hook_family_int_t *hfi, *new; ASSERT(hf != NULL); ASSERT(hf->hf_name != NULL); new = hook_family_copy(hf); if (new == NULL) return (NULL); mutex_enter(&hook_stack_lock); CVW_ENTER_WRITE(&hks->hks_lock); if (hks->hks_shutdown != 0) { CVW_EXIT_WRITE(&hks->hks_lock); mutex_exit(&hook_stack_lock); hook_family_free(new, NULL); return (NULL); } /* search family list */ hfi = hook_family_find(hf->hf_name, hks); if (hfi != NULL) { CVW_EXIT_WRITE(&hks->hks_lock); mutex_exit(&hook_stack_lock); hook_family_free(new, NULL); return (NULL); } /* * Try and set the FWF_ADD_ACTIVE flag so that we can drop all the * lock further down when calling all of the functions registered * for notification when a new hook family is added. */ if (hook_wait_setflag(&hks->hks_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) == -1) { CVW_EXIT_WRITE(&hks->hks_lock); mutex_exit(&hook_stack_lock); hook_family_free(new, NULL); return (NULL); } CVW_INIT(&new->hfi_lock); SLIST_INIT(&new->hfi_head); TAILQ_INIT(&new->hfi_nhead); hook_wait_init(&new->hfi_waiter, &new->hfi_lock); new->hfi_stack = hks; if (store != NULL) *store = new; /* Add to family list head */ SLIST_INSERT_HEAD(&hks->hks_familylist, new, hfi_entry); CVW_EXIT_WRITE(&hks->hks_lock); mutex_exit(&hook_stack_lock); hook_stack_notify_run(hks, hf->hf_name, HN_REGISTER); hook_wait_unsetflag(&hks->hks_waiter, FWF_ADD_ACTIVE); return (new); } /* * Function: hook_family_remove * Returns: int - 0 = success, else = failure * Parameters: hfi(I) - internal family pointer * * Remove family from family list. This function has been designed to be * called once and once only per hook_family_int_t. Thus when cleaning up * this structure as an orphan, callers should only call hook_family_free. */ int hook_family_remove(hook_family_int_t *hfi) { hook_stack_t *hks; boolean_t notifydone; ASSERT(hfi != NULL); hks = hfi->hfi_stack; CVW_ENTER_WRITE(&hfi->hfi_lock); notifydone = hfi->hfi_shutdown; hfi->hfi_shutdown = B_TRUE; CVW_EXIT_WRITE(&hfi->hfi_lock); CVW_ENTER_WRITE(&hks->hks_lock); if (hook_wait_setflag(&hks->hks_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) { /* * If we're trying to destroy the hook_stack_t... */ CVW_EXIT_WRITE(&hks->hks_lock); return (ENXIO); } /* * Check if the family is in use by the presence of either events * or notify callbacks on the hook family. */ if (!SLIST_EMPTY(&hfi->hfi_head) || !TAILQ_EMPTY(&hfi->hfi_nhead)) { hfi->hfi_condemned = B_TRUE; } else { VERIFY(hook_wait_destroy(&hfi->hfi_waiter) == 0); /* * Although hfi_condemned = B_FALSE is implied from creation, * putting a comment here inside the else upsets lint. */ hfi->hfi_condemned = B_FALSE; } CVW_EXIT_WRITE(&hks->hks_lock); if (!notifydone) hook_stack_notify_run(hks, hfi->hfi_family.hf_name, HN_UNREGISTER); hook_wait_unsetflag(&hks->hks_waiter, FWF_DEL_ACTIVE); /* * If we don't have to wait for anything else to disappear from this * structure then we can free it up. */ if (!hfi->hfi_condemned) hook_family_free(hfi, hks); return (0); } /* * Function: hook_family_free * Returns: None * Parameters: hfi(I) - internal family pointer * * Free alloc memory for family */ static void hook_family_free(hook_family_int_t *hfi, hook_stack_t *hks) { /* * This lock gives us possession of the hks pointer after the * SLIST_REMOVE, for which it is not needed, when hks_shutdown * is checked and hook_stack_remove called. */ mutex_enter(&hook_stack_lock); ASSERT(hfi != NULL); if (hks != NULL) { CVW_ENTER_WRITE(&hks->hks_lock); /* Remove from family list */ SLIST_REMOVE(&hks->hks_familylist, hfi, hook_family_int, hfi_entry); CVW_EXIT_WRITE(&hks->hks_lock); } /* Free name space */ if (hfi->hfi_family.hf_name != NULL) { kmem_free(hfi->hfi_family.hf_name, strlen(hfi->hfi_family.hf_name) + 1); } /* Free container */ kmem_free(hfi, sizeof (*hfi)); if (hks != NULL && hks->hks_shutdown == 2) hook_stack_remove(hks); mutex_exit(&hook_stack_lock); } /* * Function: hook_family_shutdown * Returns: int - 0 = success, else = failure * Parameters: hfi(I) - internal family pointer * * As an alternative to removing a family, we may desire to just generate * a series of callbacks to indicate that we will be going away in the * future. The hfi_condemned flag isn't set because we aren't trying to * remove the structure. */ int hook_family_shutdown(hook_family_int_t *hfi) { hook_stack_t *hks; boolean_t notifydone; ASSERT(hfi != NULL); hks = hfi->hfi_stack; CVW_ENTER_WRITE(&hfi->hfi_lock); notifydone = hfi->hfi_shutdown; hfi->hfi_shutdown = B_TRUE; CVW_EXIT_WRITE(&hfi->hfi_lock); CVW_ENTER_WRITE(&hks->hks_lock); if (hook_wait_setflag(&hks->hks_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) { /* * If we're trying to destroy the hook_stack_t... */ CVW_EXIT_WRITE(&hks->hks_lock); return (ENXIO); } CVW_EXIT_WRITE(&hks->hks_lock); if (!notifydone) hook_stack_notify_run(hks, hfi->hfi_family.hf_name, HN_UNREGISTER); hook_wait_unsetflag(&hks->hks_waiter, FWF_DEL_ACTIVE); return (0); } /* * Function: hook_family_copy * Returns: internal family pointer - NULL = Failed * Parameters: src(I) - family pointer * * Allocate internal family block and duplicate incoming family * No locks should be held across this function as it may sleep. */ static hook_family_int_t * hook_family_copy(hook_family_t *src) { hook_family_int_t *new; hook_family_t *dst; ASSERT(src != NULL); ASSERT(src->hf_name != NULL); new = (hook_family_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP); /* Copy body */ dst = &new->hfi_family; *dst = *src; SLIST_INIT(&new->hfi_head); TAILQ_INIT(&new->hfi_nhead); /* Copy name */ dst->hf_name = (char *)kmem_alloc(strlen(src->hf_name) + 1, KM_SLEEP); (void) strcpy(dst->hf_name, src->hf_name); return (new); } /* * Function: hook_family_find * Returns: internal family pointer - NULL = Not match * Parameters: family(I) - family name string * * Search family list with family name * A lock on hfi_lock must be held when called. */ static hook_family_int_t * hook_family_find(char *family, hook_stack_t *hks) { hook_family_int_t *hfi = NULL; ASSERT(family != NULL); SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) { if (strcmp(hfi->hfi_family.hf_name, family) == 0) break; } return (hfi); } /* * Function: hook_family_notify_register * Returns: int - 0 = success, else failure * Parameters: hfi(I) - hook family * callback(I) - function to be called * arg(I) - arg to provide callback when it is called * * So long as this hook stack isn't being shut down, register a new * callback to be activated each time a new event is added to this * family. * * To call this function we must have an active handle in use on the family, * so if we take this into account, then neither the hook_family_int_t nor * the hook_stack_t that owns it can disappear. We have to put some trust * in the callers to be properly synchronised... * * Holding hks_lock is required to provide synchronisation for hks_shutdown. */ int hook_family_notify_register(hook_family_int_t *hfi, hook_notify_fn_t callback, void *arg) { hook_event_int_t *hei; hook_stack_t *hks; boolean_t canrun; int error; ASSERT(hfi != NULL); canrun = B_FALSE; hks = hfi->hfi_stack; CVW_ENTER_READ(&hks->hks_lock); if ((hfi->hfi_stack->hks_shutdown != 0) || hfi->hfi_condemned || hfi->hfi_shutdown) { CVW_EXIT_READ(&hks->hks_lock); return (ESHUTDOWN); } CVW_ENTER_WRITE(&hfi->hfi_lock); canrun = (hook_wait_setflag(&hfi->hfi_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) != -1); error = hook_notify_register(&hfi->hfi_nhead, callback, arg); CVW_EXIT_WRITE(&hfi->hfi_lock); CVW_EXIT_READ(&hks->hks_lock); if (error == 0 && canrun) { SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) { callback(HN_REGISTER, arg, hfi->hfi_family.hf_name, NULL, hei->hei_event->he_name); } } if (canrun) hook_wait_unsetflag(&hfi->hfi_waiter, FWF_ADD_ACTIVE); return (error); } /* * Function: hook_family_notify_unregister * Returns: int - 0 = success, else failure * Parameters: hfi(I) - hook family * callback(I) - function to be called * * Remove a callback from the list of those executed when a new event is * added to a hook family. If the family is not in the process of being * destroyed then simulate an unregister callback for each event that is * on the family. This pairs up with the hook_family_notify_register * action that simulates register events. * The order of what happens here is important and goes like this. * 1) Remove the callback from the list of functions to be called as part * of the notify operation when an event is added or removed from the * hook family. * 2) If the hook_family_int_t structure is on death row (free_family will * be set to true) then there's nothing else to do than let it be free'd. * 3) If the structure isn't about to die, mark it up as being busy using * hook_wait_setflag and then drop the lock so the loop can be run. * 4) if hook_wait_setflag was successful, tell all of the notify callback * functions that this family has been unregistered. * 5) Cleanup */ int hook_family_notify_unregister(hook_family_int_t *hfi, hook_notify_fn_t callback) { hook_event_int_t *hei; boolean_t free_family; boolean_t canrun; int error; void *arg; canrun = B_FALSE; CVW_ENTER_WRITE(&hfi->hfi_lock); (void) hook_wait_setflag(&hfi->hfi_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE); error = hook_notify_unregister(&hfi->hfi_nhead, callback, &arg); hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE); /* * If hook_family_remove has been called but the structure was still * "busy" ... but we might have just made it "unbusy"... */ if ((error == 0) && hfi->hfi_condemned && SLIST_EMPTY(&hfi->hfi_head) && TAILQ_EMPTY(&hfi->hfi_nhead)) { free_family = B_TRUE; } else { free_family = B_FALSE; } if (error == 0 && !free_family) { canrun = (hook_wait_setflag(&hfi->hfi_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) != -1); } CVW_EXIT_WRITE(&hfi->hfi_lock); if (canrun) { SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) { callback(HN_UNREGISTER, arg, hfi->hfi_family.hf_name, NULL, hei->hei_event->he_name); } hook_wait_unsetflag(&hfi->hfi_waiter, FWF_ADD_ACTIVE); } else if (free_family) { hook_family_free(hfi, hfi->hfi_stack); } return (error); } /* * Function: hook_event_add * Returns: internal event pointer - NULL = Fail * Parameters: hfi(I) - internal family pointer * he(I) - event pointer * * Add new event to event list on specific family. * This function can fail to return successfully if (1) it cannot allocate * enough memory for its own internal data structures, (2) the event has * already been registered (for any hook family.) */ hook_event_int_t * hook_event_add(hook_family_int_t *hfi, hook_event_t *he) { hook_event_int_t *hei, *new; hook_stack_t *hks; ASSERT(hfi != NULL); ASSERT(he != NULL); ASSERT(he->he_name != NULL); new = hook_event_copy(he); if (new == NULL) return (NULL); hks = hfi->hfi_stack; CVW_ENTER_READ(&hks->hks_lock); hks = hfi->hfi_stack; if (hks->hks_shutdown != 0) { CVW_EXIT_READ(&hks->hks_lock); hook_event_free(new, NULL); return (NULL); } /* Check whether this event pointer is already registered */ hei = hook_event_checkdup(he, hks); if (hei != NULL) { CVW_EXIT_READ(&hks->hks_lock); hook_event_free(new, NULL); return (NULL); } CVW_ENTER_WRITE(&hfi->hfi_lock); if (hfi->hfi_condemned || hfi->hfi_shutdown) { CVW_EXIT_WRITE(&hfi->hfi_lock); CVW_EXIT_READ(&hks->hks_lock); hook_event_free(new, NULL); return (NULL); } CVW_EXIT_READ(&hks->hks_lock); if (hook_wait_setflag(&hfi->hfi_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) == -1) { CVW_EXIT_WRITE(&hfi->hfi_lock); hook_event_free(new, NULL); return (NULL); } TAILQ_INIT(&new->hei_nhead); hook_event_init_kstats(hfi, new); hook_wait_init(&new->hei_waiter, &new->hei_lock); /* Add to event list head */ SLIST_INSERT_HEAD(&hfi->hfi_head, new, hei_entry); CVW_EXIT_WRITE(&hfi->hfi_lock); hook_notify_run(&hfi->hfi_nhead, hfi->hfi_family.hf_name, NULL, he->he_name, HN_REGISTER); hook_wait_unsetflag(&hfi->hfi_waiter, FWF_ADD_ACTIVE); return (new); } /* * Function: hook_event_init_kstats * Returns: None * Parameters: hfi(I) - pointer to the family that owns this event. * hei(I) - pointer to the hook event that needs some kstats. * * Create a set of kstats that relate to each event registered with * the hook framework. A counter is kept for each time the event is * activated and for each time a hook is added or removed. As the * kstats just count the events as they happen, the total number of * hooks registered must be obtained by subtractived removed from added. */ static void hook_event_init_kstats(hook_family_int_t *hfi, hook_event_int_t *hei) { hook_event_kstat_t template = { { "hooksAdded", KSTAT_DATA_UINT64 }, { "hooksRemoved", KSTAT_DATA_UINT64 }, { "events", KSTAT_DATA_UINT64 } }; hook_stack_t *hks; hks = hfi->hfi_stack; hei->hei_kstatp = kstat_create_netstack(hfi->hfi_family.hf_name, 0, hei->hei_event->he_name, "hook_event", KSTAT_TYPE_NAMED, sizeof (hei->hei_kstats) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL, hks->hks_netstackid); bcopy((char *)&template, &hei->hei_kstats, sizeof (template)); if (hei->hei_kstatp != NULL) { hei->hei_kstatp->ks_data = (void *)&hei->hei_kstats; hei->hei_kstatp->ks_private = (void *)(uintptr_t)hks->hks_netstackid; kstat_install(hei->hei_kstatp); } } /* * Function: hook_event_remove * Returns: int - 0 = success, else = failure * Parameters: hfi(I) - internal family pointer * he(I) - event pointer * * Remove event from event list on specific family * * This function assumes that the caller has received a pointer to a the * hook_family_int_t via a call to net_protocol_lookup or net_protocol_unreg'. * This the hook_family_int_t is guaranteed to be around for the life of this * call, unless the caller has decided to call net_protocol_release or * net_protocol_unregister before calling net_event_unregister - an error. */ int hook_event_remove(hook_family_int_t *hfi, hook_event_t *he) { boolean_t free_family; hook_event_int_t *hei; boolean_t notifydone; ASSERT(hfi != NULL); ASSERT(he != NULL); CVW_ENTER_WRITE(&hfi->hfi_lock); /* * Set the flag so that we can call hook_event_notify_run without * holding any locks but at the same time prevent other changes to * the event at the same time. */ if (hook_wait_setflag(&hfi->hfi_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) { CVW_EXIT_WRITE(&hfi->hfi_lock); return (ENXIO); } hei = hook_event_find(hfi, he->he_name); if (hei == NULL) { hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE); CVW_EXIT_WRITE(&hfi->hfi_lock); return (ESRCH); } free_family = B_FALSE; CVW_ENTER_WRITE(&hei->hei_lock); /* * The hei_shutdown flag is used to indicate whether or not we have * done a shutdown and thus already walked through the notify list. */ notifydone = hei->hei_shutdown; hei->hei_shutdown = B_TRUE; /* * If there are any hooks still registered for this event or * there are any notifiers registered, return an error indicating * that the event is still busy. */ if (!TAILQ_EMPTY(&hei->hei_head) || !TAILQ_EMPTY(&hei->hei_nhead)) { hei->hei_condemned = B_TRUE; CVW_EXIT_WRITE(&hei->hei_lock); } else { /* hei_condemned = B_FALSE is implied from creation */ /* * Even though we know the notify list is empty, we call * hook_wait_destroy here to synchronise wait removing a * hook from an event. */ VERIFY(hook_wait_destroy(&hei->hei_waiter) == 0); CVW_EXIT_WRITE(&hei->hei_lock); if (hfi->hfi_condemned && SLIST_EMPTY(&hfi->hfi_head) && TAILQ_EMPTY(&hfi->hfi_nhead)) free_family = B_TRUE; } CVW_EXIT_WRITE(&hfi->hfi_lock); if (!notifydone) hook_notify_run(&hfi->hfi_nhead, hfi->hfi_family.hf_name, NULL, he->he_name, HN_UNREGISTER); hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE); if (!hei->hei_condemned) { hook_event_free(hei, hfi); if (free_family) hook_family_free(hfi, hfi->hfi_stack); } return (0); } /* * Function: hook_event_shutdown * Returns: int - 0 = success, else = failure * Parameters: hfi(I) - internal family pointer * he(I) - event pointer * * As with hook_family_shutdown, we want to generate the notify callbacks * as if the event was being removed but not actually do the remove. */ int hook_event_shutdown(hook_family_int_t *hfi, hook_event_t *he) { hook_event_int_t *hei; boolean_t notifydone; ASSERT(hfi != NULL); ASSERT(he != NULL); CVW_ENTER_WRITE(&hfi->hfi_lock); /* * Set the flag so that we can call hook_event_notify_run without * holding any locks but at the same time prevent other changes to * the event at the same time. */ if (hook_wait_setflag(&hfi->hfi_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) { CVW_EXIT_WRITE(&hfi->hfi_lock); return (ENXIO); } hei = hook_event_find(hfi, he->he_name); if (hei == NULL) { hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE); CVW_EXIT_WRITE(&hfi->hfi_lock); return (ESRCH); } CVW_ENTER_WRITE(&hei->hei_lock); notifydone = hei->hei_shutdown; hei->hei_shutdown = B_TRUE; CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_WRITE(&hfi->hfi_lock); if (!notifydone) hook_notify_run(&hfi->hfi_nhead, hfi->hfi_family.hf_name, NULL, he->he_name, HN_UNREGISTER); hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE); return (0); } /* * Function: hook_event_free * Returns: None * Parameters: hei(I) - internal event pointer * * Free alloc memory for event */ static void hook_event_free(hook_event_int_t *hei, hook_family_int_t *hfi) { boolean_t free_family; ASSERT(hei != NULL); if (hfi != NULL) { CVW_ENTER_WRITE(&hfi->hfi_lock); /* * Remove the event from the hook family's list. */ SLIST_REMOVE(&hfi->hfi_head, hei, hook_event_int, hei_entry); if (hfi->hfi_condemned && SLIST_EMPTY(&hfi->hfi_head) && TAILQ_EMPTY(&hfi->hfi_nhead)) { free_family = B_TRUE; } else { free_family = B_FALSE; } CVW_EXIT_WRITE(&hfi->hfi_lock); } if (hei->hei_kstatp != NULL) { ASSERT(hfi != NULL); kstat_delete_netstack(hei->hei_kstatp, hfi->hfi_stack->hks_netstackid); hei->hei_kstatp = NULL; } /* Free container */ kmem_free(hei, sizeof (*hei)); if (free_family) hook_family_free(hfi, hfi->hfi_stack); } /* * Function: hook_event_checkdup * Returns: internal event pointer - NULL = Not match * Parameters: he(I) - event pointer * * Search all of the hook families to see if the event being passed in * has already been associated with one. */ static hook_event_int_t * hook_event_checkdup(hook_event_t *he, hook_stack_t *hks) { hook_family_int_t *hfi; hook_event_int_t *hei; ASSERT(he != NULL); CVW_ENTER_READ(&hks->hks_lock); SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) { SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) { if (hei->hei_event == he) { CVW_EXIT_READ(&hks->hks_lock); return (hei); } } } CVW_EXIT_READ(&hks->hks_lock); return (NULL); } /* * Function: hook_event_copy * Returns: internal event pointer - NULL = Failed * Parameters: src(I) - event pointer * * Allocate internal event block and duplicate incoming event * No locks should be held across this function as it may sleep. */ static hook_event_int_t * hook_event_copy(hook_event_t *src) { hook_event_int_t *new; ASSERT(src != NULL); ASSERT(src->he_name != NULL); new = (hook_event_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP); /* Copy body */ TAILQ_INIT(&new->hei_head); new->hei_event = src; return (new); } /* * Function: hook_event_find * Returns: internal event pointer - NULL = Not match * Parameters: hfi(I) - internal family pointer * event(I) - event name string * * Search event list with event name * A lock on hfi->hfi_lock must be held when called. */ static hook_event_int_t * hook_event_find(hook_family_int_t *hfi, char *event) { hook_event_int_t *hei = NULL; ASSERT(hfi != NULL); ASSERT(event != NULL); SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) { if ((strcmp(hei->hei_event->he_name, event) == 0) && ((hei->hei_waiter.fw_flags & FWF_UNSAFE) == 0)) break; } return (hei); } /* * Function: hook_event_notify_register * Returns: int - 0 = success, else failure * Parameters: hfi(I) - hook family * event(I) - name of the event * callback(I) - function to be called * arg(I) - arg to provide callback when it is called * * Adds a new callback to the event named by "event" (we must find it) * that will be executed each time a new hook is added to the event. * Of course, if the stack is being shut down, this call should fail. */ int hook_event_notify_register(hook_family_int_t *hfi, char *event, hook_notify_fn_t callback, void *arg) { hook_event_int_t *hei; hook_stack_t *hks; boolean_t canrun; hook_int_t *h; int error; canrun = B_FALSE; hks = hfi->hfi_stack; CVW_ENTER_READ(&hks->hks_lock); if (hks->hks_shutdown != 0) { CVW_EXIT_READ(&hks->hks_lock); return (ESHUTDOWN); } CVW_ENTER_READ(&hfi->hfi_lock); if (hfi->hfi_condemned || hfi->hfi_shutdown) { CVW_EXIT_READ(&hfi->hfi_lock); CVW_EXIT_READ(&hks->hks_lock); return (ESHUTDOWN); } hei = hook_event_find(hfi, event); if (hei == NULL) { CVW_EXIT_READ(&hfi->hfi_lock); CVW_EXIT_READ(&hks->hks_lock); return (ESRCH); } if (hei->hei_condemned || hei->hei_shutdown) { CVW_EXIT_READ(&hfi->hfi_lock); CVW_EXIT_READ(&hks->hks_lock); return (ESHUTDOWN); } CVW_ENTER_WRITE(&hei->hei_lock); canrun = (hook_wait_setflag(&hei->hei_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) != -1); error = hook_notify_register(&hei->hei_nhead, callback, arg); CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_READ(&hfi->hfi_lock); CVW_EXIT_READ(&hks->hks_lock); if (error == 0 && canrun) { TAILQ_FOREACH(h, &hei->hei_head, hi_entry) { callback(HN_REGISTER, arg, hfi->hfi_family.hf_name, hei->hei_event->he_name, h->hi_hook.h_name); } } if (canrun) hook_wait_unsetflag(&hei->hei_waiter, FWF_ADD_ACTIVE); return (error); } /* * Function: hook_event_notify_unregister * Returns: int - 0 = success, else failure * Parameters: hfi(I) - hook family * event(I) - name of the event * callback(I) - function to be called * * Remove the given callback from the named event's list of functions * to call when a hook is added or removed. */ int hook_event_notify_unregister(hook_family_int_t *hfi, char *event, hook_notify_fn_t callback) { hook_event_int_t *hei; boolean_t free_event; boolean_t canrun; hook_int_t *h; void *arg; int error; canrun = B_FALSE; CVW_ENTER_READ(&hfi->hfi_lock); hei = hook_event_find(hfi, event); if (hei == NULL) { CVW_EXIT_READ(&hfi->hfi_lock); return (ESRCH); } CVW_ENTER_WRITE(&hei->hei_lock); (void) hook_wait_setflag(&hei->hei_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE); error = hook_notify_unregister(&hei->hei_nhead, callback, &arg); hook_wait_unsetflag(&hei->hei_waiter, FWF_DEL_ACTIVE); /* * hei_condemned has been set if someone tried to remove the * event but couldn't because there were still things attached to * it. Now that we've done a successful remove, if it is now empty * then by all rights we should be free'ing it too. Note that the * expectation is that only the caller of hook_event_add will ever * call hook_event_remove. */ if ((error == 0) && hei->hei_condemned && TAILQ_EMPTY(&hei->hei_head) && TAILQ_EMPTY(&hei->hei_nhead)) { free_event = B_TRUE; } else { free_event = B_FALSE; } if (error == 0 && !free_event) { canrun = (hook_wait_setflag(&hei->hei_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) != -1); } CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_READ(&hfi->hfi_lock); if (canrun) { TAILQ_FOREACH(h, &hei->hei_head, hi_entry) { callback(HN_UNREGISTER, arg, hfi->hfi_family.hf_name, hei->hei_event->he_name, h->hi_hook.h_name); } hook_wait_unsetflag(&hei->hei_waiter, FWF_ADD_ACTIVE); } if (free_event) { /* * It is safe to pass in hfi here, without a lock, because * our structure (hei) is still on one of its lists and thus * it won't be able to disappear yet... */ hook_event_free(hei, hfi); } return (error); } /* * Function: hook_event_notify_run * Returns: None * Parameters: nrun(I) - pointer to the list of callbacks to execute * hfi(I) - hook stack pointer to execute callbacks for * name(I) - name of a hook family * cmd(I) - either HN_UNREGISTER or HN_REGISTER * * Execute all of the callbacks registered for this event. */ static void hook_event_notify_run(hook_event_int_t *hei, hook_family_int_t *hfi, char *event, char *name, hook_notify_cmd_t cmd) { hook_notify_run(&hei->hei_nhead, hfi->hfi_family.hf_name, event, name, cmd); } /* * Function: hook_register * Returns: int - 0 = success, else = failure * Parameters: hfi(I) - internal family pointer * event(I) - event name string * h(I) - hook pointer * * Add new hook to hook list on the specified family and event. */ int hook_register(hook_family_int_t *hfi, char *event, hook_t *h) { hook_event_int_t *hei; hook_int_t *hi, *new; int error; ASSERT(hfi != NULL); ASSERT(event != NULL); ASSERT(h != NULL); if (hfi->hfi_stack->hks_shutdown) return (0); /* Alloc hook_int_t and copy hook */ new = hook_copy(h); if (new == NULL) return (ENOMEM); /* * Since hook add/remove only impact event, so it is unnecessary * to hold global family write lock. Just get read lock here to * ensure event will not be removed when doing hooks operation */ CVW_ENTER_WRITE(&hfi->hfi_lock); hei = hook_event_find(hfi, event); if (hei == NULL) { CVW_EXIT_WRITE(&hfi->hfi_lock); hook_int_free(new, hfi->hfi_stack->hks_netstackid); return (ENXIO); } CVW_ENTER_WRITE(&hei->hei_lock); /* * If we've run either the remove() or shutdown(), do not allow any * more hooks to be added to this event. */ if (hei->hei_shutdown) { error = ESHUTDOWN; goto bad_add; } hi = hook_find(hei, h); if (hi != NULL) { error = EEXIST; goto bad_add; } if (hook_wait_setflag(&hei->hei_waiter, FWF_ADD_WAIT_MASK, FWF_ADD_WANTED, FWF_ADD_ACTIVE) == -1) { error = ENOENT; bad_add: CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_WRITE(&hfi->hfi_lock); hook_int_free(new, hfi->hfi_stack->hks_netstackid); return (error); } /* Add to hook list head */ error = hook_insert(&hei->hei_head, new); if (error == 0) { hei->hei_event->he_interested = B_TRUE; hei->hei_kstats.hooks_added.value.ui64++; hook_init_kstats(hfi, hei, new); } CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_WRITE(&hfi->hfi_lock); /* * Note that the name string passed through to the notify callbacks * is from the original hook being registered, not the copy being * inserted. */ if (error == 0) hook_event_notify_run(hei, hfi, event, h->h_name, HN_REGISTER); hook_wait_unsetflag(&hei->hei_waiter, FWF_ADD_ACTIVE); return (error); } /* * Function: hook_insert * Returns: int - 0 = success, else = failure * Parameters: head(I) - pointer to hook list to insert hook onto * new(I) - pointer to hook to be inserted * * Try to insert the hook onto the list of hooks according to the hints * given in the hook to be inserted and those that already exist on the * list. For now, the implementation permits only a single hook to be * either first or last and names provided with before or after are only * loosely coupled with the action. */ static int hook_insert(hook_int_head_t *head, hook_int_t *new) { hook_int_t *before; hook_int_t *hi; hook_t *hih; hook_t *h = &new->hi_hook; switch (new->hi_hook.h_hint) { case HH_NONE : before = NULL; /* * If there is no hint present (or not one that can be * satisfied now) then try to at least respect the wishes * of those that want to be last. If there are none wanting * to be last then add the new hook to the tail of the * list - this means we keep any wanting to be first * happy without having to search for HH_FIRST. */ TAILQ_FOREACH(hi, head, hi_entry) { hih = &hi->hi_hook; if ((hih->h_hint == HH_AFTER) && (strcmp(h->h_name, (char *)hih->h_hintvalue) == 0)) { TAILQ_INSERT_BEFORE(hi, new, hi_entry); return (0); } if ((hih->h_hint == HH_BEFORE) && (before == NULL) && (strcmp(h->h_name, (char *)hih->h_hintvalue) == 0)) { before = hi; } } if (before != NULL) { TAILQ_INSERT_AFTER(head, before, new, hi_entry); return (0); } hook_insert_plain(head, new); break; case HH_FIRST : hi = TAILQ_FIRST(head); if ((hi != NULL) && (hi->hi_hook.h_hint == HH_FIRST)) return (EBUSY); TAILQ_INSERT_HEAD(head, new, hi_entry); break; case HH_LAST : hi = TAILQ_LAST(head, hook_int_head); if ((hi != NULL) && (hi->hi_hook.h_hint == HH_LAST)) return (EBUSY); TAILQ_INSERT_TAIL(head, new, hi_entry); break; case HH_BEFORE : hi = hook_find_byname(head, (char *)new->hi_hook.h_hintvalue); if (hi == NULL) return (hook_insert_afterbefore(head, new)); if (hi->hi_hook.h_hint == HH_FIRST) return (EBUSY); TAILQ_INSERT_BEFORE(hi, new, hi_entry); break; case HH_AFTER : hi = hook_find_byname(head, (char *)new->hi_hook.h_hintvalue); if (hi == NULL) return (hook_insert_afterbefore(head, new)); if (hi->hi_hook.h_hint == HH_LAST) return (EBUSY); TAILQ_INSERT_AFTER(head, hi, new, hi_entry); break; default : return (EINVAL); } return (0); } /* * Function: hook_insert_plain * Returns: int - 0 = success, else = failure * Parameters: head(I) - pointer to hook list to insert hook onto * new(I) - pointer to hook to be inserted * * Insert a hook such that it respects the wishes of those that want to * be last. If there are none wanting to be last then add the new hook * to the tail of the list - this means we keep any wanting to be first * happy without having to search for HH_FIRST. */ static void hook_insert_plain(hook_int_head_t *head, hook_int_t *new) { hook_int_t *hi; hi = TAILQ_FIRST(head); if (hi != NULL) { if (hi->hi_hook.h_hint == HH_LAST) { TAILQ_INSERT_BEFORE(hi, new, hi_entry); } else { TAILQ_INSERT_TAIL(head, new, hi_entry); } } else { TAILQ_INSERT_TAIL(head, new, hi_entry); } } /* * Function: hook_insert_afterbefore * Returns: int - 0 = success, else = failure * Parameters: head(I) - pointer to hook list to insert hook onto * new(I) - pointer to hook to be inserted * * Simple insertion of a hook specifying a HH_BEFORE or HH_AFTER was not * possible, so now we need to be more careful. The first pass is to go * through the list and look for any other hooks that also specify the * same hint name as the new one. The object of this exercise is to make * sure that hooks with HH_BEFORE always appear on the list before those * with HH_AFTER so that when said hook arrives, it can be placed in the * middle of the BEFOREs and AFTERs. If this condition does not arise, * just use hook_insert_plain() to try and insert the hook somewhere that * is innocuous to existing efforts. */ static int hook_insert_afterbefore(hook_int_head_t *head, hook_int_t *new) { hook_int_t *hi; hook_t *nh; hook_t *h; nh = &new->hi_hook; ASSERT(new->hi_hook.h_hint != HH_NONE); ASSERT(new->hi_hook.h_hint != HH_LAST); ASSERT(new->hi_hook.h_hint != HH_FIRST); /* * First, look through the list to see if there are any other * before's or after's that have a matching hint name. */ TAILQ_FOREACH(hi, head, hi_entry) { h = &hi->hi_hook; switch (h->h_hint) { case HH_FIRST : case HH_LAST : case HH_NONE : break; case HH_BEFORE : if ((nh->h_hint == HH_BEFORE) && (strcmp((char *)h->h_hintvalue, (char *)nh->h_hintvalue) == 0)) { TAILQ_INSERT_AFTER(head, hi, new, hi_entry); return (0); } if ((nh->h_hint == HH_AFTER) && (strcmp((char *)h->h_hintvalue, (char *)nh->h_hintvalue) == 0)) { TAILQ_INSERT_BEFORE(hi, new, hi_entry); return (0); } break; case HH_AFTER : if ((nh->h_hint == HH_AFTER) && (strcmp((char *)h->h_hintvalue, (char *)nh->h_hintvalue) == 0)) { TAILQ_INSERT_AFTER(head, hi, new, hi_entry); return (0); } if ((nh->h_hint == HH_BEFORE) && (strcmp((char *)h->h_hintvalue, (char *)nh->h_hintvalue) == 0)) { TAILQ_INSERT_BEFORE(hi, new, hi_entry); return (0); } break; } } hook_insert_plain(head, new); return (0); } /* * Function: hook_unregister * Returns: int - 0 = success, else = failure * Parameters: hfi(I) - internal family pointer * event(I) - event name string * h(I) - hook pointer * * Remove hook from hook list on specific family, event */ int hook_unregister(hook_family_int_t *hfi, char *event, hook_t *h) { hook_event_int_t *hei; hook_int_t *hi; boolean_t free_event; ASSERT(hfi != NULL); ASSERT(h != NULL); CVW_ENTER_WRITE(&hfi->hfi_lock); hei = hook_event_find(hfi, event); if (hei == NULL) { CVW_EXIT_WRITE(&hfi->hfi_lock); return (ENXIO); } /* Hold write lock for event */ CVW_ENTER_WRITE(&hei->hei_lock); hi = hook_find(hei, h); if (hi == NULL) { CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_WRITE(&hfi->hfi_lock); return (ENXIO); } if (hook_wait_setflag(&hei->hei_waiter, FWF_DEL_WAIT_MASK, FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) { CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_WRITE(&hfi->hfi_lock); return (ENOENT); } /* Remove from hook list */ TAILQ_REMOVE(&hei->hei_head, hi, hi_entry); free_event = B_FALSE; if (TAILQ_EMPTY(&hei->hei_head)) { hei->hei_event->he_interested = B_FALSE; /* * If the delete pending flag has been set and there are * no notifiers on the event (and we've removed the last * hook) then we need to free this event after we're done. */ if (hei->hei_condemned && TAILQ_EMPTY(&hei->hei_nhead)) free_event = B_TRUE; } hei->hei_kstats.hooks_removed.value.ui64++; CVW_EXIT_WRITE(&hei->hei_lock); CVW_EXIT_WRITE(&hfi->hfi_lock); /* * While the FWF_DEL_ACTIVE flag is set, the hook_event_int_t * will not be free'd and thus the hook_family_int_t wil not * be free'd either. */ hook_event_notify_run(hei, hfi, event, h->h_name, HN_UNREGISTER); hook_wait_unsetflag(&hei->hei_waiter, FWF_DEL_ACTIVE); hook_int_free(hi, hfi->hfi_stack->hks_netstackid); if (free_event) hook_event_free(hei, hfi); return (0); } /* * Function: hook_find_byname * Returns: internal hook pointer - NULL = Not match * Parameters: hei(I) - internal event pointer * name(I)- hook name * * Search an event's list of hooks to see if there is a hook present that * has a matching name to the one being looked for. */ static hook_int_t * hook_find_byname(hook_int_head_t *head, char *name) { hook_int_t *hi; TAILQ_FOREACH(hi, head, hi_entry) { if (strcmp(hi->hi_hook.h_name, name) == 0) return (hi); } return (NULL); } /* * Function: hook_find * Returns: internal hook pointer - NULL = Not match * Parameters: hei(I) - internal event pointer * h(I) - hook pointer * * Search an event's list of hooks to see if there is already one that * matches the hook being passed in. Currently the only criteria for a * successful search here is for the names to be the same. */ static hook_int_t * hook_find(hook_event_int_t *hei, hook_t *h) { ASSERT(hei != NULL); ASSERT(h != NULL); return (hook_find_byname(&hei->hei_head, h->h_name)); } /* * Function: hook_copy * Returns: internal hook pointer - NULL = Failed * Parameters: src(I) - hook pointer * * Allocate internal hook block and duplicate incoming hook. * No locks should be held across this function as it may sleep. * Because hook_copy() is responsible for the creation of the internal * hook structure that is used here, it takes on population the structure * with the kstat information. Note that while the kstat bits are * seeded here, their installation of the kstats is handled elsewhere. */ static hook_int_t * hook_copy(hook_t *src) { hook_int_t *new; hook_t *dst; int len; ASSERT(src != NULL); ASSERT(src->h_name != NULL); new = (hook_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP); /* Copy body */ dst = &new->hi_hook; *dst = *src; /* Copy name */ len = strlen(src->h_name); dst->h_name = (char *)kmem_alloc(len + 1, KM_SLEEP); (void) strcpy(dst->h_name, src->h_name); /* * This is initialised in this manner to make it safer to use the * same pointer in the kstats field. */ dst->h_hintvalue = (uintptr_t)""; if (dst->h_hint == HH_BEFORE || dst->h_hint == HH_AFTER) { len = strlen((char *)src->h_hintvalue); if (len > 0) { dst->h_hintvalue = (uintptr_t)kmem_alloc(len + 1, KM_SLEEP); (void) strcpy((char *)dst->h_hintvalue, (char *)src->h_hintvalue); } } return (new); } /* * Function: hook_init_kstats * Returns: None * Parameters: hfi(I) - pointer to the family that owns the event. * hei(I) - pointer to the event that owns this hook * hi(I) - pointer to the hook for which we create kstats for * * Each hook that is registered with this framework has its own kstats * set up so that we can provide an easy way in which to observe the * look of hooks (using the kstat command.) The position is set to 0 * here but is recalculated after we know the insertion has been a * success. */ static void hook_init_kstats(hook_family_int_t *hfi, hook_event_int_t *hei, hook_int_t *hi) { hook_hook_kstat_t template = { { "version", KSTAT_DATA_INT32 }, { "flags", KSTAT_DATA_UINT32 }, { "hint", KSTAT_DATA_INT32 }, { "hint_value", KSTAT_DATA_STRING }, { "position", KSTAT_DATA_INT32 }, { "hook_hits", KSTAT_DATA_UINT64 } }; hook_stack_t *hks; size_t kslen; int position; hook_int_t *h; kslen = strlen(hfi->hfi_family.hf_name) + strlen(hei->hei_event->he_name) + 2; hi->hi_ksname = (char *)kmem_zalloc(kslen, KM_SLEEP); (void) snprintf(hi->hi_ksname, kslen, "%s/%s", hfi->hfi_family.hf_name, hei->hei_event->he_name); hks = hfi->hfi_stack; hi->hi_kstatp = kstat_create_netstack(hi->hi_ksname, 0, hi->hi_hook.h_name, "hook", KSTAT_TYPE_NAMED, sizeof (hi->hi_kstats) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL, hks->hks_netstackid); /* Initialise the kstats for the structure */ bcopy(&template, &hi->hi_kstats, sizeof (template)); hi->hi_kstats.hook_version.value.i32 = hi->hi_hook.h_version; hi->hi_kstats.hook_flags.value.ui32 = hi->hi_hook.h_flags; hi->hi_kstats.hook_hint.value.i32 = hi->hi_hook.h_hint; hi->hi_kstats.hook_position.value.i32 = 0; hi->hi_kstats.hook_hits.value.ui64 = 0; switch (hi->hi_hook.h_hint) { case HH_BEFORE : case HH_AFTER : kstat_named_setstr(&(hi->hi_kstats.hook_hintvalue), (const char *)hi->hi_hook.h_hintvalue); break; default : kstat_named_setstr(&(hi->hi_kstats.hook_hintvalue), hook_hintvalue_none); break; } if (hi->hi_kstatp != NULL) { hi->hi_kstatp->ks_data = (void *)&hi->hi_kstats; hi->hi_kstatp->ks_private = (void *)(uintptr_t)hks->hks_netstackid; hi->hi_kstatp->ks_data_size += KSTAT_NAMED_STR_BUFLEN(&(hi->hi_kstats.hook_hintvalue)) + 1; kstat_install(hi->hi_kstatp); } position = 1; TAILQ_FOREACH(h, &hei->hei_head, hi_entry) { h->hi_kstats.hook_position.value.ui32 = position++; } } /* * Function: hook_int_free * Returns: None * Parameters: hi(I) - internal hook pointer * * Free memory allocated to support a hook. */ static void hook_int_free(hook_int_t *hi, netstackid_t stackid) { int len; ASSERT(hi != NULL); /* Free name space */ if (hi->hi_hook.h_name != NULL) { kmem_free(hi->hi_hook.h_name, strlen(hi->hi_hook.h_name) + 1); } if (hi->hi_ksname != NULL) { kmem_free(hi->hi_ksname, strlen(hi->hi_ksname) + 1); } /* Free the name used with the before/after hints. */ switch (hi->hi_hook.h_hint) { case HH_BEFORE : case HH_AFTER : len = strlen((char *)hi->hi_hook.h_hintvalue); if (len > 0) kmem_free((void *)hi->hi_hook.h_hintvalue, len + 1); break; default : break; } if (hi->hi_kstatp != NULL) kstat_delete_netstack(hi->hi_kstatp, stackid); /* Free container */ kmem_free(hi, sizeof (*hi)); } /* * Function: hook_alloc * Returns: hook_t * - pointer to new hook structure * Parameters: version(I) - version number of the API when compiled * * This function serves as the interface for consumers to obtain a hook_t * structure. At this point in time, there is only a single "version" of * it, leading to a straight forward function. In a perfect world the * h_vesion would be a protected data structure member, but C isn't that * advanced... */ hook_t * hook_alloc(const int h_version) { hook_t *h; h = kmem_zalloc(sizeof (hook_t), KM_SLEEP); h->h_version = h_version; return (h); } /* * Function: hook_free * Returns: None * Parameters: h(I) - external hook pointer * * This function only free's memory allocated with hook_alloc(), so that if * (for example) kernel memory was allocated for h_name, this needs to be * free'd before calling hook_free(). */ void hook_free(hook_t *h) { kmem_free(h, sizeof (*h)); } /* * Function: hook_notify_register * Returns: int - 0 = success, else failure * Parameters: head(I) - top of the list of callbacks * callback(I) - function to be called * arg(I) - arg to pass back to the function * * This function implements the modification of the list of callbacks * that are registered when someone wants to be advised of a change * that has happened. */ static int hook_notify_register(hook_notify_head_t *head, hook_notify_fn_t callback, void *arg) { hook_notify_t *hn; TAILQ_FOREACH(hn, head, hn_entry) { if (hn->hn_func == callback) { return (EEXIST); } } hn = (hook_notify_t *)kmem_alloc(sizeof (*hn), KM_SLEEP); hn->hn_func = callback; hn->hn_arg = arg; TAILQ_INSERT_TAIL(head, hn, hn_entry); return (0); } /* * Function: hook_notify_unregister * Returns: int - 0 = success, else failure * Parameters: stackid(I) - netstack identifier * callback(I) - function to be called * parg(O) - pointer to storage for pointer * * When calling this function, the provision of a valid pointer in parg * allows the caller to be made aware of what argument the hook function * was expecting. This then allows the simulation of HN_UNREGISTER events * when a notify-unregister is performed. */ static int hook_notify_unregister(hook_notify_head_t *head, hook_notify_fn_t callback, void **parg) { hook_notify_t *hn; ASSERT(parg != NULL); TAILQ_FOREACH(hn, head, hn_entry) { if (hn->hn_func == callback) break; } if (hn == NULL) return (ESRCH); *parg = hn->hn_arg; TAILQ_REMOVE(head, hn, hn_entry); kmem_free(hn, sizeof (*hn)); return (0); } /* * Function: hook_notify_run * Returns: None * Parameters: head(I) - top of the list of callbacks * family(I) - name of the hook family that owns the event * event(I) - name of the event being changed * name(I) - name of the object causing change * cmd(I) - either HN_UNREGISTER or HN_REGISTER * * This function walks through the list of registered callbacks and * executes each one, passing back the arg supplied when registered * and the name of the family (that owns the event), event (the thing * to which we're making a change) and finally a name that describes * what is being added or removed, as indicated by cmd. * * This function does not acquire or release any lock as it is required * that code calling it do so before hand. The use of hook_notify_head_t * is protected by the use of flagwait_t in the structures that own this * list and with the use of the FWF_ADD/DEL_ACTIVE flags. */ static void hook_notify_run(hook_notify_head_t *head, char *family, char *event, char *name, hook_notify_cmd_t cmd) { hook_notify_t *hn; TAILQ_FOREACH(hn, head, hn_entry) { (*hn->hn_func)(cmd, hn->hn_arg, family, event, name); } }