]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/netgraph/ng_bridge.c
netgraph/ng_bridge: Document staleness in multithreaded operation
[FreeBSD/FreeBSD.git] / sys / netgraph / ng_bridge.c
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
86 static 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 */
93 struct 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         counter_u64_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 */
111 struct 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 };
118 typedef struct ng_bridge_link const *link_cp;   /* read only access */
119
120 /* Per-node private data */
121 struct 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 };
133 typedef struct ng_bridge_private *priv_p;
134 typedef struct ng_bridge_private const *priv_cp;        /* read only access */
135
136 /* Information about a host, stored in a hash table entry */
137 struct 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 */
146 SLIST_HEAD(ng_bridge_bucket, ng_bridge_host);
147
148 /* Netgraph node methods */
149 static ng_constructor_t ng_bridge_constructor;
150 static ng_rcvmsg_t      ng_bridge_rcvmsg;
151 static ng_shutdown_t    ng_bridge_shutdown;
152 static ng_newhook_t     ng_bridge_newhook;
153 static ng_rcvdata_t     ng_bridge_rcvdata;
154 static ng_disconnect_t  ng_bridge_disconnect;
155
156 /* Other internal functions */
157 static struct   ng_bridge_host *ng_bridge_get(priv_cp priv, const u_char *addr);
158 static int      ng_bridge_put(priv_p priv, const u_char *addr, link_p link);
159 static void     ng_bridge_rehash(priv_p priv);
160 static void     ng_bridge_remove_hosts(priv_p priv, link_p link);
161 static void     ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2);
162 static const    char *ng_bridge_nodename(node_cp node);
163
164 /* Ethernet broadcast */
165 static 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  */
190 static int
191 ng_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 */
201 static const struct ng_parse_struct_field ng_bridge_host_type_fields[]
202         = NG_BRIDGE_HOST_TYPE_INFO(&ng_parse_enaddr_type);
203 static const struct ng_parse_type ng_bridge_host_type = {
204         &ng_parse_struct_type,
205         &ng_bridge_host_type_fields
206 };
207 static const struct ng_parse_array_info ng_bridge_hary_type_info = {
208         &ng_bridge_host_type,
209         ng_bridge_getTableLength
210 };
211 static const struct ng_parse_type ng_bridge_hary_type = {
212         &ng_parse_array_type,
213         &ng_bridge_hary_type_info
214 };
215 static const struct ng_parse_struct_field ng_bridge_host_ary_type_fields[]
216         = NG_BRIDGE_HOST_ARY_TYPE_INFO(&ng_bridge_hary_type);
217 static 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 */
223 static const struct ng_parse_struct_field ng_bridge_config_type_fields[]
224         = NG_BRIDGE_CONFIG_TYPE_INFO;
225 static 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 */
231 static const struct ng_parse_struct_field ng_bridge_stats_type_fields[]
232         = NG_BRIDGE_STATS_TYPE_INFO;
233 static const struct ng_parse_type ng_bridge_stats_type = {
234         &ng_parse_struct_type,
235         &ng_bridge_stats_type_fields
236 };
237
238 /* List of commands and how to convert arguments to/from ASCII */
239 static const struct ng_cmdlist ng_bridge_cmdlist[] = {
240         {
241           NGM_BRIDGE_COOKIE,
242           NGM_BRIDGE_SET_CONFIG,
243           "setconfig",
244           &ng_bridge_config_type,
245           NULL
246         },
247         {
248           NGM_BRIDGE_COOKIE,
249           NGM_BRIDGE_GET_CONFIG,
250           "getconfig",
251           NULL,
252           &ng_bridge_config_type
253         },
254         {
255           NGM_BRIDGE_COOKIE,
256           NGM_BRIDGE_RESET,
257           "reset",
258           NULL,
259           NULL
260         },
261         {
262           NGM_BRIDGE_COOKIE,
263           NGM_BRIDGE_GET_STATS,
264           "getstats",
265           &ng_parse_uint32_type,
266           &ng_bridge_stats_type
267         },
268         {
269           NGM_BRIDGE_COOKIE,
270           NGM_BRIDGE_CLR_STATS,
271           "clrstats",
272           &ng_parse_uint32_type,
273           NULL
274         },
275         {
276           NGM_BRIDGE_COOKIE,
277           NGM_BRIDGE_GETCLR_STATS,
278           "getclrstats",
279           &ng_parse_uint32_type,
280           &ng_bridge_stats_type
281         },
282         {
283           NGM_BRIDGE_COOKIE,
284           NGM_BRIDGE_GET_TABLE,
285           "gettable",
286           NULL,
287           &ng_bridge_host_ary_type
288         },
289         {
290           NGM_BRIDGE_COOKIE,
291           NGM_BRIDGE_SET_PERSISTENT,
292           "setpersistent",
293           NULL,
294           NULL
295         },
296         { 0 }
297 };
298
299 /* Node type descriptor */
300 static struct ng_type ng_bridge_typestruct = {
301         .version =      NG_ABI_VERSION,
302         .name =         NG_BRIDGE_NODE_TYPE,
303         .constructor =  ng_bridge_constructor,
304         .rcvmsg =       ng_bridge_rcvmsg,
305         .shutdown =     ng_bridge_shutdown,
306         .newhook =      ng_bridge_newhook,
307         .rcvdata =      ng_bridge_rcvdata,
308         .disconnect =   ng_bridge_disconnect,
309         .cmdlist =      ng_bridge_cmdlist,
310 };
311 NETGRAPH_INIT(bridge, &ng_bridge_typestruct);
312
313 /******************************************************************
314                     NETGRAPH NODE METHODS
315 ******************************************************************/
316
317 /*
318  * Node constructor
319  */
320 static int
321 ng_bridge_constructor(node_p node)
322 {
323         priv_p priv;
324
325         /* Allocate and initialize private info */
326         priv = malloc(sizeof(*priv), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO);
327         ng_callout_init(&priv->timer);
328
329         /* Allocate and initialize hash table, etc. */
330         priv->tab = malloc(MIN_BUCKETS * sizeof(*priv->tab),
331             M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO);
332         priv->numBuckets = MIN_BUCKETS;
333         priv->hashMask = MIN_BUCKETS - 1;
334         priv->conf.debugLevel = 1;
335         priv->conf.loopTimeout = DEFAULT_LOOP_TIMEOUT;
336         priv->conf.maxStaleness = DEFAULT_MAX_STALENESS;
337         priv->conf.minStableAge = DEFAULT_MIN_STABLE_AGE;
338         priv->sendUnknown = 1;         /* classic bridge */
339
340         /*
341          * This node has all kinds of stuff that could be screwed by SMP.
342          * Until it gets it's own internal protection, we go through in 
343          * single file. This could hurt a machine bridging between two 
344          * GB ethernets so it should be fixed. 
345          * When it's fixed the process SHOULD NOT SLEEP, spinlocks please!
346          * (and atomic ops )
347          */
348         NG_NODE_FORCE_WRITER(node);
349         NG_NODE_SET_PRIVATE(node, priv);
350         priv->node = node;
351
352         /* Start timer; timer is always running while node is alive */
353         ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0);
354
355         /* Done */
356         return (0);
357 }
358
359 /*
360  * Method for attaching a new hook
361  */
362 static  int
363 ng_bridge_newhook(node_p node, hook_p hook, const char *name)
364 {
365         const priv_p priv = NG_NODE_PRIVATE(node);
366         char linkName[NG_HOOKSIZ];
367         u_int32_t linkNum;
368         link_p link;
369         const char *prefix = NG_BRIDGE_HOOK_LINK_PREFIX;
370         bool isUplink;
371
372         /* Check for a link hook */
373         if (strlen(name) <= strlen(prefix))
374                 return (EINVAL);       /* Unknown hook name */
375
376         isUplink = (name[0] == 'u');
377         if (isUplink)
378                 prefix = NG_BRIDGE_HOOK_UPLINK_PREFIX;
379
380         /* primitive parsing */
381         linkNum = strtoul(name + strlen(prefix), NULL, 10);
382         /* validation by comparing against the reconstucted name  */
383         snprintf(linkName, sizeof(linkName), "%s%u", prefix, linkNum);
384         if (strcmp(linkName, name) != 0)
385                 return (EINVAL);
386
387         if (linkNum == 0 && isUplink)
388                 return (EINVAL);
389
390         if(NG_PEER_NODE(hook) == node)
391                 return (ELOOP);
392
393         link = malloc(sizeof(*link), M_NETGRAPH_BRIDGE, M_ZERO);
394         if (link == NULL)
395                 return (ENOMEM);
396
397         link->stats.recvOctets = counter_u64_alloc(M_WAITOK);
398         link->stats.recvPackets = counter_u64_alloc(M_WAITOK);
399         link->stats.recvMulticasts = counter_u64_alloc(M_WAITOK);
400         link->stats.recvBroadcasts = counter_u64_alloc(M_WAITOK);
401         link->stats.recvUnknown = counter_u64_alloc(M_WAITOK);
402         link->stats.recvRunts = counter_u64_alloc(M_WAITOK);
403         link->stats.recvInvalid = counter_u64_alloc(M_WAITOK);
404         link->stats.xmitOctets = counter_u64_alloc(M_WAITOK);
405         link->stats.xmitPackets = counter_u64_alloc(M_WAITOK);
406         link->stats.xmitMulticasts = counter_u64_alloc(M_WAITOK);
407         link->stats.xmitBroadcasts = counter_u64_alloc(M_WAITOK);
408         link->stats.loopDrops = counter_u64_alloc(M_WAITOK);
409         link->stats.loopDetects = counter_u64_alloc(M_WAITOK);
410         link->stats.memoryFailures = counter_u64_alloc(M_WAITOK);
411
412         link->hook = hook;
413         if (isUplink) {
414                 link->learnMac = 0;
415                 link->sendUnknown = 1;
416                 if (priv->numLinks == 0)        /* if the first link is an uplink */
417                     priv->sendUnknown = 0;      /* switch to restrictive mode */
418         } else {
419                 link->learnMac = 1;
420                 link->sendUnknown = priv->sendUnknown;
421         }
422
423         NG_HOOK_SET_PRIVATE(hook, link);
424         priv->numLinks++;
425         return (0);
426 }
427
428 /*
429  * Receive a control message
430  */
431 static void ng_bridge_clear_link_stats(struct ng_bridge_link_kernel_stats * p)
432 {
433         counter_u64_zero(p->recvOctets);
434         counter_u64_zero(p->recvPackets);
435         counter_u64_zero(p->recvMulticasts);
436         counter_u64_zero(p->recvBroadcasts);
437         counter_u64_zero(p->recvUnknown);
438         counter_u64_zero(p->recvRunts);
439         counter_u64_zero(p->recvInvalid);
440         counter_u64_zero(p->xmitOctets);
441         counter_u64_zero(p->xmitPackets);
442         counter_u64_zero(p->xmitMulticasts);
443         counter_u64_zero(p->xmitBroadcasts);
444         counter_u64_zero(p->loopDrops);
445         counter_u64_zero(p->loopDetects);
446         counter_u64_zero(p->memoryFailures);
447 };
448
449 static int
450 ng_bridge_reset_link(hook_p hook, void *arg __unused)
451 {
452         link_p priv = NG_HOOK_PRIVATE(hook);
453
454         priv->loopCount = 0;
455         ng_bridge_clear_link_stats(&priv->stats);
456         return (1);
457 }
458
459 static int
460 ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook)
461 {
462         const priv_p priv = NG_NODE_PRIVATE(node);
463         struct ng_mesg *resp = NULL;
464         int error = 0;
465         struct ng_mesg *msg;
466
467         NGI_GET_MSG(item, msg);
468         switch (msg->header.typecookie) {
469         case NGM_BRIDGE_COOKIE:
470                 switch (msg->header.cmd) {
471                 case NGM_BRIDGE_GET_CONFIG:
472                     {
473                         struct ng_bridge_config *conf;
474
475                         NG_MKRESPONSE(resp, msg,
476                             sizeof(struct ng_bridge_config), M_NOWAIT);
477                         if (resp == NULL) {
478                                 error = ENOMEM;
479                                 break;
480                         }
481                         conf = (struct ng_bridge_config *)resp->data;
482                         *conf = priv->conf;     /* no sanity checking needed */
483                         break;
484                     }
485                 case NGM_BRIDGE_SET_CONFIG:
486                     {
487                         struct ng_bridge_config *conf;
488
489                         if (msg->header.arglen
490                             != sizeof(struct ng_bridge_config)) {
491                                 error = EINVAL;
492                                 break;
493                         }
494                         conf = (struct ng_bridge_config *)msg->data;
495                         priv->conf = *conf;
496                         break;
497                     }
498                 case NGM_BRIDGE_RESET:
499                     {
500                         hook_p rethook;
501
502                         /* Flush all entries in the hash table */
503                         ng_bridge_remove_hosts(priv, NULL);
504
505                         /* Reset all loop detection counters and stats */
506                         NG_NODE_FOREACH_HOOK(node, ng_bridge_reset_link, NULL,
507                             rethook);
508                         break;
509                     }
510                 case NGM_BRIDGE_GET_STATS:
511                 case NGM_BRIDGE_CLR_STATS:
512                 case NGM_BRIDGE_GETCLR_STATS:
513                     {
514                         hook_p hook;
515                         link_p link;
516                         char linkName[NG_HOOKSIZ];
517                         int linkNum;
518                             
519                         /* Get link number */
520                         if (msg->header.arglen != sizeof(u_int32_t)) {
521                                 error = EINVAL;
522                                 break;
523                         }
524                         linkNum = *((int32_t *)msg->data);
525                         if (linkNum < 0)
526                                 snprintf(linkName, sizeof(linkName),
527                                     "%s%u", NG_BRIDGE_HOOK_UPLINK_PREFIX, -linkNum);
528                         else
529                                 snprintf(linkName, sizeof(linkName),
530                                     "%s%u", NG_BRIDGE_HOOK_LINK_PREFIX, linkNum);
531                             
532                         if ((hook = ng_findhook(node, linkName)) == NULL) {
533                                 error = ENOTCONN;
534                                 break;
535                         }
536                         link = NG_HOOK_PRIVATE(hook);
537
538                         /* Get/clear stats */
539                         if (msg->header.cmd != NGM_BRIDGE_CLR_STATS) {
540                                 struct ng_bridge_link_stats *rs;
541
542                                 NG_MKRESPONSE(resp, msg,
543                                     sizeof(link->stats), M_NOWAIT);
544                                 if (resp == NULL) {
545                                         error = ENOMEM;
546                                         break;
547                                 }
548                                 rs = (struct ng_bridge_link_stats *)resp->data;
549 #define FETCH(x)        rs->x = counter_u64_fetch(link->stats.x)
550                                 FETCH(recvOctets);
551                                 FETCH(recvPackets);
552                                 FETCH(recvMulticasts);
553                                 FETCH(recvBroadcasts);
554                                 FETCH(recvUnknown);
555                                 FETCH(recvRunts);
556                                 FETCH(recvInvalid);
557                                 FETCH(xmitOctets);
558                                 FETCH(xmitPackets);
559                                 FETCH(xmitMulticasts);
560                                 FETCH(xmitBroadcasts);
561                                 FETCH(loopDrops);
562                                 FETCH(loopDetects);
563                                 FETCH(memoryFailures);
564 #undef FETCH
565                         }
566                         if (msg->header.cmd != NGM_BRIDGE_GET_STATS)
567                                 ng_bridge_clear_link_stats(&link->stats);
568                         break;
569                     }
570                 case NGM_BRIDGE_GET_TABLE:
571                     {
572                         struct ng_bridge_host_ary *ary;
573                         struct ng_bridge_host *host;
574                         int i = 0, bucket;
575
576                         NG_MKRESPONSE(resp, msg, sizeof(*ary)
577                             + (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT);
578                         if (resp == NULL) {
579                                 error = ENOMEM;
580                                 break;
581                         }
582                         ary = (struct ng_bridge_host_ary *)resp->data;
583                         ary->numHosts = priv->numHosts;
584                         for (bucket = 0; bucket < priv->numBuckets; bucket++) {
585                                 SLIST_FOREACH(host, &priv->tab[bucket], next) {
586                                         memcpy(ary->hosts[i].addr,
587                                                host->addr,
588                                                sizeof(ary->hosts[i].addr));
589                                         ary->hosts[i].age       = host->age;
590                                         ary->hosts[i].staleness = host->staleness;
591                                         strncpy(ary->hosts[i].hook,
592                                                 NG_HOOK_NAME(host->link->hook),
593                                                 sizeof(ary->hosts[i].hook));
594                                         i++;
595                                 }
596                         }
597                         break;
598                     }
599                 case NGM_BRIDGE_SET_PERSISTENT:
600                     {
601                         priv->persistent = 1;
602                         break;
603                     }
604                 default:
605                         error = EINVAL;
606                         break;
607                 }
608                 break;
609         default:
610                 error = EINVAL;
611                 break;
612         }
613
614         /* Done */
615         NG_RESPOND_MSG(error, node, item, resp);
616         NG_FREE_MSG(msg);
617         return (error);
618 }
619
620 /*
621  * Receive data on a hook
622  */
623 struct ng_bridge_send_ctx {
624         link_p foundFirst, incoming;
625         struct mbuf * m;
626         int manycast, error;
627 };
628
629 static int
630 ng_bridge_send_ctx(hook_p dst, void *arg)
631 {
632         struct ng_bridge_send_ctx *ctx = arg;
633         link_p destLink = NG_HOOK_PRIVATE(dst);
634         struct mbuf *m2 = NULL;
635         int error = 0;
636
637         /* Skip incoming link */
638         if (destLink == ctx->incoming) {
639                 return (1);
640         }
641
642         /* Skip sending unknowns to undesired links  */
643         if (!ctx->manycast && !destLink->sendUnknown)
644                 return (1);
645
646         if (ctx->foundFirst == NULL) {
647                 /*
648                  * This is the first usable link we have found.
649                  * Reserve it for the originals.
650                  * If we never find another we save a copy.
651                  */
652                 ctx->foundFirst = destLink;
653                 return (1);
654         }
655
656         /*
657          * It's usable link but not the reserved (first) one.
658          * Copy mbuf info for sending.
659          */
660         m2 = m_dup(ctx->m, M_NOWAIT);   /* XXX m_copypacket() */
661         if (m2 == NULL) {
662                 counter_u64_add(ctx->incoming->stats.memoryFailures, 1);
663                 ctx->error = ENOBUFS;
664                 return (0);            /* abort loop */
665         }
666
667         /* Update stats */
668         counter_u64_add(destLink->stats.xmitPackets, 1);
669         counter_u64_add(destLink->stats.xmitOctets, m2->m_pkthdr.len);
670         switch (ctx->manycast) {
671          default:                                       /* unknown unicast */
672                 break;
673          case 1:                                        /* multicast */
674                 counter_u64_add(destLink->stats.xmitMulticasts, 1);
675                 break;
676          case 2:                                        /* broadcast */
677                 counter_u64_add(destLink->stats.xmitBroadcasts, 1);
678                 break;
679         }
680
681         /* Send packet */
682         NG_SEND_DATA_ONLY(error, destLink->hook, m2);
683         if(error)
684           ctx->error = error;
685         return (1);
686 }
687
688 static int
689 ng_bridge_rcvdata(hook_p hook, item_p item)
690 {
691         const node_p node = NG_HOOK_NODE(hook);
692         const priv_p priv = NG_NODE_PRIVATE(node);
693         struct ng_bridge_host *host;
694         struct ether_header *eh;
695         struct ng_bridge_send_ctx ctx = { 0 };
696         hook_p ret;
697
698         NGI_GET_M(item, ctx.m);
699
700         ctx.incoming = NG_HOOK_PRIVATE(hook);
701         /* Sanity check packet and pull up header */
702         if (ctx.m->m_pkthdr.len < ETHER_HDR_LEN) {
703                 counter_u64_add(ctx.incoming->stats.recvRunts, 1);
704                 NG_FREE_ITEM(item);
705                 NG_FREE_M(ctx.m);
706                 return (EINVAL);
707         }
708         if (ctx.m->m_len < ETHER_HDR_LEN && !(ctx.m = m_pullup(ctx.m, ETHER_HDR_LEN))) {
709                 counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
710                 NG_FREE_ITEM(item);
711                 return (ENOBUFS);
712         }
713         eh = mtod(ctx.m, struct ether_header *);
714         if ((eh->ether_shost[0] & 1) != 0) {
715                 counter_u64_add(ctx.incoming->stats.recvInvalid, 1);
716                 NG_FREE_ITEM(item);
717                 NG_FREE_M(ctx.m);
718                 return (EINVAL);
719         }
720
721         /* Is link disabled due to a loopback condition? */
722         if (ctx.incoming->loopCount != 0) {
723                 counter_u64_add(ctx.incoming->stats.loopDrops, 1);
724                 NG_FREE_ITEM(item);
725                 NG_FREE_M(ctx.m);
726                 return (ELOOP);         /* XXX is this an appropriate error? */
727         }
728
729         /* Update stats */
730         counter_u64_add(ctx.incoming->stats.recvPackets, 1);
731         counter_u64_add(ctx.incoming->stats.recvOctets, ctx.m->m_pkthdr.len);
732         if ((ctx.manycast = (eh->ether_dhost[0] & 1)) != 0) {
733                 if (ETHER_EQUAL(eh->ether_dhost, ng_bridge_bcast_addr)) {
734                         counter_u64_add(ctx.incoming->stats.recvBroadcasts, 1);
735                         ctx.manycast = 2;
736                 } else
737                         counter_u64_add(ctx.incoming->stats.recvMulticasts, 1);
738         }
739
740         /* Look up packet's source Ethernet address in hashtable */
741         if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL) {
742                 /* Update time since last heard from this host.
743                  * This is safe without locking, because it's
744                  * the only operation during shared access.
745                  */
746                 host->staleness = 0;
747
748                 /* Did host jump to a different link? */
749                 if (host->link != ctx.incoming) {
750                         /*
751                          * If the host's old link was recently established
752                          * on the old link and it's already jumped to a new
753                          * link, declare a loopback condition.
754                          */
755                         if (host->age < priv->conf.minStableAge) {
756                                 /* Log the problem */
757                                 if (priv->conf.debugLevel >= 2) {
758                                         struct ifnet *ifp = ctx.m->m_pkthdr.rcvif;
759                                         char suffix[32];
760
761                                         if (ifp != NULL)
762                                                 snprintf(suffix, sizeof(suffix),
763                                                     " (%s)", ifp->if_xname);
764                                         else
765                                                 *suffix = '\0';
766                                         log(LOG_WARNING, "ng_bridge: %s:"
767                                             " loopback detected on %s%s\n",
768                                             ng_bridge_nodename(node),
769                                             NG_HOOK_NAME(hook), suffix);
770                                 }
771
772                                 /* Mark link as linka non grata */
773                                 ctx.incoming->loopCount = priv->conf.loopTimeout;
774                                 counter_u64_add(ctx.incoming->stats.loopDetects, 1);
775
776                                 /* Forget all hosts on this link */
777                                 ng_bridge_remove_hosts(priv, ctx.incoming);
778
779                                 /* Drop packet */
780                                 counter_u64_add(ctx.incoming->stats.loopDrops, 1);
781                                 NG_FREE_ITEM(item);
782                                 NG_FREE_M(ctx.m);
783                                 return (ELOOP);         /* XXX appropriate? */
784                         }
785
786                         /* Move host over to new link */
787                         host->link = ctx.incoming;
788                         host->age = 0;
789                 }
790         } else if (ctx.incoming->learnMac) {
791                 if (!ng_bridge_put(priv, eh->ether_shost, ctx.incoming)) {
792                         counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
793                         NG_FREE_ITEM(item);
794                         NG_FREE_M(ctx.m);
795                         return (ENOMEM);
796                 }
797         }
798
799         /* Run packet through ipfw processing, if enabled */
800 #if 0
801         if (priv->conf.ipfw[linkNum] && V_fw_enable && V_ip_fw_chk_ptr != NULL) {
802                 /* XXX not implemented yet */
803         }
804 #endif
805
806         /*
807          * If unicast and destination host known, deliver to host's link,
808          * unless it is the same link as the packet came in on.
809          */
810         if (!ctx.manycast) {
811                 /* Determine packet destination link */
812                 if ((host = ng_bridge_get(priv, eh->ether_dhost)) != NULL) {
813                         link_p destLink = host->link;
814
815                         /* If destination same as incoming link, do nothing */
816                         if (destLink == ctx.incoming) {
817                                 NG_FREE_ITEM(item);
818                                 NG_FREE_M(ctx.m);
819                                 return (0);
820                         }
821
822                         /* Deliver packet out the destination link */
823                         counter_u64_add(destLink->stats.xmitPackets, 1);
824                         counter_u64_add(destLink->stats.xmitOctets, ctx.m->m_pkthdr.len);
825                         NG_FWD_NEW_DATA(ctx.error, item, destLink->hook, ctx.m);
826                         return (ctx.error);
827                 }
828
829                 /* Destination host is not known */
830                 counter_u64_add(ctx.incoming->stats.recvUnknown, 1);
831         }
832
833         /* Distribute unknown, multicast, broadcast pkts to all other links */
834         NG_NODE_FOREACH_HOOK(node, ng_bridge_send_ctx, &ctx, ret);
835
836         /* If we never saw a good link, leave. */
837         if (ctx.foundFirst == NULL || ctx.error != 0) {
838                 NG_FREE_ITEM(item);
839                 NG_FREE_M(ctx.m);
840                 return (ctx.error);
841         }
842
843         /*
844          * If we've sent all the others, send the original
845          * on the first link we found.
846          */
847         NG_FWD_NEW_DATA(ctx.error, item, ctx.foundFirst->hook, ctx.m);
848         return (ctx.error);
849 }
850
851 /*
852  * Shutdown node
853  */
854 static int
855 ng_bridge_shutdown(node_p node)
856 {
857         const priv_p priv = NG_NODE_PRIVATE(node);
858
859         /*
860          * Shut down everything including the timer.  Even if the
861          * callout has already been dequeued and is about to be
862          * run, ng_bridge_timeout() won't be fired as the node
863          * is already marked NGF_INVALID, so we're safe to free
864          * the node now.
865          */
866         KASSERT(priv->numLinks == 0 && priv->numHosts == 0,
867             ("%s: numLinks=%d numHosts=%d",
868             __func__, priv->numLinks, priv->numHosts));
869         ng_uncallout(&priv->timer, node);
870         NG_NODE_SET_PRIVATE(node, NULL);
871         NG_NODE_UNREF(node);
872         free(priv->tab, M_NETGRAPH_BRIDGE);
873         free(priv, M_NETGRAPH_BRIDGE);
874         return (0);
875 }
876
877 /*
878  * Hook disconnection.
879  */
880 static int
881 ng_bridge_disconnect(hook_p hook)
882 {
883         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
884         link_p link = NG_HOOK_PRIVATE(hook);
885
886         /* Remove all hosts associated with this link */
887         ng_bridge_remove_hosts(priv, link);
888
889         /* Free associated link information */
890         counter_u64_free(link->stats.recvOctets);
891         counter_u64_free(link->stats.recvPackets);
892         counter_u64_free(link->stats.recvMulticasts);
893         counter_u64_free(link->stats.recvBroadcasts);
894         counter_u64_free(link->stats.recvUnknown);
895         counter_u64_free(link->stats.recvRunts);
896         counter_u64_free(link->stats.recvInvalid);
897         counter_u64_free(link->stats.xmitOctets);
898         counter_u64_free(link->stats.xmitPackets);
899         counter_u64_free(link->stats.xmitMulticasts);
900         counter_u64_free(link->stats.xmitBroadcasts);
901         counter_u64_free(link->stats.loopDrops);
902         counter_u64_free(link->stats.loopDetects);
903         counter_u64_free(link->stats.memoryFailures);
904         free(link, M_NETGRAPH_BRIDGE);
905         priv->numLinks--;
906
907         /* If no more hooks, go away */
908         if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0)
909             && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))
910             && !priv->persistent) {
911                 ng_rmnode_self(NG_HOOK_NODE(hook));
912         }
913         return (0);
914 }
915
916 /******************************************************************
917                     HASH TABLE FUNCTIONS
918 ******************************************************************/
919
920 /*
921  * Hash algorithm
922  */
923 #define HASH(addr,mask)         ( (((const u_int16_t *)(addr))[0]       \
924                                  ^ ((const u_int16_t *)(addr))[1]       \
925                                  ^ ((const u_int16_t *)(addr))[2]) & (mask) )
926
927 /*
928  * Find a host entry in the table.
929  */
930 static struct ng_bridge_host *
931 ng_bridge_get(priv_cp priv, const u_char *addr)
932 {
933         const int bucket = HASH(addr, priv->hashMask);
934         struct ng_bridge_host *host;
935
936         SLIST_FOREACH(host, &priv->tab[bucket], next) {
937                 if (ETHER_EQUAL(host->addr, addr))
938                         return (host);
939         }
940         return (NULL);
941 }
942
943 /*
944  * Add a new host entry to the table. This assumes the host doesn't
945  * already exist in the table. Returns 1 on success, 0 if there
946  * was a memory allocation failure.
947  */
948 static int
949 ng_bridge_put(priv_p priv, const u_char *addr, link_p link)
950 {
951         const int bucket = HASH(addr, priv->hashMask);
952         struct ng_bridge_host *host;
953
954 #ifdef INVARIANTS
955         /* Assert that entry does not already exist in hashtable */
956         SLIST_FOREACH(host, &priv->tab[bucket], next) {
957                 KASSERT(!ETHER_EQUAL(host->addr, addr),
958                     ("%s: entry %6D exists in table", __func__, addr, ":"));
959         }
960 #endif
961
962         /* Allocate and initialize new hashtable entry */
963         host = malloc(sizeof(*host), M_NETGRAPH_BRIDGE, M_NOWAIT);
964         if (host == NULL)
965                 return (0);
966         bcopy(addr, host->addr, ETHER_ADDR_LEN);
967         host->link = link;
968         host->staleness = 0;
969         host->age = 0;
970
971         /* Add new element to hash bucket */
972         SLIST_INSERT_HEAD(&priv->tab[bucket], host, next);
973         priv->numHosts++;
974
975         /* Resize table if necessary */
976         ng_bridge_rehash(priv);
977         return (1);
978 }
979
980 /*
981  * Resize the hash table. We try to maintain the number of buckets
982  * such that the load factor is in the range 0.25 to 1.0.
983  *
984  * If we can't get the new memory then we silently fail. This is OK
985  * because things will still work and we'll try again soon anyway.
986  */
987 static void
988 ng_bridge_rehash(priv_p priv)
989 {
990         struct ng_bridge_bucket *newTab;
991         int oldBucket, newBucket;
992         int newNumBuckets;
993         u_int newMask;
994
995         /* Is table too full or too empty? */
996         if (priv->numHosts > priv->numBuckets
997             && (priv->numBuckets << 1) <= MAX_BUCKETS)
998                 newNumBuckets = priv->numBuckets << 1;
999         else if (priv->numHosts < (priv->numBuckets >> 2)
1000             && (priv->numBuckets >> 2) >= MIN_BUCKETS)
1001                 newNumBuckets = priv->numBuckets >> 2;
1002         else
1003                 return;
1004         newMask = newNumBuckets - 1;
1005
1006         /* Allocate and initialize new table */
1007         newTab = malloc(newNumBuckets * sizeof(*newTab),
1008             M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO);
1009         if (newTab == NULL)
1010                 return;
1011
1012         /* Move all entries from old table to new table */
1013         for (oldBucket = 0; oldBucket < priv->numBuckets; oldBucket++) {
1014                 struct ng_bridge_bucket *const oldList = &priv->tab[oldBucket];
1015
1016                 while (!SLIST_EMPTY(oldList)) {
1017                         struct ng_bridge_host *const host
1018                             = SLIST_FIRST(oldList);
1019
1020                         SLIST_REMOVE_HEAD(oldList, next);
1021                         newBucket = HASH(host->addr, newMask);
1022                         SLIST_INSERT_HEAD(&newTab[newBucket], host, next);
1023                 }
1024         }
1025
1026         /* Replace old table with new one */
1027         if (priv->conf.debugLevel >= 3) {
1028                 log(LOG_INFO, "ng_bridge: %s: table size %d -> %d\n",
1029                     ng_bridge_nodename(priv->node),
1030                     priv->numBuckets, newNumBuckets);
1031         }
1032         free(priv->tab, M_NETGRAPH_BRIDGE);
1033         priv->numBuckets = newNumBuckets;
1034         priv->hashMask = newMask;
1035         priv->tab = newTab;
1036         return;
1037 }
1038
1039 /******************************************************************
1040                     MISC FUNCTIONS
1041 ******************************************************************/
1042
1043 /*
1044  * Remove all hosts associated with a specific link from the hashtable.
1045  * If linkNum == -1, then remove all hosts in the table.
1046  */
1047 static void
1048 ng_bridge_remove_hosts(priv_p priv, link_p link)
1049 {
1050         int bucket;
1051
1052         for (bucket = 0; bucket < priv->numBuckets; bucket++) {
1053                 struct ng_bridge_host **hptr = &SLIST_FIRST(&priv->tab[bucket]);
1054
1055                 while (*hptr != NULL) {
1056                         struct ng_bridge_host *const host = *hptr;
1057
1058                         if (link == NULL || host->link == link) {
1059                                 *hptr = SLIST_NEXT(host, next);
1060                                 free(host, M_NETGRAPH_BRIDGE);
1061                                 priv->numHosts--;
1062                         } else
1063                                 hptr = &SLIST_NEXT(host, next);
1064                 }
1065         }
1066 }
1067
1068 /*
1069  * Handle our once-per-second timeout event. We do two things:
1070  * we decrement link->loopCount for those links being muted due to
1071  * a detected loopback condition, and we remove any hosts from
1072  * the hashtable whom we haven't heard from in a long while.
1073  */
1074 static int
1075 ng_bridge_unmute(hook_p hook, void *arg)
1076 {
1077         link_p link = NG_HOOK_PRIVATE(hook);
1078         node_p node = NG_HOOK_NODE(hook);
1079         priv_p priv = NG_NODE_PRIVATE(node);
1080         int *counter = arg;
1081
1082         if (link->loopCount != 0) {
1083                 link->loopCount--;
1084                 if (link->loopCount == 0 && priv->conf.debugLevel >= 2) {
1085                         log(LOG_INFO, "ng_bridge: %s:"
1086                             " restoring looped back %s\n",
1087                             ng_bridge_nodename(node), NG_HOOK_NAME(hook));
1088                 }
1089         }
1090         (*counter)++;
1091         return (1);
1092 }
1093
1094 static void
1095 ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2)
1096 {
1097         const priv_p priv = NG_NODE_PRIVATE(node);
1098         int bucket;
1099         int counter = 0;
1100         hook_p ret;
1101
1102         /* Update host time counters and remove stale entries */
1103         for (bucket = 0; bucket < priv->numBuckets; bucket++) {
1104                 struct ng_bridge_host **hptr = &SLIST_FIRST(&priv->tab[bucket]);
1105
1106                 while (*hptr != NULL) {
1107                         struct ng_bridge_host *const host = *hptr;
1108
1109                         /* Remove hosts we haven't heard from in a while */
1110                         if (++host->staleness >= priv->conf.maxStaleness) {
1111                                 *hptr = SLIST_NEXT(host, next);
1112                                 free(host, M_NETGRAPH_BRIDGE);
1113                                 priv->numHosts--;
1114                         } else {
1115                                 if (host->age < 0xffff)
1116                                         host->age++;
1117                                 hptr = &SLIST_NEXT(host, next);
1118                                 counter++;
1119                         }
1120                 }
1121         }
1122         KASSERT(priv->numHosts == counter,
1123             ("%s: hosts: %d != %d", __func__, priv->numHosts, counter));
1124
1125         /* Decrease table size if necessary */
1126         ng_bridge_rehash(priv);
1127
1128         /* Decrease loop counter on muted looped back links */
1129         counter = 0;
1130         NG_NODE_FOREACH_HOOK(node, ng_bridge_unmute, &counter, ret);
1131         KASSERT(priv->numLinks == counter,
1132             ("%s: links: %d != %d", __func__, priv->numLinks, counter));
1133
1134         /* Register a new timeout, keeping the existing node reference */
1135         ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0);
1136 }
1137
1138 /*
1139  * Return node's "name", even if it doesn't have one.
1140  */
1141 static const char *
1142 ng_bridge_nodename(node_cp node)
1143 {
1144         static char name[NG_NODESIZ];
1145
1146         if (NG_NODE_HAS_NAME(node))
1147                 snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node));
1148         else
1149                 snprintf(name, sizeof(name), "[%x]", ng_node2ID(node));
1150         return name;
1151 }