1/*-
2 * Copyright (c) 2000 Whistle Communications, Inc.
3 * All rights reserved.
4 *
5 * Subject to the following obligations and disclaimer of warranty, use and
6 * redistribution of this software, in source or object code forms, with or
7 * without modifications are expressly permitted by Whistle Communications;
8 * provided, however, that:
9 * 1. Any and all reproductions of the source or object code must include the
10 *    copyright notice above and the following disclaimer of warranties; and
11 * 2. No rights are granted, in any manner or form, to use Whistle
12 *    Communications, Inc. trademarks, including the mark "WHISTLE
13 *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
14 *    such appears in the above copyright notice or in the software.
15 *
16 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
17 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
18 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
19 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
21 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
22 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
23 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
24 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
25 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
26 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
27 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
28 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
32 * OF SUCH DAMAGE.
33 *
34 * Author: Archie Cobbs <archie@freebsd.org>
35 *
36 * $FreeBSD$
37 */
38
39/*
40 * ng_bridge(4) netgraph node type
41 *
42 * The node performs standard intelligent Ethernet bridging over
43 * each of its connected hooks, or links.  A simple loop detection
44 * algorithm is included which disables a link for priv->conf.loopTimeout
45 * seconds when a host is seen to have jumped from one link to
46 * another within priv->conf.minStableAge seconds.
47 *
48 * We keep a hashtable that maps Ethernet addresses to host info,
49 * which is contained in struct ng_bridge_host's. These structures
50 * tell us on which link the host may be found. A host's entry will
51 * expire after priv->conf.maxStaleness seconds.
52 *
53 * This node is optimzed for stable networks, where machines jump
54 * from one port to the other only rarely.
55 */
56
57#include <sys/param.h>
58#include <sys/systm.h>
59#include <sys/kernel.h>
60#include <sys/lock.h>
61#include <sys/malloc.h>
62#include <sys/mbuf.h>
63#include <sys/errno.h>
64#include <sys/rwlock.h>
65#include <sys/syslog.h>
66#include <sys/socket.h>
67#include <sys/ctype.h>
68#include <sys/types.h>
69#include <sys/counter.h>
70
71#include <net/if.h>
72#include <net/if_var.h>
73#include <net/ethernet.h>
74#include <net/vnet.h>
75
76#include <netinet/in.h>
77#if 0	/* not used yet */
78#include <netinet/ip_fw.h>
79#endif
80#include <netgraph/ng_message.h>
81#include <netgraph/netgraph.h>
82#include <netgraph/ng_parse.h>
83#include <netgraph/ng_bridge.h>
84
85#ifdef NG_SEPARATE_MALLOC
86static MALLOC_DEFINE(M_NETGRAPH_BRIDGE, "netgraph_bridge",
87    "netgraph bridge node");
88#else
89#define M_NETGRAPH_BRIDGE M_NETGRAPH
90#endif
91
92/* Counter based stats */
93struct ng_bridge_link_kernel_stats {
94	counter_u64_t	recvOctets;	/* total octets rec'd on link */
95	counter_u64_t	recvPackets;	/* total pkts rec'd on link */
96	counter_u64_t	recvMulticasts;	/* multicast pkts rec'd on link */
97	counter_u64_t	recvBroadcasts;	/* broadcast pkts rec'd on link */
98	counter_u64_t	recvUnknown;	/* pkts rec'd with unknown dest addr */
99	counter_u64_t	recvRunts;	/* pkts rec'd less than 14 bytes */
100	counter_u64_t	recvInvalid;	/* pkts rec'd with bogus source addr */
101	counter_u64_t	xmitOctets;	/* total octets xmit'd on link */
102	counter_u64_t	xmitPackets;	/* total pkts xmit'd on link */
103	counter_u64_t	xmitMulticasts;	/* multicast pkts xmit'd on link */
104	counter_u64_t	xmitBroadcasts;	/* broadcast pkts xmit'd on link */
105	counter_u64_t	loopDrops;	/* pkts dropped due to loopback */
106	u_int64_t	loopDetects;	/* number of loop detections */
107	counter_u64_t	memoryFailures;	/* times couldn't get mem or mbuf */
108};
109
110/* Per-link private data */
111struct ng_bridge_link {
112	hook_p				hook;		/* netgraph hook */
113	u_int16_t			loopCount;	/* loop ignore timer */
114	unsigned int			learnMac : 1,   /* autolearn macs */
115					sendUnknown : 1;/* send unknown macs out */
116	struct ng_bridge_link_kernel_stats stats;	/* link stats */
117};
118typedef struct ng_bridge_link const *link_cp;	/* read only access */
119
120/* Per-node private data */
121struct ng_bridge_private {
122	struct ng_bridge_bucket	*tab;		/* hash table bucket array */
123	struct ng_bridge_config	conf;		/* node configuration */
124	node_p			node;		/* netgraph node */
125	u_int			numHosts;	/* num entries in table */
126	u_int			numBuckets;	/* num buckets in table */
127	u_int			hashMask;	/* numBuckets - 1 */
128	int			numLinks;	/* num connected links */
129	unsigned int		persistent : 1,	/* can exist w/o hooks */
130				sendUnknown : 1;/* links receive unknowns by default */
131	struct callout		timer;		/* one second periodic timer */
132};
133typedef struct ng_bridge_private *priv_p;
134typedef struct ng_bridge_private const *priv_cp;	/* read only access */
135
136/* Information about a host, stored in a hash table entry */
137struct ng_bridge_host {
138	u_char		addr[6];	/* ethernet address */
139	link_p		link;		/* link where addr can be found */
140	u_int16_t	age;		/* seconds ago entry was created */
141	u_int16_t	staleness;	/* seconds ago host last heard from */
142	SLIST_ENTRY(ng_bridge_host)	next;	/* next entry in bucket */
143};
144
145/* Hash table bucket declaration */
146SLIST_HEAD(ng_bridge_bucket, ng_bridge_host);
147
148/* Netgraph node methods */
149static ng_constructor_t	ng_bridge_constructor;
150static ng_rcvmsg_t	ng_bridge_rcvmsg;
151static ng_shutdown_t	ng_bridge_shutdown;
152static ng_newhook_t	ng_bridge_newhook;
153static ng_rcvdata_t	ng_bridge_rcvdata;
154static ng_disconnect_t	ng_bridge_disconnect;
155
156/* Other internal functions */
157static struct	ng_bridge_host *ng_bridge_get(priv_cp priv, const u_char *addr);
158static int	ng_bridge_put(priv_p priv, const u_char *addr, link_p link);
159static void	ng_bridge_rehash(priv_p priv);
160static void	ng_bridge_remove_hosts(priv_p priv, link_p link);
161static void	ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2);
162static const	char *ng_bridge_nodename(node_cp node);
163
164/* Ethernet broadcast */
165static const u_char ng_bridge_bcast_addr[ETHER_ADDR_LEN] =
166    { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
167
168/* Compare Ethernet addresses using 32 and 16 bit words instead of bytewise */
169#define ETHER_EQUAL(a,b)	(((const u_int32_t *)(a))[0] \
170					== ((const u_int32_t *)(b))[0] \
171				    && ((const u_int16_t *)(a))[2] \
172					== ((const u_int16_t *)(b))[2])
173
174/* Minimum and maximum number of hash buckets. Must be a power of two. */
175#define MIN_BUCKETS		(1 << 5)	/* 32 */
176#define MAX_BUCKETS		(1 << 14)	/* 16384 */
177
178/* Configuration default values */
179#define DEFAULT_LOOP_TIMEOUT	60
180#define DEFAULT_MAX_STALENESS	(15 * 60)	/* same as ARP timeout */
181#define DEFAULT_MIN_STABLE_AGE	1
182
183/******************************************************************
184		    NETGRAPH PARSE TYPES
185******************************************************************/
186
187/*
188 * How to determine the length of the table returned by NGM_BRIDGE_GET_TABLE
189 */
190static int
191ng_bridge_getTableLength(const struct ng_parse_type *type,
192	const u_char *start, const u_char *buf)
193{
194	const struct ng_bridge_host_ary *const hary
195	    = (const struct ng_bridge_host_ary *)(buf - sizeof(u_int32_t));
196
197	return hary->numHosts;
198}
199
200/* Parse type for struct ng_bridge_host_ary */
201static const struct ng_parse_struct_field ng_bridge_host_type_fields[]
202	= NG_BRIDGE_HOST_TYPE_INFO(&ng_parse_enaddr_type);
203static const struct ng_parse_type ng_bridge_host_type = {
204	&ng_parse_struct_type,
205	&ng_bridge_host_type_fields
206};
207static const struct ng_parse_array_info ng_bridge_hary_type_info = {
208	&ng_bridge_host_type,
209	ng_bridge_getTableLength
210};
211static const struct ng_parse_type ng_bridge_hary_type = {
212	&ng_parse_array_type,
213	&ng_bridge_hary_type_info
214};
215static const struct ng_parse_struct_field ng_bridge_host_ary_type_fields[]
216	= NG_BRIDGE_HOST_ARY_TYPE_INFO(&ng_bridge_hary_type);
217static const struct ng_parse_type ng_bridge_host_ary_type = {
218	&ng_parse_struct_type,
219	&ng_bridge_host_ary_type_fields
220};
221
222/* Parse type for struct ng_bridge_config */
223static const struct ng_parse_struct_field ng_bridge_config_type_fields[]
224	= NG_BRIDGE_CONFIG_TYPE_INFO;
225static const struct ng_parse_type ng_bridge_config_type = {
226	&ng_parse_struct_type,
227	&ng_bridge_config_type_fields
228};
229
230/* Parse type for struct ng_bridge_link_stat */
231static const struct ng_parse_struct_field ng_bridge_stats_type_fields[]
232	= NG_BRIDGE_STATS_TYPE_INFO;
233static const struct ng_parse_type ng_bridge_stats_type = {
234	&ng_parse_struct_type,
235	&ng_bridge_stats_type_fields
236};
237/* Parse type for struct ng_bridge_move_host */
238static const struct ng_parse_struct_field ng_bridge_move_host_type_fields[]
239	= NG_BRIDGE_MOVE_HOST_TYPE_INFO(&ng_parse_enaddr_type);
240static const struct ng_parse_type ng_bridge_move_host_type = {
241	&ng_parse_struct_type,
242	&ng_bridge_move_host_type_fields
243};
244
245/* List of commands and how to convert arguments to/from ASCII */
246static const struct ng_cmdlist ng_bridge_cmdlist[] = {
247	{
248	  NGM_BRIDGE_COOKIE,
249	  NGM_BRIDGE_SET_CONFIG,
250	  "setconfig",
251	  &ng_bridge_config_type,
252	  NULL
253	},
254	{
255	  NGM_BRIDGE_COOKIE,
256	  NGM_BRIDGE_GET_CONFIG,
257	  "getconfig",
258	  NULL,
259	  &ng_bridge_config_type
260	},
261	{
262	  NGM_BRIDGE_COOKIE,
263	  NGM_BRIDGE_RESET,
264	  "reset",
265	  NULL,
266	  NULL
267	},
268	{
269	  NGM_BRIDGE_COOKIE,
270	  NGM_BRIDGE_GET_STATS,
271	  "getstats",
272	  &ng_parse_uint32_type,
273	  &ng_bridge_stats_type
274	},
275	{
276	  NGM_BRIDGE_COOKIE,
277	  NGM_BRIDGE_CLR_STATS,
278	  "clrstats",
279	  &ng_parse_uint32_type,
280	  NULL
281	},
282	{
283	  NGM_BRIDGE_COOKIE,
284	  NGM_BRIDGE_GETCLR_STATS,
285	  "getclrstats",
286	  &ng_parse_uint32_type,
287	  &ng_bridge_stats_type
288	},
289	{
290	  NGM_BRIDGE_COOKIE,
291	  NGM_BRIDGE_GET_TABLE,
292	  "gettable",
293	  NULL,
294	  &ng_bridge_host_ary_type
295	},
296	{
297	  NGM_BRIDGE_COOKIE,
298	  NGM_BRIDGE_SET_PERSISTENT,
299	  "setpersistent",
300	  NULL,
301	  NULL
302	},
303	{
304	  NGM_BRIDGE_COOKIE,
305	  NGM_BRIDGE_MOVE_HOST,
306	  "movehost",
307	  &ng_bridge_move_host_type,
308	  NULL
309	},
310	{ 0 }
311};
312
313/* Node type descriptor */
314static struct ng_type ng_bridge_typestruct = {
315	.version =	NG_ABI_VERSION,
316	.name =		NG_BRIDGE_NODE_TYPE,
317	.constructor =	ng_bridge_constructor,
318	.rcvmsg =	ng_bridge_rcvmsg,
319	.shutdown =	ng_bridge_shutdown,
320	.newhook =	ng_bridge_newhook,
321	.rcvdata =	ng_bridge_rcvdata,
322	.disconnect =	ng_bridge_disconnect,
323	.cmdlist =	ng_bridge_cmdlist,
324};
325NETGRAPH_INIT(bridge, &ng_bridge_typestruct);
326
327/******************************************************************
328		    NETGRAPH NODE METHODS
329******************************************************************/
330
331/*
332 * Node constructor
333 */
334static int
335ng_bridge_constructor(node_p node)
336{
337	priv_p priv;
338
339	/* Allocate and initialize private info */
340	priv = malloc(sizeof(*priv), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO);
341	ng_callout_init(&priv->timer);
342
343	/* Allocate and initialize hash table, etc. */
344	priv->tab = malloc(MIN_BUCKETS * sizeof(*priv->tab),
345	    M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO);
346	priv->numBuckets = MIN_BUCKETS;
347	priv->hashMask = MIN_BUCKETS - 1;
348	priv->conf.debugLevel = 1;
349	priv->conf.loopTimeout = DEFAULT_LOOP_TIMEOUT;
350	priv->conf.maxStaleness = DEFAULT_MAX_STALENESS;
351	priv->conf.minStableAge = DEFAULT_MIN_STABLE_AGE;
352	priv->sendUnknown = 1;	       /* classic bridge */
353
354	NG_NODE_SET_PRIVATE(node, priv);
355	priv->node = node;
356
357	/* Start timer; timer is always running while node is alive */
358	ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0);
359
360	/* Done */
361	return (0);
362}
363
364/*
365 * Method for attaching a new hook
366 */
367static	int
368ng_bridge_newhook(node_p node, hook_p hook, const char *name)
369{
370	const priv_p priv = NG_NODE_PRIVATE(node);
371	char linkName[NG_HOOKSIZ];
372	u_int32_t linkNum;
373	link_p link;
374	const char *prefix = NG_BRIDGE_HOOK_LINK_PREFIX;
375	bool isUplink;
376
377	/* Check for a link hook */
378	if (strlen(name) <= strlen(prefix))
379		return (EINVAL);       /* Unknown hook name */
380
381	isUplink = (name[0] == 'u');
382	if (isUplink)
383		prefix = NG_BRIDGE_HOOK_UPLINK_PREFIX;
384
385	/* primitive parsing */
386	linkNum = strtoul(name + strlen(prefix), NULL, 10);
387	/* validation by comparing against the reconstucted name  */
388	snprintf(linkName, sizeof(linkName), "%s%u", prefix, linkNum);
389	if (strcmp(linkName, name) != 0)
390		return (EINVAL);
391
392	if (linkNum == 0 && isUplink)
393		return (EINVAL);
394
395	if(NG_PEER_NODE(hook) == node)
396	        return (ELOOP);
397
398	link = malloc(sizeof(*link), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO);
399
400	link->stats.recvOctets = counter_u64_alloc(M_WAITOK);
401	link->stats.recvPackets = counter_u64_alloc(M_WAITOK);
402	link->stats.recvMulticasts = counter_u64_alloc(M_WAITOK);
403	link->stats.recvBroadcasts = counter_u64_alloc(M_WAITOK);
404	link->stats.recvUnknown = counter_u64_alloc(M_WAITOK);
405	link->stats.recvRunts = counter_u64_alloc(M_WAITOK);
406	link->stats.recvInvalid = counter_u64_alloc(M_WAITOK);
407	link->stats.xmitOctets = counter_u64_alloc(M_WAITOK);
408	link->stats.xmitPackets = counter_u64_alloc(M_WAITOK);
409	link->stats.xmitMulticasts = counter_u64_alloc(M_WAITOK);
410	link->stats.xmitBroadcasts = counter_u64_alloc(M_WAITOK);
411	link->stats.loopDrops = counter_u64_alloc(M_WAITOK);
412	link->stats.memoryFailures = counter_u64_alloc(M_WAITOK);
413
414	link->hook = hook;
415	if (isUplink) {
416		link->learnMac = 0;
417		link->sendUnknown = 1;
418		if (priv->numLinks == 0)	/* if the first link is an uplink */
419		    priv->sendUnknown = 0;	/* switch to restrictive mode */
420	} else {
421		link->learnMac = 1;
422		link->sendUnknown = priv->sendUnknown;
423	}
424
425	NG_HOOK_SET_PRIVATE(hook, link);
426	priv->numLinks++;
427	return (0);
428}
429
430/*
431 * Receive a control message
432 */
433static void ng_bridge_clear_link_stats(struct ng_bridge_link_kernel_stats * p)
434{
435	counter_u64_zero(p->recvOctets);
436	counter_u64_zero(p->recvPackets);
437	counter_u64_zero(p->recvMulticasts);
438	counter_u64_zero(p->recvBroadcasts);
439	counter_u64_zero(p->recvUnknown);
440	counter_u64_zero(p->recvRunts);
441	counter_u64_zero(p->recvInvalid);
442	counter_u64_zero(p->xmitOctets);
443	counter_u64_zero(p->xmitPackets);
444	counter_u64_zero(p->xmitMulticasts);
445	counter_u64_zero(p->xmitBroadcasts);
446	counter_u64_zero(p->loopDrops);
447	p->loopDetects = 0;
448	counter_u64_zero(p->memoryFailures);
449};
450
451static int
452ng_bridge_reset_link(hook_p hook, void *arg __unused)
453{
454	link_p priv = NG_HOOK_PRIVATE(hook);
455
456	priv->loopCount = 0;
457	ng_bridge_clear_link_stats(&priv->stats);
458	return (1);
459}
460
461
462static int
463ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook)
464{
465	const priv_p priv = NG_NODE_PRIVATE(node);
466	struct ng_mesg *resp = NULL;
467	int error = 0;
468	struct ng_mesg *msg;
469
470	NGI_GET_MSG(item, msg);
471	switch (msg->header.typecookie) {
472#ifdef NGM_BRIDGE_TABLE_ABI
473	case NGM_BRIDGE_COOKIE_TBL:
474		switch (msg->header.cmd) {
475		case NGM_BRIDGE_GET_CONFIG:
476		    {
477			struct ng_bridge_config_tbl *conf;
478
479			NG_MKRESPONSE(resp, msg, sizeof(*conf),
480			    M_NOWAIT|M_ZERO);
481			if (resp == NULL) {
482				error = ENOMEM;
483				break;
484			}
485			conf = (struct ng_bridge_config_tbl *)resp->data;
486			conf->cfg = priv->conf;
487			break;
488		    }
489		case NGM_BRIDGE_SET_CONFIG:
490		    {
491			struct ng_bridge_config_tbl *conf;
492
493			if (msg->header.arglen != sizeof(*conf)) {
494				error = EINVAL;
495				break;
496			}
497			conf = (struct ng_bridge_config_tbl *)msg->data;
498			priv->conf = conf->cfg;
499			break;
500		    }
501		case NGM_BRIDGE_GET_TABLE:
502		    {
503			struct ng_bridge_host_tbl_ary *ary;
504			struct ng_bridge_host *host;
505			int i, bucket;
506
507			NG_MKRESPONSE(resp, msg, sizeof(*ary) +
508			    (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT);
509			if (resp == NULL) {
510				error = ENOMEM;
511				break;
512			}
513			ary = (struct ng_bridge_host_tbl_ary *)resp->data;
514			ary->numHosts = priv->numHosts;
515			i = 0;
516			for (bucket = 0; bucket < priv->numBuckets; bucket++) {
517				SLIST_FOREACH(host, &priv->tab[bucket], next) {
518					const char *name = NG_HOOK_NAME(host->link->hook);
519					const char *prefix = name[0] == 'u' ?
520					    NG_BRIDGE_HOOK_UPLINK_PREFIX :
521					    NG_BRIDGE_HOOK_LINK_PREFIX;
522
523					memcpy(ary->hosts[i].addr, host->addr,
524					    sizeof(ary->hosts[i].addr));
525					ary->hosts[i].age = host->age;
526					ary->hosts[i].staleness = host->staleness;
527				        ary->hosts[i].linkNum = strtol(
528					    name + strlen(prefix), NULL, 10);
529					i++;
530				}
531			}
532			break;
533		    }
534		}
535		/* If already handled break, otherwise use new ABI. */
536		if (resp != NULL || error != 0)
537		    break;
538#endif /* NGM_BRIDGE_TABLE_ABI */
539	case NGM_BRIDGE_COOKIE:
540		switch (msg->header.cmd) {
541		case NGM_BRIDGE_GET_CONFIG:
542		    {
543			struct ng_bridge_config *conf;
544
545			NG_MKRESPONSE(resp, msg,
546			    sizeof(struct ng_bridge_config), M_NOWAIT);
547			if (resp == NULL) {
548				error = ENOMEM;
549				break;
550			}
551			conf = (struct ng_bridge_config *)resp->data;
552			*conf = priv->conf;	/* no sanity checking needed */
553			break;
554		    }
555		case NGM_BRIDGE_SET_CONFIG:
556		    {
557			struct ng_bridge_config *conf;
558
559			if (msg->header.arglen
560			    != sizeof(struct ng_bridge_config)) {
561				error = EINVAL;
562				break;
563			}
564			conf = (struct ng_bridge_config *)msg->data;
565			priv->conf = *conf;
566			break;
567		    }
568		case NGM_BRIDGE_RESET:
569		    {
570			hook_p rethook;
571
572			/* Flush all entries in the hash table */
573			ng_bridge_remove_hosts(priv, NULL);
574
575			/* Reset all loop detection counters and stats */
576			NG_NODE_FOREACH_HOOK(node, ng_bridge_reset_link, NULL,
577			    rethook);
578			break;
579		    }
580		case NGM_BRIDGE_GET_STATS:
581		case NGM_BRIDGE_CLR_STATS:
582		case NGM_BRIDGE_GETCLR_STATS:
583		    {
584			hook_p hook;
585			link_p link;
586			char linkName[NG_HOOKSIZ];
587			int linkNum;
588
589			/* Get link number */
590			if (msg->header.arglen != sizeof(u_int32_t)) {
591				error = EINVAL;
592				break;
593			}
594			linkNum = *((int32_t *)msg->data);
595			if (linkNum < 0)
596				snprintf(linkName, sizeof(linkName),
597				    "%s%u", NG_BRIDGE_HOOK_UPLINK_PREFIX, -linkNum);
598			else
599				snprintf(linkName, sizeof(linkName),
600				    "%s%u", NG_BRIDGE_HOOK_LINK_PREFIX, linkNum);
601
602			if ((hook = ng_findhook(node, linkName)) == NULL) {
603				error = ENOTCONN;
604				break;
605			}
606			link = NG_HOOK_PRIVATE(hook);
607
608			/* Get/clear stats */
609			if (msg->header.cmd != NGM_BRIDGE_CLR_STATS) {
610				struct ng_bridge_link_stats *rs;
611
612				NG_MKRESPONSE(resp, msg,
613				    sizeof(link->stats), M_NOWAIT);
614				if (resp == NULL) {
615					error = ENOMEM;
616					break;
617				}
618				rs = (struct ng_bridge_link_stats *)resp->data;
619#define FETCH(x)	rs->x = counter_u64_fetch(link->stats.x)
620				FETCH(recvOctets);
621				FETCH(recvPackets);
622				FETCH(recvMulticasts);
623				FETCH(recvBroadcasts);
624				FETCH(recvUnknown);
625				FETCH(recvRunts);
626				FETCH(recvInvalid);
627				FETCH(xmitOctets);
628				FETCH(xmitPackets);
629				FETCH(xmitMulticasts);
630				FETCH(xmitBroadcasts);
631				FETCH(loopDrops);
632				rs->loopDetects = link->stats.loopDetects;
633				FETCH(memoryFailures);
634#undef FETCH
635			}
636			if (msg->header.cmd != NGM_BRIDGE_GET_STATS)
637				ng_bridge_clear_link_stats(&link->stats);
638			break;
639		    }
640		case NGM_BRIDGE_GET_TABLE:
641		    {
642			struct ng_bridge_host_ary *ary;
643			struct ng_bridge_host *host;
644			int i = 0, bucket;
645
646			NG_MKRESPONSE(resp, msg, sizeof(*ary)
647			    + (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT);
648			if (resp == NULL) {
649				error = ENOMEM;
650				break;
651			}
652			ary = (struct ng_bridge_host_ary *)resp->data;
653			ary->numHosts = priv->numHosts;
654			for (bucket = 0; bucket < priv->numBuckets; bucket++) {
655				SLIST_FOREACH(host, &priv->tab[bucket], next) {
656					memcpy(ary->hosts[i].addr,
657					       host->addr,
658					       sizeof(ary->hosts[i].addr));
659					ary->hosts[i].age       = host->age;
660					ary->hosts[i].staleness = host->staleness;
661					strncpy(ary->hosts[i].hook,
662						NG_HOOK_NAME(host->link->hook),
663						sizeof(ary->hosts[i].hook));
664					i++;
665				}
666			}
667			break;
668		    }
669		case NGM_BRIDGE_SET_PERSISTENT:
670		    {
671			priv->persistent = 1;
672			break;
673		    }
674		case NGM_BRIDGE_MOVE_HOST:
675		{
676			struct ng_bridge_move_host *mh;
677			hook_p hook;
678
679			if (msg->header.arglen < sizeof(*mh)) {
680				error = EINVAL;
681				break;
682			}
683			mh = (struct ng_bridge_move_host *)msg->data;
684			hook = (mh->hook[0] == 0)
685			    ? lasthook
686			    : ng_findhook(node, mh->hook);
687			if (hook == NULL) {
688				error = ENOENT;
689				break;
690			}
691			error = ng_bridge_put(priv, mh->addr, NG_HOOK_PRIVATE(hook));
692			break;
693		}
694		default:
695			error = EINVAL;
696			break;
697		}
698		break;
699	default:
700		error = EINVAL;
701		break;
702	}
703
704	/* Done */
705	NG_RESPOND_MSG(error, node, item, resp);
706	NG_FREE_MSG(msg);
707	return (error);
708}
709
710/*
711 * Receive data on a hook
712 */
713struct ng_bridge_send_ctx {
714	link_p foundFirst, incoming;
715	struct mbuf * m;
716	int manycast, error;
717};
718
719/*
720 * Update stats and send out
721 */
722static inline int
723ng_bridge_send_data(link_cp dst, int manycast, struct mbuf *m, item_p item) {
724	int error = 0;
725	size_t len = m->m_pkthdr.len;
726
727	if(item != NULL)
728		NG_FWD_NEW_DATA(error, item, dst->hook, m);
729	else
730		NG_SEND_DATA_ONLY(error, dst->hook, m);
731
732	if (error) {
733		/* The packet is still ours */
734		if (item != NULL)
735			NG_FREE_ITEM(item);
736		if (m != NULL)
737			NG_FREE_M(m);
738		return (error);
739	}
740
741	counter_u64_add(dst->stats.xmitPackets, 1);
742	counter_u64_add(dst->stats.xmitOctets, len);
743	switch (manycast) {
744	default:		       /* unknown unicast */
745		break;
746	case 1:			       /* multicast */
747		counter_u64_add(dst->stats.xmitMulticasts, 1);
748		break;
749	case 2:			       /* broadcast */
750		counter_u64_add(dst->stats.xmitBroadcasts, 1);
751		break;
752	}
753	return (0);
754}
755
756/*
757 * Loop body for sending to multiple destinations
758 * return 0 to stop looping
759 */
760static int
761ng_bridge_send_ctx(hook_p dst, void *arg)
762{
763	struct ng_bridge_send_ctx *ctx = arg;
764	link_p destLink = NG_HOOK_PRIVATE(dst);
765	struct mbuf *m2 = NULL;
766	int error = 0;
767
768	/* Skip incoming link */
769	if (destLink == ctx->incoming) {
770		return (1);
771	}
772
773	/* Skip sending unknowns to undesired links  */
774	if (!ctx->manycast && !destLink->sendUnknown)
775		return (1);
776
777	if (ctx->foundFirst == NULL) {
778		/*
779		 * This is the first usable link we have found.
780		 * Reserve it for the originals.
781		 * If we never find another we save a copy.
782		 */
783		ctx->foundFirst = destLink;
784		return (1);
785	}
786
787	/*
788	 * It's usable link but not the reserved (first) one.
789	 * Copy mbuf info for sending.
790	 */
791	m2 = m_dup(ctx->m, M_NOWAIT);
792	if (m2 == NULL) {
793		counter_u64_add(ctx->incoming->stats.memoryFailures, 1);
794		ctx->error = ENOBUFS;
795		return (0);	       /* abort loop, do not try again and again */
796	}
797
798	/* Send packet */
799	error = ng_bridge_send_data(destLink, ctx->manycast, m2, NULL);
800	if (error)
801	  ctx->error = error;
802	return (1);
803}
804
805static int
806ng_bridge_rcvdata(hook_p hook, item_p item)
807{
808	const node_p node = NG_HOOK_NODE(hook);
809	const priv_p priv = NG_NODE_PRIVATE(node);
810	struct ng_bridge_host *host;
811	struct ether_header *eh;
812	struct ng_bridge_send_ctx ctx = { 0 };
813	hook_p ret;
814
815	NGI_GET_M(item, ctx.m);
816
817	ctx.incoming = NG_HOOK_PRIVATE(hook);
818	/* Sanity check packet and pull up header */
819	if (ctx.m->m_pkthdr.len < ETHER_HDR_LEN) {
820		counter_u64_add(ctx.incoming->stats.recvRunts, 1);
821		NG_FREE_ITEM(item);
822		NG_FREE_M(ctx.m);
823		return (EINVAL);
824	}
825	if (ctx.m->m_len < ETHER_HDR_LEN && !(ctx.m = m_pullup(ctx.m, ETHER_HDR_LEN))) {
826		counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
827		NG_FREE_ITEM(item);
828		return (ENOBUFS);
829	}
830	eh = mtod(ctx.m, struct ether_header *);
831	if ((eh->ether_shost[0] & 1) != 0) {
832		counter_u64_add(ctx.incoming->stats.recvInvalid, 1);
833		NG_FREE_ITEM(item);
834		NG_FREE_M(ctx.m);
835		return (EINVAL);
836	}
837
838	/* Is link disabled due to a loopback condition? */
839	if (ctx.incoming->loopCount != 0) {
840		counter_u64_add(ctx.incoming->stats.loopDrops, 1);
841		NG_FREE_ITEM(item);
842		NG_FREE_M(ctx.m);
843		return (ELOOP);
844	}
845
846	/* Update stats */
847	counter_u64_add(ctx.incoming->stats.recvPackets, 1);
848	counter_u64_add(ctx.incoming->stats.recvOctets, ctx.m->m_pkthdr.len);
849	if ((ctx.manycast = (eh->ether_dhost[0] & 1)) != 0) {
850		if (ETHER_EQUAL(eh->ether_dhost, ng_bridge_bcast_addr)) {
851			counter_u64_add(ctx.incoming->stats.recvBroadcasts, 1);
852			ctx.manycast = 2;
853		} else
854			counter_u64_add(ctx.incoming->stats.recvMulticasts, 1);
855	}
856
857	/* Look up packet's source Ethernet address in hashtable */
858	if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL)
859		/* Update time since last heard from this host.
860		 * This is safe without locking, because it's
861		 * the only operation during shared access.
862		 */
863		if (__predict_false(host->staleness > 0))
864			host->staleness = 0;
865
866	if ((host == NULL && ctx.incoming->learnMac) ||
867	    (host != NULL && host->link != ctx.incoming)) {
868		struct ng_mesg *msg;
869		struct ng_bridge_move_host *mh;
870		int error = 0;
871
872		NG_MKMESSAGE(msg, NGM_BRIDGE_COOKIE, NGM_BRIDGE_MOVE_HOST,
873		    sizeof(*mh), M_NOWAIT);
874		if (msg == NULL) {
875			counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
876			NG_FREE_ITEM(item);
877			NG_FREE_M(ctx.m);
878			return (ENOMEM);
879		}
880		mh = (struct ng_bridge_move_host *)msg->data;
881		strncpy(mh->hook, NG_HOOK_NAME(ctx.incoming->hook),
882		    sizeof(mh->hook));
883		memcpy(mh->addr, eh->ether_shost, sizeof(mh->addr));
884		NG_SEND_MSG_ID(error, node, msg, NG_NODE_ID(node),
885		    NG_NODE_ID(node));
886		if (error)
887			counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
888	}
889
890	if (host != NULL && host->link != ctx.incoming) {
891		if (host->age < priv->conf.minStableAge) {
892			/* Drop packet on instable links */
893			counter_u64_add(ctx.incoming->stats.loopDrops, 1);
894			NG_FREE_ITEM(item);
895			NG_FREE_M(ctx.m);
896			return (ELOOP);
897		}
898	}
899
900	/* Run packet through ipfw processing, if enabled */
901#if 0
902	if (priv->conf.ipfw[linkNum] && V_fw_enable && V_ip_fw_chk_ptr != NULL) {
903		/* XXX not implemented yet */
904	}
905#endif
906
907	/*
908	 * If unicast and destination host known, deliver to host's link,
909	 * unless it is the same link as the packet came in on.
910	 */
911	if (!ctx.manycast) {
912
913		/* Determine packet destination link */
914		if ((host = ng_bridge_get(priv, eh->ether_dhost)) != NULL) {
915			link_p destLink = host->link;
916
917			/* If destination same as incoming link, do nothing */
918			if (destLink == ctx.incoming) {
919				NG_FREE_ITEM(item);
920				NG_FREE_M(ctx.m);
921				return (0);
922			}
923
924			/* Deliver packet out the destination link */
925			return (ng_bridge_send_data(destLink, ctx.manycast, ctx.m, item));
926		}
927
928		/* Destination host is not known */
929		counter_u64_add(ctx.incoming->stats.recvUnknown, 1);
930	}
931
932	/* Distribute unknown, multicast, broadcast pkts to all other links */
933	NG_NODE_FOREACH_HOOK(node, ng_bridge_send_ctx, &ctx, ret);
934
935	/* Finally send out on the first link found */
936	if (ctx.foundFirst != NULL) {
937		int error = ng_bridge_send_data(ctx.foundFirst, ctx.manycast, ctx.m, item);
938		if (error)
939			ctx.error = error;
940	} else {		       /* nothing to send at all */
941		NG_FREE_ITEM(item);
942		NG_FREE_M(ctx.m);
943	}
944
945	return (ctx.error);
946}
947
948/*
949 * Shutdown node
950 */
951static int
952ng_bridge_shutdown(node_p node)
953{
954	const priv_p priv = NG_NODE_PRIVATE(node);
955
956	/*
957	 * Shut down everything including the timer.  Even if the
958	 * callout has already been dequeued and is about to be
959	 * run, ng_bridge_timeout() won't be fired as the node
960	 * is already marked NGF_INVALID, so we're safe to free
961	 * the node now.
962	 */
963	KASSERT(priv->numLinks == 0 && priv->numHosts == 0,
964	    ("%s: numLinks=%d numHosts=%d",
965	    __func__, priv->numLinks, priv->numHosts));
966	ng_uncallout(&priv->timer, node);
967	NG_NODE_SET_PRIVATE(node, NULL);
968	NG_NODE_UNREF(node);
969	free(priv->tab, M_NETGRAPH_BRIDGE);
970	free(priv, M_NETGRAPH_BRIDGE);
971	return (0);
972}
973
974/*
975 * Hook disconnection.
976 */
977static int
978ng_bridge_disconnect(hook_p hook)
979{
980	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
981	link_p link = NG_HOOK_PRIVATE(hook);
982
983	/* Remove all hosts associated with this link */
984	ng_bridge_remove_hosts(priv, link);
985
986	/* Free associated link information */
987	counter_u64_free(link->stats.recvOctets);
988	counter_u64_free(link->stats.recvPackets);
989	counter_u64_free(link->stats.recvMulticasts);
990	counter_u64_free(link->stats.recvBroadcasts);
991	counter_u64_free(link->stats.recvUnknown);
992	counter_u64_free(link->stats.recvRunts);
993	counter_u64_free(link->stats.recvInvalid);
994	counter_u64_free(link->stats.xmitOctets);
995	counter_u64_free(link->stats.xmitPackets);
996	counter_u64_free(link->stats.xmitMulticasts);
997	counter_u64_free(link->stats.xmitBroadcasts);
998	counter_u64_free(link->stats.loopDrops);
999	counter_u64_free(link->stats.memoryFailures);
1000	free(link, M_NETGRAPH_BRIDGE);
1001	priv->numLinks--;
1002
1003	/* If no more hooks, go away */
1004	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0)
1005	    && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))
1006	    && !priv->persistent) {
1007		ng_rmnode_self(NG_HOOK_NODE(hook));
1008	}
1009	return (0);
1010}
1011
1012/******************************************************************
1013		    HASH TABLE FUNCTIONS
1014******************************************************************/
1015
1016/*
1017 * Hash algorithm
1018 */
1019#define HASH(addr,mask)		( (((const u_int16_t *)(addr))[0] 	\
1020				 ^ ((const u_int16_t *)(addr))[1] 	\
1021				 ^ ((const u_int16_t *)(addr))[2]) & (mask) )
1022
1023/*
1024 * Find a host entry in the table.
1025 */
1026static struct ng_bridge_host *
1027ng_bridge_get(priv_cp priv, const u_char *addr)
1028{
1029	const int bucket = HASH(addr, priv->hashMask);
1030	struct ng_bridge_host *host;
1031
1032	SLIST_FOREACH(host, &priv->tab[bucket], next) {
1033		if (ETHER_EQUAL(host->addr, addr))
1034			return (host);
1035	}
1036	return (NULL);
1037}
1038
1039/*
1040 * Add a host entry to the table. If it already exists, move it
1041 * to the new link. Returns 0 on success.
1042 */
1043static int
1044ng_bridge_put(priv_p priv, const u_char *addr, link_p link)
1045{
1046	const int bucket = HASH(addr, priv->hashMask);
1047	struct ng_bridge_host *host;
1048
1049	if ((host = ng_bridge_get(priv, addr)) != NULL) {
1050		/* Host already on the correct link? */
1051		if (host->link == link)
1052			return 0;
1053
1054		/* Move old host over to new link */
1055		if (host->age >= priv->conf.minStableAge) {
1056			host->link = link;
1057			host->age = 0;
1058			return (0);
1059		}
1060		/*
1061		 * If the host was recently moved to the old link and
1062		 * it's now jumping to a new link, declare a loopback
1063		 * condition.
1064		 */
1065		if (priv->conf.debugLevel >= 2)
1066		    log(LOG_WARNING, "ng_bridge: %s:"
1067			" loopback detected on %s\n",
1068			ng_bridge_nodename(priv->node),
1069			NG_HOOK_NAME(link->hook));
1070
1071		/* Mark link as linka non grata */
1072		link->loopCount = priv->conf.loopTimeout;
1073		link->stats.loopDetects++;
1074
1075		/* Forget all hosts on this link */
1076		ng_bridge_remove_hosts(priv, link);
1077		return (ELOOP);
1078	}
1079
1080	/* Allocate and initialize new hashtable entry */
1081	host = malloc(sizeof(*host), M_NETGRAPH_BRIDGE, M_NOWAIT);
1082	if (host == NULL)
1083		return (ENOMEM);
1084	bcopy(addr, host->addr, ETHER_ADDR_LEN);
1085	host->link = link;
1086	host->staleness = 0;
1087	host->age = 0;
1088
1089	/* Add new element to hash bucket */
1090	SLIST_INSERT_HEAD(&priv->tab[bucket], host, next);
1091	priv->numHosts++;
1092
1093	/* Resize table if necessary */
1094	ng_bridge_rehash(priv);
1095	return (0);
1096}
1097
1098/*
1099 * Resize the hash table. We try to maintain the number of buckets
1100 * such that the load factor is in the range 0.25 to 1.0.
1101 *
1102 * If we can't get the new memory then we silently fail. This is OK
1103 * because things will still work and we'll try again soon anyway.
1104 */
1105static void
1106ng_bridge_rehash(priv_p priv)
1107{
1108	struct ng_bridge_bucket *newTab;
1109	int oldBucket, newBucket;
1110	int newNumBuckets;
1111	u_int newMask;
1112
1113	/* Is table too full or too empty? */
1114	if (priv->numHosts > priv->numBuckets
1115	    && (priv->numBuckets << 1) <= MAX_BUCKETS)
1116		newNumBuckets = priv->numBuckets << 1;
1117	else if (priv->numHosts < (priv->numBuckets >> 2)
1118	    && (priv->numBuckets >> 2) >= MIN_BUCKETS)
1119		newNumBuckets = priv->numBuckets >> 2;
1120	else
1121		return;
1122	newMask = newNumBuckets - 1;
1123
1124	/* Allocate and initialize new table */
1125	newTab = malloc(newNumBuckets * sizeof(*newTab),
1126	    M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO);
1127	if (newTab == NULL)
1128		return;
1129
1130	/* Move all entries from old table to new table */
1131	for (oldBucket = 0; oldBucket < priv->numBuckets; oldBucket++) {
1132		struct ng_bridge_bucket *const oldList = &priv->tab[oldBucket];
1133
1134		while (!SLIST_EMPTY(oldList)) {
1135			struct ng_bridge_host *const host
1136			    = SLIST_FIRST(oldList);
1137
1138			SLIST_REMOVE_HEAD(oldList, next);
1139			newBucket = HASH(host->addr, newMask);
1140			SLIST_INSERT_HEAD(&newTab[newBucket], host, next);
1141		}
1142	}
1143
1144	/* Replace old table with new one */
1145	if (priv->conf.debugLevel >= 3) {
1146		log(LOG_INFO, "ng_bridge: %s: table size %d -> %d\n",
1147		    ng_bridge_nodename(priv->node),
1148		    priv->numBuckets, newNumBuckets);
1149	}
1150	free(priv->tab, M_NETGRAPH_BRIDGE);
1151	priv->numBuckets = newNumBuckets;
1152	priv->hashMask = newMask;
1153	priv->tab = newTab;
1154	return;
1155}
1156
1157/******************************************************************
1158		    MISC FUNCTIONS
1159******************************************************************/
1160
1161
1162/*
1163 * Remove all hosts associated with a specific link from the hashtable.
1164 * If linkNum == -1, then remove all hosts in the table.
1165 */
1166static void
1167ng_bridge_remove_hosts(priv_p priv, link_p link)
1168{
1169	int bucket;
1170
1171	for (bucket = 0; bucket < priv->numBuckets; bucket++) {
1172		struct ng_bridge_host **hptr = &SLIST_FIRST(&priv->tab[bucket]);
1173
1174		while (*hptr != NULL) {
1175			struct ng_bridge_host *const host = *hptr;
1176
1177			if (link == NULL || host->link == link) {
1178				*hptr = SLIST_NEXT(host, next);
1179				free(host, M_NETGRAPH_BRIDGE);
1180				priv->numHosts--;
1181			} else
1182				hptr = &SLIST_NEXT(host, next);
1183		}
1184	}
1185}
1186
1187/*
1188 * Handle our once-per-second timeout event. We do two things:
1189 * we decrement link->loopCount for those links being muted due to
1190 * a detected loopback condition, and we remove any hosts from
1191 * the hashtable whom we haven't heard from in a long while.
1192 */
1193static int
1194ng_bridge_unmute(hook_p hook, void *arg)
1195{
1196	link_p link = NG_HOOK_PRIVATE(hook);
1197	node_p node = NG_HOOK_NODE(hook);
1198	priv_p priv = NG_NODE_PRIVATE(node);
1199	int *counter = arg;
1200
1201	if (link->loopCount != 0) {
1202		link->loopCount--;
1203		if (link->loopCount == 0 && priv->conf.debugLevel >= 2) {
1204			log(LOG_INFO, "ng_bridge: %s:"
1205			    " restoring looped back %s\n",
1206			    ng_bridge_nodename(node), NG_HOOK_NAME(hook));
1207		}
1208	}
1209	(*counter)++;
1210	return (1);
1211}
1212
1213static void
1214ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2)
1215{
1216	const priv_p priv = NG_NODE_PRIVATE(node);
1217	int bucket;
1218	int counter = 0;
1219	hook_p ret;
1220
1221	/* Update host time counters and remove stale entries */
1222	for (bucket = 0; bucket < priv->numBuckets; bucket++) {
1223		struct ng_bridge_host **hptr = &SLIST_FIRST(&priv->tab[bucket]);
1224
1225		while (*hptr != NULL) {
1226			struct ng_bridge_host *const host = *hptr;
1227
1228			/* Remove hosts we haven't heard from in a while */
1229			if (++host->staleness >= priv->conf.maxStaleness) {
1230				*hptr = SLIST_NEXT(host, next);
1231				free(host, M_NETGRAPH_BRIDGE);
1232				priv->numHosts--;
1233			} else {
1234				if (host->age < 0xffff)
1235					host->age++;
1236				hptr = &SLIST_NEXT(host, next);
1237				counter++;
1238			}
1239		}
1240	}
1241	KASSERT(priv->numHosts == counter,
1242	    ("%s: hosts: %d != %d", __func__, priv->numHosts, counter));
1243
1244	/* Decrease table size if necessary */
1245	ng_bridge_rehash(priv);
1246
1247	/* Decrease loop counter on muted looped back links */
1248	counter = 0;
1249	NG_NODE_FOREACH_HOOK(node, ng_bridge_unmute, &counter, ret);
1250	KASSERT(priv->numLinks == counter,
1251	    ("%s: links: %d != %d", __func__, priv->numLinks, counter));
1252
1253	/* Register a new timeout, keeping the existing node reference */
1254	ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0);
1255}
1256
1257/*
1258 * Return node's "name", even if it doesn't have one.
1259 */
1260static const char *
1261ng_bridge_nodename(node_cp node)
1262{
1263	static char name[NG_NODESIZ];
1264
1265	if (NG_NODE_HAS_NAME(node))
1266		snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node));
1267	else
1268		snprintf(name, sizeof(name), "[%x]", ng_node2ID(node));
1269	return name;
1270}
1271
1272