1/*
2 * ng_one2many.c
3 */
4
5/*-
6 * Copyright (c) 2000 Whistle Communications, Inc.
7 * All rights reserved.
8 *
9 * Subject to the following obligations and disclaimer of warranty, use and
10 * redistribution of this software, in source or object code forms, with or
11 * without modifications are expressly permitted by Whistle Communications;
12 * provided, however, that:
13 * 1. Any and all reproductions of the source or object code must include the
14 *    copyright notice above and the following disclaimer of warranties; and
15 * 2. No rights are granted, in any manner or form, to use Whistle
16 *    Communications, Inc. trademarks, including the mark "WHISTLE
17 *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
18 *    such appears in the above copyright notice or in the software.
19 *
20 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
21 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
22 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
23 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
25 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
26 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
27 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
28 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
29 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
30 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
31 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
36 * OF SUCH DAMAGE.
37 *
38 * Author: Archie Cobbs <archie@freebsd.org>
39 *
40 * $FreeBSD$
41 */
42
43/*
44 * ng_one2many(4) netgraph node type
45 *
46 * Packets received on the "one" hook are sent out each of the
47 * "many" hooks accoring to an algorithm. Packets received on any
48 * "many" hook are always delivered to the "one" hook.
49 */
50
51#include <sys/param.h>
52#include <sys/systm.h>
53#include <sys/kernel.h>
54#include <sys/malloc.h>
55#include <sys/ctype.h>
56#include <sys/mbuf.h>
57#include <sys/errno.h>
58
59#include <netgraph/ng_message.h>
60#include <netgraph/netgraph.h>
61#include <netgraph/ng_parse.h>
62#include <netgraph/ng_one2many.h>
63
64/* Per-link private data */
65struct ng_one2many_link {
66	hook_p				hook;	/* netgraph hook */
67	struct ng_one2many_link_stats	stats;	/* link stats */
68};
69
70/* Per-node private data */
71struct ng_one2many_private {
72	node_p				node;		/* link to node */
73	struct ng_one2many_config	conf;		/* node configuration */
74	struct ng_one2many_link		one;		/* "one" hook */
75	struct ng_one2many_link		many[NG_ONE2MANY_MAX_LINKS];
76	u_int16_t			nextMany;	/* next round-robin */
77	u_int16_t			numActiveMany;	/* # active "many" */
78	u_int16_t			activeMany[NG_ONE2MANY_MAX_LINKS];
79};
80typedef struct ng_one2many_private *priv_p;
81
82/* Netgraph node methods */
83static ng_constructor_t	ng_one2many_constructor;
84static ng_rcvmsg_t	ng_one2many_rcvmsg;
85static ng_shutdown_t	ng_one2many_shutdown;
86static ng_newhook_t	ng_one2many_newhook;
87static ng_rcvdata_t	ng_one2many_rcvdata;
88static ng_disconnect_t	ng_one2many_disconnect;
89
90/* Other functions */
91static void		ng_one2many_update_many(priv_p priv);
92static void		ng_one2many_notify(priv_p priv, uint32_t cmd);
93
94/******************************************************************
95		    NETGRAPH PARSE TYPES
96******************************************************************/
97
98/* Parse type for struct ng_one2many_config */
99static const struct ng_parse_fixedarray_info
100    ng_one2many_enableLinks_array_type_info = {
101	&ng_parse_uint8_type,
102	NG_ONE2MANY_MAX_LINKS
103};
104static const struct ng_parse_type ng_one2many_enableLinks_array_type = {
105	&ng_parse_fixedarray_type,
106	&ng_one2many_enableLinks_array_type_info,
107};
108static const struct ng_parse_struct_field ng_one2many_config_type_fields[]
109	= NG_ONE2MANY_CONFIG_TYPE_INFO(&ng_one2many_enableLinks_array_type);
110static const struct ng_parse_type ng_one2many_config_type = {
111	&ng_parse_struct_type,
112	&ng_one2many_config_type_fields
113};
114
115/* Parse type for struct ng_one2many_link_stats */
116static const struct ng_parse_struct_field ng_one2many_link_stats_type_fields[]
117	= NG_ONE2MANY_LINK_STATS_TYPE_INFO;
118static const struct ng_parse_type ng_one2many_link_stats_type = {
119	&ng_parse_struct_type,
120	&ng_one2many_link_stats_type_fields
121};
122
123/* List of commands and how to convert arguments to/from ASCII */
124static const struct ng_cmdlist ng_one2many_cmdlist[] = {
125	{
126	  NGM_ONE2MANY_COOKIE,
127	  NGM_ONE2MANY_SET_CONFIG,
128	  "setconfig",
129	  &ng_one2many_config_type,
130	  NULL
131	},
132	{
133	  NGM_ONE2MANY_COOKIE,
134	  NGM_ONE2MANY_GET_CONFIG,
135	  "getconfig",
136	  NULL,
137	  &ng_one2many_config_type
138	},
139	{
140	  NGM_ONE2MANY_COOKIE,
141	  NGM_ONE2MANY_GET_STATS,
142	  "getstats",
143	  &ng_parse_int32_type,
144	  &ng_one2many_link_stats_type
145	},
146	{
147	  NGM_ONE2MANY_COOKIE,
148	  NGM_ONE2MANY_CLR_STATS,
149	  "clrstats",
150	  &ng_parse_int32_type,
151	  NULL,
152	},
153	{
154	  NGM_ONE2MANY_COOKIE,
155	  NGM_ONE2MANY_GETCLR_STATS,
156	  "getclrstats",
157	  &ng_parse_int32_type,
158	  &ng_one2many_link_stats_type
159	},
160	{ 0 }
161};
162
163/* Node type descriptor */
164static struct ng_type ng_one2many_typestruct = {
165	.version =	NG_ABI_VERSION,
166	.name =		NG_ONE2MANY_NODE_TYPE,
167	.constructor =	ng_one2many_constructor,
168	.rcvmsg =	ng_one2many_rcvmsg,
169	.shutdown =	ng_one2many_shutdown,
170	.newhook =	ng_one2many_newhook,
171	.rcvdata =	ng_one2many_rcvdata,
172	.disconnect =	ng_one2many_disconnect,
173	.cmdlist =	ng_one2many_cmdlist,
174};
175NETGRAPH_INIT(one2many, &ng_one2many_typestruct);
176
177/******************************************************************
178		    NETGRAPH NODE METHODS
179******************************************************************/
180
181/*
182 * Node constructor
183 */
184static int
185ng_one2many_constructor(node_p node)
186{
187	priv_p priv;
188
189	/* Allocate and initialize private info */
190	priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO);
191	priv->conf.xmitAlg = NG_ONE2MANY_XMIT_ROUNDROBIN;
192	priv->conf.failAlg = NG_ONE2MANY_FAIL_MANUAL;
193
194	/* cross reference */
195	NG_NODE_SET_PRIVATE(node, priv);
196	priv->node = node;
197
198	/* Done */
199	return (0);
200}
201
202/*
203 * Method for attaching a new hook
204 */
205static	int
206ng_one2many_newhook(node_p node, hook_p hook, const char *name)
207{
208	const priv_p priv = NG_NODE_PRIVATE(node);
209	struct ng_one2many_link *link;
210	int linkNum;
211	u_long i;
212
213	/* Which hook? */
214	if (strncmp(name, NG_ONE2MANY_HOOK_MANY_PREFIX,
215	    strlen(NG_ONE2MANY_HOOK_MANY_PREFIX)) == 0) {
216		const char *cp;
217		char *eptr;
218
219		cp = name + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX);
220		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
221			return (EINVAL);
222		i = strtoul(cp, &eptr, 10);
223		if (*eptr != '\0' || i < 0 || i >= NG_ONE2MANY_MAX_LINKS)
224			return (EINVAL);
225		linkNum = (int)i;
226		link = &priv->many[linkNum];
227	} else if (strcmp(name, NG_ONE2MANY_HOOK_ONE) == 0) {
228		linkNum = NG_ONE2MANY_ONE_LINKNUM;
229		link = &priv->one;
230	} else
231		return (EINVAL);
232
233	/* Is hook already connected? (should never happen) */
234	if (link->hook != NULL)
235		return (EISCONN);
236
237	/* Setup private info for this link */
238	NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)linkNum);
239	link->hook = hook;
240	bzero(&link->stats, sizeof(link->stats));
241	if (linkNum != NG_ONE2MANY_ONE_LINKNUM) {
242		priv->conf.enabledLinks[linkNum] = 1;	/* auto-enable link */
243		ng_one2many_update_many(priv);
244	}
245
246	/* Done */
247	return (0);
248}
249
250/*
251 * Receive a control message
252 */
253static int
254ng_one2many_rcvmsg(node_p node, item_p item, hook_p lasthook)
255{
256	const priv_p priv = NG_NODE_PRIVATE(node);
257	struct ng_mesg *resp = NULL;
258	int error = 0;
259	struct ng_mesg *msg;
260
261	NGI_GET_MSG(item, msg);
262	switch (msg->header.typecookie) {
263	case NGM_ONE2MANY_COOKIE:
264		switch (msg->header.cmd) {
265		case NGM_ONE2MANY_SET_CONFIG:
266		    {
267			struct ng_one2many_config *conf;
268			int i;
269
270			/* Check that new configuration is valid */
271			if (msg->header.arglen != sizeof(*conf)) {
272				error = EINVAL;
273				break;
274			}
275			conf = (struct ng_one2many_config *)msg->data;
276			switch (conf->xmitAlg) {
277			case NG_ONE2MANY_XMIT_ROUNDROBIN:
278			case NG_ONE2MANY_XMIT_ALL:
279			case NG_ONE2MANY_XMIT_FAILOVER:
280				break;
281			default:
282				error = EINVAL;
283				break;
284			}
285			switch (conf->failAlg) {
286			case NG_ONE2MANY_FAIL_MANUAL:
287			case NG_ONE2MANY_FAIL_NOTIFY:
288				break;
289			default:
290				error = EINVAL;
291				break;
292			}
293			if (error != 0)
294				break;
295
296			/* Normalized many link enabled bits */
297			for (i = 0; i < NG_ONE2MANY_MAX_LINKS; i++)
298				conf->enabledLinks[i] = !!conf->enabledLinks[i];
299
300			/* Copy config and reset */
301			bcopy(conf, &priv->conf, sizeof(*conf));
302			ng_one2many_update_many(priv);
303			break;
304		    }
305		case NGM_ONE2MANY_GET_CONFIG:
306		    {
307			struct ng_one2many_config *conf;
308
309			NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT);
310			if (resp == NULL) {
311				error = ENOMEM;
312				break;
313			}
314			conf = (struct ng_one2many_config *)resp->data;
315			bcopy(&priv->conf, conf, sizeof(priv->conf));
316			break;
317		    }
318		case NGM_ONE2MANY_GET_STATS:
319		case NGM_ONE2MANY_CLR_STATS:
320		case NGM_ONE2MANY_GETCLR_STATS:
321		    {
322			struct ng_one2many_link *link;
323			int linkNum;
324
325			/* Get link */
326			if (msg->header.arglen != sizeof(int32_t)) {
327				error = EINVAL;
328				break;
329			}
330			linkNum = *((int32_t *)msg->data);
331			if (linkNum == NG_ONE2MANY_ONE_LINKNUM)
332				link = &priv->one;
333			else if (linkNum >= 0
334			    && linkNum < NG_ONE2MANY_MAX_LINKS) {
335				link = &priv->many[linkNum];
336			} else {
337				error = EINVAL;
338				break;
339			}
340
341			/* Get/clear stats */
342			if (msg->header.cmd != NGM_ONE2MANY_CLR_STATS) {
343				NG_MKRESPONSE(resp, msg,
344				    sizeof(link->stats), M_NOWAIT);
345				if (resp == NULL) {
346					error = ENOMEM;
347					break;
348				}
349				bcopy(&link->stats,
350				    resp->data, sizeof(link->stats));
351			}
352			if (msg->header.cmd != NGM_ONE2MANY_GET_STATS)
353				bzero(&link->stats, sizeof(link->stats));
354			break;
355		    }
356		default:
357			error = EINVAL;
358			break;
359		}
360		break;
361	/*
362	 * One of our downstreams notifies us of link change. If we are
363	 * configured to listen to these message, then we remove/add
364	 * this hook from array of active hooks.
365	 */
366	case NGM_FLOW_COOKIE:
367	    {
368		int linkNum;
369
370		if (priv->conf.failAlg != NG_ONE2MANY_FAIL_NOTIFY)
371			break;
372
373		if (lasthook == NULL)
374			break;
375
376		linkNum = (intptr_t)NG_HOOK_PRIVATE(lasthook);
377		if (linkNum == NG_ONE2MANY_ONE_LINKNUM)
378			break;
379
380		KASSERT((linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS),
381		    ("%s: linkNum=%d", __func__, linkNum));
382
383		switch (msg->header.cmd) {
384		case NGM_LINK_IS_UP:
385			priv->conf.enabledLinks[linkNum] = 1;
386			ng_one2many_update_many(priv);
387			break;
388		case NGM_LINK_IS_DOWN:
389			priv->conf.enabledLinks[linkNum] = 0;
390			ng_one2many_update_many(priv);
391			break;
392		default:
393			break;
394		}
395		break;
396	    }
397	default:
398		error = EINVAL;
399		break;
400	}
401
402	/* Done */
403	NG_RESPOND_MSG(error, node, item, resp);
404	NG_FREE_MSG(msg);
405	return (error);
406}
407
408/*
409 * Receive data on a hook
410 */
411static int
412ng_one2many_rcvdata(hook_p hook, item_p item)
413{
414	const node_p node = NG_HOOK_NODE(hook);
415	const priv_p priv = NG_NODE_PRIVATE(node);
416	struct ng_one2many_link *src;
417	struct ng_one2many_link *dst = NULL;
418	int error = 0;
419	int linkNum;
420	int i;
421	struct mbuf *m;
422
423	m = NGI_M(item); /* just peaking, mbuf still owned by item */
424	/* Get link number */
425	linkNum = (intptr_t)NG_HOOK_PRIVATE(hook);
426	KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM
427	    || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS),
428	    ("%s: linkNum=%d", __func__, linkNum));
429
430	/* Figure out source link */
431	src = (linkNum == NG_ONE2MANY_ONE_LINKNUM) ?
432	    &priv->one : &priv->many[linkNum];
433	KASSERT(src->hook != NULL, ("%s: no src%d", __func__, linkNum));
434
435	/* Update receive stats */
436	src->stats.recvPackets++;
437	src->stats.recvOctets += m->m_pkthdr.len;
438
439	/* Figure out destination link */
440	if (linkNum == NG_ONE2MANY_ONE_LINKNUM) {
441		if (priv->numActiveMany == 0) {
442			NG_FREE_ITEM(item);
443			return (ENOTCONN);
444		}
445		switch(priv->conf.xmitAlg) {
446		case NG_ONE2MANY_XMIT_ROUNDROBIN:
447			dst = &priv->many[priv->activeMany[priv->nextMany]];
448			priv->nextMany = (priv->nextMany + 1) % priv->numActiveMany;
449			break;
450		case NG_ONE2MANY_XMIT_ALL:
451			/* no need to copy data for the 1st one */
452			dst = &priv->many[priv->activeMany[0]];
453
454			/* make copies of data and send for all links
455			 * except the first one, which we'll do last
456			 */
457			for (i = 1; i < priv->numActiveMany; i++) {
458				struct mbuf *m2;
459				struct ng_one2many_link *mdst;
460
461				mdst = &priv->many[priv->activeMany[i]];
462				m2 = m_dup(m, M_NOWAIT);        /* XXX m_copypacket() */
463				if (m2 == NULL) {
464					mdst->stats.memoryFailures++;
465					NG_FREE_ITEM(item);
466					NG_FREE_M(m);
467					return (ENOBUFS);
468				}
469				/* Update transmit stats */
470				mdst->stats.xmitPackets++;
471				mdst->stats.xmitOctets += m->m_pkthdr.len;
472				NG_SEND_DATA_ONLY(error, mdst->hook, m2);
473			}
474			break;
475		case NG_ONE2MANY_XMIT_FAILOVER:
476			dst = &priv->many[priv->activeMany[0]];
477			break;
478#ifdef INVARIANTS
479		default:
480			panic("%s: invalid xmitAlg", __func__);
481#endif
482		}
483	} else {
484		dst = &priv->one;
485	}
486
487	/* Update transmit stats */
488	dst->stats.xmitPackets++;
489	dst->stats.xmitOctets += m->m_pkthdr.len;
490
491	/* Deliver packet */
492	NG_FWD_ITEM_HOOK(error, item, dst->hook);
493	return (error);
494}
495
496/*
497 * Shutdown node
498 */
499static int
500ng_one2many_shutdown(node_p node)
501{
502	const priv_p priv = NG_NODE_PRIVATE(node);
503
504	KASSERT(priv->numActiveMany == 0,
505	    ("%s: numActiveMany=%d", __func__, priv->numActiveMany));
506	free(priv, M_NETGRAPH);
507	NG_NODE_SET_PRIVATE(node, NULL);
508	NG_NODE_UNREF(node);
509	return (0);
510}
511
512/*
513 * Hook disconnection.
514 */
515static int
516ng_one2many_disconnect(hook_p hook)
517{
518	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
519	int linkNum;
520
521	/* Get link number */
522	linkNum = (intptr_t)NG_HOOK_PRIVATE(hook);
523	KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM
524	    || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS),
525	    ("%s: linkNum=%d", __func__, linkNum));
526
527	/* Nuke the link */
528	if (linkNum == NG_ONE2MANY_ONE_LINKNUM)
529		priv->one.hook = NULL;
530	else {
531		priv->many[linkNum].hook = NULL;
532		priv->conf.enabledLinks[linkNum] = 0;
533		ng_one2many_update_many(priv);
534	}
535
536	/* If no hooks left, go away */
537	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0)
538	&& (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
539		ng_rmnode_self(NG_HOOK_NODE(hook));
540	return (0);
541}
542
543/******************************************************************
544		    	OTHER FUNCTIONS
545******************************************************************/
546
547/*
548 * Update internal state after the addition or removal of a "many" link
549 */
550static void
551ng_one2many_update_many(priv_p priv)
552{
553	uint16_t saveActive = priv->numActiveMany;
554	int linkNum;
555
556	/* Update list of which "many" links are up */
557	priv->numActiveMany = 0;
558	for (linkNum = 0; linkNum < NG_ONE2MANY_MAX_LINKS; linkNum++) {
559		switch (priv->conf.failAlg) {
560		case NG_ONE2MANY_FAIL_MANUAL:
561		case NG_ONE2MANY_FAIL_NOTIFY:
562			if (priv->many[linkNum].hook != NULL
563			    && priv->conf.enabledLinks[linkNum]) {
564				priv->activeMany[priv->numActiveMany] = linkNum;
565				priv->numActiveMany++;
566			}
567			break;
568#ifdef INVARIANTS
569		default:
570			panic("%s: invalid failAlg", __func__);
571#endif
572		}
573	}
574
575	if (priv->numActiveMany == 0 && saveActive > 0)
576		ng_one2many_notify(priv, NGM_LINK_IS_DOWN);
577
578	if (saveActive == 0 && priv->numActiveMany > 0)
579		ng_one2many_notify(priv, NGM_LINK_IS_UP);
580
581	/* Update transmit algorithm state */
582	switch (priv->conf.xmitAlg) {
583	case NG_ONE2MANY_XMIT_ROUNDROBIN:
584		if (priv->numActiveMany > 0)
585			priv->nextMany %= priv->numActiveMany;
586		break;
587	case NG_ONE2MANY_XMIT_ALL:
588	case NG_ONE2MANY_XMIT_FAILOVER:
589		break;
590#ifdef INVARIANTS
591	default:
592		panic("%s: invalid xmitAlg", __func__);
593#endif
594	}
595}
596
597/*
598 * Notify upstream if we are out of links, or we have at least one link.
599 */
600static void
601ng_one2many_notify(priv_p priv, uint32_t cmd)
602{
603	struct ng_mesg *msg;
604	int dummy_error = 0;
605
606	if (priv->one.hook == NULL)
607		return;
608
609	NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT);
610	if (msg != NULL)
611		NG_SEND_MSG_HOOK(dummy_error, priv->node, msg, priv->one.hook, 0);
612}
613