Expand PMF_FN_* macros.
[netbsd-mini2440.git] / sys / netinet / igmp.c
bloba98bf45d7510bdefcfe633d69bcb7d24a4f9c4e7
1 /* $NetBSD: igmp.c,v 1.50 2009/09/13 18:45:11 pooka Exp $ */
3 /*
4 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
5 * All rights reserved.
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.
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.
33 * Internet Group Management Protocol (IGMP) routines.
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.
39 * MULTICAST Revision: 1.3
42 #include <sys/cdefs.h>
43 __KERNEL_RCSID(0, "$NetBSD: igmp.c,v 1.50 2009/09/13 18:45:11 pooka Exp $");
45 #include "opt_mrouting.h"
47 #include <sys/param.h>
48 #include <sys/mbuf.h>
49 #include <sys/socket.h>
50 #include <sys/socketvar.h>
51 #include <sys/protosw.h>
52 #include <sys/systm.h>
53 #include <sys/sysctl.h>
55 #include <net/if.h>
56 #include <net/route.h>
57 #include <net/net_stats.h>
59 #include <netinet/in.h>
60 #include <netinet/in_var.h>
61 #include <netinet/in_systm.h>
62 #include <netinet/ip.h>
63 #include <netinet/ip_var.h>
64 #include <netinet/igmp.h>
65 #include <netinet/igmp_var.h>
67 #include <machine/stdarg.h>
69 #define IP_MULTICASTOPTS 0
71 static struct pool igmp_rti_pool;
73 static percpu_t *igmpstat_percpu;
75 #define IGMP_STATINC(x) _NET_STATINC(igmpstat_percpu, x)
77 int igmp_timers_are_running;
78 static LIST_HEAD(, router_info) rti_head = LIST_HEAD_INITIALIZER(rti_head);
80 void igmp_sendpkt(struct in_multi *, int);
81 static int rti_fill(struct in_multi *);
82 static struct router_info *rti_find(struct ifnet *);
83 static void rti_delete(struct ifnet *);
85 static void sysctl_net_inet_igmp_setup(struct sysctllog **);
87 static int
88 rti_fill(struct in_multi *inm)
90 struct router_info *rti;
92 /* this function is called at splsoftnet() */
93 LIST_FOREACH(rti, &rti_head, rti_link) {
94 if (rti->rti_ifp == inm->inm_ifp) {
95 inm->inm_rti = rti;
96 if (rti->rti_type == IGMP_v1_ROUTER)
97 return (IGMP_v1_HOST_MEMBERSHIP_REPORT);
98 else
99 return (IGMP_v2_HOST_MEMBERSHIP_REPORT);
103 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
104 if (rti == NULL)
105 return 0;
106 rti->rti_ifp = inm->inm_ifp;
107 rti->rti_type = IGMP_v2_ROUTER;
108 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
109 inm->inm_rti = rti;
110 return (IGMP_v2_HOST_MEMBERSHIP_REPORT);
113 static struct router_info *
114 rti_find(struct ifnet *ifp)
116 struct router_info *rti;
117 int s = splsoftnet();
119 LIST_FOREACH(rti, &rti_head, rti_link) {
120 if (rti->rti_ifp == ifp)
121 return (rti);
124 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
125 if (rti == NULL) {
126 splx(s);
127 return NULL;
129 rti->rti_ifp = ifp;
130 rti->rti_type = IGMP_v2_ROUTER;
131 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
132 splx(s);
133 return (rti);
136 static void
137 rti_delete(struct ifnet *ifp) /* MUST be called at splsoftnet */
139 struct router_info *rti;
141 LIST_FOREACH(rti, &rti_head, rti_link) {
142 if (rti->rti_ifp == ifp) {
143 LIST_REMOVE(rti, rti_link);
144 pool_put(&igmp_rti_pool, rti);
145 return;
150 void
151 igmp_init(void)
154 sysctl_net_inet_igmp_setup(NULL);
155 pool_init(&igmp_rti_pool, sizeof(struct router_info), 0, 0, 0,
156 "igmppl", NULL, IPL_SOFTNET);
157 igmpstat_percpu = percpu_alloc(sizeof(uint64_t) * IGMP_NSTATS);
160 void
161 igmp_input(struct mbuf *m, ...)
163 int proto;
164 int iphlen;
165 struct ifnet *ifp = m->m_pkthdr.rcvif;
166 struct ip *ip = mtod(m, struct ip *);
167 struct igmp *igmp;
168 u_int minlen;
169 struct in_multi *inm;
170 struct in_multistep step;
171 struct router_info *rti;
172 struct in_ifaddr *ia;
173 u_int timer;
174 va_list ap;
175 u_int16_t ip_len;
177 va_start(ap, m);
178 iphlen = va_arg(ap, int);
179 proto = va_arg(ap, int);
180 va_end(ap);
182 IGMP_STATINC(IGMP_STAT_RCV_TOTAL);
185 * Validate lengths
187 minlen = iphlen + IGMP_MINLEN;
188 ip_len = ntohs(ip->ip_len);
189 if (ip_len < minlen) {
190 IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
191 m_freem(m);
192 return;
194 if (((m->m_flags & M_EXT) && (ip->ip_src.s_addr & IN_CLASSA_NET) == 0)
195 || m->m_len < minlen) {
196 if ((m = m_pullup(m, minlen)) == 0) {
197 IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
198 return;
200 ip = mtod(m, struct ip *);
204 * Validate checksum
206 m->m_data += iphlen;
207 m->m_len -= iphlen;
208 igmp = mtod(m, struct igmp *);
209 /* No need to assert alignment here. */
210 if (in_cksum(m, ip_len - iphlen)) {
211 IGMP_STATINC(IGMP_STAT_RCV_BADSUM);
212 m_freem(m);
213 return;
215 m->m_data -= iphlen;
216 m->m_len += iphlen;
218 switch (igmp->igmp_type) {
220 case IGMP_HOST_MEMBERSHIP_QUERY:
221 IGMP_STATINC(IGMP_STAT_RCV_QUERIES);
223 if (ifp->if_flags & IFF_LOOPBACK)
224 break;
226 if (igmp->igmp_code == 0) {
227 rti = rti_find(ifp);
228 if (rti == NULL)
229 break;
230 rti->rti_type = IGMP_v1_ROUTER;
231 rti->rti_age = 0;
233 if (ip->ip_dst.s_addr != INADDR_ALLHOSTS_GROUP) {
234 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
235 m_freem(m);
236 return;
240 * Start the timers in all of our membership records
241 * for the interface on which the query arrived,
242 * except those that are already running and those
243 * that belong to a "local" group (224.0.0.X).
245 IN_FIRST_MULTI(step, inm);
246 while (inm != NULL) {
247 if (inm->inm_ifp == ifp &&
248 inm->inm_timer == 0 &&
249 !IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
250 inm->inm_state = IGMP_DELAYING_MEMBER;
251 inm->inm_timer = IGMP_RANDOM_DELAY(
252 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
253 igmp_timers_are_running = 1;
255 IN_NEXT_MULTI(step, inm);
257 } else {
258 if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
259 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
260 m_freem(m);
261 return;
264 timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
265 if (timer == 0)
266 timer =1;
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). For
273 * timers already running, check if they need to be
274 * reset.
276 IN_FIRST_MULTI(step, inm);
277 while (inm != NULL) {
278 if (inm->inm_ifp == ifp &&
279 !IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
280 (ip->ip_dst.s_addr == INADDR_ALLHOSTS_GROUP ||
281 in_hosteq(ip->ip_dst, inm->inm_addr))) {
282 switch (inm->inm_state) {
283 case IGMP_DELAYING_MEMBER:
284 if (inm->inm_timer <= timer)
285 break;
286 /* FALLTHROUGH */
287 case IGMP_IDLE_MEMBER:
288 case IGMP_LAZY_MEMBER:
289 case IGMP_AWAKENING_MEMBER:
290 inm->inm_state =
291 IGMP_DELAYING_MEMBER;
292 inm->inm_timer =
293 IGMP_RANDOM_DELAY(timer);
294 igmp_timers_are_running = 1;
295 break;
296 case IGMP_SLEEPING_MEMBER:
297 inm->inm_state =
298 IGMP_AWAKENING_MEMBER;
299 break;
302 IN_NEXT_MULTI(step, inm);
306 break;
308 case IGMP_v1_HOST_MEMBERSHIP_REPORT:
309 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
311 if (ifp->if_flags & IFF_LOOPBACK)
312 break;
314 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
315 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
316 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
317 m_freem(m);
318 return;
322 * KLUDGE: if the IP source address of the report has an
323 * unspecified (i.e., zero) subnet number, as is allowed for
324 * a booting host, replace it with the correct subnet number
325 * so that a process-level multicast routing daemon can
326 * determine which subnet it arrived from. This is necessary
327 * to compensate for the lack of any way for a process to
328 * determine the arrival interface of an incoming packet.
330 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
331 IFP_TO_IA(ifp, ia); /* XXX */
332 if (ia)
333 ip->ip_src.s_addr = ia->ia_subnet;
337 * If we belong to the group being reported, stop
338 * our timer for that group.
340 IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
341 if (inm != NULL) {
342 inm->inm_timer = 0;
343 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
345 switch (inm->inm_state) {
346 case IGMP_IDLE_MEMBER:
347 case IGMP_LAZY_MEMBER:
348 case IGMP_AWAKENING_MEMBER:
349 case IGMP_SLEEPING_MEMBER:
350 inm->inm_state = IGMP_SLEEPING_MEMBER;
351 break;
352 case IGMP_DELAYING_MEMBER:
353 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
354 inm->inm_state = IGMP_LAZY_MEMBER;
355 else
356 inm->inm_state = IGMP_SLEEPING_MEMBER;
357 break;
361 break;
363 case IGMP_v2_HOST_MEMBERSHIP_REPORT:
364 #ifdef MROUTING
366 * Make sure we don't hear our own membership report. Fast
367 * leave requires knowing that we are the only member of a
368 * group.
370 IFP_TO_IA(ifp, ia); /* XXX */
371 if (ia && in_hosteq(ip->ip_src, ia->ia_addr.sin_addr))
372 break;
373 #endif
375 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
377 if (ifp->if_flags & IFF_LOOPBACK)
378 break;
380 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
381 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
382 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
383 m_freem(m);
384 return;
388 * KLUDGE: if the IP source address of the report has an
389 * unspecified (i.e., zero) subnet number, as is allowed for
390 * a booting host, replace it with the correct subnet number
391 * so that a process-level multicast routing daemon can
392 * determine which subnet it arrived from. This is necessary
393 * to compensate for the lack of any way for a process to
394 * determine the arrival interface of an incoming packet.
396 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
397 #ifndef MROUTING
398 IFP_TO_IA(ifp, ia); /* XXX */
399 #endif
400 if (ia)
401 ip->ip_src.s_addr = ia->ia_subnet;
405 * If we belong to the group being reported, stop
406 * our timer for that group.
408 IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
409 if (inm != NULL) {
410 inm->inm_timer = 0;
411 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
413 switch (inm->inm_state) {
414 case IGMP_DELAYING_MEMBER:
415 case IGMP_IDLE_MEMBER:
416 case IGMP_AWAKENING_MEMBER:
417 inm->inm_state = IGMP_LAZY_MEMBER;
418 break;
419 case IGMP_LAZY_MEMBER:
420 case IGMP_SLEEPING_MEMBER:
421 break;
425 break;
430 * Pass all valid IGMP packets up to any process(es) listening
431 * on a raw IGMP socket.
433 rip_input(m, iphlen, proto);
434 return;
438 igmp_joingroup(struct in_multi *inm)
440 int report_type;
441 int s = splsoftnet();
443 inm->inm_state = IGMP_IDLE_MEMBER;
445 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
446 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0) {
447 report_type = rti_fill(inm);
448 if (report_type == 0) {
449 splx(s);
450 return ENOMEM;
452 igmp_sendpkt(inm, report_type);
453 inm->inm_state = IGMP_DELAYING_MEMBER;
454 inm->inm_timer = IGMP_RANDOM_DELAY(
455 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
456 igmp_timers_are_running = 1;
457 } else
458 inm->inm_timer = 0;
459 splx(s);
460 return 0;
463 void
464 igmp_leavegroup(struct in_multi *inm)
467 switch (inm->inm_state) {
468 case IGMP_DELAYING_MEMBER:
469 case IGMP_IDLE_MEMBER:
470 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
471 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0)
472 if (inm->inm_rti->rti_type != IGMP_v1_ROUTER)
473 igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
474 break;
475 case IGMP_LAZY_MEMBER:
476 case IGMP_AWAKENING_MEMBER:
477 case IGMP_SLEEPING_MEMBER:
478 break;
482 void
483 igmp_fasttimo(void)
485 struct in_multi *inm;
486 struct in_multistep step;
489 * Quick check to see if any work needs to be done, in order
490 * to minimize the overhead of fasttimo processing.
492 if (!igmp_timers_are_running)
493 return;
495 mutex_enter(softnet_lock);
496 KERNEL_LOCK(1, NULL);
498 igmp_timers_are_running = 0;
499 IN_FIRST_MULTI(step, inm);
500 while (inm != NULL) {
501 if (inm->inm_timer == 0) {
502 /* do nothing */
503 } else if (--inm->inm_timer == 0) {
504 if (inm->inm_state == IGMP_DELAYING_MEMBER) {
505 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
506 igmp_sendpkt(inm,
507 IGMP_v1_HOST_MEMBERSHIP_REPORT);
508 else
509 igmp_sendpkt(inm,
510 IGMP_v2_HOST_MEMBERSHIP_REPORT);
511 inm->inm_state = IGMP_IDLE_MEMBER;
513 } else {
514 igmp_timers_are_running = 1;
516 IN_NEXT_MULTI(step, inm);
519 KERNEL_UNLOCK_ONE(NULL);
520 mutex_exit(softnet_lock);
523 void
524 igmp_slowtimo(void)
526 struct router_info *rti;
528 mutex_enter(softnet_lock);
529 KERNEL_LOCK(1, NULL);
530 LIST_FOREACH(rti, &rti_head, rti_link) {
531 if (rti->rti_type == IGMP_v1_ROUTER &&
532 ++rti->rti_age >= IGMP_AGE_THRESHOLD) {
533 rti->rti_type = IGMP_v2_ROUTER;
536 KERNEL_UNLOCK_ONE(NULL);
537 mutex_exit(softnet_lock);
540 void
541 igmp_sendpkt(struct in_multi *inm, int type)
543 struct mbuf *m;
544 struct igmp *igmp;
545 struct ip *ip;
546 struct ip_moptions imo;
547 #ifdef MROUTING
548 extern struct socket *ip_mrouter;
549 #endif /* MROUTING */
551 MGETHDR(m, M_DONTWAIT, MT_HEADER);
552 if (m == NULL)
553 return;
555 * Assume max_linkhdr + sizeof(struct ip) + IGMP_MINLEN
556 * is smaller than mbuf size returned by MGETHDR.
558 m->m_data += max_linkhdr;
559 m->m_len = sizeof(struct ip) + IGMP_MINLEN;
560 m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
562 ip = mtod(m, struct ip *);
563 ip->ip_tos = 0;
564 ip->ip_len = htons(sizeof(struct ip) + IGMP_MINLEN);
565 ip->ip_off = htons(0);
566 ip->ip_p = IPPROTO_IGMP;
567 ip->ip_src = zeroin_addr;
568 ip->ip_dst = inm->inm_addr;
570 m->m_data += sizeof(struct ip);
571 m->m_len -= sizeof(struct ip);
572 igmp = mtod(m, struct igmp *);
573 igmp->igmp_type = type;
574 igmp->igmp_code = 0;
575 igmp->igmp_group = inm->inm_addr;
576 igmp->igmp_cksum = 0;
577 igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
578 m->m_data -= sizeof(struct ip);
579 m->m_len += sizeof(struct ip);
581 imo.imo_multicast_ifp = inm->inm_ifp;
582 imo.imo_multicast_ttl = 1;
583 #ifdef RSVP_ISI
584 imo.imo_multicast_vif = -1;
585 #endif
587 * Request loopback of the report if we are acting as a multicast
588 * router, so that the process-level routing demon can hear it.
590 #ifdef MROUTING
591 imo.imo_multicast_loop = (ip_mrouter != NULL);
592 #else
593 imo.imo_multicast_loop = 0;
594 #endif /* MROUTING */
596 ip_output(m, NULL, NULL, IP_MULTICASTOPTS, &imo, NULL);
598 IGMP_STATINC(IGMP_STAT_SND_REPORTS);
601 void
602 igmp_purgeif(struct ifnet *ifp) /* MUST be called at splsoftnet() */
604 rti_delete(ifp); /* manipulates pools */
607 static int
608 sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS)
611 return (NETSTAT_SYSCTL(igmpstat_percpu, IGMP_NSTATS));
614 static void
615 sysctl_net_inet_igmp_setup(struct sysctllog **clog)
618 sysctl_createv(clog, 0, NULL, NULL,
619 CTLFLAG_PERMANENT,
620 CTLTYPE_NODE, "net", NULL,
621 NULL, 0, NULL, 0,
622 CTL_NET, CTL_EOL);
623 sysctl_createv(clog, 0, NULL, NULL,
624 CTLFLAG_PERMANENT,
625 CTLTYPE_NODE, "inet", NULL,
626 NULL, 0, NULL, 0,
627 CTL_NET, PF_INET, CTL_EOL);
628 sysctl_createv(clog, 0, NULL, NULL,
629 CTLFLAG_PERMANENT,
630 CTLTYPE_NODE, "igmp",
631 SYSCTL_DESCR("Internet Group Management Protocol"),
632 NULL, 0, NULL, 0,
633 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_EOL);
635 sysctl_createv(clog, 0, NULL, NULL,
636 CTLFLAG_PERMANENT,
637 CTLTYPE_STRUCT, "stats",
638 SYSCTL_DESCR("IGMP statistics"),
639 sysctl_net_inet_igmp_stats, 0, NULL, 0,
640 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_CREATE, CTL_EOL);