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