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 | */ |
72 | typedef 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 | */ |
87 | static struct pool igmp_rti_pool __cacheline_aligned; |
88 | static LIST_HEAD(, router_info) rti_head __cacheline_aligned; |
89 | static int igmp_timers_on __cacheline_aligned; |
90 | static percpu_t * igmpstat_percpu __read_mostly; |
91 | |
92 | #define IGMP_STATINC(x) _NET_STATINC(igmpstat_percpu, x) |
93 | |
94 | static void igmp_sendpkt(struct in_multi *, int); |
95 | static int rti_fill(struct in_multi *); |
96 | static router_info_t * rti_find(struct ifnet *); |
97 | static void rti_delete(struct ifnet *); |
98 | static 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 | */ |
104 | static int |
105 | rti_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 | */ |
133 | static router_info_t * |
134 | rti_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 | */ |
158 | static void |
159 | rti_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 | |
174 | void |
175 | igmp_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 | |
184 | void |
185 | igmp_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 | |
480 | drop: |
481 | m_put_rcvif_psref(ifp, &psref); |
482 | m_freem(m); |
483 | return; |
484 | } |
485 | |
486 | int |
487 | igmp_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 | |
511 | void |
512 | igmp_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 | |
531 | void |
532 | igmp_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 | |
573 | void |
574 | igmp_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 | */ |
592 | static void |
593 | igmp_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 | |
658 | void |
659 | igmp_purgeif(ifnet_t *ifp) |
660 | { |
661 | in_multi_lock(RW_WRITER); |
662 | rti_delete(ifp); |
663 | in_multi_unlock(); |
664 | } |
665 | |
666 | static int |
667 | sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS) |
668 | { |
669 | return NETSTAT_SYSCTL(igmpstat_percpu, IGMP_NSTATS); |
670 | } |
671 | |
672 | static void |
673 | sysctl_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 | |