xref: /freebsd/sys/netinet/igmp.c (revision 17ee9d00bc1ae1e598c38f25826f861e4bc6c3ce)
1 /*
2  * Copyright (c) 1988 Stephen Deering.
3  * Copyright (c) 1992, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Stephen Deering of Stanford University.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *	This product includes software developed by the University of
20  *	California, Berkeley and its contributors.
21  * 4. Neither the name of the University nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  *	@(#)igmp.c	8.1 (Berkeley) 7/19/93
38  * $Id: igmp.c,v 1.6 1994/10/31 06:36:47 pst Exp $
39  */
40 
41 /*
42  * Internet Group Management Protocol (IGMP) routines.
43  *
44  * Written by Steve Deering, Stanford, May 1988.
45  * Modified by Rosen Sharma, Stanford, Aug 1994.
46  *
47  * MULTICAST 1.4
48  */
49 
50 #include <sys/param.h>
51 #include <sys/systm.h>
52 #include <sys/mbuf.h>
53 #include <sys/socket.h>
54 #include <sys/protosw.h>
55 #include <sys/proc.h>		/* XXX needed for sysctl.h */
56 #include <vm/vm.h>		/* XXX needed for sysctl.h */
57 #include <sys/sysctl.h>
58 
59 #include <net/if.h>
60 #include <net/route.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 extern struct ifnet loif;
71 
72 struct igmpstat igmpstat;
73 
74 static int igmp_timers_are_running = 0;
75 static u_long igmp_all_hosts_group;
76 static struct router_info *Head = 0;
77 
78 static void igmp_sendpkt(struct in_multi *, int);
79 static void igmp_sendleave(struct in_multi *);
80 
81 void
82 igmp_init()
83 {
84 	/*
85 	 * To avoid byte-swapping the same value over and over again.
86 	 */
87 	igmp_all_hosts_group = htonl(INADDR_ALLHOSTS_GROUP);
88 	Head = (struct router_info *) 0;
89 }
90 
91 int
92 fill_rti(inm)
93 	struct in_multi *inm;
94 {
95 	register struct router_info *rti = Head;
96 
97 #ifdef IGMP_DEBUG
98 	printf("[igmp.c, _fill_rti] --> entering \n");
99 #endif
100 	while (rti) {
101 		if (rti->ifp == inm->inm_ifp){ /* ? is it ok to compare */
102 					       /* pointers */
103 			inm->inm_rti  = rti;
104 #ifdef IGMP_DEBUG
105 			printf("[igmp.c, _fill_rti] --> found old entry \n");
106 #endif
107 			if (rti->type == IGMP_OLD_ROUTER)
108 				return IGMP_HOST_MEMBERSHIP_REPORT;
109 			else
110 				return IGMP_HOST_NEW_MEMBERSHIP_REPORT;
111 		}
112 		rti = rti->next;
113 	}
114 	MALLOC(rti, struct router_info *, sizeof *rti, M_MRTABLE, M_NOWAIT);
115 	rti->ifp = inm->inm_ifp;
116 	rti->type = IGMP_NEW_ROUTER;
117 	rti->time = IGMP_AGE_THRESHOLD;
118 	rti->next = Head;
119 	Head = rti;
120 	inm->inm_rti = rti;
121 #ifdef IGMP_DEBUG
122 	printf("[igmp.c, _fill_rti] --> created new entry \n");
123 #endif
124 	return IGMP_HOST_NEW_MEMBERSHIP_REPORT;
125 }
126 
127 struct router_info *
128 find_rti(ifp)
129 	struct ifnet *ifp;
130 {
131         register struct router_info *rti = Head;
132 
133 #ifdef IGMP_DEBUG
134 	printf("[igmp.c, _find_rti] --> entering \n");
135 #endif
136         while (rti) {
137                 if (rti->ifp == ifp){ /* ? is it ok to compare pointers */
138 #ifdef IGMP_DEBUG
139 			printf("[igmp.c, _find_rti] --> found old entry \n");
140 #endif
141                         return rti;
142                 }
143                 rti = rti->next;
144         }
145 	MALLOC(rti, struct router_info *, sizeof *rti, M_MRTABLE, M_NOWAIT);
146         rti->ifp = ifp;
147         rti->type = IGMP_NEW_ROUTER;
148         rti->time = IGMP_AGE_THRESHOLD;
149         rti->next = Head;
150         Head = rti;
151 #ifdef IGMP_DEBUG
152 	printf("[igmp.c, _find_rti] --> created an entry \n");
153 #endif
154         return rti;
155 }
156 
157 void
158 igmp_input(m, iphlen)
159 	register struct mbuf *m;
160 	register int iphlen;
161 {
162 	register struct igmp *igmp;
163 	register struct ip *ip;
164 	register int igmplen;
165 	register struct ifnet *ifp = m->m_pkthdr.rcvif;
166 	register int minlen;
167 	register struct in_multi *inm;
168 	register struct in_ifaddr *ia;
169 	struct in_multistep step;
170 	struct router_info *rti;
171 
172 	static int timer; /** timer value in the igmp query header **/
173 
174 	++igmpstat.igps_rcv_total;
175 
176 	ip = mtod(m, struct ip *);
177 	igmplen = ip->ip_len;
178 
179 	/*
180 	 * Validate lengths
181 	 */
182 	if (igmplen < IGMP_MINLEN) {
183 		++igmpstat.igps_rcv_tooshort;
184 		m_freem(m);
185 		return;
186 	}
187 	minlen = iphlen + IGMP_MINLEN;
188 	if ((m->m_flags & M_EXT || m->m_len < minlen) &&
189 	    (m = m_pullup(m, minlen)) == 0) {
190 		++igmpstat.igps_rcv_tooshort;
191 		return;
192 	}
193 
194 	/*
195 	 * Validate checksum
196 	 */
197 	m->m_data += iphlen;
198 	m->m_len -= iphlen;
199 	igmp = mtod(m, struct igmp *);
200 	if (in_cksum(m, igmplen)) {
201 		++igmpstat.igps_rcv_badsum;
202 		m_freem(m);
203 		return;
204 	}
205 	m->m_data -= iphlen;
206 	m->m_len += iphlen;
207 
208 	ip = mtod(m, struct ip *);
209 	timer = ntohs(igmp->igmp_code);
210 	rti = find_rti(ifp);
211 
212 	switch (igmp->igmp_type) {
213 
214 	case IGMP_HOST_MEMBERSHIP_QUERY:
215 		++igmpstat.igps_rcv_queries;
216 
217 		if (ifp == &loif)
218 			break;
219 
220 		if (igmp->igmp_code == 0) {
221 
222 			rti->type = IGMP_OLD_ROUTER; rti->time = 0;
223 
224 			/*
225 			** Do exactly as RFC 1112 says
226 			*/
227 
228 			if (ip->ip_dst.s_addr != igmp_all_hosts_group) {
229 				++igmpstat.igps_rcv_badqueries;
230 				m_freem(m);
231 				return;
232 			}
233 
234 			/*
235 			 * Start the timers in all of our membership records for
236 			 * the interface on which the query arrived, except those
237 			 * that are already running and those that belong to the
238 			 * "all-hosts" group.
239 			 */
240 			IN_FIRST_MULTI(step, inm);
241 			while (inm != NULL) {
242 				if (inm->inm_ifp == ifp
243 				    && inm->inm_timer == 0
244 				    && inm->inm_addr.s_addr
245 				    != igmp_all_hosts_group) {
246 
247 					inm->inm_state = IGMP_DELAYING_MEMBER;
248 					inm->inm_timer = IGMP_RANDOM_DELAY(
249 				IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ );
250 
251 					igmp_timers_are_running = 1;
252 				}
253 				IN_NEXT_MULTI(step, inm);
254 			}
255 		} else {
256 		    /*
257 		    ** New Router
258 		    */
259 
260 		    if (ip->ip_dst.s_addr != igmp_all_hosts_group) {
261 			if (!(m->m_flags & M_MCAST)) {
262 			    ++igmpstat.igps_rcv_badqueries;
263 			    m_freem(m);
264 			    return;
265 			}
266 		    }
267 		    if (ip->ip_dst.s_addr == igmp_all_hosts_group) {
268 
269 			/*
270 			 * - Start the timers in all of our membership records
271 			 *   for the interface on which the query arrived
272 			 *   excl. those that belong to the "all-hosts" group.
273 			 * - For timers already running check if they need to
274 			 *   be reset.
275 			 * - Use the igmp->igmp_code filed as the maximum
276 			 *   delay possible
277 			 */
278 			IN_FIRST_MULTI(step, inm);
279 			while (inm != NULL){
280 			    switch(inm->inm_state){
281 			      case IGMP_IDLE_MEMBER:
282 			      case IGMP_LAZY_MEMBER:
283 			      case IGMP_AWAKENING_MEMBER:
284 				if (inm->inm_ifp == ifp  &&
285 				    inm->inm_addr.s_addr !=
286 						igmp_all_hosts_group) {
287 				    inm->inm_timer = IGMP_RANDOM_DELAY(timer);
288 				    igmp_timers_are_running = 1;
289 				    inm->inm_state = IGMP_DELAYING_MEMBER;
290 				}
291 				break;
292 			      case IGMP_DELAYING_MEMBER:
293 				if (inm->inm_ifp == ifp &&
294 				    (inm->inm_timer >
295 					timer * PR_FASTHZ / IGMP_TIMER_SCALE)
296 					&&
297 				    inm->inm_addr.s_addr !=
298 						igmp_all_hosts_group) {
299 				    inm->inm_timer = IGMP_RANDOM_DELAY(timer);
300 				    igmp_timers_are_running = 1;
301 				    inm->inm_state = IGMP_DELAYING_MEMBER;
302 				}
303 				break;
304 			      case IGMP_SLEEPING_MEMBER:
305 				inm->inm_state = IGMP_AWAKENING_MEMBER;
306 				break;
307 			    }
308 			    IN_NEXT_MULTI(step, inm);
309 			  }
310 		    } else {
311 		      /*
312 		      ** group specific query
313 		      */
314 
315 		      IN_FIRST_MULTI(step, inm);
316 		      while (inm != NULL) {
317 			if (inm->inm_addr.s_addr == ip->ip_dst.s_addr) {
318 			  switch(inm->inm_state ){
319 			  case IGMP_IDLE_MEMBER:
320 			  case IGMP_LAZY_MEMBER:
321 			  case IGMP_AWAKENING_MEMBER:
322 			    inm->inm_state = IGMP_DELAYING_MEMBER;
323 			    if (inm->inm_ifp == ifp ) {
324 			      inm->inm_timer = IGMP_RANDOM_DELAY(timer);
325 			      igmp_timers_are_running = 1;
326 			      inm->inm_state = IGMP_DELAYING_MEMBER;
327 			    }
328 			    break;
329 			  case IGMP_DELAYING_MEMBER:
330 			    inm->inm_state = IGMP_DELAYING_MEMBER;
331 			    if (inm->inm_ifp == ifp &&
332 				(inm->inm_timer >
333 				 timer * PR_FASTHZ / IGMP_TIMER_SCALE) ) {
334 			      inm->inm_timer = IGMP_RANDOM_DELAY(timer);
335 			      igmp_timers_are_running = 1;
336 			      inm->inm_state = IGMP_DELAYING_MEMBER;
337 			    }
338 			    break;
339 			  case IGMP_SLEEPING_MEMBER:
340 			    inm->inm_state = IGMP_AWAKENING_MEMBER;
341 			    break;
342 			  }
343 			}
344 			IN_NEXT_MULTI(step, inm);
345 		}
346 	      }
347 	    }
348 		break;
349 
350 	case IGMP_HOST_MEMBERSHIP_REPORT:
351 		++igmpstat.igps_rcv_reports;
352 
353 		if (ifp == &loif)
354 			break;
355 
356 		if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr)) ||
357 		    igmp->igmp_group.s_addr != ip->ip_dst.s_addr) {
358 			++igmpstat.igps_rcv_badreports;
359 			m_freem(m);
360 			return;
361 		}
362 
363 		/*
364 		 * KLUDGE: if the IP source address of the report has an
365 		 * unspecified (i.e., zero) subnet number, as is allowed for
366 		 * a booting host, replace it with the correct subnet number
367 		 * so that a process-level multicast routing demon can
368 		 * determine which subnet it arrived from.  This is necessary
369 		 * to compensate for the lack of any way for a process to
370 		 * determine the arrival interface of an incoming packet.
371 		 */
372 		if ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0) {
373 			IFP_TO_IA(ifp, ia);
374 			if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet);
375 		}
376 
377 		/*
378 		 * If we belong to the group being reported, stop
379 		 * our timer for that group.
380 		 */
381 		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
382 		if (inm != NULL) {
383 			inm->inm_timer = 0;
384 			++igmpstat.igps_rcv_ourreports;
385 		}
386 
387 		if (inm != NULL) {
388 		  inm->inm_timer = 0;
389 		  ++igmpstat.igps_rcv_ourreports;
390 
391 		  switch(inm->inm_state){
392 		  case IGMP_IDLE_MEMBER:
393 		  case IGMP_LAZY_MEMBER:
394 		  case IGMP_AWAKENING_MEMBER:
395 		  case IGMP_SLEEPING_MEMBER:
396 		    inm->inm_state = IGMP_SLEEPING_MEMBER;
397 		    break;
398 		  case IGMP_DELAYING_MEMBER:
399 		    /** check this out - this was  if (oldrouter)    **/
400 		    if (inm->inm_rti->type == IGMP_OLD_ROUTER)
401 			inm->inm_state = IGMP_LAZY_MEMBER;
402 		    else inm->inm_state = IGMP_SLEEPING_MEMBER;
403 		    break;
404 		  }
405 		}
406 
407 		break;
408 
409 	      case IGMP_HOST_NEW_MEMBERSHIP_REPORT:
410 		/*
411 		 * an new report
412 		 */
413 		++igmpstat.igps_rcv_reports;
414 
415 		if (ifp == &loif)
416 		  break;
417 
418 		if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr)) ||
419 		    igmp->igmp_group.s_addr != ip->ip_dst.s_addr) {
420 		  ++igmpstat.igps_rcv_badreports;
421 		  m_freem(m);
422 		  return;
423 		}
424 
425 		/*
426 		 * KLUDGE: if the IP source address of the report has an
427 		 * unspecified (i.e., zero) subnet number, as is allowed for
428 		 * a booting host, replace it with the correct subnet number
429 		 * so that a process-level multicast routing demon can
430 		 * determine which subnet it arrived from.  This is necessary
431 		 * to compensate for the lack of any way for a process to
432 		 * determine the arrival interface of an incoming packet.
433 		 */
434 		if ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0) {
435 		  IFP_TO_IA(ifp, ia);
436 		  if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet);
437 		}
438 
439 		/*
440 		 * If we belong to the group being reported, stop
441 		 * our timer for that group.
442 		 */
443 		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
444 		if (inm != NULL) {
445 		  inm->inm_timer = 0;
446 		  ++igmpstat.igps_rcv_ourreports;
447 
448 		  switch(inm->inm_state){
449 		  case IGMP_DELAYING_MEMBER:
450 		  case IGMP_IDLE_MEMBER:
451 		    inm->inm_state = IGMP_LAZY_MEMBER;
452 		    break;
453 		  case IGMP_AWAKENING_MEMBER:
454 		    inm->inm_state = IGMP_LAZY_MEMBER;
455 		    break;
456 		  case IGMP_LAZY_MEMBER:
457 		  case IGMP_SLEEPING_MEMBER:
458 		    break;
459 		  }
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);
468 }
469 
470 void
471 igmp_joingroup(inm)
472 	struct in_multi *inm;
473 {
474 	register int s = splnet();
475 
476 	inm->inm_state = IGMP_IDLE_MEMBER;
477 
478 	if (inm->inm_addr.s_addr == igmp_all_hosts_group ||
479 	    inm->inm_ifp == &loif)
480 		inm->inm_timer = 0;
481 	else {
482 		igmp_sendpkt(inm,fill_rti(inm));
483 		inm->inm_timer = IGMP_RANDOM_DELAY(
484 					IGMP_MAX_HOST_REPORT_DELAY*PR_FASTHZ);
485 		inm->inm_state = IGMP_DELAYING_MEMBER;
486 		igmp_timers_are_running = 1;
487 	}
488 	splx(s);
489 }
490 
491 void
492 igmp_leavegroup(inm)
493 	struct in_multi *inm;
494 {
495 	/*
496 	 * No action required on leaving a group.
497 	 */
498          switch(inm->inm_state){
499 	 case IGMP_DELAYING_MEMBER:
500 	 case IGMP_IDLE_MEMBER:
501 	   if (!(inm->inm_addr.s_addr == igmp_all_hosts_group ||
502 	       inm->inm_ifp == &loif))
503 	       if (inm->inm_rti->type != IGMP_OLD_ROUTER)
504 		   igmp_sendleave(inm);
505 	   break;
506 	 case IGMP_LAZY_MEMBER:
507 	 case IGMP_AWAKENING_MEMBER:
508 	 case IGMP_SLEEPING_MEMBER:
509 	   break;
510 	 }
511 }
512 
513 void
514 igmp_fasttimo()
515 {
516 	register struct in_multi *inm;
517 	register int s;
518 	struct in_multistep step;
519 
520 	/*
521 	 * Quick check to see if any work needs to be done, in order
522 	 * to minimize the overhead of fasttimo processing.
523 	 */
524 	if (!igmp_timers_are_running)
525 		return;
526 
527 	s = splnet();
528 	igmp_timers_are_running = 0;
529 	IN_FIRST_MULTI(step, inm);
530 	while (inm != NULL) {
531 		if (inm->inm_timer == 0) {
532 			/* do nothing */
533 		} else if (--inm->inm_timer == 0) {
534 		  if (inm->inm_state == IGMP_DELAYING_MEMBER) {
535 		    if (inm->inm_rti->type == IGMP_OLD_ROUTER)
536 			igmp_sendpkt(inm, IGMP_HOST_MEMBERSHIP_REPORT);
537 		    else
538 			igmp_sendpkt(inm, IGMP_HOST_NEW_MEMBERSHIP_REPORT);
539 		    inm->inm_state = IGMP_IDLE_MEMBER;
540 		  }
541 		} else {
542 			igmp_timers_are_running = 1;
543 		}
544 		IN_NEXT_MULTI(step, inm);
545 	}
546 	splx(s);
547 }
548 
549 void
550 igmp_slowtimo()
551 {
552 	int s = splnet();
553 	register struct router_info *rti =  Head;
554 
555 #ifdef IGMP_DEBUG
556 	printf("[igmp.c,_slowtimo] -- > entering \n");
557 #endif
558 	while (rti) {
559 		rti->time ++;
560 		if (rti->time >= IGMP_AGE_THRESHOLD){
561 			rti->type = IGMP_NEW_ROUTER;
562 			rti->time = IGMP_AGE_THRESHOLD;
563 		}
564 		rti = rti->next;
565 	}
566 #ifdef IGMP_DEBUG
567 	printf("[igmp.c,_slowtimo] -- > exiting \n");
568 #endif
569 	splx(s);
570 }
571 
572 static void
573 igmp_sendpkt(inm, type)
574 	struct in_multi *inm;
575 	int type;
576 {
577         struct mbuf *m;
578         struct igmp *igmp;
579         struct ip *ip;
580         struct ip_moptions *imo;
581 
582         MGETHDR(m, M_DONTWAIT, MT_HEADER);
583         if (m == NULL)
584                 return;
585 
586 	MALLOC(imo, struct ip_moptions *, sizeof *imo, M_IPMOPTS, M_DONTWAIT);
587 	if (!imo) {
588 		m_free(m);
589 		return;
590 	}
591 
592 	m->m_pkthdr.rcvif = &loif;
593 	m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
594 	MH_ALIGN(m, IGMP_MINLEN + sizeof(struct ip));
595 	m->m_data += sizeof(struct ip);
596         m->m_len = IGMP_MINLEN;
597         igmp = mtod(m, struct igmp *);
598         igmp->igmp_type   = type;
599         igmp->igmp_code   = 0;
600         igmp->igmp_group  = inm->inm_addr;
601         igmp->igmp_cksum  = 0;
602         igmp->igmp_cksum  = in_cksum(m, IGMP_MINLEN);
603 
604         m->m_data -= sizeof(struct ip);
605         m->m_len += sizeof(struct ip);
606         ip = mtod(m, struct ip *);
607         ip->ip_tos        = 0;
608         ip->ip_len        = sizeof(struct ip) + IGMP_MINLEN;
609         ip->ip_off        = 0;
610         ip->ip_p          = IPPROTO_IGMP;
611         ip->ip_src.s_addr = INADDR_ANY;
612         ip->ip_dst        = igmp->igmp_group;
613 
614         imo->imo_multicast_ifp  = inm->inm_ifp;
615         imo->imo_multicast_ttl  = 1;
616         /*
617          * Request loopback of the report if we are acting as a multicast
618          * router, so that the process-level routing demon can hear it.
619          */
620         imo->imo_multicast_loop = (ip_mrouter != NULL);
621 
622         ip_output(m, (struct mbuf *)0, (struct route *)0, 0, imo);
623 
624 	FREE(imo, M_IPMOPTS);
625         ++igmpstat.igps_snd_reports;
626 
627 }
628 
629 static void
630 igmp_sendleave(inm)
631 	struct in_multi *inm;
632 {
633 	igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
634 }
635 
636 int
637 igmp_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
638 	    void *newp, size_t newlen)
639 {
640 	/* All sysctl names at this level are terminal. */
641 	if (namelen != 1)
642 		return ENOTDIR;	/* XXX overloaded */
643 
644 	switch(name[0]) {
645 	case IGMPCTL_STATS:
646 		return sysctl_rdstruct(oldp, oldlenp, newp, &igmpstat,
647 				       sizeof igmpstat);
648 	default:
649 		return ENOPROTOOPT;
650 	}
651 }
652