/* * ng_fwdswitchswitch.c * * Copyright (c) 2002, Iscanet Internet Services S.r.l. * Copyright (c) 2000, Whistle Communications, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source or object code must include the above * copyright notice, this list of conditions, the following disclaimer * in the documentation and/or other materials provider with the * distribution. * 2. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Author: Rocco Lucia * * $Id: ng_fwdswitch.c,v 1.5 2004/02/12 01:52:09 rlucia Exp $ */ /* * ng_fwdswitch(4) netgraph node type * * Packets received on the "in" hook are sent out each of the * "out" hooks in round-robin fashion. Packets received on any * "out" hook are always delivered to the "one" hook. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ng_fwdswitch.h" #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) /* Internal forwarding table */ struct fwd_networks_priv { struct in_addr ipnetwork; struct in_addr netmask; u_int16_t dsti; u_int32_t mtchcnt; }; /* Per-link private data */ struct ng_fwdswitch_link { hook_p hook; /* netgraph hook */ struct ng_fwdswitch_link_stats stats; /* link stats */ }; /* Per-node private data */ struct ng_fwdswitch_private { struct ng_fwdswitch_config conf; /* node configuration */ struct ng_fwdswitch_link one; /* "one" hook */ struct ng_fwdswitch_link two; /* "two" hook */ struct ng_fwdswitch_link out[NG_FWDSWITCH_MAX_LINKS]; u_int16_t nextOut; /* next round-robin */ u_int16_t numActiveOut; /* # active "out" */ u_int16_t activeOut[NG_FWDSWITCH_MAX_LINKS]; u_int16_t fwdtable_len; struct ng_fwdswitch_fwdconfig *fwdconfig; struct fwd_networks_priv *fwdtable; u_int8_t fwd_deflink; }; typedef struct ng_fwdswitch_private *priv_p; /* struct fwd_networks { u_int32_t ipnetwork; u_int32_t netmask; u_int16_t interface; u_int32_t matchcount; }; */ int fwd_netnum = 0; /* Netgraph node methods */ static ng_constructor_t ng_fwdswitch_constructor; static ng_rcvmsg_t ng_fwdswitch_rcvmsg; static ng_shutdown_t ng_fwdswitch_rmnode; static ng_newhook_t ng_fwdswitch_newhook; static ng_rcvdata_t ng_fwdswitch_rcvdata; static ng_disconnect_t ng_fwdswitch_disconnect; /* Other functions */ static void ng_fwdswitch_update_out(priv_p priv); /* Store each hook's link number in the private field */ #define LINK_NUM(hook) (*(int16_t *)(&(hook)->private)) /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* Parse type for struct ng_fwdswitch_config */ static const struct ng_parse_fixedarray_info ng_fwdswitch_enableLinks_array_type_info = { &ng_parse_uint8_type, NG_FWDSWITCH_MAX_LINKS }; static const struct ng_parse_type ng_fwdswitch_enableLinks_array_type = { &ng_parse_fixedarray_type, &ng_fwdswitch_enableLinks_array_type_info, }; static const struct ng_parse_struct_field ng_fwdswitch_config_type_fields[] = NG_FWDSWITCH_CONFIG_TYPE_INFO(&ng_fwdswitch_enableLinks_array_type); static const struct ng_parse_type ng_fwdswitch_config_type = { &ng_parse_struct_type, &ng_fwdswitch_config_type_fields, }; /* Parse type for struct ng_fwdswitch_link_stats */ static const struct ng_parse_struct_field ng_fwdswitch_link_stats_type_fields[] = NG_FWDSWITCH_LINK_STATS_TYPE_INFO; static const struct ng_parse_type ng_fwdswitch_link_stats_type = { &ng_parse_struct_type, &ng_fwdswitch_link_stats_type_fields }; static int ng_fwdswitch_setfwdconfig(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc); static int ng_fwdswitch_buildfwdtable(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc); /* Parse type for struct ng_fwdswitch_fwdcfg */ static const struct ng_parse_struct_field ng_fwdswitch_fwdconfig_table_type_fields[] = { { "ipaddr", &ng_parse_ipaddr_type }, { "netmask", &ng_parse_ipaddr_type }, { "dstlink", &ng_parse_uint8_type }, { "matchcount", &ng_parse_uint32_type }, { NULL } }; static const struct ng_parse_type ng_fwdswitch_fwdconfig_table_type = { &ng_parse_struct_type, &ng_fwdswitch_fwdconfig_table_type_fields }; /* Parse type for the field 'fwd_table' in struct ng_fwdswitch_fwdconfig */ static int ng_fwdswitch_fwdconfigary_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_fwdswitch_fwdconfig *fc; fc = (const struct ng_fwdswitch_fwdconfig *) (buf - OFFSETOF(struct ng_fwdswitch_fwdconfig, fwd_table)); return fc->fwd_table_len; } static const struct ng_parse_array_info ng_fwdswitch_fwdconfigary_info = { &ng_fwdswitch_fwdconfig_table_type, &ng_fwdswitch_fwdconfigary_getLength, NULL }; static const struct ng_parse_type ng_fwdswitch_fwdconfigary_type = { &ng_parse_array_type, &ng_fwdswitch_fwdconfigary_info }; /* Parse type for struct ng_fwdswitch_fwdconfig */ static const struct ng_parse_struct_field ng_fwdswitch_fwdconfig_type_fields[] = NG_FWDSWITCH_FWDCONFIG_TYPE_INFO(&ng_fwdswitch_fwdconfigary_type); static const struct ng_parse_type ng_fwdswitch_fwdconfig_type = { &ng_parse_struct_type, &ng_fwdswitch_fwdconfig_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_fwdswitch_cmdlist[] = { { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_SET_CONFIG, "setconfig", &ng_fwdswitch_config_type, NULL }, { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_GET_CONFIG, "getconfig", NULL, &ng_fwdswitch_config_type }, { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_GET_STATS, "getstats", &ng_parse_int32_type, &ng_fwdswitch_link_stats_type }, { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_CLR_STATS, "clrstats", &ng_parse_int32_type, NULL, }, { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_GETCLR_STATS, "getclrstats", &ng_parse_int32_type, &ng_fwdswitch_link_stats_type }, { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_SET_FWDCONFIG, "setfwdconfig", &ng_fwdswitch_fwdconfig_type, NULL }, { NGM_FWDSWITCH_COOKIE, NGM_FWDSWITCH_GET_FWDCONFIG, "getfwdconfig", NULL, &ng_fwdswitch_fwdconfig_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_fwdswitch_typestruct = { NG_VERSION, NG_FWDSWITCH_NODE_TYPE, NULL, ng_fwdswitch_constructor, ng_fwdswitch_rcvmsg, ng_fwdswitch_rmnode, ng_fwdswitch_newhook, NULL, NULL, ng_fwdswitch_rcvdata, ng_fwdswitch_rcvdata, ng_fwdswitch_disconnect, ng_fwdswitch_cmdlist, }; NETGRAPH_INIT(fwdswitch, &ng_fwdswitch_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_fwdswitch_constructor(node_p *nodep) { priv_p priv; int error; /* Allocate and initialize private info */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); priv->conf.xmitAlg = NG_FWDSWITCH_XMIT_ROUNDROBIN; priv->conf.failAlg = NG_FWDSWITCH_FAIL_MANUAL; priv->fwdtable_len = 0; /* Call superclass constructor */ if ((error = ng_make_node_common(&ng_fwdswitch_typestruct, nodep))) { FREE(priv, M_NETGRAPH); return (error); } (*nodep)->private = priv; /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_fwdswitch_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = node->private; struct ng_fwdswitch_link *link; int linkNum; u_long i; /* Which hook? */ if (strncmp(name, NG_FWDSWITCH_HOOK_MANY_PREFIX, strlen(NG_FWDSWITCH_HOOK_MANY_PREFIX)) == 0) { const char *cp; char *eptr; cp = name + strlen(NG_FWDSWITCH_HOOK_MANY_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); i = strtoul(cp, &eptr, 10); if (*eptr != '\0' || i < 0 || i >= NG_FWDSWITCH_MAX_LINKS) return (EINVAL); linkNum = (int)i; link = &priv->out[linkNum]; } else if (strcmp(name, NG_FWDSWITCH_HOOK_ONE) == 0) { linkNum = NG_FWDSWITCH_ONE_LINKNUM; link = &priv->one; } else if (strcmp(name, NG_FWDSWITCH_HOOK_TWO) == 0) { linkNum = NG_FWDSWITCH_TWO_LINKNUM; link = &priv->two; } else return (EINVAL); /* Is hook already connected? (should never happen) */ if (link->hook != NULL) return (EISCONN); /* Setup private info for this link */ LINK_NUM(hook) = linkNum; link->hook = hook; bzero(&link->stats, sizeof(link->stats)); if (linkNum != NG_FWDSWITCH_ONE_LINKNUM && linkNum != NG_FWDSWITCH_TWO_LINKNUM) { priv->conf.enabledLinks[linkNum] = 1; /* auto-enable link */ ng_fwdswitch_update_out(priv); } /* Done */ return (0); } /* * Receive a control message */ static int ng_fwdswitch_rcvmsg(node_p node, struct ng_mesg *msg, const char *retaddr, struct ng_mesg **rptr) { const priv_p priv = node->private; struct fwd_networks_priv *fwdtable; struct ng_mesg *resp = NULL; int error = 0, i = 0; switch (msg->header.typecookie) { case NGM_FWDSWITCH_COOKIE: switch (msg->header.cmd) { case NGM_FWDSWITCH_SET_CONFIG: { struct ng_fwdswitch_config *conf; int i; /* Check that new configuration is valid */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf = (struct ng_fwdswitch_config *)msg->data; switch (conf->xmitAlg) { case NG_FWDSWITCH_XMIT_ROUNDROBIN: break; default: error = EINVAL; break; } switch (conf->failAlg) { case NG_FWDSWITCH_FAIL_MANUAL: break; default: error = EINVAL; break; } if (error != 0) break; /* Normalized out link enabled bits */ for (i = 0; i < NG_FWDSWITCH_MAX_LINKS; i++) conf->enabledLinks[i] = !!conf->enabledLinks[i]; /* Copy config and reset */ bcopy(conf, &priv->conf, sizeof(*conf)); ng_fwdswitch_update_out(priv); break; } case NGM_FWDSWITCH_GET_CONFIG: { struct ng_fwdswitch_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_fwdswitch_config *)resp->data; bcopy(&priv->conf, conf, sizeof(priv->conf)); break; } case NGM_FWDSWITCH_GET_STATS: case NGM_FWDSWITCH_CLR_STATS: case NGM_FWDSWITCH_GETCLR_STATS: { struct ng_fwdswitch_link *link; int linkNum; /* Get link */ if (msg->header.arglen != sizeof(int32_t)) { error = EINVAL; break; } linkNum = *((int32_t *)msg->data); if (linkNum == NG_FWDSWITCH_ONE_LINKNUM) link = &priv->one; else if (linkNum == NG_FWDSWITCH_TWO_LINKNUM) link = &priv->two; else if (linkNum >= 0 && linkNum < NG_FWDSWITCH_MAX_LINKS) { link = &priv->out[linkNum]; } else { error = EINVAL; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_FWDSWITCH_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_FWDSWITCH_GET_STATS) bzero(&link->stats, sizeof(link->stats)); break; } case NGM_FWDSWITCH_SET_FWDCONFIG: { struct ng_fwdswitch_fwdconfig *const fc = (struct ng_fwdswitch_fwdconfig *)msg->data; /* Sanity check */ /* if (msg->header.arglen < sizeof(*fc) || msg->header.arglen != NG_FWDSWITCH_FWDCONFIG_SIZE(fc->fwd_table_len)) { error = EINVAL; break; } */ /* Import the switch configuration */ if ((error = ng_fwdswitch_setfwdconfig(priv, fc)) != 0) { break; } break; } case NGM_FWDSWITCH_GET_FWDCONFIG: { struct ng_fwdswitch_fwdconfig *const fc = (struct ng_fwdswitch_fwdconfig *) priv->fwdconfig; fwdtable = priv->fwdtable; for(i=0;i < fc->fwd_table_len; i++ ) { fc->fwd_table[i].matchcount = fwdtable[i].mtchcnt; }; /* Sanity check */ /* if (msg->header.arglen == 0) { error = EINVAL; break; } */ msg->data[msg->header.arglen - 1] = '\0'; /* Build response */ NG_MKRESPONSE(resp, msg, NG_FWDSWITCH_FWDCONFIG_SIZE(fc->fwd_table_len), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(fc, resp->data, NG_FWDSWITCH_FWDCONFIG_SIZE(fc->fwd_table_len)); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } /* Done */ if (rptr) *rptr = resp; else if (resp != NULL) FREE(resp, M_NETGRAPH); FREE(msg, M_NETGRAPH); return (error); } /* * Receive data on a hook */ static int ng_fwdswitch_rcvdata(hook_p hook, struct mbuf *m, meta_p meta) { const node_p node = hook->node; const priv_p priv = node->private; struct ng_fwdswitch_link *src; struct ng_fwdswitch_link *dst; struct fwd_networks_priv *fwdtable; struct ip *ip; struct ether_header *eh; int error = 0, net; int linkNum; /* Get link number */ linkNum = LINK_NUM(hook); KASSERT(linkNum == NG_FWDSWITCH_ONE_LINKNUM || linkNum == NG_FWDSWITCH_TWO_LINKNUM || (linkNum >= 0 && linkNum < NG_FWDSWITCH_MAX_LINKS), ("%s: linkNum=%d", __FUNCTION__, linkNum)); /* Figure out source link */ if( linkNum == NG_FWDSWITCH_ONE_LINKNUM ) { src = &priv->one; } else if( linkNum == NG_FWDSWITCH_TWO_LINKNUM ) { src = &priv->two; } else { NG_FREE_DATA(m, meta); return(error); } KASSERT(src->hook != NULL, ("%s: no src%d", __FUNCTION__, linkNum)); /* Update receive stats */ src->stats.recvPackets++; src->stats.recvOctets += m->m_pkthdr.len; if (priv->numActiveOut == 0) { NG_FREE_DATA(m, meta); return (ENOTCONN); } if (m->m_len < (sizeof(*eh)+sizeof(*ip)) ) { m = m_pullup(m, (sizeof(*eh)+sizeof(*ip)) ); if (m == NULL) { NG_FREE_META(meta); return(ENOBUFS); }; } fwdtable = priv->fwdtable; eh = mtod(m, struct ether_header *); if (eh->ether_type == htons(ETHERTYPE_IP) ) { ip = m->m_data + sizeof(*eh); for(net=0; net < priv->fwdtable_len; net++) { if ( ((ip->ip_src.s_addr & fwdtable[net].netmask.s_addr) == fwdtable[net].ipnetwork.s_addr ) || ((ip->ip_dst.s_addr & fwdtable[net].netmask.s_addr) == fwdtable[net].ipnetwork.s_addr ) ) { dst = &priv->out[fwdtable[net].dsti]; dst->stats.xmitPackets++; dst->stats.xmitOctets += m->m_pkthdr.len; fwdtable[net].mtchcnt++; NG_SEND_DATA(error, dst->hook, m, meta); return(error); } } } dst = &priv->out[priv->fwd_deflink]; dst->stats.xmitPackets++; dst->stats.xmitOctets += m->m_pkthdr.len; NG_SEND_DATA(error, dst->hook, m, meta); return (error); } /* * Shutdown node */ static int ng_fwdswitch_rmnode(node_p node) { const priv_p priv = node->private; ng_unname(node); ng_cutlinks(node); KASSERT(priv->numActiveOut == 0, ("%s: numActiveOut=%d", __FUNCTION__, priv->numActiveOut)); FREE(priv, M_NETGRAPH); node->private = NULL; ng_unref(node); return (0); } /* * Hook disconnection. */ static int ng_fwdswitch_disconnect(hook_p hook) { const priv_p priv = hook->node->private; int linkNum; /* Get link number */ linkNum = LINK_NUM(hook); KASSERT(linkNum == NG_FWDSWITCH_ONE_LINKNUM || linkNum == NG_FWDSWITCH_TWO_LINKNUM || (linkNum >= 0 && linkNum < NG_FWDSWITCH_MAX_LINKS), ("%s: linkNum=%d", __FUNCTION__, linkNum)); /* Nuke the link */ if (linkNum == NG_FWDSWITCH_ONE_LINKNUM) priv->one.hook = NULL; else if (linkNum == NG_FWDSWITCH_TWO_LINKNUM) priv->two.hook = NULL; else { priv->out[linkNum].hook = NULL; priv->conf.enabledLinks[linkNum] = 0; ng_fwdswitch_update_out(priv); } /* If no hooks left, go away */ if (hook->node->numhooks == 0) ng_rmnode(hook->node); return (0); } /* * Update internal state after the addition or removal of a "out" link */ static void ng_fwdswitch_update_out(priv_p priv) { int linkNum; /* Update list of which "out" links are up */ priv->numActiveOut = 0; for (linkNum = 0; linkNum < NG_FWDSWITCH_MAX_LINKS; linkNum++) { switch (priv->conf.failAlg) { case NG_FWDSWITCH_FAIL_MANUAL: if (priv->out[linkNum].hook != NULL && priv->conf.enabledLinks[linkNum]) { priv->activeOut[priv->numActiveOut] = linkNum; priv->numActiveOut++; } break; #ifdef INVARIANTS default: panic("%s: invalid failAlg", __FUNCTION__); #endif } } /* Update transmit algorithm state */ switch (priv->conf.xmitAlg) { case NG_FWDSWITCH_XMIT_ROUNDROBIN: if (priv->numActiveOut > 0) priv->nextOut %= priv->numActiveOut; break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __FUNCTION__); #endif } } static int ng_fwdswitch_setfwdconfig(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc0) { struct ng_fwdswitch_fwdconfig *fc; int size,error; /* Save this configuration */ size = NG_FWDSWITCH_FWDCONFIG_SIZE(fc0->fwd_table_len); MALLOC(fc, struct ng_fwdswitch_fwdconfig *, size, M_NETGRAPH, M_NOWAIT); if (fc == NULL) return (ENOMEM); bcopy(fc0, fc, size); /* Free old config, and use the new one */ if (priv->fwdconfig != NULL) FREE(priv->fwdconfig, M_NETGRAPH); priv->fwdconfig = fc; /* Make the configuration effective */ if ((error = ng_fwdswitch_buildfwdtable(priv, fc)) != 0) { return(error); } return (0); } static int ng_fwdswitch_buildfwdtable(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc) { struct fwd_networks_priv *fwdtable; int size,i; /* Allocate a new forwarding table */ priv->fwdtable_len = fc->fwd_table_len; size = sizeof(struct fwd_networks_priv)*priv->fwdtable_len; MALLOC(fwdtable, struct fwd_networks_priv *, size, M_NETGRAPH, M_NOWAIT); if (fwdtable == NULL) return(ENOMEM); /* Import the configuration into the table */ for(i = 0; i < priv->fwdtable_len; i++ ) { fwdtable[i].ipnetwork.s_addr = fc->fwd_table[i].ipaddr.s_addr; fwdtable[i].netmask.s_addr = fc->fwd_table[i].netmask.s_addr; fwdtable[i].dsti = fc->fwd_table[i].dstlink; fwdtable[i].mtchcnt = fc->fwd_table[i].matchcount; } /* Be sure to stop the processing for a while, everything will goes to the default hook, but I'll fix this */ priv->fwdtable_len = 0; /* Free the old table, and use this one */ if (priv->fwdtable != NULL) FREE(priv->fwdtable, M_NETGRAPH); priv->fwdtable = fwdtable; priv->fwd_deflink = fc->fwd_deflink; /* Let's start again the packet processing */ priv->fwdtable_len = fc->fwd_table_len; return(0); }