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