2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2019-2021 IKS Service GmbH
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * Author: Lutz Donnerhacke <lutz@donnerhacke.de>
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/kernel.h>
34 #include <sys/malloc.h>
35 #include <sys/ctype.h>
36 #include <sys/errno.h>
37 #include <sys/syslog.h>
38 #include <sys/types.h>
39 #include <sys/counter.h>
41 #include <net/ethernet.h>
43 #include <netgraph/ng_message.h>
44 #include <netgraph/ng_parse.h>
45 #include <netgraph/ng_vlan_rotate.h>
46 #include <netgraph/netgraph.h>
49 * This section contains the netgraph method declarations for the
50 * sample node. These methods define the netgraph 'type'.
53 static ng_constructor_t ng_vlanrotate_constructor;
54 static ng_rcvmsg_t ng_vlanrotate_rcvmsg;
55 static ng_shutdown_t ng_vlanrotate_shutdown;
56 static ng_newhook_t ng_vlanrotate_newhook;
57 static ng_rcvdata_t ng_vlanrotate_rcvdata;
58 static ng_disconnect_t ng_vlanrotate_disconnect;
60 /* Parse type for struct ng_vlanrotate_conf */
61 static const struct ng_parse_struct_field ng_vlanrotate_conf_fields[] = {
62 {"rot", &ng_parse_int8_type},
63 {"min", &ng_parse_uint8_type},
64 {"max", &ng_parse_uint8_type},
67 static const struct ng_parse_type ng_vlanrotate_conf_type = {
68 &ng_parse_struct_type,
69 &ng_vlanrotate_conf_fields
72 /* Parse type for struct ng_vlanrotate_stat */
73 static struct ng_parse_fixedarray_info ng_vlanrotate_stat_hist_info = {
74 &ng_parse_uint64_type,
75 NG_VLANROTATE_MAX_VLANS
77 static struct ng_parse_type ng_vlanrotate_stat_hist = {
78 &ng_parse_fixedarray_type,
79 &ng_vlanrotate_stat_hist_info
81 static const struct ng_parse_struct_field ng_vlanrotate_stat_fields[] = {
82 {"drops", &ng_parse_uint64_type},
83 {"excessive", &ng_parse_uint64_type},
84 {"incomplete", &ng_parse_uint64_type},
85 {"histogram", &ng_vlanrotate_stat_hist},
88 static struct ng_parse_type ng_vlanrotate_stat_type = {
89 &ng_parse_struct_type,
90 &ng_vlanrotate_stat_fields
94 /* List of commands and how to convert arguments to/from ASCII */
95 static const struct ng_cmdlist ng_vlanrotate_cmdlist[] = {
97 NGM_VLANROTATE_COOKIE,
98 NGM_VLANROTATE_GET_CONF,
101 &ng_vlanrotate_conf_type,
104 NGM_VLANROTATE_COOKIE,
105 NGM_VLANROTATE_SET_CONF,
107 &ng_vlanrotate_conf_type,
111 NGM_VLANROTATE_COOKIE,
112 NGM_VLANROTATE_GET_STAT,
115 &ng_vlanrotate_stat_type
118 NGM_VLANROTATE_COOKIE,
119 NGM_VLANROTATE_CLR_STAT,
122 &ng_vlanrotate_stat_type
125 NGM_VLANROTATE_COOKIE,
126 NGM_VLANROTATE_GETCLR_STAT,
129 &ng_vlanrotate_stat_type
134 /* Netgraph node type descriptor */
135 static struct ng_type typestruct = {
136 .version = NG_ABI_VERSION,
137 .name = NG_VLANROTATE_NODE_TYPE,
138 .constructor = ng_vlanrotate_constructor,
139 .rcvmsg = ng_vlanrotate_rcvmsg,
140 .shutdown = ng_vlanrotate_shutdown,
141 .newhook = ng_vlanrotate_newhook,
142 .rcvdata = ng_vlanrotate_rcvdata,
143 .disconnect = ng_vlanrotate_disconnect,
144 .cmdlist = ng_vlanrotate_cmdlist,
146 NETGRAPH_INIT(vlanrotate, &typestruct);
148 struct ng_vlanrotate_kernel_stats {
149 counter_u64_t drops, excessive, incomplete;
150 counter_u64_t histogram[NG_VLANROTATE_MAX_VLANS];
153 /* Information we store for each node */
155 hook_p original_hook;
157 hook_p excessive_hook;
158 hook_p incomplete_hook;
159 struct ng_vlanrotate_conf conf;
160 struct ng_vlanrotate_kernel_stats stats;
162 typedef struct vlanrotate *vlanrotate_p;
165 * Set up the private data structure.
168 ng_vlanrotate_constructor(node_p node)
172 vlanrotate_p vrp = malloc(sizeof(*vrp), M_NETGRAPH, M_WAITOK | M_ZERO);
174 vrp->conf.max = NG_VLANROTATE_MAX_VLANS;
176 vrp->stats.drops = counter_u64_alloc(M_WAITOK);
177 vrp->stats.excessive = counter_u64_alloc(M_WAITOK);
178 vrp->stats.incomplete = counter_u64_alloc(M_WAITOK);
179 for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++)
180 vrp->stats.histogram[i] = counter_u64_alloc(M_WAITOK);
182 NG_NODE_SET_PRIVATE(node, vrp);
187 * Give our ok for a hook to be added.
190 ng_vlanrotate_newhook(node_p node, hook_p hook, const char *name)
192 const vlanrotate_p vrp = NG_NODE_PRIVATE(node);
195 if (strcmp(name, NG_VLANROTATE_HOOK_ORDERED) == 0) {
196 dst = &vrp->ordered_hook;
197 } else if (strcmp(name, NG_VLANROTATE_HOOK_ORIGINAL) == 0) {
198 dst = &vrp->original_hook;
199 } else if (strcmp(name, NG_VLANROTATE_HOOK_EXCESSIVE) == 0) {
200 dst = &vrp->excessive_hook;
201 } else if (strcmp(name, NG_VLANROTATE_HOOK_INCOMPLETE) == 0) {
202 dst = &vrp->incomplete_hook;
204 return (EINVAL); /* not a hook we know about */
207 return (EADDRINUSE); /* don't override */
214 * Get a netgraph control message.
215 * A response is not required.
218 ng_vlanrotate_rcvmsg(node_p node, item_p item, hook_p lasthook)
220 const vlanrotate_p vrp = NG_NODE_PRIVATE(node);
221 struct ng_mesg *resp = NULL;
223 struct ng_vlanrotate_conf *pcf;
226 NGI_GET_MSG(item, msg);
227 /* Deal with message according to cookie and command */
228 switch (msg->header.typecookie) {
229 case NGM_VLANROTATE_COOKIE:
230 switch (msg->header.cmd) {
231 case NGM_VLANROTATE_GET_CONF:
232 NG_MKRESPONSE(resp, msg, sizeof(vrp->conf), M_NOWAIT);
237 *((struct ng_vlanrotate_conf *)resp->data) = vrp->conf;
239 case NGM_VLANROTATE_SET_CONF:
240 if (msg->header.arglen != sizeof(*pcf)) {
245 pcf = (struct ng_vlanrotate_conf *)msg->data;
247 if (pcf->max == 0) /* keep current value */
248 pcf->max = vrp->conf.max;
250 if ((pcf->max > NG_VLANROTATE_MAX_VLANS) ||
251 (pcf->min > pcf->max) ||
252 (abs(pcf->rot) >= pcf->max)) {
259 case NGM_VLANROTATE_GET_STAT:
260 case NGM_VLANROTATE_GETCLR_STAT:
262 struct ng_vlanrotate_stat *p;
265 NG_MKRESPONSE(resp, msg, sizeof(*p), M_NOWAIT);
270 p = (struct ng_vlanrotate_stat *)resp->data;
271 p->drops = counter_u64_fetch(vrp->stats.drops);
272 p->excessive = counter_u64_fetch(vrp->stats.excessive);
273 p->incomplete = counter_u64_fetch(vrp->stats.incomplete);
274 for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++)
275 p->histogram[i] = counter_u64_fetch(vrp->stats.histogram[i]);
276 if (msg->header.cmd != NGM_VLANROTATE_GETCLR_STAT)
279 case NGM_VLANROTATE_CLR_STAT:
283 counter_u64_zero(vrp->stats.drops);
284 counter_u64_zero(vrp->stats.excessive);
285 counter_u64_zero(vrp->stats.incomplete);
286 for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++)
287 counter_u64_zero(vrp->stats.histogram[i]);
291 error = EINVAL; /* unknown command */
296 error = EINVAL; /* unknown cookie type */
300 /* Take care of synchronous response, if any */
301 NG_RESPOND_MSG(error, node, item, resp);
302 /* Free the message and return */
308 * Receive data, and do rotate the vlans as desired.
310 * Rotating is quite complicated if the rotation offset and the number
311 * of vlans are not relativly prime. In this case multiple slices need
312 * to be rotated separately.
314 * Rotation can be additive or subtractive. Some examples:
315 * 01234 5 vlans given
321 * First some helper functions ...
324 struct ether_vlan_stack_entry {
329 struct ether_vlan_stack_header {
330 uint8_t dst[ETHER_ADDR_LEN];
331 uint8_t src[ETHER_ADDR_LEN];
332 struct ether_vlan_stack_entry vlan_stack[1];
336 ng_vlanrotate_gcd(int a, int b)
341 return ng_vlanrotate_gcd(b, a % b);
345 ng_vlanrotate_rotate(struct ether_vlan_stack_entry arr[], int d, int n)
348 struct ether_vlan_stack_entry temp;
350 /* for each commensurable slice */
351 for (i = ng_vlanrotate_gcd(d, n); i-- > 0;) {
352 /* rotate left aka downwards */
371 ng_vlanrotate_rcvdata(hook_p hook, item_p item)
373 const vlanrotate_p vrp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
374 struct ether_vlan_stack_header *evsh;
375 struct mbuf *m = NULL;
383 if (hook == vrp->ordered_hook) {
384 rotate = +vrp->conf.rot;
385 dst_hook = vrp->original_hook;
386 } else if (hook == vrp->original_hook) {
387 rotate = -vrp->conf.rot;
388 dst_hook = vrp->ordered_hook;
390 dst_hook = vrp->original_hook;
391 goto send; /* everything else goes out unmodified */
394 if (dst_hook == NULL) {
399 /* count the vlans */
400 for (vlans = 0; vlans <= NG_VLANROTATE_MAX_VLANS; vlans++) {
401 size_t expected_len = sizeof(struct ether_vlan_stack_header)
402 + vlans * sizeof(struct ether_vlan_stack_entry);
404 if (m->m_len < expected_len) {
405 m = m_pullup(m, expected_len);
412 evsh = mtod(m, struct ether_vlan_stack_header *);
413 switch (ntohs(evsh->vlan_stack[vlans].proto)) {
416 case ETHERTYPE_8021Q9100:
417 case ETHERTYPE_8021Q9200:
418 case ETHERTYPE_8021Q9300:
425 if ((vlans > vrp->conf.max) || (vlans >= NG_VLANROTATE_MAX_VLANS)) {
426 counter_u64_add(vrp->stats.excessive, 1);
427 dst_hook = vrp->excessive_hook;
431 if ((vlans < vrp->conf.min) || (vlans <= abs(rotate))) {
432 counter_u64_add(vrp->stats.incomplete, 1);
433 dst_hook = vrp->incomplete_hook;
436 counter_u64_add(vrp->stats.histogram[vlans], 1);
438 /* rotating upwards always (using modular arithmetics) */
441 } else if (rotate > 0) {
442 ng_vlanrotate_rotate(evsh->vlan_stack, rotate, vlans);
444 ng_vlanrotate_rotate(evsh->vlan_stack, vlans + rotate, vlans);
448 if (dst_hook == NULL)
450 NG_FWD_NEW_DATA(error, item, dst_hook, m);
454 counter_u64_add(vrp->stats.drops, 1);
462 * Do local shutdown processing..
463 * All our links and the name have already been removed.
466 ng_vlanrotate_shutdown(node_p node)
468 const vlanrotate_p vrp = NG_NODE_PRIVATE(node);
471 NG_NODE_SET_PRIVATE(node, NULL);
473 counter_u64_free(vrp->stats.drops);
474 counter_u64_free(vrp->stats.excessive);
475 counter_u64_free(vrp->stats.incomplete);
476 for (i = 0; i < NG_VLANROTATE_MAX_VLANS; i++)
477 counter_u64_free(vrp->stats.histogram[i]);
479 free(vrp, M_NETGRAPH);
487 * For this type, removal of the last link destroys the node
490 ng_vlanrotate_disconnect(hook_p hook)
492 const vlanrotate_p vrp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
494 if (vrp->original_hook == hook)
495 vrp->original_hook = NULL;
496 if (vrp->ordered_hook == hook)
497 vrp->ordered_hook = NULL;
498 if (vrp->excessive_hook == hook)
499 vrp->excessive_hook = NULL;
500 if (vrp->incomplete_hook == hook)
501 vrp->incomplete_hook = NULL;
503 /* during shutdown the node is invalid, don't shutdown twice */
504 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
505 (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
506 ng_rmnode_self(NG_HOOK_NODE(hook));