/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019-2021 IKS Service GmbH * * 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 code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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: Lutz Donnerhacke <lutz@donnerhacke.de> */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/mbuf.h> #include <sys/malloc.h> #include <sys/ctype.h> #include <sys/errno.h> #include <sys/syslog.h> #include <sys/types.h> #include <sys/counter.h> #include <net/ethernet.h> #include <netgraph/ng_message.h> #include <netgraph/ng_parse.h> #include <netgraph/ng_vlan_rotate.h> #include <netgraph/netgraph.h> /* * This section contains the netgraph method declarations for the * sample node. These methods define the netgraph 'type'. */ static ng_constructor_t ng_vlanrotate_constructor; static ng_rcvmsg_t ng_vlanrotate_rcvmsg; static ng_shutdown_t ng_vlanrotate_shutdown; static ng_newhook_t ng_vlanrotate_newhook; static ng_rcvdata_t ng_vlanrotate_rcvdata; static ng_disconnect_t ng_vlanrotate_disconnect; /* Parse type for struct ng_vlanrotate_conf */ static const struct ng_parse_struct_field ng_vlanrotate_conf_fields[] = { {"rot", &ng_parse_int8_type}, {"min", &ng_parse_uint8_type}, {"max", &ng_parse_uint8_type}, {NULL} }; static const struct ng_parse_type ng_vlanrotate_conf_type = { &ng_parse_struct_type, &ng_vlanrotate_conf_fields }; /* Parse type for struct ng_vlanrotate_stat */ static struct ng_parse_fixedarray_info ng_vlanrotate_stat_hist_info = { &ng_parse_uint64_type, NG_VLANROTATE_MAX_VLANS }; static struct ng_parse_type ng_vlanrotate_stat_hist = { &ng_parse_fixedarray_type, &ng_vlanrotate_stat_hist_info }; static const struct ng_parse_struct_field ng_vlanrotate_stat_fields[] = { {"drops", &ng_parse_uint64_type}, {"excessive", &ng_parse_uint64_type}, {"incomplete", &ng_parse_uint64_type}, {"histogram", &ng_vlanrotate_stat_hist}, {NULL} }; static struct ng_parse_type ng_vlanrotate_stat_type = { &ng_parse_struct_type, &ng_vlanrotate_stat_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_vlanrotate_cmdlist[] = { { NGM_VLANROTATE_COOKIE, NGM_VLANROTATE_GET_CONF, "getconf", NULL, &ng_vlanrotate_conf_type, }, { NGM_VLANROTATE_COOKIE, NGM_VLANROTATE_SET_CONF, "setconf", &ng_vlanrotate_conf_type, NULL }, { NGM_VLANROTATE_COOKIE, NGM_VLANROTATE_GET_STAT, "getstat", NULL, &ng_vlanrotate_stat_type }, { NGM_VLANROTATE_COOKIE, NGM_VLANROTATE_CLR_STAT, "clrstat", NULL, &ng_vlanrotate_stat_type }, { NGM_VLANROTATE_COOKIE, NGM_VLANROTATE_GETCLR_STAT, "getclrstat", NULL, &ng_vlanrotate_stat_type }, {0} }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_VLANROTATE_NODE_TYPE, .constructor = ng_vlanrotate_constructor, .rcvmsg = ng_vlanrotate_rcvmsg, .shutdown = ng_vlanrotate_shutdown, .newhook = ng_vlanrotate_newhook, .rcvdata = ng_vlanrotate_rcvdata, .disconnect = ng_vlanrotate_disconnect, .cmdlist = ng_vlanrotate_cmdlist, }; NETGRAPH_INIT(vlanrotate, &typestruct); struct ng_vlanrotate_kernel_stats { counter_u64_t drops, excessive, incomplete; counter_u64_t histogram[NG_VLANROTATE_MAX_VLANS]; }; /* Information we store for each node */ struct vlanrotate { hook_p original_hook; hook_p ordered_hook; hook_p excessive_hook; hook_p incomplete_hook; struct ng_vlanrotate_conf conf; struct ng_vlanrotate_kernel_stats stats; }; typedef struct vlanrotate *vlanrotate_p; /* * Set up the private data structure. */ static int ng_vlanrotate_constructor(node_p node) { int i; vlanrotate_p vrp = malloc(sizeof(*vrp), M_NETGRAPH, M_WAITOK | M_ZERO); vrp->conf.max = NG_VLANROTATE_MAX_VLANS; vrp->stats.drops = counter_u64_alloc(M_WAITOK); vrp->stats.excessive = counter_u64_alloc(M_WAITOK); vrp->stats.incomplete = counter_u64_alloc(M_WAITOK); for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++) vrp->stats.histogram[i] = counter_u64_alloc(M_WAITOK); NG_NODE_SET_PRIVATE(node, vrp); return (0); } /* * Give our ok for a hook to be added. */ static int ng_vlanrotate_newhook(node_p node, hook_p hook, const char *name) { const vlanrotate_p vrp = NG_NODE_PRIVATE(node); hook_p *dst = NULL; if (strcmp(name, NG_VLANROTATE_HOOK_ORDERED) == 0) { dst = &vrp->ordered_hook; } else if (strcmp(name, NG_VLANROTATE_HOOK_ORIGINAL) == 0) { dst = &vrp->original_hook; } else if (strcmp(name, NG_VLANROTATE_HOOK_EXCESSIVE) == 0) { dst = &vrp->excessive_hook; } else if (strcmp(name, NG_VLANROTATE_HOOK_INCOMPLETE) == 0) { dst = &vrp->incomplete_hook; } else return (EINVAL); /* not a hook we know about */ if (*dst != NULL) return (EADDRINUSE); /* don't override */ *dst = hook; return (0); } /* * Get a netgraph control message. * A response is not required. */ static int ng_vlanrotate_rcvmsg(node_p node, item_p item, hook_p lasthook) { const vlanrotate_p vrp = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; struct ng_mesg *msg; struct ng_vlanrotate_conf *pcf; int error = 0; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_VLANROTATE_COOKIE: switch (msg->header.cmd) { case NGM_VLANROTATE_GET_CONF: NG_MKRESPONSE(resp, msg, sizeof(vrp->conf), M_NOWAIT); if (!resp) { error = ENOMEM; break; } *((struct ng_vlanrotate_conf *)resp->data) = vrp->conf; break; case NGM_VLANROTATE_SET_CONF: if (msg->header.arglen != sizeof(*pcf)) { error = EINVAL; break; } pcf = (struct ng_vlanrotate_conf *)msg->data; if (pcf->max == 0) /* keep current value */ pcf->max = vrp->conf.max; if ((pcf->max > NG_VLANROTATE_MAX_VLANS) || (pcf->min > pcf->max) || (abs(pcf->rot) >= pcf->max)) { error = EINVAL; break; } vrp->conf = *pcf; break; case NGM_VLANROTATE_GET_STAT: case NGM_VLANROTATE_GETCLR_STAT: { struct ng_vlanrotate_stat *p; int i; NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT); if (!resp) { error = ENOMEM; break; } p = (struct ng_vlanrotate_stat *)resp->data; p->drops = counter_u64_fetch(vrp->stats.drops); p->excessive = counter_u64_fetch(vrp->stats.excessive); p->incomplete = counter_u64_fetch(vrp->stats.incomplete); for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++) p->histogram[i] = counter_u64_fetch(vrp->stats.histogram[i]); if (msg->header.cmd != NGM_VLANROTATE_GETCLR_STAT) break; } case NGM_VLANROTATE_CLR_STAT: { int i; counter_u64_zero(vrp->stats.drops); counter_u64_zero(vrp->stats.excessive); counter_u64_zero(vrp->stats.incomplete); for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++) counter_u64_zero(vrp->stats.histogram[i]); break; } default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } /* Take care of synchronous response, if any */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return (error); } /* * Receive data, and do rotate the vlans as desired. * * Rotating is quite complicated if the rotation offset and the number * of vlans are not relativly prime. In this case multiple slices need * to be rotated separately. * * Rotation can be additive or subtractive. Some examples: * 01234 5 vlans given * ----- * 34012 +2 rotate * 12340 +4 rotate * 12340 -1 rotate * * First some helper functions ... */ struct ether_vlan_stack_entry { uint16_t proto; uint16_t tag; } __packed; struct ether_vlan_stack_header { uint8_t dst[ETHER_ADDR_LEN]; uint8_t src[ETHER_ADDR_LEN]; struct ether_vlan_stack_entry vlan_stack[1]; } __packed; static int ng_vlanrotate_gcd(int a, int b) { if (b == 0) return a; else return ng_vlanrotate_gcd(b, a % b); } static void ng_vlanrotate_rotate(struct ether_vlan_stack_entry arr[], int d, int n) { int i, j, k; struct ether_vlan_stack_entry temp; /* for each commensurable slice */ for (i = ng_vlanrotate_gcd(d, n); i-- > 0;) { /* rotate left aka downwards */ temp = arr[i]; j = i; while (1) { k = j + d; if (k >= n) k = k - n; if (k == i) break; arr[j] = arr[k]; j = k; } arr[j] = temp; } } static int ng_vlanrotate_rcvdata(hook_p hook, item_p item) { const vlanrotate_p vrp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ether_vlan_stack_header *evsh; struct mbuf *m = NULL; hook_p dst_hook; int8_t rotate; int8_t vlans = 0; int error = ENOSYS; NGI_GET_M(item, m); if (hook == vrp->ordered_hook) { rotate = +vrp->conf.rot; dst_hook = vrp->original_hook; } else if (hook == vrp->original_hook) { rotate = -vrp->conf.rot; dst_hook = vrp->ordered_hook; } else { dst_hook = vrp->original_hook; goto send; /* everything else goes out unmodified */ } if (dst_hook == NULL) { error = ENETDOWN; goto fail; } /* count the vlans */ for (vlans = 0; vlans <= NG_VLANROTATE_MAX_VLANS; vlans++) { size_t expected_len = sizeof(struct ether_vlan_stack_header) + vlans * sizeof(struct ether_vlan_stack_entry); if (m->m_len < expected_len) { m = m_pullup(m, expected_len); if (m == NULL) { error = EINVAL; goto fail; } } evsh = mtod(m, struct ether_vlan_stack_header *); switch (ntohs(evsh->vlan_stack[vlans].proto)) { case ETHERTYPE_VLAN: case ETHERTYPE_QINQ: case ETHERTYPE_8021Q9100: case ETHERTYPE_8021Q9200: case ETHERTYPE_8021Q9300: break; default: goto out; } } out: if ((vlans > vrp->conf.max) || (vlans >= NG_VLANROTATE_MAX_VLANS)) { counter_u64_add(vrp->stats.excessive, 1); dst_hook = vrp->excessive_hook; goto send; } if ((vlans < vrp->conf.min) || (vlans <= abs(rotate))) { counter_u64_add(vrp->stats.incomplete, 1); dst_hook = vrp->incomplete_hook; goto send; } counter_u64_add(vrp->stats.histogram[vlans], 1); /* rotating upwards always (using modular arithmetics) */ if (rotate == 0) { /* nothing to do */ } else if (rotate > 0) { ng_vlanrotate_rotate(evsh->vlan_stack, rotate, vlans); } else { ng_vlanrotate_rotate(evsh->vlan_stack, vlans + rotate, vlans); } send: if (dst_hook == NULL) goto fail; NG_FWD_NEW_DATA(error, item, dst_hook, m); return 0; fail: counter_u64_add(vrp->stats.drops, 1); if (m != NULL) m_freem(m); NG_FREE_ITEM(item); return (error); } /* * Do local shutdown processing.. * All our links and the name have already been removed. */ static int ng_vlanrotate_shutdown(node_p node) { const vlanrotate_p vrp = NG_NODE_PRIVATE(node); int i; NG_NODE_SET_PRIVATE(node, NULL); counter_u64_free(vrp->stats.drops); counter_u64_free(vrp->stats.excessive); counter_u64_free(vrp->stats.incomplete); for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++) counter_u64_free(vrp->stats.histogram[i]); free(vrp, M_NETGRAPH); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection * For this type, removal of the last link destroys the node */ static int ng_vlanrotate_disconnect(hook_p hook) { const vlanrotate_p vrp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (vrp->original_hook == hook) vrp->original_hook = NULL; if (vrp->ordered_hook == hook) vrp->ordered_hook = NULL; if (vrp->excessive_hook == hook) vrp->excessive_hook = NULL; if (vrp->incomplete_hook == hook) vrp->incomplete_hook = NULL; /* during shutdown the node is invalid, don't shutdown twice */ 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); }