/* * ng_one2many.c */ /*- * Copyright (c) 2000 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. * * Author: Archie Cobbs <archie@freebsd.org> */ /* * ng_one2many(4) netgraph node type * * Packets received on the "one" hook are sent out each of the * "many" hooks according to an algorithm. Packets received on any * "many" hook are always delivered to the "one" hook. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/malloc.h> #include <sys/ctype.h> #include <sys/mbuf.h> #include <sys/errno.h> #include <netgraph/ng_message.h> #include <netgraph/netgraph.h> #include <netgraph/ng_parse.h> #include <netgraph/ng_one2many.h> /* Per-link private data */ struct ng_one2many_link { hook_p hook; /* netgraph hook */ struct ng_one2many_link_stats stats; /* link stats */ }; /* Per-node private data */ struct ng_one2many_private { node_p node; /* link to node */ struct ng_one2many_config conf; /* node configuration */ struct ng_one2many_link one; /* "one" hook */ struct ng_one2many_link many[NG_ONE2MANY_MAX_LINKS]; u_int16_t nextMany; /* next round-robin */ u_int16_t numActiveMany; /* # active "many" */ u_int16_t activeMany[NG_ONE2MANY_MAX_LINKS]; }; typedef struct ng_one2many_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_one2many_constructor; static ng_rcvmsg_t ng_one2many_rcvmsg; static ng_shutdown_t ng_one2many_shutdown; static ng_newhook_t ng_one2many_newhook; static ng_rcvdata_t ng_one2many_rcvdata; static ng_disconnect_t ng_one2many_disconnect; /* Other functions */ static void ng_one2many_update_many(priv_p priv); static void ng_one2many_notify(priv_p priv, uint32_t cmd); /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* Parse type for struct ng_one2many_config */ static const struct ng_parse_fixedarray_info ng_one2many_enableLinks_array_type_info = { &ng_parse_uint8_type, NG_ONE2MANY_MAX_LINKS }; static const struct ng_parse_type ng_one2many_enableLinks_array_type = { &ng_parse_fixedarray_type, &ng_one2many_enableLinks_array_type_info, }; static const struct ng_parse_struct_field ng_one2many_config_type_fields[] = NG_ONE2MANY_CONFIG_TYPE_INFO(&ng_one2many_enableLinks_array_type); static const struct ng_parse_type ng_one2many_config_type = { &ng_parse_struct_type, &ng_one2many_config_type_fields }; /* Parse type for struct ng_one2many_link_stats */ static const struct ng_parse_struct_field ng_one2many_link_stats_type_fields[] = NG_ONE2MANY_LINK_STATS_TYPE_INFO; static const struct ng_parse_type ng_one2many_link_stats_type = { &ng_parse_struct_type, &ng_one2many_link_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_one2many_cmdlist[] = { { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_SET_CONFIG, "setconfig", &ng_one2many_config_type, NULL }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GET_CONFIG, "getconfig", NULL, &ng_one2many_config_type }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GET_STATS, "getstats", &ng_parse_int32_type, &ng_one2many_link_stats_type }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_CLR_STATS, "clrstats", &ng_parse_int32_type, NULL, }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GETCLR_STATS, "getclrstats", &ng_parse_int32_type, &ng_one2many_link_stats_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_one2many_typestruct = { .version = NG_ABI_VERSION, .name = NG_ONE2MANY_NODE_TYPE, .constructor = ng_one2many_constructor, .rcvmsg = ng_one2many_rcvmsg, .shutdown = ng_one2many_shutdown, .newhook = ng_one2many_newhook, .rcvdata = ng_one2many_rcvdata, .disconnect = ng_one2many_disconnect, .cmdlist = ng_one2many_cmdlist, }; NETGRAPH_INIT(one2many, &ng_one2many_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_one2many_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); priv->conf.xmitAlg = NG_ONE2MANY_XMIT_ROUNDROBIN; priv->conf.failAlg = NG_ONE2MANY_FAIL_MANUAL; /* cross reference */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_one2many_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_one2many_link *link; int linkNum; u_long i; /* Which hook? */ if (strncmp(name, NG_ONE2MANY_HOOK_MANY_PREFIX, strlen(NG_ONE2MANY_HOOK_MANY_PREFIX)) == 0) { const char *cp; char *eptr; cp = name + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); i = strtoul(cp, &eptr, 10); if (*eptr != '\0' || i >= NG_ONE2MANY_MAX_LINKS) return (EINVAL); linkNum = (int)i; link = &priv->many[linkNum]; } else if (strcmp(name, NG_ONE2MANY_HOOK_ONE) == 0) { linkNum = NG_ONE2MANY_ONE_LINKNUM; link = &priv->one; } else return (EINVAL); /* Is hook already connected? (should never happen) */ if (link->hook != NULL) return (EISCONN); /* Setup private info for this link */ NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)linkNum); link->hook = hook; bzero(&link->stats, sizeof(link->stats)); if (linkNum != NG_ONE2MANY_ONE_LINKNUM) { priv->conf.enabledLinks[linkNum] = 1; /* auto-enable link */ ng_one2many_update_many(priv); } /* Done */ return (0); } /* * Receive a control message */ static int ng_one2many_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_ONE2MANY_COOKIE: switch (msg->header.cmd) { case NGM_ONE2MANY_SET_CONFIG: { struct ng_one2many_config *conf; int i; /* Check that new configuration is valid */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf = (struct ng_one2many_config *)msg->data; switch (conf->xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: case NG_ONE2MANY_XMIT_ALL: case NG_ONE2MANY_XMIT_FAILOVER: break; default: error = EINVAL; break; } switch (conf->failAlg) { case NG_ONE2MANY_FAIL_MANUAL: case NG_ONE2MANY_FAIL_NOTIFY: break; default: error = EINVAL; break; } if (error != 0) break; /* Normalized many link enabled bits */ for (i = 0; i < NG_ONE2MANY_MAX_LINKS; i++) conf->enabledLinks[i] = !!conf->enabledLinks[i]; /* Copy config and reset */ bcopy(conf, &priv->conf, sizeof(*conf)); ng_one2many_update_many(priv); break; } case NGM_ONE2MANY_GET_CONFIG: { struct ng_one2many_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_one2many_config *)resp->data; bcopy(&priv->conf, conf, sizeof(priv->conf)); break; } case NGM_ONE2MANY_GET_STATS: case NGM_ONE2MANY_CLR_STATS: case NGM_ONE2MANY_GETCLR_STATS: { struct ng_one2many_link *link; int linkNum; /* Get link */ if (msg->header.arglen != sizeof(int32_t)) { error = EINVAL; break; } linkNum = *((int32_t *)msg->data); if (linkNum == NG_ONE2MANY_ONE_LINKNUM) link = &priv->one; else if (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS) { link = &priv->many[linkNum]; } else { error = EINVAL; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_ONE2MANY_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(link->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&link->stats, resp->data, sizeof(link->stats)); } if (msg->header.cmd != NGM_ONE2MANY_GET_STATS) bzero(&link->stats, sizeof(link->stats)); break; } default: error = EINVAL; break; } break; /* * One of our downstreams notifies us of link change. If we are * configured to listen to these message, then we remove/add * this hook from array of active hooks. */ case NGM_FLOW_COOKIE: { int linkNum; if (priv->conf.failAlg != NG_ONE2MANY_FAIL_NOTIFY) break; if (lasthook == NULL) break; linkNum = (intptr_t)NG_HOOK_PRIVATE(lasthook); if (linkNum == NG_ONE2MANY_ONE_LINKNUM) break; KASSERT((linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); switch (msg->header.cmd) { case NGM_LINK_IS_UP: priv->conf.enabledLinks[linkNum] = 1; ng_one2many_update_many(priv); break; case NGM_LINK_IS_DOWN: priv->conf.enabledLinks[linkNum] = 0; ng_one2many_update_many(priv); break; default: break; } break; } default: error = EINVAL; break; } /* Done */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook */ static int ng_one2many_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ng_one2many_link *src; struct ng_one2many_link *dst = NULL; int error = 0; int linkNum; int i; struct mbuf *m; m = NGI_M(item); /* just peaking, mbuf still owned by item */ /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); /* Figure out source link */ src = (linkNum == NG_ONE2MANY_ONE_LINKNUM) ? &priv->one : &priv->many[linkNum]; KASSERT(src->hook != NULL, ("%s: no src%d", __func__, linkNum)); /* Update receive stats */ src->stats.recvPackets++; src->stats.recvOctets += m->m_pkthdr.len; /* Figure out destination link */ if (linkNum == NG_ONE2MANY_ONE_LINKNUM) { if (priv->numActiveMany == 0) { NG_FREE_ITEM(item); return (ENOTCONN); } switch(priv->conf.xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: dst = &priv->many[priv->activeMany[priv->nextMany]]; priv->nextMany = (priv->nextMany + 1) % priv->numActiveMany; break; case NG_ONE2MANY_XMIT_ALL: /* no need to copy data for the 1st one */ dst = &priv->many[priv->activeMany[0]]; /* make modifiable copies of data and send for all * links except the first one, which we'll do last */ for (i = 1; i < priv->numActiveMany; i++) { struct mbuf *m2; struct ng_one2many_link *mdst; mdst = &priv->many[priv->activeMany[i]]; m2 = m_dup(m, M_NOWAIT); if (m2 == NULL) { mdst->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOBUFS); } /* Update transmit stats */ mdst->stats.xmitPackets++; mdst->stats.xmitOctets += m->m_pkthdr.len; NG_SEND_DATA_ONLY(error, mdst->hook, m2); } break; case NG_ONE2MANY_XMIT_FAILOVER: dst = &priv->many[priv->activeMany[0]]; break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __func__); #endif } } else { dst = &priv->one; } /* Update transmit stats */ dst->stats.xmitPackets++; dst->stats.xmitOctets += m->m_pkthdr.len; /* Deliver packet */ NG_FWD_ITEM_HOOK(error, item, dst->hook); return (error); } /* * Shutdown node */ static int ng_one2many_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); KASSERT(priv->numActiveMany == 0, ("%s: numActiveMany=%d", __func__, priv->numActiveMany)); free(priv, M_NETGRAPH); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection. */ static int ng_one2many_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int linkNum; /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); /* Nuke the link */ if (linkNum == NG_ONE2MANY_ONE_LINKNUM) priv->one.hook = NULL; else { priv->many[linkNum].hook = NULL; priv->conf.enabledLinks[linkNum] = 0; ng_one2many_update_many(priv); } /* If no hooks left, go away */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /****************************************************************** OTHER FUNCTIONS ******************************************************************/ /* * Update internal state after the addition or removal of a "many" link */ static void ng_one2many_update_many(priv_p priv) { uint16_t saveActive = priv->numActiveMany; int linkNum; /* Update list of which "many" links are up */ priv->numActiveMany = 0; for (linkNum = 0; linkNum < NG_ONE2MANY_MAX_LINKS; linkNum++) { switch (priv->conf.failAlg) { case NG_ONE2MANY_FAIL_MANUAL: case NG_ONE2MANY_FAIL_NOTIFY: if (priv->many[linkNum].hook != NULL && priv->conf.enabledLinks[linkNum]) { priv->activeMany[priv->numActiveMany] = linkNum; priv->numActiveMany++; } break; #ifdef INVARIANTS default: panic("%s: invalid failAlg", __func__); #endif } } if (priv->numActiveMany == 0 && saveActive > 0) ng_one2many_notify(priv, NGM_LINK_IS_DOWN); if (saveActive == 0 && priv->numActiveMany > 0) ng_one2many_notify(priv, NGM_LINK_IS_UP); /* Update transmit algorithm state */ switch (priv->conf.xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: if (priv->numActiveMany > 0) priv->nextMany %= priv->numActiveMany; break; case NG_ONE2MANY_XMIT_ALL: case NG_ONE2MANY_XMIT_FAILOVER: break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __func__); #endif } } /* * Notify upstream if we are out of links, or we have at least one link. */ static void ng_one2many_notify(priv_p priv, uint32_t cmd) { struct ng_mesg *msg; int dummy_error = 0; if (priv->one.hook == NULL) return; NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); if (msg != NULL) NG_SEND_MSG_HOOK(dummy_error, priv->node, msg, priv->one.hook, 0); }