xref: /freebsd/sys/netinet/igmp.c (revision 5ebc7e6281887681c3a348a5a4c902e262ccd656)
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.9 1995/04/26 18:10:53 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 struct igmpstat igmpstat;
71 
72 static int igmp_timers_are_running = 0;
73 static u_long igmp_all_hosts_group;
74 static struct router_info *Head = 0;
75 
76 static void igmp_sendpkt(struct in_multi *, int);
77 static void igmp_sendleave(struct in_multi *);
78 
79 void
80 igmp_init()
81 {
82 	/*
83 	 * To avoid byte-swapping the same value over and over again.
84 	 */
85 	igmp_all_hosts_group = htonl(INADDR_ALLHOSTS_GROUP);
86 	Head = (struct router_info *) 0;
87 }
88 
89 int
90 fill_rti(inm)
91 	struct in_multi *inm;
92 {
93 	register struct router_info *rti = Head;
94 
95 #ifdef IGMP_DEBUG
96 	printf("[igmp.c, _fill_rti] --> entering \n");
97 #endif
98 	while (rti) {
99 		if (rti->ifp == inm->inm_ifp){ /* ? is it ok to compare */
100 					       /* pointers */
101 			inm->inm_rti  = rti;
102 #ifdef IGMP_DEBUG
103 			printf("[igmp.c, _fill_rti] --> found old entry \n");
104 #endif
105 			if (rti->type == IGMP_OLD_ROUTER)
106 				return IGMP_HOST_MEMBERSHIP_REPORT;
107 			else
108 				return IGMP_HOST_NEW_MEMBERSHIP_REPORT;
109 		}
110 		rti = rti->next;
111 	}
112 	MALLOC(rti, struct router_info *, sizeof *rti, M_MRTABLE, M_NOWAIT);
113 	rti->ifp = inm->inm_ifp;
114 	rti->type = IGMP_NEW_ROUTER;
115 	rti->time = IGMP_AGE_THRESHOLD;
116 	rti->next = Head;
117 	Head = rti;
118 	inm->inm_rti = rti;
119 #ifdef IGMP_DEBUG
120 	printf("[igmp.c, _fill_rti] --> created new entry \n");
121 #endif
122 	return IGMP_HOST_NEW_MEMBERSHIP_REPORT;
123 }
124 
125 struct router_info *
126 find_rti(ifp)
127 	struct ifnet *ifp;
128 {
129         register struct router_info *rti = Head;
130 
131 #ifdef IGMP_DEBUG
132 	printf("[igmp.c, _find_rti] --> entering \n");
133 #endif
134         while (rti) {
135                 if (rti->ifp == ifp){ /* ? is it ok to compare pointers */
136 #ifdef IGMP_DEBUG
137 			printf("[igmp.c, _find_rti] --> found old entry \n");
138 #endif
139                         return rti;
140                 }
141                 rti = rti->next;
142         }
143 	MALLOC(rti, struct router_info *, sizeof *rti, M_MRTABLE, M_NOWAIT);
144         rti->ifp = ifp;
145         rti->type = IGMP_NEW_ROUTER;
146         rti->time = IGMP_AGE_THRESHOLD;
147         rti->next = Head;
148         Head = rti;
149 #ifdef IGMP_DEBUG
150 	printf("[igmp.c, _find_rti] --> created an entry \n");
151 #endif
152         return rti;
153 }
154 
155 void
156 igmp_input(m, iphlen)
157 	register struct mbuf *m;
158 	register int iphlen;
159 {
160 	register struct igmp *igmp;
161 	register struct ip *ip;
162 	register int igmplen;
163 	register struct ifnet *ifp = m->m_pkthdr.rcvif;
164 	register int minlen;
165 	register struct in_multi *inm;
166 	register struct in_ifaddr *ia;
167 	struct in_multistep step;
168 	struct router_info *rti;
169 
170 	int timer; /** timer value in the igmp query header **/
171 
172 	++igmpstat.igps_rcv_total;
173 
174 	ip = mtod(m, struct ip *);
175 	igmplen = ip->ip_len;
176 
177 	/*
178 	 * Validate lengths
179 	 */
180 	if (igmplen < IGMP_MINLEN) {
181 		++igmpstat.igps_rcv_tooshort;
182 		m_freem(m);
183 		return;
184 	}
185 	minlen = iphlen + IGMP_MINLEN;
186 	if ((m->m_flags & M_EXT || m->m_len < minlen) &&
187 	    (m = m_pullup(m, minlen)) == 0) {
188 		++igmpstat.igps_rcv_tooshort;
189 		return;
190 	}
191 
192 	/*
193 	 * Validate checksum
194 	 */
195 	m->m_data += iphlen;
196 	m->m_len -= iphlen;
197 	igmp = mtod(m, struct igmp *);
198 	if (in_cksum(m, igmplen)) {
199 		++igmpstat.igps_rcv_badsum;
200 		m_freem(m);
201 		return;
202 	}
203 	m->m_data -= iphlen;
204 	m->m_len += iphlen;
205 
206 	ip = mtod(m, struct ip *);
207 	timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
208 	rti = find_rti(ifp);
209 
210 	switch (igmp->igmp_type) {
211 
212 	case IGMP_HOST_MEMBERSHIP_QUERY:
213 		++igmpstat.igps_rcv_queries;
214 
215 		if (ifp->if_flags & IFF_LOOPBACK)
216 			break;
217 
218 		if (igmp->igmp_code == 0) {
219 
220 			rti->type = IGMP_OLD_ROUTER; rti->time = 0;
221 
222 			/*
223 			** Do exactly as RFC 1112 says
224 			*/
225 
226 			if (ip->ip_dst.s_addr != igmp_all_hosts_group) {
227 				++igmpstat.igps_rcv_badqueries;
228 				m_freem(m);
229 				return;
230 			}
231 
232 			/*
233 			 * Start the timers in all of our membership records for
234 			 * the interface on which the query arrived, except those
235 			 * that are already running and those that belong to the
236 			 * "all-hosts" group.
237 			 */
238 			IN_FIRST_MULTI(step, inm);
239 			while (inm != NULL) {
240 				if (inm->inm_ifp == ifp
241 				    && inm->inm_timer == 0
242 				    && inm->inm_addr.s_addr
243 				    != igmp_all_hosts_group) {
244 
245 					inm->inm_state = IGMP_DELAYING_MEMBER;
246 					inm->inm_timer = IGMP_RANDOM_DELAY(
247 				IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ );
248 
249 					igmp_timers_are_running = 1;
250 				}
251 				IN_NEXT_MULTI(step, inm);
252 			}
253 		} else {
254 		    /*
255 		    ** New Router
256 		    */
257 
258 		    if (ip->ip_dst.s_addr != igmp_all_hosts_group) {
259 			if (!(m->m_flags & M_MCAST)) {
260 			    ++igmpstat.igps_rcv_badqueries;
261 			    m_freem(m);
262 			    return;
263 			}
264 		    }
265 		    if (ip->ip_dst.s_addr == igmp_all_hosts_group) {
266 
267 			/*
268 			 * - Start the timers in all of our membership records
269 			 *   for the interface on which the query arrived
270 			 *   excl. those that belong to the "all-hosts" group.
271 			 * - For timers already running check if they need to
272 			 *   be reset.
273 			 * - Use the igmp->igmp_code filed as the maximum
274 			 *   delay possible
275 			 */
276 			IN_FIRST_MULTI(step, inm);
277 			while (inm != NULL){
278 			    switch(inm->inm_state){
279 			      case IGMP_IDLE_MEMBER:
280 			      case IGMP_LAZY_MEMBER:
281 			      case IGMP_AWAKENING_MEMBER:
282 				if (inm->inm_ifp == ifp  &&
283 				    inm->inm_addr.s_addr !=
284 						igmp_all_hosts_group) {
285 				    inm->inm_timer = IGMP_RANDOM_DELAY(timer);
286 				    igmp_timers_are_running = 1;
287 				    inm->inm_state = IGMP_DELAYING_MEMBER;
288 				}
289 				break;
290 			      case IGMP_DELAYING_MEMBER:
291 				if (inm->inm_ifp == ifp &&
292 				    (inm->inm_timer > timer) &&
293 				    inm->inm_addr.s_addr !=
294 						igmp_all_hosts_group) {
295 				    inm->inm_timer = IGMP_RANDOM_DELAY(timer);
296 				    igmp_timers_are_running = 1;
297 				    inm->inm_state = IGMP_DELAYING_MEMBER;
298 				}
299 				break;
300 			      case IGMP_SLEEPING_MEMBER:
301 				inm->inm_state = IGMP_AWAKENING_MEMBER;
302 				break;
303 			    }
304 			    IN_NEXT_MULTI(step, inm);
305 			  }
306 		    } else {
307 		      /*
308 		      ** group specific query
309 		      */
310 
311 		      IN_FIRST_MULTI(step, inm);
312 		      while (inm != NULL) {
313 			if (inm->inm_addr.s_addr == ip->ip_dst.s_addr) {
314 			  switch(inm->inm_state ){
315 			  case IGMP_IDLE_MEMBER:
316 			  case IGMP_LAZY_MEMBER:
317 			  case IGMP_AWAKENING_MEMBER:
318 			    inm->inm_state = IGMP_DELAYING_MEMBER;
319 			    if (inm->inm_ifp == ifp ) {
320 			      inm->inm_timer = IGMP_RANDOM_DELAY(timer);
321 			      igmp_timers_are_running = 1;
322 			      inm->inm_state = IGMP_DELAYING_MEMBER;
323 			    }
324 			    break;
325 			  case IGMP_DELAYING_MEMBER:
326 			    inm->inm_state = IGMP_DELAYING_MEMBER;
327 			    if (inm->inm_ifp == ifp &&
328 				(inm->inm_timer > timer) ) {
329 			      inm->inm_timer = IGMP_RANDOM_DELAY(timer);
330 			      igmp_timers_are_running = 1;
331 			      inm->inm_state = IGMP_DELAYING_MEMBER;
332 			    }
333 			    break;
334 			  case IGMP_SLEEPING_MEMBER:
335 			    inm->inm_state = IGMP_AWAKENING_MEMBER;
336 			    break;
337 			  }
338 			}
339 			IN_NEXT_MULTI(step, inm);
340 		}
341 	      }
342 	    }
343 		break;
344 
345 	case IGMP_HOST_MEMBERSHIP_REPORT:
346 		++igmpstat.igps_rcv_reports;
347 
348 		if (ifp->if_flags & IFF_LOOPBACK)
349 			break;
350 
351 		if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr)) ||
352 		    igmp->igmp_group.s_addr != ip->ip_dst.s_addr) {
353 			++igmpstat.igps_rcv_badreports;
354 			m_freem(m);
355 			return;
356 		}
357 
358 		/*
359 		 * KLUDGE: if the IP source address of the report has an
360 		 * unspecified (i.e., zero) subnet number, as is allowed for
361 		 * a booting host, replace it with the correct subnet number
362 		 * so that a process-level multicast routing demon can
363 		 * determine which subnet it arrived from.  This is necessary
364 		 * to compensate for the lack of any way for a process to
365 		 * determine the arrival interface of an incoming packet.
366 		 */
367 		if ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0) {
368 			IFP_TO_IA(ifp, ia);
369 			if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet);
370 		}
371 
372 		/*
373 		 * If we belong to the group being reported, stop
374 		 * our timer for that group.
375 		 */
376 		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
377 		if (inm != NULL) {
378 			inm->inm_timer = 0;
379 			++igmpstat.igps_rcv_ourreports;
380 		}
381 
382 		if (inm != NULL) {
383 		  inm->inm_timer = 0;
384 		  ++igmpstat.igps_rcv_ourreports;
385 
386 		  switch(inm->inm_state){
387 		  case IGMP_IDLE_MEMBER:
388 		  case IGMP_LAZY_MEMBER:
389 		  case IGMP_AWAKENING_MEMBER:
390 		  case IGMP_SLEEPING_MEMBER:
391 		    inm->inm_state = IGMP_SLEEPING_MEMBER;
392 		    break;
393 		  case IGMP_DELAYING_MEMBER:
394 		    /** check this out - this was  if (oldrouter)    **/
395 		    if (inm->inm_rti->type == IGMP_OLD_ROUTER)
396 			inm->inm_state = IGMP_LAZY_MEMBER;
397 		    else inm->inm_state = IGMP_SLEEPING_MEMBER;
398 		    break;
399 		  }
400 		}
401 
402 		break;
403 
404 	      case IGMP_HOST_NEW_MEMBERSHIP_REPORT:
405 		/*
406 		 * an new report
407 		 */
408 		++igmpstat.igps_rcv_reports;
409 
410 		if (ifp->if_flags & IFF_LOOPBACK)
411 		  break;
412 
413 		if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr)) ||
414 		    igmp->igmp_group.s_addr != ip->ip_dst.s_addr) {
415 		  ++igmpstat.igps_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 demon 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 ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0) {
430 		  IFP_TO_IA(ifp, ia);
431 		  if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet);
432 		}
433 
434 		/*
435 		 * If we belong to the group being reported, stop
436 		 * our timer for that group.
437 		 */
438 		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
439 		if (inm != NULL) {
440 		  inm->inm_timer = 0;
441 		  ++igmpstat.igps_rcv_ourreports;
442 
443 		  switch(inm->inm_state){
444 		  case IGMP_DELAYING_MEMBER:
445 		  case IGMP_IDLE_MEMBER:
446 		    inm->inm_state = IGMP_LAZY_MEMBER;
447 		    break;
448 		  case IGMP_AWAKENING_MEMBER:
449 		    inm->inm_state = IGMP_LAZY_MEMBER;
450 		    break;
451 		  case IGMP_LAZY_MEMBER:
452 		  case IGMP_SLEEPING_MEMBER:
453 		    break;
454 		  }
455 		}
456 	}
457 
458 	/*
459 	 * Pass all valid IGMP packets up to any process(es) listening
460 	 * on a raw IGMP socket.
461 	 */
462 	rip_input(m);
463 }
464 
465 void
466 igmp_joingroup(inm)
467 	struct in_multi *inm;
468 {
469 	register int s = splnet();
470 
471 	inm->inm_state = IGMP_IDLE_MEMBER;
472 
473 	if (inm->inm_addr.s_addr == igmp_all_hosts_group ||
474 	    inm->inm_ifp->if_flags & IFF_LOOPBACK)
475 		inm->inm_timer = 0;
476 	else {
477 		igmp_sendpkt(inm,fill_rti(inm));
478 		inm->inm_timer = IGMP_RANDOM_DELAY(
479 					IGMP_MAX_HOST_REPORT_DELAY*PR_FASTHZ);
480 		inm->inm_state = IGMP_DELAYING_MEMBER;
481 		igmp_timers_are_running = 1;
482 	}
483 	splx(s);
484 }
485 
486 void
487 igmp_leavegroup(inm)
488 	struct in_multi *inm;
489 {
490 	/*
491 	 * No action required on leaving a group.
492 	 */
493          switch(inm->inm_state){
494 	 case IGMP_DELAYING_MEMBER:
495 	 case IGMP_IDLE_MEMBER:
496 	   if (!(inm->inm_addr.s_addr == igmp_all_hosts_group ||
497 	       inm->inm_ifp->if_flags & IFF_LOOPBACK))
498 	       if (inm->inm_rti->type != IGMP_OLD_ROUTER)
499 		   igmp_sendleave(inm);
500 	   break;
501 	 case IGMP_LAZY_MEMBER:
502 	 case IGMP_AWAKENING_MEMBER:
503 	 case IGMP_SLEEPING_MEMBER:
504 	   break;
505 	 }
506 }
507 
508 void
509 igmp_fasttimo()
510 {
511 	register struct in_multi *inm;
512 	register int s;
513 	struct in_multistep step;
514 
515 	/*
516 	 * Quick check to see if any work needs to be done, in order
517 	 * to minimize the overhead of fasttimo processing.
518 	 */
519 	if (!igmp_timers_are_running)
520 		return;
521 
522 	s = splnet();
523 	igmp_timers_are_running = 0;
524 	IN_FIRST_MULTI(step, inm);
525 	while (inm != NULL) {
526 		if (inm->inm_timer == 0) {
527 			/* do nothing */
528 		} else if (--inm->inm_timer == 0) {
529 		  if (inm->inm_state == IGMP_DELAYING_MEMBER) {
530 		    if (inm->inm_rti->type == IGMP_OLD_ROUTER)
531 			igmp_sendpkt(inm, IGMP_HOST_MEMBERSHIP_REPORT);
532 		    else
533 			igmp_sendpkt(inm, IGMP_HOST_NEW_MEMBERSHIP_REPORT);
534 		    inm->inm_state = IGMP_IDLE_MEMBER;
535 		  }
536 		} else {
537 			igmp_timers_are_running = 1;
538 		}
539 		IN_NEXT_MULTI(step, inm);
540 	}
541 	splx(s);
542 }
543 
544 void
545 igmp_slowtimo()
546 {
547 	int s = splnet();
548 	register struct router_info *rti =  Head;
549 
550 #ifdef IGMP_DEBUG
551 	printf("[igmp.c,_slowtimo] -- > entering \n");
552 #endif
553 	while (rti) {
554 		rti->time ++;
555 		if (rti->time >= IGMP_AGE_THRESHOLD){
556 			rti->type = IGMP_NEW_ROUTER;
557 			rti->time = IGMP_AGE_THRESHOLD;
558 		}
559 		rti = rti->next;
560 	}
561 #ifdef IGMP_DEBUG
562 	printf("[igmp.c,_slowtimo] -- > exiting \n");
563 #endif
564 	splx(s);
565 }
566 
567 static void
568 igmp_sendpkt(inm, type)
569 	struct in_multi *inm;
570 	int type;
571 {
572         struct mbuf *m;
573         struct igmp *igmp;
574         struct ip *ip;
575         struct ip_moptions *imo;
576 
577         MGETHDR(m, M_DONTWAIT, MT_HEADER);
578         if (m == NULL)
579                 return;
580 
581 	MALLOC(imo, struct ip_moptions *, sizeof *imo, M_IPMOPTS, M_DONTWAIT);
582 	if (!imo) {
583 		m_free(m);
584 		return;
585 	}
586 
587 	m->m_pkthdr.rcvif = loif;
588 	m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
589 	MH_ALIGN(m, IGMP_MINLEN + sizeof(struct ip));
590 	m->m_data += sizeof(struct ip);
591         m->m_len = IGMP_MINLEN;
592         igmp = mtod(m, struct igmp *);
593         igmp->igmp_type   = type;
594         igmp->igmp_code   = 0;
595         igmp->igmp_group  = inm->inm_addr;
596         igmp->igmp_cksum  = 0;
597         igmp->igmp_cksum  = in_cksum(m, IGMP_MINLEN);
598 
599         m->m_data -= sizeof(struct ip);
600         m->m_len += sizeof(struct ip);
601         ip = mtod(m, struct ip *);
602         ip->ip_tos        = 0;
603         ip->ip_len        = sizeof(struct ip) + IGMP_MINLEN;
604         ip->ip_off        = 0;
605         ip->ip_p          = IPPROTO_IGMP;
606         ip->ip_src.s_addr = INADDR_ANY;
607         ip->ip_dst        = igmp->igmp_group;
608 
609         imo->imo_multicast_ifp  = inm->inm_ifp;
610         imo->imo_multicast_ttl  = 1;
611         /*
612          * Request loopback of the report if we are acting as a multicast
613          * router, so that the process-level routing demon can hear it.
614          */
615         imo->imo_multicast_loop = (ip_mrouter != NULL);
616 
617         ip_output(m, (struct mbuf *)0, (struct route *)0, 0, imo);
618 
619 	FREE(imo, M_IPMOPTS);
620         ++igmpstat.igps_snd_reports;
621 
622 }
623 
624 static void
625 igmp_sendleave(inm)
626 	struct in_multi *inm;
627 {
628 	igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
629 }
630 
631 int
632 igmp_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
633 	    void *newp, size_t newlen)
634 {
635 	/* All sysctl names at this level are terminal. */
636 	if (namelen != 1)
637 		return ENOTDIR;	/* XXX overloaded */
638 
639 	switch(name[0]) {
640 	case IGMPCTL_STATS:
641 		return sysctl_rdstruct(oldp, oldlenp, newp, &igmpstat,
642 				       sizeof igmpstat);
643 	default:
644 		return ENOPROTOOPT;
645 	}
646 }
647