igmp.c revision 1.56 1 /* $NetBSD: igmp.c,v 1.56 2015/08/24 22:21:26 pooka Exp $ */
2
3 /*
4 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the project nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 /*
33 * Internet Group Management Protocol (IGMP) routines.
34 *
35 * Written by Steve Deering, Stanford, May 1988.
36 * Modified by Rosen Sharma, Stanford, Aug 1994.
37 * Modified by Bill Fenner, Xerox PARC, Feb 1995.
38 *
39 * MULTICAST Revision: 1.3
40 */
41
42 #include <sys/cdefs.h>
43 __KERNEL_RCSID(0, "$NetBSD: igmp.c,v 1.56 2015/08/24 22:21:26 pooka Exp $");
44
45 #ifdef _KERNEL_OPT
46 #include "opt_mrouting.h"
47 #endif
48
49 #include <sys/param.h>
50 #include <sys/mbuf.h>
51 #include <sys/socket.h>
52 #include <sys/socketvar.h>
53 #include <sys/protosw.h>
54 #include <sys/systm.h>
55 #include <sys/cprng.h>
56 #include <sys/sysctl.h>
57
58 #include <net/if.h>
59 #include <net/route.h>
60 #include <net/net_stats.h>
61
62 #include <netinet/in.h>
63 #include <netinet/in_var.h>
64 #include <netinet/in_systm.h>
65 #include <netinet/ip.h>
66 #include <netinet/ip_var.h>
67 #include <netinet/igmp.h>
68 #include <netinet/igmp_var.h>
69
70 /*
71 * Per-interface router version information.
72 */
73 typedef struct router_info {
74 LIST_ENTRY(router_info) rti_link;
75 ifnet_t * rti_ifp;
76 int rti_type; /* type of router on this interface */
77 int rti_age; /* time since last v1 query */
78 } router_info_t;
79
80 /*
81 * The router-info list and the timer flag are protected by in_multilock.
82 *
83 * Lock order:
84 *
85 * softnet_lock ->
86 * in_multilock
87 */
88 static struct pool igmp_rti_pool __cacheline_aligned;
89 static LIST_HEAD(, router_info) rti_head __cacheline_aligned;
90 static int igmp_timers_on __cacheline_aligned;
91 static percpu_t * igmpstat_percpu __read_mostly;
92
93 #define IGMP_STATINC(x) _NET_STATINC(igmpstat_percpu, x)
94
95 static void igmp_sendpkt(struct in_multi *, int);
96 static int rti_fill(struct in_multi *);
97 static router_info_t * rti_find(struct ifnet *);
98 static void rti_delete(struct ifnet *);
99 static void sysctl_net_inet_igmp_setup(struct sysctllog **);
100
101 /*
102 * rti_fill: associate router information with the given multicast group;
103 * if there is no router information for the interface, then create it.
104 */
105 static int
106 rti_fill(struct in_multi *inm)
107 {
108 router_info_t *rti;
109
110 KASSERT(in_multi_lock_held());
111
112 LIST_FOREACH(rti, &rti_head, rti_link) {
113 if (rti->rti_ifp == inm->inm_ifp) {
114 inm->inm_rti = rti;
115 return rti->rti_type == IGMP_v1_ROUTER ?
116 IGMP_v1_HOST_MEMBERSHIP_REPORT :
117 IGMP_v2_HOST_MEMBERSHIP_REPORT;
118 }
119 }
120 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
121 if (rti == NULL) {
122 return 0;
123 }
124 rti->rti_ifp = inm->inm_ifp;
125 rti->rti_type = IGMP_v2_ROUTER;
126 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
127 inm->inm_rti = rti;
128 return IGMP_v2_HOST_MEMBERSHIP_REPORT;
129 }
130
131 /*
132 * rti_find: lookup or create router information for the given interface.
133 */
134 static router_info_t *
135 rti_find(ifnet_t *ifp)
136 {
137 router_info_t *rti;
138
139 KASSERT(in_multi_lock_held());
140
141 LIST_FOREACH(rti, &rti_head, rti_link) {
142 if (rti->rti_ifp == ifp)
143 return rti;
144 }
145 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
146 if (rti == NULL) {
147 return NULL;
148 }
149 rti->rti_ifp = ifp;
150 rti->rti_type = IGMP_v2_ROUTER;
151 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
152 return rti;
153 }
154
155 /*
156 * rti_delete: remove and free the router information entry for the
157 * given interface.
158 */
159 static void
160 rti_delete(ifnet_t *ifp)
161 {
162 router_info_t *rti;
163
164 KASSERT(in_multi_lock_held());
165
166 LIST_FOREACH(rti, &rti_head, rti_link) {
167 if (rti->rti_ifp == ifp) {
168 LIST_REMOVE(rti, rti_link);
169 pool_put(&igmp_rti_pool, rti);
170 break;
171 }
172 }
173 }
174
175 void
176 igmp_init(void)
177 {
178 pool_init(&igmp_rti_pool, sizeof(router_info_t), 0, 0, 0,
179 "igmppl", NULL, IPL_SOFTNET);
180 igmpstat_percpu = percpu_alloc(sizeof(uint64_t) * IGMP_NSTATS);
181 sysctl_net_inet_igmp_setup(NULL);
182 LIST_INIT(&rti_head);
183 }
184
185 void
186 igmp_input(struct mbuf *m, ...)
187 {
188 ifnet_t *ifp = m->m_pkthdr.rcvif;
189 struct ip *ip = mtod(m, struct ip *);
190 struct igmp *igmp;
191 u_int minlen, timer;
192 struct in_multi *inm;
193 struct in_ifaddr *ia;
194 int proto, ip_len, iphlen;
195 va_list ap;
196
197 va_start(ap, m);
198 iphlen = va_arg(ap, int);
199 proto = va_arg(ap, int);
200 va_end(ap);
201
202 IGMP_STATINC(IGMP_STAT_RCV_TOTAL);
203
204 /*
205 * Validate lengths
206 */
207 minlen = iphlen + IGMP_MINLEN;
208 ip_len = ntohs(ip->ip_len);
209 if (ip_len < minlen) {
210 IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
211 m_freem(m);
212 return;
213 }
214 if (((m->m_flags & M_EXT) && (ip->ip_src.s_addr & IN_CLASSA_NET) == 0)
215 || m->m_len < minlen) {
216 if ((m = m_pullup(m, minlen)) == NULL) {
217 IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
218 return;
219 }
220 ip = mtod(m, struct ip *);
221 }
222
223 /*
224 * Validate checksum
225 */
226 m->m_data += iphlen;
227 m->m_len -= iphlen;
228 igmp = mtod(m, struct igmp *);
229 /* No need to assert alignment here. */
230 if (in_cksum(m, ip_len - iphlen)) {
231 IGMP_STATINC(IGMP_STAT_RCV_BADSUM);
232 m_freem(m);
233 return;
234 }
235 m->m_data -= iphlen;
236 m->m_len += iphlen;
237
238 switch (igmp->igmp_type) {
239
240 case IGMP_HOST_MEMBERSHIP_QUERY:
241 IGMP_STATINC(IGMP_STAT_RCV_QUERIES);
242
243 if (ifp->if_flags & IFF_LOOPBACK)
244 break;
245
246 if (igmp->igmp_code == 0) {
247 struct in_multistep step;
248 router_info_t *rti;
249
250 if (ip->ip_dst.s_addr != INADDR_ALLHOSTS_GROUP) {
251 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
252 m_freem(m);
253 return;
254 }
255
256 in_multi_lock(RW_WRITER);
257 rti = rti_find(ifp);
258 if (rti == NULL) {
259 in_multi_unlock();
260 break;
261 }
262 rti->rti_type = IGMP_v1_ROUTER;
263 rti->rti_age = 0;
264
265 /*
266 * Start the timers in all of our membership records
267 * for the interface on which the query arrived,
268 * except those that are already running and those
269 * that belong to a "local" group (224.0.0.X).
270 */
271
272 inm = in_first_multi(&step);
273 while (inm != NULL) {
274 if (inm->inm_ifp == ifp &&
275 inm->inm_timer == 0 &&
276 !IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
277 inm->inm_state = IGMP_DELAYING_MEMBER;
278 inm->inm_timer = IGMP_RANDOM_DELAY(
279 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
280 igmp_timers_on = true;
281 }
282 inm = in_next_multi(&step);
283 }
284 in_multi_unlock();
285 } else {
286 struct in_multistep step;
287
288 if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
289 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
290 m_freem(m);
291 return;
292 }
293
294 timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
295 if (timer == 0)
296 timer = 1;
297
298 /*
299 * Start the timers in all of our membership records
300 * for the interface on which the query arrived,
301 * except those that are already running and those
302 * that belong to a "local" group (224.0.0.X). For
303 * timers already running, check if they need to be
304 * reset.
305 */
306 in_multi_lock(RW_WRITER);
307 inm = in_first_multi(&step);
308 while (inm != NULL) {
309 if (inm->inm_ifp == ifp &&
310 !IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
311 (ip->ip_dst.s_addr == INADDR_ALLHOSTS_GROUP ||
312 in_hosteq(ip->ip_dst, inm->inm_addr))) {
313 switch (inm->inm_state) {
314 case IGMP_DELAYING_MEMBER:
315 if (inm->inm_timer <= timer)
316 break;
317 /* FALLTHROUGH */
318 case IGMP_IDLE_MEMBER:
319 case IGMP_LAZY_MEMBER:
320 case IGMP_AWAKENING_MEMBER:
321 inm->inm_state =
322 IGMP_DELAYING_MEMBER;
323 inm->inm_timer =
324 IGMP_RANDOM_DELAY(timer);
325 igmp_timers_on = true;
326 break;
327 case IGMP_SLEEPING_MEMBER:
328 inm->inm_state =
329 IGMP_AWAKENING_MEMBER;
330 break;
331 }
332 }
333 inm = in_next_multi(&step);
334 }
335 in_multi_unlock();
336 }
337
338 break;
339
340 case IGMP_v1_HOST_MEMBERSHIP_REPORT:
341 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
342
343 if (ifp->if_flags & IFF_LOOPBACK)
344 break;
345
346 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
347 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
348 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
349 m_freem(m);
350 return;
351 }
352
353 /*
354 * KLUDGE: if the IP source address of the report has an
355 * unspecified (i.e., zero) subnet number, as is allowed for
356 * a booting host, replace it with the correct subnet number
357 * so that a process-level multicast routing daemon can
358 * determine which subnet it arrived from. This is necessary
359 * to compensate for the lack of any way for a process to
360 * determine the arrival interface of an incoming packet.
361 */
362 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
363 IFP_TO_IA(ifp, ia); /* XXX */
364 if (ia)
365 ip->ip_src.s_addr = ia->ia_subnet;
366 }
367
368 /*
369 * If we belong to the group being reported, stop
370 * our timer for that group.
371 */
372 in_multi_lock(RW_WRITER);
373 inm = in_lookup_multi(igmp->igmp_group, ifp);
374 if (inm != NULL) {
375 inm->inm_timer = 0;
376 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
377
378 switch (inm->inm_state) {
379 case IGMP_IDLE_MEMBER:
380 case IGMP_LAZY_MEMBER:
381 case IGMP_AWAKENING_MEMBER:
382 case IGMP_SLEEPING_MEMBER:
383 inm->inm_state = IGMP_SLEEPING_MEMBER;
384 break;
385 case IGMP_DELAYING_MEMBER:
386 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
387 inm->inm_state = IGMP_LAZY_MEMBER;
388 else
389 inm->inm_state = IGMP_SLEEPING_MEMBER;
390 break;
391 }
392 }
393 in_multi_unlock();
394 break;
395
396 case IGMP_v2_HOST_MEMBERSHIP_REPORT:
397 #ifdef MROUTING
398 /*
399 * Make sure we don't hear our own membership report. Fast
400 * leave requires knowing that we are the only member of a
401 * group.
402 */
403 IFP_TO_IA(ifp, ia); /* XXX */
404 if (ia && in_hosteq(ip->ip_src, ia->ia_addr.sin_addr))
405 break;
406 #endif
407
408 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
409
410 if (ifp->if_flags & IFF_LOOPBACK)
411 break;
412
413 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
414 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
415 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
416 m_freem(m);
417 return;
418 }
419
420 /*
421 * KLUDGE: if the IP source address of the report has an
422 * unspecified (i.e., zero) subnet number, as is allowed for
423 * a booting host, replace it with the correct subnet number
424 * so that a process-level multicast routing daemon can
425 * determine which subnet it arrived from. This is necessary
426 * to compensate for the lack of any way for a process to
427 * determine the arrival interface of an incoming packet.
428 */
429 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
430 #ifndef MROUTING
431 IFP_TO_IA(ifp, ia); /* XXX */
432 #endif
433 if (ia)
434 ip->ip_src.s_addr = ia->ia_subnet;
435 }
436
437 /*
438 * If we belong to the group being reported, stop
439 * our timer for that group.
440 */
441 in_multi_lock(RW_WRITER);
442 inm = in_lookup_multi(igmp->igmp_group, ifp);
443 if (inm != NULL) {
444 inm->inm_timer = 0;
445 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
446
447 switch (inm->inm_state) {
448 case IGMP_DELAYING_MEMBER:
449 case IGMP_IDLE_MEMBER:
450 case IGMP_AWAKENING_MEMBER:
451 inm->inm_state = IGMP_LAZY_MEMBER;
452 break;
453 case IGMP_LAZY_MEMBER:
454 case IGMP_SLEEPING_MEMBER:
455 break;
456 }
457 }
458 in_multi_unlock();
459 break;
460
461 }
462
463 /*
464 * Pass all valid IGMP packets up to any process(es) listening
465 * on a raw IGMP socket.
466 */
467 rip_input(m, iphlen, proto);
468 return;
469 }
470
471 int
472 igmp_joingroup(struct in_multi *inm)
473 {
474 KASSERT(in_multi_lock_held());
475 inm->inm_state = IGMP_IDLE_MEMBER;
476
477 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
478 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0) {
479 int report_type;
480
481 report_type = rti_fill(inm);
482 if (report_type == 0) {
483 return ENOMEM;
484 }
485 igmp_sendpkt(inm, report_type);
486 inm->inm_state = IGMP_DELAYING_MEMBER;
487 inm->inm_timer = IGMP_RANDOM_DELAY(
488 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
489 igmp_timers_on = true;
490 } else
491 inm->inm_timer = 0;
492
493 return 0;
494 }
495
496 void
497 igmp_leavegroup(struct in_multi *inm)
498 {
499 KASSERT(in_multi_lock_held());
500
501 switch (inm->inm_state) {
502 case IGMP_DELAYING_MEMBER:
503 case IGMP_IDLE_MEMBER:
504 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
505 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0)
506 if (inm->inm_rti->rti_type != IGMP_v1_ROUTER)
507 igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
508 break;
509 case IGMP_LAZY_MEMBER:
510 case IGMP_AWAKENING_MEMBER:
511 case IGMP_SLEEPING_MEMBER:
512 break;
513 }
514 }
515
516 void
517 igmp_fasttimo(void)
518 {
519 struct in_multi *inm;
520 struct in_multistep step;
521
522 /*
523 * Quick check to see if any work needs to be done, in order
524 * to minimize the overhead of fasttimo processing.
525 */
526 if (!igmp_timers_on) {
527 return;
528 }
529
530 /* XXX: Needed for ip_output(). */
531 mutex_enter(softnet_lock);
532
533 in_multi_lock(RW_WRITER);
534 igmp_timers_on = false;
535 inm = in_first_multi(&step);
536 while (inm != NULL) {
537 if (inm->inm_timer == 0) {
538 /* do nothing */
539 } else if (--inm->inm_timer == 0) {
540 if (inm->inm_state == IGMP_DELAYING_MEMBER) {
541 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
542 igmp_sendpkt(inm,
543 IGMP_v1_HOST_MEMBERSHIP_REPORT);
544 else
545 igmp_sendpkt(inm,
546 IGMP_v2_HOST_MEMBERSHIP_REPORT);
547 inm->inm_state = IGMP_IDLE_MEMBER;
548 }
549 } else {
550 igmp_timers_on = true;
551 }
552 inm = in_next_multi(&step);
553 }
554 in_multi_unlock();
555 mutex_exit(softnet_lock);
556 }
557
558 void
559 igmp_slowtimo(void)
560 {
561 router_info_t *rti;
562
563 in_multi_lock(RW_WRITER);
564 LIST_FOREACH(rti, &rti_head, rti_link) {
565 if (rti->rti_type == IGMP_v1_ROUTER &&
566 ++rti->rti_age >= IGMP_AGE_THRESHOLD) {
567 rti->rti_type = IGMP_v2_ROUTER;
568 }
569 }
570 in_multi_unlock();
571 }
572
573 /*
574 * igmp_sendpkt: construct an IGMP packet, given the multicast structure
575 * and the type, and send the datagram.
576 */
577 static void
578 igmp_sendpkt(struct in_multi *inm, int type)
579 {
580 struct mbuf *m;
581 struct igmp *igmp;
582 struct ip *ip;
583 struct ip_moptions imo;
584
585 KASSERT(in_multi_lock_held());
586
587 MGETHDR(m, M_DONTWAIT, MT_HEADER);
588 if (m == NULL)
589 return;
590
591 /*
592 * Assume max_linkhdr + sizeof(struct ip) + IGMP_MINLEN
593 * is smaller than mbuf size returned by MGETHDR.
594 */
595 m->m_data += max_linkhdr;
596 m->m_len = sizeof(struct ip) + IGMP_MINLEN;
597 m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
598
599 ip = mtod(m, struct ip *);
600 ip->ip_tos = 0;
601 ip->ip_len = htons(sizeof(struct ip) + IGMP_MINLEN);
602 ip->ip_off = htons(0);
603 ip->ip_p = IPPROTO_IGMP;
604 ip->ip_src = zeroin_addr;
605 ip->ip_dst = inm->inm_addr;
606
607 m->m_data += sizeof(struct ip);
608 m->m_len -= sizeof(struct ip);
609 igmp = mtod(m, struct igmp *);
610 igmp->igmp_type = type;
611 igmp->igmp_code = 0;
612 igmp->igmp_group = inm->inm_addr;
613 igmp->igmp_cksum = 0;
614 igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
615 m->m_data -= sizeof(struct ip);
616 m->m_len += sizeof(struct ip);
617
618 imo.imo_multicast_ifp = inm->inm_ifp;
619 imo.imo_multicast_ttl = 1;
620 #ifdef RSVP_ISI
621 imo.imo_multicast_vif = -1;
622 #endif
623 /*
624 * Request loopback of the report if we are acting as a multicast
625 * router, so that the process-level routing demon can hear it.
626 */
627 #ifdef MROUTING
628 extern struct socket *ip_mrouter;
629 imo.imo_multicast_loop = (ip_mrouter != NULL);
630 #else
631 imo.imo_multicast_loop = 0;
632 #endif
633
634 /*
635 * Note: IP_IGMP_MCAST indicates that in_multilock is held.
636 * The caller must still acquire softnet_lock for ip_output().
637 */
638 KASSERT(mutex_owned(softnet_lock));
639 ip_output(m, NULL, NULL, IP_IGMP_MCAST, &imo, NULL);
640 IGMP_STATINC(IGMP_STAT_SND_REPORTS);
641 }
642
643 void
644 igmp_purgeif(ifnet_t *ifp)
645 {
646 in_multi_lock(RW_WRITER);
647 rti_delete(ifp);
648 in_multi_unlock();
649 }
650
651 static int
652 sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS)
653 {
654 return NETSTAT_SYSCTL(igmpstat_percpu, IGMP_NSTATS);
655 }
656
657 static void
658 sysctl_net_inet_igmp_setup(struct sysctllog **clog)
659 {
660 sysctl_createv(clog, 0, NULL, NULL,
661 CTLFLAG_PERMANENT,
662 CTLTYPE_NODE, "inet", NULL,
663 NULL, 0, NULL, 0,
664 CTL_NET, PF_INET, CTL_EOL);
665 sysctl_createv(clog, 0, NULL, NULL,
666 CTLFLAG_PERMANENT,
667 CTLTYPE_NODE, "igmp",
668 SYSCTL_DESCR("Internet Group Management Protocol"),
669 NULL, 0, NULL, 0,
670 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_EOL);
671 sysctl_createv(clog, 0, NULL, NULL,
672 CTLFLAG_PERMANENT,
673 CTLTYPE_STRUCT, "stats",
674 SYSCTL_DESCR("IGMP statistics"),
675 sysctl_net_inet_igmp_stats, 0, NULL, 0,
676 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_CREATE, CTL_EOL);
677 }
678