diff options
Diffstat (limited to 'sys/netgraph/ng_base.c')
-rw-r--r-- | sys/netgraph/ng_base.c | 1633 |
1 files changed, 1633 insertions, 0 deletions
diff --git a/sys/netgraph/ng_base.c b/sys/netgraph/ng_base.c new file mode 100644 index 0000000..4234355 --- /dev/null +++ b/sys/netgraph/ng_base.c @@ -0,0 +1,1633 @@ + +/* + * ng_base.c + * + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Authors: Julian Elischer <julian@whistle.com> + * Archie Cobbs <archie@whistle.com> + * + * $FreeBSD$ + * $Whistle: ng_base.c,v 1.39 1999/01/28 23:54:53 julian Exp $ + */ + +/* + * This file implements the base netgraph code. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/syslog.h> +#include <sys/linker.h> +#include <sys/queue.h> +#include <sys/mbuf.h> +#include <sys/socketvar.h> + +#include <net/netisr.h> + +#include <netgraph/ng_message.h> +#include <netgraph/netgraph.h> + +/* List of all nodes */ +static LIST_HEAD(, ng_node) nodelist; + +/* List of installed types */ +static LIST_HEAD(, ng_type) typelist; + +/* Internal functions */ +static int ng_add_hook(node_p node, const char *name, hook_p * hookp); +static int ng_connect(hook_p hook1, hook_p hook2); +static void ng_disconnect_hook(hook_p hook); +static int ng_generic_msg(node_p here, struct ng_mesg *msg, + const char *retaddr, struct ng_mesg ** resp); +static node_p ng_decodeidname(const char *name); +static int ngb_mod_event(module_t mod, int event, void *data); +static void ngintr(void); + +/* Our own netgraph malloc type */ +MALLOC_DEFINE(M_NETGRAPH, "netgraph", "netgraph structures and ctrl messages"); + +/* Set this to Debugger("X") to catch all errors as they occur */ +#ifndef TRAP_ERROR +#define TRAP_ERROR +#endif + +/************************************************************************ + Node routines +************************************************************************/ + +/* + * Instantiate a node of the requested type + */ +int +ng_make_node(const char *typename, node_p *nodepp) +{ + struct ng_type *type; + + /* Check that the type makes sense */ + if (typename == NULL) { + TRAP_ERROR; + return (EINVAL); + } + + /* Locate the node type */ + if ((type = ng_findtype(typename)) == NULL) { + char *path, filename[NG_TYPELEN + 4]; + linker_file_t lf; + int error; + + /* Not found, try to load it as a loadable module */ + snprintf(filename, sizeof(filename), "ng_%s.ko", typename); + if ((path = linker_search_path(filename)) == NULL) + return (ENXIO); + error = linker_load_file(path, &lf); + FREE(path, M_LINKER); + if (error != 0) + return (error); + lf->userrefs++; /* pretend loaded by the syscall */ + + /* Try again, as now the type should have linked itself in */ + if ((type = ng_findtype(typename)) == NULL) + return (ENXIO); + } + + /* Call the constructor */ + if (type->constructor != NULL) + return ((*type->constructor)(nodepp)); + else + return (ng_make_node_common(type, nodepp)); +} + +/* + * Generic node creation. Called by node constructors. + * The returned node has a reference count of 1. + */ +int +ng_make_node_common(struct ng_type *type, node_p *nodepp) +{ + node_p node; + + /* Require the node type to have been already installed */ + if (ng_findtype(type->name) == NULL) { + TRAP_ERROR; + return (EINVAL); + } + + /* Make a node and try attach it to the type */ + MALLOC(node, node_p, sizeof(*node), M_NETGRAPH, M_WAITOK); + if (node == NULL) { + TRAP_ERROR; + return (ENOMEM); + } + bzero(node, sizeof(*node)); + node->type = type; + node->refs++; /* note reference */ + type->refs++; + + /* Link us into the node linked list */ + LIST_INSERT_HEAD(&nodelist, node, nodes); + + /* Initialize hook list for new node */ + LIST_INIT(&node->hooks); + + /* Done */ + *nodepp = node; + return (0); +} + +/* + * Forceably start the shutdown process on a node. Either call + * it's shutdown method, or do the default shutdown if there is + * no type-specific method. + * + * Persistent nodes must have a type-specific method which + * resets the NG_INVALID flag. + */ +void +ng_rmnode(node_p node) +{ + /* Check if it's already shutting down */ + if ((node->flags & NG_INVALID) != 0) + return; + + /* Add an extra reference so it doesn't go away during this */ + node->refs++; + + /* Mark it invalid so any newcomers know not to try use it */ + node->flags |= NG_INVALID; + + /* Ask the type if it has anything to do in this case */ + if (node->type && node->type->shutdown) + (*node->type->shutdown)(node); + else { /* do the default thing */ + ng_unname(node); + ng_cutlinks(node); + ng_unref(node); + } + + /* Remove extra reference, possibly the last */ + ng_unref(node); +} + +/* + * Called by the destructor to remove any STANDARD external references + */ +void +ng_cutlinks(node_p node) +{ + hook_p hook; + + /* Make sure that this is set to stop infinite loops */ + node->flags |= NG_INVALID; + + /* If we have sleepers, wake them up; they'll see NG_INVALID */ + if (node->sleepers) + wakeup(node); + + /* Notify all remaining connected nodes to disconnect */ + while ((hook = LIST_FIRST(&node->hooks)) != NULL) + ng_destroy_hook(hook); +} + +/* + * Remove a reference to the node, possibly the last + */ +void +ng_unref(node_p node) +{ + if (--node->refs <= 0) { + node->type->refs--; + LIST_REMOVE(node, nodes); + FREE(node, M_NETGRAPH); + } +} + +/* + * Wait for a node to come ready. Returns a node with a reference count; + * don't forget to drop it when we are done with it using ng_release_node(). + */ +int +ng_wait_node(node_p node, char *msg) +{ + int s, error = 0; + + if (msg == NULL) + msg = "netgraph"; + s = splnet(); + node->sleepers++; + node->refs++; /* the sleeping process counts as a reference */ + while ((node->flags & (NG_BUSY | NG_INVALID)) == NG_BUSY) + error = tsleep(node, (PZERO + 1) | PCATCH, msg, 0); + node->sleepers--; + if (node->flags & NG_INVALID) { + TRAP_ERROR; + error = ENXIO; + } else { +#ifdef DIAGNOSTIC + if (node->refs == 1) { + panic(__FUNCTION__); + error = ENXIO; + } +#endif + node->flags |= NG_BUSY; + } + splx(s); + + /* Release the reference we had on it */ + if (error != 0) + ng_unref(node); + return error; +} + +/* + * Release a node acquired via ng_wait_node() + */ +void +ng_release_node(node_p node) +{ + /* Declare that we don't want it */ + node->flags &= ~NG_BUSY; + + /* If we have sleepers, then wake them up */ + if (node->sleepers) + wakeup(node); + + /* We also have a reference.. drop it too */ + ng_unref(node); +} + +/************************************************************************ + Node name handling +************************************************************************/ + +/* + * Assign a node a name. Once assigned, the name cannot be changed. + */ +int +ng_name_node(node_p node, const char *name) +{ + int i; + + /* Check the name is valid */ + for (i = 0; i < NG_NODELEN + 1; i++) { + if (name[i] == '\0' || name[i] == '.' || name[i] == ':') + break; + } + if (i == 0 || name[i] != '\0') { + TRAP_ERROR; + return (EINVAL); + } + if (ng_decodeidname(name) != NULL) { + TRAP_ERROR; + return (EINVAL); + } + + /* Check the node isn't already named */ + if (node->name != NULL) { + TRAP_ERROR; + return (EISCONN); + } + + /* Check the name isn't already being used */ + if (ng_findname(node, name) != NULL) { + TRAP_ERROR; + return (EADDRINUSE); + } + + /* Allocate space and copy it */ + MALLOC(node->name, char *, strlen(name) + 1, M_NETGRAPH, M_WAITOK); + if (node->name == NULL) { + TRAP_ERROR; + return (ENOMEM); + } + strcpy(node->name, name); + + /* The name counts as a reference */ + node->refs++; + return (0); +} + +/* + * Find a node by absolute name. The name should NOT end with ':' + * The name "." means "this node" and "[xxx]" means "the node + * with ID (ie, at address) xxx". + * + * Returns the node if found, else NULL. + */ +node_p +ng_findname(node_p this, const char *name) +{ + node_p node, temp; + + /* "." means "this node" */ + if (strcmp(name, ".") == 0) + return(this); + + /* Check for name-by-ID */ + if ((temp = ng_decodeidname(name)) != NULL) { + + /* Make sure the ID really points to a node */ + LIST_FOREACH(node, &nodelist, nodes) { + if (node == temp) + break; + } + return (node); + } + + /* Find node by name */ + LIST_FOREACH(node, &nodelist, nodes) { + if (node->name != NULL && strcmp(node->name, name) == 0) + break; + } + return (node); +} + +/* + * Decode a ID name, eg. "[f03034de]". Returns NULL if the + * string is not valid, otherwise returns the ID cast to a + * node pointer. + * + * NOTE: the returned pointer is not necessarily valid! + */ +static node_p +ng_decodeidname(const char *name) +{ + u_int32_t val; + int k, len; + + /* Basic checks */ + for (len = 0; name[len] != '\0'; len++) { + const char ch = name[len]; + + if (len == 0) { + if (ch != '[') + return (NULL); + } else if (name[len + 1] == '\0') { + if (ch != ']') + return (NULL); + } else if (!((ch >= '0' && ch <= '9') + || (ch >= 'a' && ch <= 'f') + || (ch >= 'A' && ch <= 'F'))) + return (NULL); + } + if (len < 3 || len > (sizeof(val) * 2) + 2 || name[1] == '0') + return (NULL); + + /* Convert number to binary */ + for (val = 0, k = 1; k < len - 1; k++) { + const char ch = name[k]; + + if (ch <= '9') + val = (val << 4) | ((ch - '0') & 0xf); + else + val = (val << 4) | (((ch & 0xdf) - 'A' + 10) & 0xf); + } + return ((node_p) val); +} + +/* + * Remove a name from a node. This should only be called + * when shutting down and removing the node. + */ +void +ng_unname(node_p node) +{ + if (node->name) { + FREE(node->name, M_NETGRAPH); + node->name = NULL; + ng_unref(node); + } +} + +/************************************************************************ + Hook routines + + Names are not optional. Hooks are always connected, except for a + brief moment within these routines. + +************************************************************************/ + +/* + * Remove a hook reference + */ +static void +ng_unref_hook(hook_p hook) +{ + if (--hook->refs == 0) + FREE(hook, M_NETGRAPH); +} + +/* + * Add an unconnected hook to a node. Only used internally. + */ +static int +ng_add_hook(node_p node, const char *name, hook_p *hookp) +{ + hook_p hook; + int error = 0; + + /* Check that the given name is good */ + if (name == NULL) { + TRAP_ERROR; + return (EINVAL); + } + LIST_FOREACH(hook, &node->hooks, hooks) { + if (strcmp(hook->name, name) == 0) { + TRAP_ERROR; + return (EEXIST); + } + } + + /* Allocate the hook and link it up */ + MALLOC(hook, hook_p, sizeof(*hook), M_NETGRAPH, M_WAITOK); + if (hook == NULL) { + TRAP_ERROR; + return (ENOMEM); + } + bzero(hook, sizeof(*hook)); + hook->refs = 1; + hook->flags = HK_INVALID; + hook->node = node; + node->refs++; /* each hook counts as a reference */ + + /* Check if the node type code has something to say about it */ + if (node->type->newhook != NULL) + if ((error = (*node->type->newhook)(node, hook, name)) != 0) + goto fail; + + /* + * The 'type' agrees so far, so go ahead and link it in. + * We'll ask again later when we actually connect the hooks. + */ + LIST_INSERT_HEAD(&node->hooks, hook, hooks); + node->numhooks++; + + /* Set hook name */ + MALLOC(hook->name, char *, strlen(name) + 1, M_NETGRAPH, M_WAITOK); + if (hook->name == NULL) { + error = ENOMEM; + LIST_REMOVE(hook, hooks); + node->numhooks--; +fail: + hook->node = NULL; + ng_unref(node); + ng_unref_hook(hook); /* this frees the hook */ + return (error); + } + strcpy(hook->name, name); + if (hookp) + *hookp = hook; + return (error); +} + +/* + * Connect a pair of hooks. Only used internally. + */ +static int +ng_connect(hook_p hook1, hook_p hook2) +{ + int error; + + hook1->peer = hook2; + hook2->peer = hook1; + + /* Give each node the opportunity to veto the impending connection */ + if (hook1->node->type->connect) { + if ((error = (*hook1->node->type->connect) (hook1))) { + ng_destroy_hook(hook1); /* also zaps hook2 */ + return (error); + } + } + if (hook2->node->type->connect) { + if ((error = (*hook2->node->type->connect) (hook2))) { + ng_destroy_hook(hook2); /* also zaps hook1 */ + return (error); + } + } + hook1->flags &= ~HK_INVALID; + hook2->flags &= ~HK_INVALID; + return (0); +} + +/* + * Destroy a hook + * + * As hooks are always attached, this really destroys two hooks. + * The one given, and the one attached to it. Disconnect the hooks + * from each other first. + */ +void +ng_destroy_hook(hook_p hook) +{ + hook_p peer = hook->peer; + + hook->flags |= HK_INVALID; /* as soon as possible */ + if (peer) { + peer->flags |= HK_INVALID; /* as soon as possible */ + hook->peer = NULL; + peer->peer = NULL; + ng_disconnect_hook(peer); + } + ng_disconnect_hook(hook); +} + +/* + * Notify the node of the hook's demise. This may result in more actions + * (e.g. shutdown) but we don't do that ourselves and don't know what + * happens there. If there is no appropriate handler, then just remove it + * (and decrement the reference count of it's node which in turn might + * make something happen). + */ +static void +ng_disconnect_hook(hook_p hook) +{ + node_p node = hook->node; + + /* + * Remove the hook from the node's list to avoid possible recursion + * in case the disconnection results in node shutdown. + */ + LIST_REMOVE(hook, hooks); + node->numhooks--; + if (node->type->disconnect) { + /* + * The type handler may elect to destroy the peer so don't + * trust its existance after this point. + */ + (*node->type->disconnect) (hook); + } + ng_unref(node); /* might be the last reference */ + if (hook->name) + FREE(hook->name, M_NETGRAPH); + hook->node = NULL; /* may still be referenced elsewhere */ + ng_unref_hook(hook); +} + +/* + * Take two hooks on a node and merge the connection so that the given node + * is effectively bypassed. + */ +int +ng_bypass(hook_p hook1, hook_p hook2) +{ + if (hook1->node != hook2->node) + return (EINVAL); + hook1->peer->peer = hook2->peer; + hook2->peer->peer = hook1->peer; + + /* XXX If we ever cache methods on hooks update them as well */ + hook1->peer = NULL; + hook2->peer = NULL; + ng_destroy_hook(hook1); + ng_destroy_hook(hook2); + return (0); +} + +/* + * Install a new netgraph type + */ +int +ng_newtype(struct ng_type *tp) +{ + const size_t namelen = strlen(tp->name); + + /* Check version and type name fields */ + if (tp->version != NG_VERSION || namelen == 0 || namelen > NG_TYPELEN) { + TRAP_ERROR; + return (EINVAL); + } + + /* Check for name collision */ + if (ng_findtype(tp->name) != NULL) { + TRAP_ERROR; + return (EEXIST); + } + + /* Link in new type */ + LIST_INSERT_HEAD(&typelist, tp, types); + tp->refs = 0; + return (0); +} + +/* + * Look for a type of the name given + */ +struct ng_type * +ng_findtype(const char *typename) +{ + struct ng_type *type; + + LIST_FOREACH(type, &typelist, types) { + if (strcmp(type->name, typename) == 0) + break; + } + return (type); +} + + +/************************************************************************ + Composite routines +************************************************************************/ + +/* + * Make a peer and connect. The order is arranged to minimise + * the work needed to back out in case of error. + */ +int +ng_mkpeer(node_p node, const char *name, const char *name2, char *type) +{ + node_p node2; + hook_p hook; + hook_p hook2; + int error; + + if ((error = ng_add_hook(node, name, &hook))) + return (error); + if ((error = ng_make_node(type, &node2))) { + ng_destroy_hook(hook); + return (error); + } + if ((error = ng_add_hook(node2, name2, &hook2))) { + ng_rmnode(node2); + ng_destroy_hook(hook); + return (error); + } + + /* + * Actually link the two hooks together.. on failure they are + * destroyed so we don't have to do that here. + */ + if ((error = ng_connect(hook, hook2))) + ng_rmnode(node2); + return (error); +} + +/* + * Connect two nodes using the specified hooks + */ +int +ng_con_nodes(node_p node, const char *name, node_p node2, const char *name2) +{ + int error; + hook_p hook; + hook_p hook2; + + if ((error = ng_add_hook(node, name, &hook))) + return (error); + if ((error = ng_add_hook(node2, name2, &hook2))) { + ng_destroy_hook(hook); + return (error); + } + return (ng_connect(hook, hook2)); +} + +/* + * Parse and verify a string of the form: <NODE:><PATH> + * + * Such a string can refer to a specific node or a specific hook + * on a specific node, depending on how you look at it. In the + * latter case, the PATH component must not end in a dot. + * + * Both <NODE:> and <PATH> are optional. The <PATH> is a string + * of hook names separated by dots. This breaks out the original + * string, setting *nodep to "NODE" (or NULL if none) and *pathp + * to "PATH" (or NULL if degenerate). Also, *hookp will point to + * the final hook component of <PATH>, if any, otherwise NULL. + * + * This returns -1 if the path is malformed. The char ** are optional. + */ + +int +ng_path_parse(char *addr, char **nodep, char **pathp, char **hookp) +{ + char *node, *path, *hook; + int k; + + /* + * Extract absolute NODE, if any + */ + for (path = addr; *path && *path != ':'; path++); + if (*path) { + node = addr; /* Here's the NODE */ + *path++ = '\0'; /* Here's the PATH */ + + /* Node name must not be empty */ + if (!*node) + return -1; + + /* A name of "." is OK; otherwise '.' not allowed */ + if (strcmp(node, ".") != 0) { + for (k = 0; node[k]; k++) + if (node[k] == '.') + return -1; + } + } else { + node = NULL; /* No absolute NODE */ + path = addr; /* Here's the PATH */ + } + + /* Snoop for illegal characters in PATH */ + for (k = 0; path[k]; k++) + if (path[k] == ':') + return -1; + + /* Check for no repeated dots in PATH */ + for (k = 0; path[k]; k++) + if (path[k] == '.' && path[k + 1] == '.') + return -1; + + /* Remove extra (degenerate) dots from beginning or end of PATH */ + if (path[0] == '.') + path++; + if (*path && path[strlen(path) - 1] == '.') + path[strlen(path) - 1] = 0; + + /* If PATH has a dot, then we're not talking about a hook */ + if (*path) { + for (hook = path, k = 0; path[k]; k++) + if (path[k] == '.') { + hook = NULL; + break; + } + } else + path = hook = NULL; + + /* Done */ + if (nodep) + *nodep = node; + if (pathp) + *pathp = path; + if (hookp) + *hookp = hook; + return (0); +} + +/* + * Given a path, which may be absolute or relative, and a starting node, + * return the destination node. Compute the "return address" if desired. + */ +int +ng_path2node(node_p here, const char *address, node_p *destp, char **rtnp) +{ + const node_p start = here; + char fullpath[NG_PATHLEN + 1]; + char *nodename, *path, pbuf[2]; + node_p node; + char *cp; + + /* Initialize */ + if (rtnp) + *rtnp = NULL; + if (destp == NULL) + return EINVAL; + *destp = NULL; + + /* Make a writable copy of address for ng_path_parse() */ + strncpy(fullpath, address, sizeof(fullpath) - 1); + fullpath[sizeof(fullpath) - 1] = '\0'; + + /* Parse out node and sequence of hooks */ + if (ng_path_parse(fullpath, &nodename, &path, NULL) < 0) { + TRAP_ERROR; + return EINVAL; + } + if (path == NULL) { + pbuf[0] = '.'; /* Needs to be writable */ + pbuf[1] = '\0'; + path = pbuf; + } + + /* For an absolute address, jump to the starting node */ + if (nodename) { + node = ng_findname(here, nodename); + if (node == NULL) { + TRAP_ERROR; + return (ENOENT); + } + } else + node = here; + + /* Now follow the sequence of hooks */ + for (cp = path; node != NULL && *cp != '\0'; ) { + hook_p hook; + char *segment; + + /* + * Break out the next path segment. Replace the dot we just + * found with a NUL; "cp" points to the next segment (or the + * NUL at the end). + */ + for (segment = cp; *cp != '\0'; cp++) { + if (*cp == '.') { + *cp++ = '\0'; + break; + } + } + + /* Empty segment */ + if (*segment == '\0') + continue; + + /* We have a segment, so look for a hook by that name */ + LIST_FOREACH(hook, &node->hooks, hooks) { + if (hook->name && strcmp(hook->name, segment) == 0) + break; + } + + /* Can't get there from here... */ + if (hook == NULL + || hook->peer == NULL + || (hook->flags & HK_INVALID) != 0) { + TRAP_ERROR; + return (ENOENT); + } + + /* Hop on over to the next node */ + node = hook->peer->node; + } + + /* If node somehow missing, fail here (probably this is not needed) */ + if (node == NULL) { + TRAP_ERROR; + return (ENXIO); + } + + /* Now compute return address, i.e., the path to the sender */ + if (rtnp != NULL) { + MALLOC(*rtnp, char *, NG_NODELEN + 2, M_NETGRAPH, M_WAITOK); + if (*rtnp == NULL) { + TRAP_ERROR; + return (ENOMEM); + } + if (start->name != NULL) + sprintf(*rtnp, "%s:", start->name); + else + sprintf(*rtnp, "[%lx]:", (u_long) start); + } + + /* Done */ + *destp = node; + return (0); +} + +/* + * Call the appropriate message handler for the object. + * It is up to the message handler to free the message. + * If it's a generic message, handle it generically, otherwise + * call the type's message handler (if it exists) + */ + +#define CALL_MSG_HANDLER(error, node, msg, retaddr, resp) \ +do { \ + if((msg)->header.typecookie == NGM_GENERIC_COOKIE) { \ + (error) = ng_generic_msg((node), (msg), \ + (retaddr), (resp)); \ + } else { \ + if ((node)->type->rcvmsg != NULL) { \ + (error) = (*(node)->type->rcvmsg)((node), \ + (msg), (retaddr), (resp)); \ + } else { \ + TRAP_ERROR; \ + FREE((msg), M_NETGRAPH); \ + (error) = EINVAL; \ + } \ + } \ +} while (0) + + +/* + * Send a control message to a node + */ +int +ng_send_msg(node_p here, struct ng_mesg *msg, const char *address, + struct ng_mesg **rptr) +{ + node_p dest = NULL; + char *retaddr = NULL; + int error; + + /* Find the target node */ + error = ng_path2node(here, address, &dest, &retaddr); + if (error) { + FREE(msg, M_NETGRAPH); + return error; + } + + /* Make sure the resp field is null before we start */ + if (rptr != NULL) + *rptr = NULL; + + CALL_MSG_HANDLER(error, dest, msg, retaddr, rptr); + + /* Make sure that if there is a response, it has the RESP bit set */ + if ((error == 0) && rptr && *rptr) + (*rptr)->header.flags |= NGF_RESP; + + /* + * If we had a return address it is up to us to free it. They should + * have taken a copy if they needed to make a delayed response. + */ + if (retaddr) + FREE(retaddr, M_NETGRAPH); + return (error); +} + +/* + * Implement the 'generic' control messages + */ +static int +ng_generic_msg(node_p here, struct ng_mesg *msg, const char *retaddr, + struct ng_mesg **resp) +{ + int error = 0; + + if (msg->header.typecookie != NGM_GENERIC_COOKIE) { + TRAP_ERROR; + FREE(msg, M_NETGRAPH); + return (EINVAL); + } + switch (msg->header.cmd) { + case NGM_SHUTDOWN: + ng_rmnode(here); + break; + case NGM_MKPEER: + { + struct ngm_mkpeer *const mkp = (struct ngm_mkpeer *) msg->data; + + if (msg->header.arglen != sizeof(*mkp)) { + TRAP_ERROR; + return (EINVAL); + } + mkp->type[sizeof(mkp->type) - 1] = '\0'; + mkp->ourhook[sizeof(mkp->ourhook) - 1] = '\0'; + mkp->peerhook[sizeof(mkp->peerhook) - 1] = '\0'; + error = ng_mkpeer(here, mkp->ourhook, mkp->peerhook, mkp->type); + break; + } + case NGM_CONNECT: + { + struct ngm_connect *const con = + (struct ngm_connect *) msg->data; + node_p node2; + + if (msg->header.arglen != sizeof(*con)) { + TRAP_ERROR; + return (EINVAL); + } + con->path[sizeof(con->path) - 1] = '\0'; + con->ourhook[sizeof(con->ourhook) - 1] = '\0'; + con->peerhook[sizeof(con->peerhook) - 1] = '\0'; + error = ng_path2node(here, con->path, &node2, NULL); + if (error) + break; + error = ng_con_nodes(here, con->ourhook, node2, con->peerhook); + break; + } + case NGM_NAME: + { + struct ngm_name *const nam = (struct ngm_name *) msg->data; + + if (msg->header.arglen != sizeof(*nam)) { + TRAP_ERROR; + return (EINVAL); + } + nam->name[sizeof(nam->name) - 1] = '\0'; + error = ng_name_node(here, nam->name); + break; + } + case NGM_RMHOOK: + { + struct ngm_rmhook *const rmh = (struct ngm_rmhook *) msg->data; + hook_p hook; + + if (msg->header.arglen != sizeof(*rmh)) { + TRAP_ERROR; + return (EINVAL); + } + rmh->ourhook[sizeof(rmh->ourhook) - 1] = '\0'; + LIST_FOREACH(hook, &here->hooks, hooks) { + if (hook->name && strcmp(hook->name, rmh->ourhook) == 0) + break; + } + if (hook) + ng_destroy_hook(hook); + break; + } + case NGM_NODEINFO: + { + struct nodeinfo *ni; + struct ng_mesg *rp; + + /* Get response struct */ + if (resp == NULL) { + error = EINVAL; + break; + } + NG_MKRESPONSE(rp, msg, sizeof(*ni), M_NOWAIT); + if (rp == NULL) { + error = ENOMEM; + break; + } + + /* Fill in node info */ + ni = (struct nodeinfo *) rp->data; + if (here->name != NULL) + strncpy(ni->name, here->name, NG_NODELEN); + strncpy(ni->type, here->type->name, NG_TYPELEN); + ni->id = (u_int32_t) here; + ni->hooks = here->numhooks; + *resp = rp; + break; + } + case NGM_LISTHOOKS: + { + const int nhooks = here->numhooks; + struct hooklist *hl; + struct nodeinfo *ni; + struct ng_mesg *rp; + hook_p hook; + + /* Get response struct */ + if (resp == NULL) { + error = EINVAL; + break; + } + NG_MKRESPONSE(rp, msg, sizeof(*hl) + + (nhooks * sizeof(struct linkinfo)), M_NOWAIT); + if (rp == NULL) { + error = ENOMEM; + break; + } + hl = (struct hooklist *) rp->data; + ni = &hl->nodeinfo; + + /* Fill in node info */ + if (here->name) + strncpy(ni->name, here->name, NG_NODELEN); + strncpy(ni->type, here->type->name, NG_TYPELEN); + ni->id = (u_int32_t) here; + + /* Cycle through the linked list of hooks */ + ni->hooks = 0; + LIST_FOREACH(hook, &here->hooks, hooks) { + struct linkinfo *const link = &hl->link[ni->hooks]; + + if (ni->hooks >= nhooks) { + log(LOG_ERR, "%s: number of %s changed\n", + __FUNCTION__, "hooks"); + break; + } + if ((hook->flags & HK_INVALID) != 0) + continue; + strncpy(link->ourhook, hook->name, NG_HOOKLEN); + strncpy(link->peerhook, hook->peer->name, NG_HOOKLEN); + if (hook->peer->node->name != NULL) + strncpy(link->nodeinfo.name, + hook->peer->node->name, NG_NODELEN); + strncpy(link->nodeinfo.type, + hook->peer->node->type->name, NG_TYPELEN); + link->nodeinfo.id = (u_int32_t) hook->peer->node; + link->nodeinfo.hooks = hook->peer->node->numhooks; + ni->hooks++; + } + *resp = rp; + break; + } + + case NGM_LISTNAMES: + case NGM_LISTNODES: + { + const int unnamed = (msg->header.cmd == NGM_LISTNODES); + struct namelist *nl; + struct ng_mesg *rp; + node_p node; + int num = 0; + + if (resp == NULL) { + error = EINVAL; + break; + } + + /* Count number of nodes */ + LIST_FOREACH(node, &nodelist, nodes) { + if (unnamed || node->name != NULL) + num++; + } + + /* Get response struct */ + if (resp == NULL) { + error = EINVAL; + break; + } + NG_MKRESPONSE(rp, msg, sizeof(*nl) + + (num * sizeof(struct nodeinfo)), M_NOWAIT); + if (rp == NULL) { + error = ENOMEM; + break; + } + nl = (struct namelist *) rp->data; + + /* Cycle through the linked list of nodes */ + nl->numnames = 0; + LIST_FOREACH(node, &nodelist, nodes) { + struct nodeinfo *const np = &nl->nodeinfo[nl->numnames]; + + if (nl->numnames >= num) { + log(LOG_ERR, "%s: number of %s changed\n", + __FUNCTION__, "nodes"); + break; + } + if ((node->flags & NG_INVALID) != 0) + continue; + if (!unnamed && node->name == NULL) + continue; + if (node->name != NULL) + strncpy(np->name, node->name, NG_NODELEN); + strncpy(np->type, node->type->name, NG_TYPELEN); + np->id = (u_int32_t) node; + np->hooks = node->numhooks; + nl->numnames++; + } + *resp = rp; + break; + } + + case NGM_LISTTYPES: + { + struct typelist *tl; + struct ng_mesg *rp; + struct ng_type *type; + int num = 0; + + if (resp == NULL) { + error = EINVAL; + break; + } + + /* Count number of types */ + LIST_FOREACH(type, &typelist, types) + num++; + + /* Get response struct */ + if (resp == NULL) { + error = EINVAL; + break; + } + NG_MKRESPONSE(rp, msg, sizeof(*tl) + + (num * sizeof(struct typeinfo)), M_NOWAIT); + if (rp == NULL) { + error = ENOMEM; + break; + } + tl = (struct typelist *) rp->data; + + /* Cycle through the linked list of types */ + tl->numtypes = 0; + LIST_FOREACH(type, &typelist, types) { + struct typeinfo *const tp = &tl->typeinfo[tl->numtypes]; + + if (tl->numtypes >= num) { + log(LOG_ERR, "%s: number of %s changed\n", + __FUNCTION__, "types"); + break; + } + strncpy(tp->typename, type->name, NG_TYPELEN); + tp->numnodes = type->refs; + tl->numtypes++; + } + *resp = rp; + break; + } + + case NGM_TEXT_STATUS: + /* + * This one is tricky as it passes the command down to the + * actual node, even though it is a generic type command. + * This means we must assume that the msg is already freed + * when control passes back to us. + */ + if (resp == NULL) { + error = EINVAL; + break; + } + if (here->type->rcvmsg != NULL) + return((*here->type->rcvmsg)(here, msg, retaddr, resp)); + /* Fall through if rcvmsg not supported */ + default: + TRAP_ERROR; + error = EINVAL; + } + FREE(msg, M_NETGRAPH); + return (error); +} + +/* + * Send a data packet to a node. If the recipient has no + * 'receive data' method, then silently discard the packet. + */ +int +ng_send_data(hook_p hook, struct mbuf *m, meta_p meta) +{ + int (*rcvdata)(hook_p, struct mbuf *, meta_p); + int error; + +#ifdef DIAGNOSTIC + if ((m->m_flags & M_PKTHDR) == 0) + panic(__FUNCTION__); +#endif + if (hook && (hook->flags & HK_INVALID) == 0) { + rcvdata = hook->peer->node->type->rcvdata; + if (rcvdata != NULL) + error = (*rcvdata)(hook->peer, m, meta); + else { + error = 0; + NG_FREE_DATA(m, meta); + } + } else { + TRAP_ERROR; + error = ENOTCONN; + NG_FREE_DATA(m, meta); + } + return (error); +} + +/* + * Send a queued data packet to a node. If the recipient has no + * 'receive queued data' method, then try the 'receive data' method above. + */ +int +ng_send_dataq(hook_p hook, struct mbuf *m, meta_p meta) +{ + int (*rcvdataq)(hook_p, struct mbuf *, meta_p); + int error; + +#ifdef DIAGNOSTIC + if ((m->m_flags & M_PKTHDR) == 0) + panic(__FUNCTION__); +#endif + if (hook && (hook->flags & HK_INVALID) == 0) { + rcvdataq = hook->peer->node->type->rcvdataq; + if (rcvdataq != NULL) + error = (*rcvdataq)(hook->peer, m, meta); + else { + error = ng_send_data(hook, m, meta); + } + } else { + TRAP_ERROR; + error = ENOTCONN; + NG_FREE_DATA(m, meta); + } + return (error); +} + +/************************************************************************ + Module routines +************************************************************************/ + +/* + * Handle the loading/unloading of a netgraph node type module + */ +int +ng_mod_event(module_t mod, int event, void *data) +{ + struct ng_type *const type = data; + int s, error = 0; + + switch (event) { + case MOD_LOAD: + + /* Register new netgraph node type */ + s = splnet(); + if ((error = ng_newtype(type)) != 0) { + splx(s); + break; + } + + /* Call type specific code */ + if (type->mod_event != NULL) + if ((error = (*type->mod_event)(mod, event, data)) != 0) + LIST_REMOVE(type, types); + splx(s); + break; + + case MOD_UNLOAD: + s = splnet(); + if (type->refs != 0) /* make sure no nodes exist! */ + error = EBUSY; + else { + if (type->mod_event != NULL) { /* check with type */ + error = (*type->mod_event)(mod, event, data); + if (error != 0) { /* type refuses.. */ + splx(s); + break; + } + } + LIST_REMOVE(type, types); + } + splx(s); + break; + + default: + if (type->mod_event != NULL) + error = (*type->mod_event)(mod, event, data); + else + error = 0; /* XXX ? */ + break; + } + return (error); +} + +/* + * Handle loading and unloading for this code. + * The only thing we need to link into is the NETISR strucure. + */ +static int +ngb_mod_event(module_t mod, int event, void *data) +{ + int s, error = 0; + + switch (event) { + case MOD_LOAD: + /* Register line discipline */ + s = splimp(); + error = register_netisr(NETISR_NETGRAPH, ngintr); + splx(s); + break; + case MOD_UNLOAD: + /* You cant unload it because an interface may be using it. */ + error = EBUSY; + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +static moduledata_t netgraph_mod = { + "netgraph", + ngb_mod_event, + (NULL) +}; +DECLARE_MODULE(netgraph, netgraph_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); + +/************************************************************************ + Queueing routines +************************************************************************/ + +/* The structure for queueing across ISR switches */ +struct ng_queue_entry { + u_long flags; + struct ng_queue_entry *next; + union { + struct { + hook_p da_hook; /* target hook */ + struct mbuf *da_m; + meta_p da_meta; + } data; + struct { + struct ng_mesg *msg_msg; + node_p msg_node; + void *msg_retaddr; + } msg; + } body; +}; +#define NGQF_DATA 0x01 /* the queue element is data */ +#define NGQF_MESG 0x02 /* the queue element is a message */ + +static struct ng_queue_entry *ngqbase; /* items to be unqueued */ +static struct ng_queue_entry *ngqlast; /* last item queued */ +static const int ngqroom = 64; /* max items to queue */ +static int ngqsize; /* number of items in queue */ + +static struct ng_queue_entry *ngqfree; /* free ones */ +static const int ngqfreemax = 16;/* cache at most this many */ +static int ngqfreesize; /* number of cached entries */ + +/* + * Get a queue entry + */ +static struct ng_queue_entry * +ng_getqblk(void) +{ + register struct ng_queue_entry *q; + int s; + + /* Could be guarding against tty ints or whatever */ + s = splhigh(); + + /* Try get a cached queue block, or else allocate a new one */ + if ((q = ngqfree) == NULL) { + splx(s); + if (ngqsize < ngqroom) { /* don't worry about races */ + MALLOC(q, struct ng_queue_entry *, + sizeof(*q), M_NETGRAPH, M_NOWAIT); + } + } else { + ngqfree = q->next; + ngqfreesize--; + splx(s); + } + return (q); +} + +/* + * Release a queue entry + */ +#define RETURN_QBLK(q) \ +do { \ + int s; \ + if (ngqfreesize < ngqfreemax) { /* don't worry about races */ \ + s = splhigh(); \ + (q)->next = ngqfree; \ + ngqfree = (q); \ + ngqfreesize++; \ + splx(s); \ + } else { \ + FREE((q), M_NETGRAPH); \ + } \ +} while (0) + +/* + * Running at a raised (but we don't know which) processor priority level, + * put the data onto a queue to be picked up by another PPL (probably splnet) + */ +int +ng_queue_data(hook_p hook, struct mbuf *m, meta_p meta) +{ + struct ng_queue_entry *q; + int s; + + if (hook == NULL) { + NG_FREE_DATA(m, meta); + return (0); + } + if ((q = ng_getqblk()) == NULL) { + NG_FREE_DATA(m, meta); + return (ENOBUFS); + } + + /* Fill out the contents */ + q->flags = NGQF_DATA; + q->next = NULL; + q->body.data.da_hook = hook; + q->body.data.da_m = m; + q->body.data.da_meta = meta; + hook->refs++; /* don't let it go away while on the queue */ + + /* Put it on the queue */ + s = splhigh(); + if (ngqbase) { + ngqlast->next = q; + } else { + ngqbase = q; + } + ngqlast = q; + ngqsize++; + splx(s); + + /* Schedule software interrupt to handle it later */ + schednetisr(NETISR_NETGRAPH); + return (0); +} + +/* + * Running at a raised (but we don't know which) processor priority level, + * put the msg onto a queue to be picked up by another PPL (probably splnet) + */ +int +ng_queue_msg(node_p here, struct ng_mesg * msg, int len, const char *address) +{ + register struct ng_queue_entry *q; + int s; + node_p dest = NULL; + char *retaddr = NULL; + int error; + + /* Find the target node. Note that this trashes address */ + error = ng_path2node(here, address, &dest, &retaddr); + if (error) { + FREE(msg, M_NETGRAPH); + return (error); + } + if ((q = ng_getqblk()) == NULL) { + FREE(msg, M_NETGRAPH); + if (retaddr) + FREE(retaddr, M_NETGRAPH); + return (ENOBUFS); + } + + /* Fill out the contents */ + q->flags = NGQF_MESG; + q->next = NULL; + q->body.msg.msg_node = dest; + q->body.msg.msg_msg = msg; + q->body.msg.msg_retaddr = retaddr; + dest->refs++; /* don't let it go away while on the queue */ + + /* Put it on the queue */ + s = splhigh(); + if (ngqbase) { + ngqlast->next = q; + } else { + ngqbase = q; + } + ngqlast = q; + ngqsize++; + splx(s); + + /* Schedule software interrupt to handle it later */ + schednetisr(NETISR_NETGRAPH); + return (0); +} + +/* + * Pick an item off the queue, process it, and dispose of the queue entry. + * Should be running at splnet. + */ +static void +ngintr(void) +{ + hook_p hook; + struct ng_queue_entry *ngq; + struct mbuf *m; + meta_p meta; + void *retaddr; + struct ng_mesg *msg; + node_p node; + int error = 0; + int s; + + while (1) { + s = splhigh(); + if ((ngq = ngqbase)) { + ngqbase = ngq->next; + ngqsize--; + } + splx(s); + if (ngq == NULL) + return; + switch (ngq->flags) { + case NGQF_DATA: + hook = ngq->body.data.da_hook; + m = ngq->body.data.da_m; + meta = ngq->body.data.da_meta; + RETURN_QBLK(ngq); + NG_SEND_DATAQ(error, hook, m, meta); + ng_unref_hook(hook); + break; + case NGQF_MESG: + node = ngq->body.msg.msg_node; + msg = ngq->body.msg.msg_msg; + retaddr = ngq->body.msg.msg_retaddr; + RETURN_QBLK(ngq); + if (node->flags & NG_INVALID) { + FREE(msg, M_NETGRAPH); + } else { + CALL_MSG_HANDLER(error, node, msg, + retaddr, NULL); + } + ng_unref(node); + if (retaddr) + FREE(retaddr, M_NETGRAPH); + break; + default: + RETURN_QBLK(ngq); + } + } +} + + |