1/* $NetBSD: igmp.c,v 1.62 2016/08/01 03:15:30 ozaki-r 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.62 2016/08/01 03:15:30 ozaki-r 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/net_stats.h>
60
61#include <netinet/in.h>
62#include <netinet/in_var.h>
63#include <netinet/in_systm.h>
64#include <netinet/ip.h>
65#include <netinet/ip_var.h>
66#include <netinet/igmp.h>
67#include <netinet/igmp_var.h>
68
69/*
70 * Per-interface router version information.
71 */
72typedef struct router_info {
73 LIST_ENTRY(router_info) rti_link;
74 ifnet_t * rti_ifp;
75 int rti_type; /* type of router on this interface */
76 int rti_age; /* time since last v1 query */
77} router_info_t;
78
79/*
80 * The router-info list and the timer flag are protected by in_multilock.
81 *
82 * Lock order:
83 *
84 * softnet_lock ->
85 * in_multilock
86 */
87static struct pool igmp_rti_pool __cacheline_aligned;
88static LIST_HEAD(, router_info) rti_head __cacheline_aligned;
89static int igmp_timers_on __cacheline_aligned;
90static percpu_t * igmpstat_percpu __read_mostly;
91
92#define IGMP_STATINC(x) _NET_STATINC(igmpstat_percpu, x)
93
94static void igmp_sendpkt(struct in_multi *, int);
95static int rti_fill(struct in_multi *);
96static router_info_t * rti_find(struct ifnet *);
97static void rti_delete(struct ifnet *);
98static void sysctl_net_inet_igmp_setup(struct sysctllog **);
99
100/*
101 * rti_fill: associate router information with the given multicast group;
102 * if there is no router information for the interface, then create it.
103 */
104static int
105rti_fill(struct in_multi *inm)
106{
107 router_info_t *rti;
108
109 KASSERT(in_multi_lock_held());
110
111 LIST_FOREACH(rti, &rti_head, rti_link) {
112 if (rti->rti_ifp == inm->inm_ifp) {
113 inm->inm_rti = rti;
114 return rti->rti_type == IGMP_v1_ROUTER ?
115 IGMP_v1_HOST_MEMBERSHIP_REPORT :
116 IGMP_v2_HOST_MEMBERSHIP_REPORT;
117 }
118 }
119 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
120 if (rti == NULL) {
121 return 0;
122 }
123 rti->rti_ifp = inm->inm_ifp;
124 rti->rti_type = IGMP_v2_ROUTER;
125 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
126 inm->inm_rti = rti;
127 return IGMP_v2_HOST_MEMBERSHIP_REPORT;
128}
129
130/*
131 * rti_find: lookup or create router information for the given interface.
132 */
133static router_info_t *
134rti_find(ifnet_t *ifp)
135{
136 router_info_t *rti;
137
138 KASSERT(in_multi_lock_held());
139
140 LIST_FOREACH(rti, &rti_head, rti_link) {
141 if (rti->rti_ifp == ifp)
142 return rti;
143 }
144 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
145 if (rti == NULL) {
146 return NULL;
147 }
148 rti->rti_ifp = ifp;
149 rti->rti_type = IGMP_v2_ROUTER;
150 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
151 return rti;
152}
153
154/*
155 * rti_delete: remove and free the router information entry for the
156 * given interface.
157 */
158static void
159rti_delete(ifnet_t *ifp)
160{
161 router_info_t *rti;
162
163 KASSERT(in_multi_lock_held());
164
165 LIST_FOREACH(rti, &rti_head, rti_link) {
166 if (rti->rti_ifp == ifp) {
167 LIST_REMOVE(rti, rti_link);
168 pool_put(&igmp_rti_pool, rti);
169 break;
170 }
171 }
172}
173
174void
175igmp_init(void)
176{
177 pool_init(&igmp_rti_pool, sizeof(router_info_t), 0, 0, 0,
178 "igmppl", NULL, IPL_SOFTNET);
179 igmpstat_percpu = percpu_alloc(sizeof(uint64_t) * IGMP_NSTATS);
180 sysctl_net_inet_igmp_setup(NULL);
181 LIST_INIT(&rti_head);
182}
183
184void
185igmp_input(struct mbuf *m, ...)
186{
187 ifnet_t *ifp;
188 struct ip *ip = mtod(m, struct ip *);
189 struct igmp *igmp;
190 u_int minlen, timer;
191 struct in_multi *inm;
192 struct in_ifaddr *ia;
193 int proto, ip_len, iphlen;
194 va_list ap;
195 struct psref psref;
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 ifp = m_get_rcvif_psref(m, &psref);
239 if (__predict_false(ifp == NULL))
240 goto drop;
241
242 switch (igmp->igmp_type) {
243
244 case IGMP_HOST_MEMBERSHIP_QUERY:
245 IGMP_STATINC(IGMP_STAT_RCV_QUERIES);
246
247 if (ifp->if_flags & IFF_LOOPBACK)
248 break;
249
250 if (igmp->igmp_code == 0) {
251 struct in_multistep step;
252 router_info_t *rti;
253
254 if (ip->ip_dst.s_addr != INADDR_ALLHOSTS_GROUP) {
255 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
256 goto drop;
257 }
258
259 in_multi_lock(RW_WRITER);
260 rti = rti_find(ifp);
261 if (rti == NULL) {
262 in_multi_unlock();
263 break;
264 }
265 rti->rti_type = IGMP_v1_ROUTER;
266 rti->rti_age = 0;
267
268 /*
269 * Start the timers in all of our membership records
270 * for the interface on which the query arrived,
271 * except those that are already running and those
272 * that belong to a "local" group (224.0.0.X).
273 */
274
275 inm = in_first_multi(&step);
276 while (inm != NULL) {
277 if (inm->inm_ifp == ifp &&
278 inm->inm_timer == 0 &&
279 !IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
280 inm->inm_state = IGMP_DELAYING_MEMBER;
281 inm->inm_timer = IGMP_RANDOM_DELAY(
282 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
283 igmp_timers_on = true;
284 }
285 inm = in_next_multi(&step);
286 }
287 in_multi_unlock();
288 } else {
289 struct in_multistep step;
290
291 if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
292 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
293 goto drop;
294 }
295
296 timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
297 if (timer == 0)
298 timer = 1;
299
300 /*
301 * Start the timers in all of our membership records
302 * for the interface on which the query arrived,
303 * except those that are already running and those
304 * that belong to a "local" group (224.0.0.X). For
305 * timers already running, check if they need to be
306 * reset.
307 */
308 in_multi_lock(RW_WRITER);
309 inm = in_first_multi(&step);
310 while (inm != NULL) {
311 if (inm->inm_ifp == ifp &&
312 !IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
313 (ip->ip_dst.s_addr == INADDR_ALLHOSTS_GROUP ||
314 in_hosteq(ip->ip_dst, inm->inm_addr))) {
315 switch (inm->inm_state) {
316 case IGMP_DELAYING_MEMBER:
317 if (inm->inm_timer <= timer)
318 break;
319 /* FALLTHROUGH */
320 case IGMP_IDLE_MEMBER:
321 case IGMP_LAZY_MEMBER:
322 case IGMP_AWAKENING_MEMBER:
323 inm->inm_state =
324 IGMP_DELAYING_MEMBER;
325 inm->inm_timer =
326 IGMP_RANDOM_DELAY(timer);
327 igmp_timers_on = true;
328 break;
329 case IGMP_SLEEPING_MEMBER:
330 inm->inm_state =
331 IGMP_AWAKENING_MEMBER;
332 break;
333 }
334 }
335 inm = in_next_multi(&step);
336 }
337 in_multi_unlock();
338 }
339
340 break;
341
342 case IGMP_v1_HOST_MEMBERSHIP_REPORT:
343 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
344
345 if (ifp->if_flags & IFF_LOOPBACK)
346 break;
347
348 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
349 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
350 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
351 goto drop;
352 }
353
354 /*
355 * KLUDGE: if the IP source address of the report has an
356 * unspecified (i.e., zero) subnet number, as is allowed for
357 * a booting host, replace it with the correct subnet number
358 * so that a process-level multicast routing daemon can
359 * determine which subnet it arrived from. This is necessary
360 * to compensate for the lack of any way for a process to
361 * determine the arrival interface of an incoming packet.
362 */
363 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
364 int s = pserialize_read_enter();
365 ia = in_get_ia_from_ifp(ifp); /* XXX */
366 if (ia)
367 ip->ip_src.s_addr = ia->ia_subnet;
368 pserialize_read_exit(s);
369 }
370
371 /*
372 * If we belong to the group being reported, stop
373 * our timer for that group.
374 */
375 in_multi_lock(RW_WRITER);
376 inm = in_lookup_multi(igmp->igmp_group, ifp);
377 if (inm != NULL) {
378 inm->inm_timer = 0;
379 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
380
381 switch (inm->inm_state) {
382 case IGMP_IDLE_MEMBER:
383 case IGMP_LAZY_MEMBER:
384 case IGMP_AWAKENING_MEMBER:
385 case IGMP_SLEEPING_MEMBER:
386 inm->inm_state = IGMP_SLEEPING_MEMBER;
387 break;
388 case IGMP_DELAYING_MEMBER:
389 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
390 inm->inm_state = IGMP_LAZY_MEMBER;
391 else
392 inm->inm_state = IGMP_SLEEPING_MEMBER;
393 break;
394 }
395 }
396 in_multi_unlock();
397 break;
398
399 case IGMP_v2_HOST_MEMBERSHIP_REPORT: {
400 int s = pserialize_read_enter();
401#ifdef MROUTING
402 /*
403 * Make sure we don't hear our own membership report. Fast
404 * leave requires knowing that we are the only member of a
405 * group.
406 */
407 ia = in_get_ia_from_ifp(ifp); /* XXX */
408 if (ia && in_hosteq(ip->ip_src, ia->ia_addr.sin_addr)) {
409 pserialize_read_exit(s);
410 break;
411 }
412#endif
413
414 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
415
416 if (ifp->if_flags & IFF_LOOPBACK) {
417 pserialize_read_exit(s);
418 break;
419 }
420
421 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
422 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
423 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
424 pserialize_read_exit(s);
425 goto drop;
426 }
427
428 /*
429 * KLUDGE: if the IP source address of the report has an
430 * unspecified (i.e., zero) subnet number, as is allowed for
431 * a booting host, replace it with the correct subnet number
432 * so that a process-level multicast routing daemon can
433 * determine which subnet it arrived from. This is necessary
434 * to compensate for the lack of any way for a process to
435 * determine the arrival interface of an incoming packet.
436 */
437 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
438#ifndef MROUTING
439 ia = in_get_ia_from_ifp(ifp); /* XXX */
440#endif
441 if (ia)
442 ip->ip_src.s_addr = ia->ia_subnet;
443 }
444 pserialize_read_exit(s);
445
446 /*
447 * If we belong to the group being reported, stop
448 * our timer for that group.
449 */
450 in_multi_lock(RW_WRITER);
451 inm = in_lookup_multi(igmp->igmp_group, ifp);
452 if (inm != NULL) {
453 inm->inm_timer = 0;
454 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
455
456 switch (inm->inm_state) {
457 case IGMP_DELAYING_MEMBER:
458 case IGMP_IDLE_MEMBER:
459 case IGMP_AWAKENING_MEMBER:
460 inm->inm_state = IGMP_LAZY_MEMBER;
461 break;
462 case IGMP_LAZY_MEMBER:
463 case IGMP_SLEEPING_MEMBER:
464 break;
465 }
466 }
467 in_multi_unlock();
468 break;
469 }
470 }
471 m_put_rcvif_psref(ifp, &psref);
472
473 /*
474 * Pass all valid IGMP packets up to any process(es) listening
475 * on a raw IGMP socket.
476 */
477 rip_input(m, iphlen, proto);
478 return;
479
480drop:
481 m_put_rcvif_psref(ifp, &psref);
482 m_freem(m);
483 return;
484}
485
486int
487igmp_joingroup(struct in_multi *inm)
488{
489 KASSERT(in_multi_lock_held());
490 inm->inm_state = IGMP_IDLE_MEMBER;
491
492 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
493 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0) {
494 int report_type;
495
496 report_type = rti_fill(inm);
497 if (report_type == 0) {
498 return ENOMEM;
499 }
500 igmp_sendpkt(inm, report_type);
501 inm->inm_state = IGMP_DELAYING_MEMBER;
502 inm->inm_timer = IGMP_RANDOM_DELAY(
503 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
504 igmp_timers_on = true;
505 } else
506 inm->inm_timer = 0;
507
508 return 0;
509}
510
511void
512igmp_leavegroup(struct in_multi *inm)
513{
514 KASSERT(in_multi_lock_held());
515
516 switch (inm->inm_state) {
517 case IGMP_DELAYING_MEMBER:
518 case IGMP_IDLE_MEMBER:
519 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
520 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0)
521 if (inm->inm_rti->rti_type != IGMP_v1_ROUTER)
522 igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
523 break;
524 case IGMP_LAZY_MEMBER:
525 case IGMP_AWAKENING_MEMBER:
526 case IGMP_SLEEPING_MEMBER:
527 break;
528 }
529}
530
531void
532igmp_fasttimo(void)
533{
534 struct in_multi *inm;
535 struct in_multistep step;
536
537 /*
538 * Quick check to see if any work needs to be done, in order
539 * to minimize the overhead of fasttimo processing.
540 */
541 if (!igmp_timers_on) {
542 return;
543 }
544
545 /* XXX: Needed for ip_output(). */
546 mutex_enter(softnet_lock);
547
548 in_multi_lock(RW_WRITER);
549 igmp_timers_on = false;
550 inm = in_first_multi(&step);
551 while (inm != NULL) {
552 if (inm->inm_timer == 0) {
553 /* do nothing */
554 } else if (--inm->inm_timer == 0) {
555 if (inm->inm_state == IGMP_DELAYING_MEMBER) {
556 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
557 igmp_sendpkt(inm,
558 IGMP_v1_HOST_MEMBERSHIP_REPORT);
559 else
560 igmp_sendpkt(inm,
561 IGMP_v2_HOST_MEMBERSHIP_REPORT);
562 inm->inm_state = IGMP_IDLE_MEMBER;
563 }
564 } else {
565 igmp_timers_on = true;
566 }
567 inm = in_next_multi(&step);
568 }
569 in_multi_unlock();
570 mutex_exit(softnet_lock);
571}
572
573void
574igmp_slowtimo(void)
575{
576 router_info_t *rti;
577
578 in_multi_lock(RW_WRITER);
579 LIST_FOREACH(rti, &rti_head, rti_link) {
580 if (rti->rti_type == IGMP_v1_ROUTER &&
581 ++rti->rti_age >= IGMP_AGE_THRESHOLD) {
582 rti->rti_type = IGMP_v2_ROUTER;
583 }
584 }
585 in_multi_unlock();
586}
587
588/*
589 * igmp_sendpkt: construct an IGMP packet, given the multicast structure
590 * and the type, and send the datagram.
591 */
592static void
593igmp_sendpkt(struct in_multi *inm, int type)
594{
595 struct mbuf *m;
596 struct igmp *igmp;
597 struct ip *ip;
598 struct ip_moptions imo;
599
600 KASSERT(in_multi_lock_held());
601
602 MGETHDR(m, M_DONTWAIT, MT_HEADER);
603 if (m == NULL)
604 return;
605
606 /*
607 * Assume max_linkhdr + sizeof(struct ip) + IGMP_MINLEN
608 * is smaller than mbuf size returned by MGETHDR.
609 */
610 m->m_data += max_linkhdr;
611 m->m_len = sizeof(struct ip) + IGMP_MINLEN;
612 m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
613
614 ip = mtod(m, struct ip *);
615 ip->ip_tos = 0;
616 ip->ip_len = htons(sizeof(struct ip) + IGMP_MINLEN);
617 ip->ip_off = htons(0);
618 ip->ip_p = IPPROTO_IGMP;
619 ip->ip_src = zeroin_addr;
620 ip->ip_dst = inm->inm_addr;
621
622 m->m_data += sizeof(struct ip);
623 m->m_len -= sizeof(struct ip);
624 igmp = mtod(m, struct igmp *);
625 igmp->igmp_type = type;
626 igmp->igmp_code = 0;
627 igmp->igmp_group = inm->inm_addr;
628 igmp->igmp_cksum = 0;
629 igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
630 m->m_data -= sizeof(struct ip);
631 m->m_len += sizeof(struct ip);
632
633 imo.imo_multicast_if_index = if_get_index(inm->inm_ifp);
634 imo.imo_multicast_ttl = 1;
635#ifdef RSVP_ISI
636 imo.imo_multicast_vif = -1;
637#endif
638 /*
639 * Request loopback of the report if we are acting as a multicast
640 * router, so that the process-level routing demon can hear it.
641 */
642#ifdef MROUTING
643 extern struct socket *ip_mrouter;
644 imo.imo_multicast_loop = (ip_mrouter != NULL);
645#else
646 imo.imo_multicast_loop = 0;
647#endif
648
649 /*
650 * Note: IP_IGMP_MCAST indicates that in_multilock is held.
651 * The caller must still acquire softnet_lock for ip_output().
652 */
653 KASSERT(mutex_owned(softnet_lock));
654 ip_output(m, NULL, NULL, IP_IGMP_MCAST, &imo, NULL);
655 IGMP_STATINC(IGMP_STAT_SND_REPORTS);
656}
657
658void
659igmp_purgeif(ifnet_t *ifp)
660{
661 in_multi_lock(RW_WRITER);
662 rti_delete(ifp);
663 in_multi_unlock();
664}
665
666static int
667sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS)
668{
669 return NETSTAT_SYSCTL(igmpstat_percpu, IGMP_NSTATS);
670}
671
672static void
673sysctl_net_inet_igmp_setup(struct sysctllog **clog)
674{
675 sysctl_createv(clog, 0, NULL, NULL,
676 CTLFLAG_PERMANENT,
677 CTLTYPE_NODE, "inet", NULL,
678 NULL, 0, NULL, 0,
679 CTL_NET, PF_INET, CTL_EOL);
680 sysctl_createv(clog, 0, NULL, NULL,
681 CTLFLAG_PERMANENT,
682 CTLTYPE_NODE, "igmp",
683 SYSCTL_DESCR("Internet Group Management Protocol"),
684 NULL, 0, NULL, 0,
685 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_EOL);
686 sysctl_createv(clog, 0, NULL, NULL,
687 CTLFLAG_PERMANENT,
688 CTLTYPE_STRUCT, "stats",
689 SYSCTL_DESCR("IGMP statistics"),
690 sysctl_net_inet_igmp_stats, 0, NULL, 0,
691 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_CREATE, CTL_EOL);
692}
693