FreeBSD/Linux Kernel Cross Reference
sys/netinet/igmp.c
1 /* $NetBSD: igmp.c,v 1.49 2008/05/04 07:22:14 thorpej 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.49 2008/05/04 07:22:14 thorpej Exp $");
44
45 #include "opt_mrouting.h"
46
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>
54
55 #include <net/if.h>
56 #include <net/route.h>
57 #include <net/net_stats.h>
58
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>
66
67 #include <machine/stdarg.h>
68
69 #define IP_MULTICASTOPTS 0
70
71 POOL_INIT(igmp_rti_pool, sizeof(struct router_info), 0, 0, 0, "igmppl", NULL,
72 IPL_SOFTNET);
73
74 static percpu_t *igmpstat_percpu;
75
76 #define IGMP_STATINC(x) _NET_STATINC(igmpstat_percpu, x)
77
78 int igmp_timers_are_running;
79 static LIST_HEAD(, router_info) rti_head = LIST_HEAD_INITIALIZER(rti_head);
80
81 void igmp_sendpkt(struct in_multi *, int);
82 static int rti_fill(struct in_multi *);
83 static struct router_info *rti_find(struct ifnet *);
84 static void rti_delete(struct ifnet *);
85
86 static int
87 rti_fill(struct in_multi *inm)
88 {
89 struct router_info *rti;
90
91 /* this function is called at splsoftnet() */
92 LIST_FOREACH(rti, &rti_head, rti_link) {
93 if (rti->rti_ifp == inm->inm_ifp) {
94 inm->inm_rti = rti;
95 if (rti->rti_type == IGMP_v1_ROUTER)
96 return (IGMP_v1_HOST_MEMBERSHIP_REPORT);
97 else
98 return (IGMP_v2_HOST_MEMBERSHIP_REPORT);
99 }
100 }
101
102 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
103 if (rti == NULL)
104 return 0;
105 rti->rti_ifp = inm->inm_ifp;
106 rti->rti_type = IGMP_v2_ROUTER;
107 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
108 inm->inm_rti = rti;
109 return (IGMP_v2_HOST_MEMBERSHIP_REPORT);
110 }
111
112 static struct router_info *
113 rti_find(struct ifnet *ifp)
114 {
115 struct router_info *rti;
116 int s = splsoftnet();
117
118 LIST_FOREACH(rti, &rti_head, rti_link) {
119 if (rti->rti_ifp == ifp)
120 return (rti);
121 }
122
123 rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
124 if (rti == NULL) {
125 splx(s);
126 return NULL;
127 }
128 rti->rti_ifp = ifp;
129 rti->rti_type = IGMP_v2_ROUTER;
130 LIST_INSERT_HEAD(&rti_head, rti, rti_link);
131 splx(s);
132 return (rti);
133 }
134
135 static void
136 rti_delete(struct ifnet *ifp) /* MUST be called at splsoftnet */
137 {
138 struct router_info *rti;
139
140 LIST_FOREACH(rti, &rti_head, rti_link) {
141 if (rti->rti_ifp == ifp) {
142 LIST_REMOVE(rti, rti_link);
143 pool_put(&igmp_rti_pool, rti);
144 return;
145 }
146 }
147 }
148
149 void
150 igmp_init(void)
151 {
152
153 igmpstat_percpu = percpu_alloc(sizeof(uint64_t) * IGMP_NSTATS);
154 }
155
156 void
157 igmp_input(struct mbuf *m, ...)
158 {
159 int proto;
160 int iphlen;
161 struct ifnet *ifp = m->m_pkthdr.rcvif;
162 struct ip *ip = mtod(m, struct ip *);
163 struct igmp *igmp;
164 u_int minlen;
165 struct in_multi *inm;
166 struct in_multistep step;
167 struct router_info *rti;
168 struct in_ifaddr *ia;
169 u_int timer;
170 va_list ap;
171 u_int16_t ip_len;
172
173 va_start(ap, m);
174 iphlen = va_arg(ap, int);
175 proto = va_arg(ap, int);
176 va_end(ap);
177
178 IGMP_STATINC(IGMP_STAT_RCV_TOTAL);
179
180 /*
181 * Validate lengths
182 */
183 minlen = iphlen + IGMP_MINLEN;
184 ip_len = ntohs(ip->ip_len);
185 if (ip_len < minlen) {
186 IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
187 m_freem(m);
188 return;
189 }
190 if (((m->m_flags & M_EXT) && (ip->ip_src.s_addr & IN_CLASSA_NET) == 0)
191 || m->m_len < minlen) {
192 if ((m = m_pullup(m, minlen)) == 0) {
193 IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
194 return;
195 }
196 ip = mtod(m, struct ip *);
197 }
198
199 /*
200 * Validate checksum
201 */
202 m->m_data += iphlen;
203 m->m_len -= iphlen;
204 igmp = mtod(m, struct igmp *);
205 /* No need to assert alignment here. */
206 if (in_cksum(m, ip_len - iphlen)) {
207 IGMP_STATINC(IGMP_STAT_RCV_BADSUM);
208 m_freem(m);
209 return;
210 }
211 m->m_data -= iphlen;
212 m->m_len += iphlen;
213
214 switch (igmp->igmp_type) {
215
216 case IGMP_HOST_MEMBERSHIP_QUERY:
217 IGMP_STATINC(IGMP_STAT_RCV_QUERIES);
218
219 if (ifp->if_flags & IFF_LOOPBACK)
220 break;
221
222 if (igmp->igmp_code == 0) {
223 rti = rti_find(ifp);
224 if (rti == NULL)
225 break;
226 rti->rti_type = IGMP_v1_ROUTER;
227 rti->rti_age = 0;
228
229 if (ip->ip_dst.s_addr != INADDR_ALLHOSTS_GROUP) {
230 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
231 m_freem(m);
232 return;
233 }
234
235 /*
236 * Start the timers in all of our membership records
237 * for the interface on which the query arrived,
238 * except those that are already running and those
239 * that belong to a "local" group (224.0.0.X).
240 */
241 IN_FIRST_MULTI(step, inm);
242 while (inm != NULL) {
243 if (inm->inm_ifp == ifp &&
244 inm->inm_timer == 0 &&
245 !IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
246 inm->inm_state = IGMP_DELAYING_MEMBER;
247 inm->inm_timer = IGMP_RANDOM_DELAY(
248 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
249 igmp_timers_are_running = 1;
250 }
251 IN_NEXT_MULTI(step, inm);
252 }
253 } else {
254 if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
255 IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
256 m_freem(m);
257 return;
258 }
259
260 timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
261 if (timer == 0)
262 timer =1;
263
264 /*
265 * Start the timers in all of our membership records
266 * for the interface on which the query arrived,
267 * except those that are already running and those
268 * that belong to a "local" group (224.0.0.X). For
269 * timers already running, check if they need to be
270 * reset.
271 */
272 IN_FIRST_MULTI(step, inm);
273 while (inm != NULL) {
274 if (inm->inm_ifp == ifp &&
275 !IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
276 (ip->ip_dst.s_addr == INADDR_ALLHOSTS_GROUP ||
277 in_hosteq(ip->ip_dst, inm->inm_addr))) {
278 switch (inm->inm_state) {
279 case IGMP_DELAYING_MEMBER:
280 if (inm->inm_timer <= timer)
281 break;
282 /* FALLTHROUGH */
283 case IGMP_IDLE_MEMBER:
284 case IGMP_LAZY_MEMBER:
285 case IGMP_AWAKENING_MEMBER:
286 inm->inm_state =
287 IGMP_DELAYING_MEMBER;
288 inm->inm_timer =
289 IGMP_RANDOM_DELAY(timer);
290 igmp_timers_are_running = 1;
291 break;
292 case IGMP_SLEEPING_MEMBER:
293 inm->inm_state =
294 IGMP_AWAKENING_MEMBER;
295 break;
296 }
297 }
298 IN_NEXT_MULTI(step, inm);
299 }
300 }
301
302 break;
303
304 case IGMP_v1_HOST_MEMBERSHIP_REPORT:
305 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
306
307 if (ifp->if_flags & IFF_LOOPBACK)
308 break;
309
310 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
311 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
312 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
313 m_freem(m);
314 return;
315 }
316
317 /*
318 * KLUDGE: if the IP source address of the report has an
319 * unspecified (i.e., zero) subnet number, as is allowed for
320 * a booting host, replace it with the correct subnet number
321 * so that a process-level multicast routing daemon can
322 * determine which subnet it arrived from. This is necessary
323 * to compensate for the lack of any way for a process to
324 * determine the arrival interface of an incoming packet.
325 */
326 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
327 IFP_TO_IA(ifp, ia); /* XXX */
328 if (ia)
329 ip->ip_src.s_addr = ia->ia_subnet;
330 }
331
332 /*
333 * If we belong to the group being reported, stop
334 * our timer for that group.
335 */
336 IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
337 if (inm != NULL) {
338 inm->inm_timer = 0;
339 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
340
341 switch (inm->inm_state) {
342 case IGMP_IDLE_MEMBER:
343 case IGMP_LAZY_MEMBER:
344 case IGMP_AWAKENING_MEMBER:
345 case IGMP_SLEEPING_MEMBER:
346 inm->inm_state = IGMP_SLEEPING_MEMBER;
347 break;
348 case IGMP_DELAYING_MEMBER:
349 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
350 inm->inm_state = IGMP_LAZY_MEMBER;
351 else
352 inm->inm_state = IGMP_SLEEPING_MEMBER;
353 break;
354 }
355 }
356
357 break;
358
359 case IGMP_v2_HOST_MEMBERSHIP_REPORT:
360 #ifdef MROUTING
361 /*
362 * Make sure we don't hear our own membership report. Fast
363 * leave requires knowing that we are the only member of a
364 * group.
365 */
366 IFP_TO_IA(ifp, ia); /* XXX */
367 if (ia && in_hosteq(ip->ip_src, ia->ia_addr.sin_addr))
368 break;
369 #endif
370
371 IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
372
373 if (ifp->if_flags & IFF_LOOPBACK)
374 break;
375
376 if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
377 !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
378 IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
379 m_freem(m);
380 return;
381 }
382
383 /*
384 * KLUDGE: if the IP source address of the report has an
385 * unspecified (i.e., zero) subnet number, as is allowed for
386 * a booting host, replace it with the correct subnet number
387 * so that a process-level multicast routing daemon can
388 * determine which subnet it arrived from. This is necessary
389 * to compensate for the lack of any way for a process to
390 * determine the arrival interface of an incoming packet.
391 */
392 if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
393 #ifndef MROUTING
394 IFP_TO_IA(ifp, ia); /* XXX */
395 #endif
396 if (ia)
397 ip->ip_src.s_addr = ia->ia_subnet;
398 }
399
400 /*
401 * If we belong to the group being reported, stop
402 * our timer for that group.
403 */
404 IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
405 if (inm != NULL) {
406 inm->inm_timer = 0;
407 IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
408
409 switch (inm->inm_state) {
410 case IGMP_DELAYING_MEMBER:
411 case IGMP_IDLE_MEMBER:
412 case IGMP_AWAKENING_MEMBER:
413 inm->inm_state = IGMP_LAZY_MEMBER;
414 break;
415 case IGMP_LAZY_MEMBER:
416 case IGMP_SLEEPING_MEMBER:
417 break;
418 }
419 }
420
421 break;
422
423 }
424
425 /*
426 * Pass all valid IGMP packets up to any process(es) listening
427 * on a raw IGMP socket.
428 */
429 rip_input(m, iphlen, proto);
430 return;
431 }
432
433 int
434 igmp_joingroup(struct in_multi *inm)
435 {
436 int report_type;
437 int s = splsoftnet();
438
439 inm->inm_state = IGMP_IDLE_MEMBER;
440
441 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
442 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0) {
443 report_type = rti_fill(inm);
444 if (report_type == 0) {
445 splx(s);
446 return ENOMEM;
447 }
448 igmp_sendpkt(inm, report_type);
449 inm->inm_state = IGMP_DELAYING_MEMBER;
450 inm->inm_timer = IGMP_RANDOM_DELAY(
451 IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
452 igmp_timers_are_running = 1;
453 } else
454 inm->inm_timer = 0;
455 splx(s);
456 return 0;
457 }
458
459 void
460 igmp_leavegroup(struct in_multi *inm)
461 {
462
463 switch (inm->inm_state) {
464 case IGMP_DELAYING_MEMBER:
465 case IGMP_IDLE_MEMBER:
466 if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
467 (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0)
468 if (inm->inm_rti->rti_type != IGMP_v1_ROUTER)
469 igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
470 break;
471 case IGMP_LAZY_MEMBER:
472 case IGMP_AWAKENING_MEMBER:
473 case IGMP_SLEEPING_MEMBER:
474 break;
475 }
476 }
477
478 void
479 igmp_fasttimo(void)
480 {
481 struct in_multi *inm;
482 struct in_multistep step;
483
484 /*
485 * Quick check to see if any work needs to be done, in order
486 * to minimize the overhead of fasttimo processing.
487 */
488 if (!igmp_timers_are_running)
489 return;
490
491 mutex_enter(softnet_lock);
492 KERNEL_LOCK(1, NULL);
493
494 igmp_timers_are_running = 0;
495 IN_FIRST_MULTI(step, inm);
496 while (inm != NULL) {
497 if (inm->inm_timer == 0) {
498 /* do nothing */
499 } else if (--inm->inm_timer == 0) {
500 if (inm->inm_state == IGMP_DELAYING_MEMBER) {
501 if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
502 igmp_sendpkt(inm,
503 IGMP_v1_HOST_MEMBERSHIP_REPORT);
504 else
505 igmp_sendpkt(inm,
506 IGMP_v2_HOST_MEMBERSHIP_REPORT);
507 inm->inm_state = IGMP_IDLE_MEMBER;
508 }
509 } else {
510 igmp_timers_are_running = 1;
511 }
512 IN_NEXT_MULTI(step, inm);
513 }
514
515 KERNEL_UNLOCK_ONE(NULL);
516 mutex_exit(softnet_lock);
517 }
518
519 void
520 igmp_slowtimo(void)
521 {
522 struct router_info *rti;
523
524 mutex_enter(softnet_lock);
525 KERNEL_LOCK(1, NULL);
526 LIST_FOREACH(rti, &rti_head, rti_link) {
527 if (rti->rti_type == IGMP_v1_ROUTER &&
528 ++rti->rti_age >= IGMP_AGE_THRESHOLD) {
529 rti->rti_type = IGMP_v2_ROUTER;
530 }
531 }
532 KERNEL_UNLOCK_ONE(NULL);
533 mutex_exit(softnet_lock);
534 }
535
536 void
537 igmp_sendpkt(struct in_multi *inm, int type)
538 {
539 struct mbuf *m;
540 struct igmp *igmp;
541 struct ip *ip;
542 struct ip_moptions imo;
543 #ifdef MROUTING
544 extern struct socket *ip_mrouter;
545 #endif /* MROUTING */
546
547 MGETHDR(m, M_DONTWAIT, MT_HEADER);
548 if (m == NULL)
549 return;
550 /*
551 * Assume max_linkhdr + sizeof(struct ip) + IGMP_MINLEN
552 * is smaller than mbuf size returned by MGETHDR.
553 */
554 m->m_data += max_linkhdr;
555 m->m_len = sizeof(struct ip) + IGMP_MINLEN;
556 m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
557
558 ip = mtod(m, struct ip *);
559 ip->ip_tos = 0;
560 ip->ip_len = htons(sizeof(struct ip) + IGMP_MINLEN);
561 ip->ip_off = htons(0);
562 ip->ip_p = IPPROTO_IGMP;
563 ip->ip_src = zeroin_addr;
564 ip->ip_dst = inm->inm_addr;
565
566 m->m_data += sizeof(struct ip);
567 m->m_len -= sizeof(struct ip);
568 igmp = mtod(m, struct igmp *);
569 igmp->igmp_type = type;
570 igmp->igmp_code = 0;
571 igmp->igmp_group = inm->inm_addr;
572 igmp->igmp_cksum = 0;
573 igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
574 m->m_data -= sizeof(struct ip);
575 m->m_len += sizeof(struct ip);
576
577 imo.imo_multicast_ifp = inm->inm_ifp;
578 imo.imo_multicast_ttl = 1;
579 #ifdef RSVP_ISI
580 imo.imo_multicast_vif = -1;
581 #endif
582 /*
583 * Request loopback of the report if we are acting as a multicast
584 * router, so that the process-level routing demon can hear it.
585 */
586 #ifdef MROUTING
587 imo.imo_multicast_loop = (ip_mrouter != NULL);
588 #else
589 imo.imo_multicast_loop = 0;
590 #endif /* MROUTING */
591
592 ip_output(m, NULL, NULL, IP_MULTICASTOPTS, &imo, NULL);
593
594 IGMP_STATINC(IGMP_STAT_SND_REPORTS);
595 }
596
597 void
598 igmp_purgeif(struct ifnet *ifp) /* MUST be called at splsoftnet() */
599 {
600 rti_delete(ifp); /* manipulates pools */
601 }
602
603 static int
604 sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS)
605 {
606
607 return (NETSTAT_SYSCTL(igmpstat_percpu, IGMP_NSTATS));
608 }
609
610 SYSCTL_SETUP(sysctl_net_inet_igmp_setup, "sysctl net.inet.igmp subtree setup")
611 {
612
613 sysctl_createv(clog, 0, NULL, NULL,
614 CTLFLAG_PERMANENT,
615 CTLTYPE_NODE, "net", NULL,
616 NULL, 0, NULL, 0,
617 CTL_NET, CTL_EOL);
618 sysctl_createv(clog, 0, NULL, NULL,
619 CTLFLAG_PERMANENT,
620 CTLTYPE_NODE, "inet", NULL,
621 NULL, 0, NULL, 0,
622 CTL_NET, PF_INET, CTL_EOL);
623 sysctl_createv(clog, 0, NULL, NULL,
624 CTLFLAG_PERMANENT,
625 CTLTYPE_NODE, "igmp",
626 SYSCTL_DESCR("Internet Group Management Protocol"),
627 NULL, 0, NULL, 0,
628 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_EOL);
629
630 sysctl_createv(clog, 0, NULL, NULL,
631 CTLFLAG_PERMANENT,
632 CTLTYPE_STRUCT, "stats",
633 SYSCTL_DESCR("IGMP statistics"),
634 sysctl_net_inet_igmp_stats, 0, NULL, 0,
635 CTL_NET, PF_INET, IPPROTO_IGMP, CTL_CREATE, CTL_EOL);
636 }
Cache object: cff6f9d2e49cf62ff6272449e30073a5
|