Changeset View
Standalone View
sys/netgraph/ng_vlan_rotate.c
- This file was added.
| /*- | |||||
| * Spdx-License-Identifier: BSD-2-Clause-FreeBSD | |||||
| * | |||||
| * Copyright (c) 2019-2021 IKS Service GmbH | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
gbe: The "All rights reserved." should be dropped. | |||||
| * 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> | |||||
| * | |||||
| * $FreeBSD$ | |||||
| */ | |||||
Done Inline ActionsSame. kp: Same. | |||||
| #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; | |||||
| /* | |||||
Done Inline ActionsThis line is (just) too long. kp: This line is (just) too long. | |||||
| * 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) | |||||
| { | |||||
Done Inline ActionsOr maybe just 'else' in the above if/else cascade? kp: Or maybe just 'else' in the above if/else cascade? | |||||
| 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; | |||||
Done Inline ActionsSpurious whitespace on blank lines in a couple spots kevans: Spurious whitespace on blank lines in a couple spots | |||||
| int error = 0; | |||||
| NGI_GET_MSG(item, msg); | |||||
| /* Deal with message according to cookie and command */ | |||||
Done Inline ActionsThe various case lines should line up with the switch kevans: The various case lines should line up with the switch | |||||
| 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; | |||||
| } | |||||
Done Inline ActionsLine length. kp: Line length. | |||||
| *((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; | |||||
Done Inline ActionsSpace between if and opening paren, error assignments should move to the next line kevans: Space between `if` and opening paren, error assignments should move to the next line | |||||
| 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)) { | |||||
Done Inline ActionsIndentation on this guy should be tabs all the way kevans: Indentation on this guy should be tabs all the way | |||||
| 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; | |||||
Done Inline ActionsWe don't traditionally space assignments out like this, but I don't know that anyone's going to hassle over it. kevans: We don't traditionally space assignments out like this, but I don't know that anyone's going to… | |||||
| arr[j] = arr[k]; | |||||
| j = k; | |||||
| } | |||||
| arr[j] = temp; | |||||
| } | |||||
| } | |||||
| static int | |||||
| ng_vlanrotate_rcvdata(hook_p hook, item_p item) | |||||
Done Inline ActionsLine length. kp: Line length. | |||||
| { | |||||
| 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: | |||||
Done Inline ActionsOpening comment marker should rest on its own line kevans: Opening comment marker should rest on its own line | |||||
| case ETHERTYPE_QINQ: | |||||
| case ETHERTYPE_8021Q9100: | |||||
| case ETHERTYPE_8021Q9200: | |||||
| case ETHERTYPE_8021Q9300: | |||||
| break; | |||||
| default: | |||||
| goto out; | |||||
| } | |||||
| } | |||||
Done Inline ActionsAre netgraph nodes implicitly locked? kp: Are netgraph nodes implicitly locked?
If not, this could potentially miscount if two cores run… | |||||
Done Inline ActionsThey are not locked automatically (which is good). donner: They are not locked automatically (which is good).
So you are right, I've to switch to the… | |||||
| 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); | |||||
Done Inline ActionsOperator belongs to the line before it, with the wrapping starting just after. kevans: Operator belongs to the line before it, with the wrapping starting just after. | |||||
| 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); | |||||
| } | |||||
The "All rights reserved." should be dropped.