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$ |
---|
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 | * Modified by Bill Fenner, Xerox PARC, Feb 1995. |
---|
47 | * Modified to fully comply to IGMPv2 by Bill Fenner, Oct 1995. |
---|
48 | * |
---|
49 | * MULTICAST Revision: 3.5.1.4 |
---|
50 | */ |
---|
51 | |
---|
52 | #include <sys/param.h> |
---|
53 | #include <sys/systm.h> |
---|
54 | #include <sys/mbuf.h> |
---|
55 | #include <sys/socket.h> |
---|
56 | #include <sys/protosw.h> |
---|
57 | #include <sys/kernel.h> |
---|
58 | #include <sys/sysctl.h> |
---|
59 | |
---|
60 | #include <net/if.h> |
---|
61 | #include <net/route.h> |
---|
62 | |
---|
63 | #include <netinet/in.h> |
---|
64 | #include <netinet/in_var.h> |
---|
65 | #include <netinet/in_systm.h> |
---|
66 | #include <netinet/ip.h> |
---|
67 | #include <netinet/ip_var.h> |
---|
68 | #include <netinet/igmp.h> |
---|
69 | #include <netinet/igmp_var.h> |
---|
70 | |
---|
71 | static struct router_info * |
---|
72 | find_rti __P((struct ifnet *ifp)); |
---|
73 | |
---|
74 | static struct igmpstat igmpstat; |
---|
75 | |
---|
76 | SYSCTL_STRUCT(_net_inet_igmp, IGMPCTL_STATS, stats, CTLFLAG_RD, |
---|
77 | &igmpstat, igmpstat, ""); |
---|
78 | |
---|
79 | static int igmp_timers_are_running; |
---|
80 | static u_long igmp_all_hosts_group; |
---|
81 | static u_long igmp_all_rtrs_group; |
---|
82 | static struct mbuf *router_alert; |
---|
83 | static struct router_info *Head; |
---|
84 | |
---|
85 | static void igmp_sendpkt(struct in_multi *, int, unsigned long); |
---|
86 | |
---|
87 | void |
---|
88 | igmp_init() |
---|
89 | { |
---|
90 | struct ipoption *ra; |
---|
91 | |
---|
92 | /* |
---|
93 | * To avoid byte-swapping the same value over and over again. |
---|
94 | */ |
---|
95 | igmp_all_hosts_group = htonl(INADDR_ALLHOSTS_GROUP); |
---|
96 | igmp_all_rtrs_group = htonl(INADDR_ALLRTRS_GROUP); |
---|
97 | |
---|
98 | igmp_timers_are_running = 0; |
---|
99 | |
---|
100 | /* |
---|
101 | * Construct a Router Alert option to use in outgoing packets |
---|
102 | */ |
---|
103 | MGET(router_alert, M_DONTWAIT, MT_DATA); |
---|
104 | ra = mtod(router_alert, struct ipoption *); |
---|
105 | ra->ipopt_dst.s_addr = 0; |
---|
106 | ra->ipopt_list[0] = IPOPT_RA; /* Router Alert Option */ |
---|
107 | ra->ipopt_list[1] = 0x04; /* 4 bytes long */ |
---|
108 | ra->ipopt_list[2] = 0x00; |
---|
109 | ra->ipopt_list[3] = 0x00; |
---|
110 | router_alert->m_len = sizeof(ra->ipopt_dst) + ra->ipopt_list[1]; |
---|
111 | |
---|
112 | Head = (struct router_info *) 0; |
---|
113 | } |
---|
114 | |
---|
115 | static struct router_info * |
---|
116 | find_rti(ifp) |
---|
117 | struct ifnet *ifp; |
---|
118 | { |
---|
119 | register struct router_info *rti = Head; |
---|
120 | |
---|
121 | #ifdef IGMP_DEBUG |
---|
122 | printf("[igmp.c, _find_rti] --> entering \n"); |
---|
123 | #endif |
---|
124 | while (rti) { |
---|
125 | if (rti->rti_ifp == ifp) { |
---|
126 | #ifdef IGMP_DEBUG |
---|
127 | printf("[igmp.c, _find_rti] --> found old entry \n"); |
---|
128 | #endif |
---|
129 | return rti; |
---|
130 | } |
---|
131 | rti = rti->rti_next; |
---|
132 | } |
---|
133 | MALLOC(rti, struct router_info *, sizeof *rti, M_MRTABLE, M_NOWAIT); |
---|
134 | rti->rti_ifp = ifp; |
---|
135 | rti->rti_type = IGMP_V2_ROUTER; |
---|
136 | rti->rti_time = 0; |
---|
137 | rti->rti_next = Head; |
---|
138 | Head = rti; |
---|
139 | #ifdef IGMP_DEBUG |
---|
140 | printf("[igmp.c, _find_rti] --> created an entry \n"); |
---|
141 | #endif |
---|
142 | return rti; |
---|
143 | } |
---|
144 | |
---|
145 | void |
---|
146 | igmp_input(m, iphlen) |
---|
147 | register struct mbuf *m; |
---|
148 | register int iphlen; |
---|
149 | { |
---|
150 | register struct igmp *igmp; |
---|
151 | register struct ip *ip; |
---|
152 | register int igmplen; |
---|
153 | register struct ifnet *ifp = m->m_pkthdr.rcvif; |
---|
154 | register int minlen; |
---|
155 | register struct in_multi *inm; |
---|
156 | register struct in_ifaddr *ia; |
---|
157 | struct in_multistep step; |
---|
158 | struct router_info *rti; |
---|
159 | |
---|
160 | int timer; /** timer value in the igmp query header **/ |
---|
161 | |
---|
162 | ++igmpstat.igps_rcv_total; |
---|
163 | |
---|
164 | ip = mtod(m, struct ip *); |
---|
165 | igmplen = ip->ip_len; |
---|
166 | |
---|
167 | /* |
---|
168 | * Validate lengths |
---|
169 | */ |
---|
170 | if (igmplen < IGMP_MINLEN) { |
---|
171 | ++igmpstat.igps_rcv_tooshort; |
---|
172 | m_freem(m); |
---|
173 | return; |
---|
174 | } |
---|
175 | minlen = iphlen + IGMP_MINLEN; |
---|
176 | if ((m->m_flags & M_EXT || m->m_len < minlen) && |
---|
177 | (m = m_pullup(m, minlen)) == 0) { |
---|
178 | ++igmpstat.igps_rcv_tooshort; |
---|
179 | return; |
---|
180 | } |
---|
181 | |
---|
182 | /* |
---|
183 | * Validate checksum |
---|
184 | */ |
---|
185 | m->m_data += iphlen; |
---|
186 | m->m_len -= iphlen; |
---|
187 | igmp = mtod(m, struct igmp *); |
---|
188 | if (in_cksum(m, igmplen)) { |
---|
189 | ++igmpstat.igps_rcv_badsum; |
---|
190 | m_freem(m); |
---|
191 | return; |
---|
192 | } |
---|
193 | m->m_data -= iphlen; |
---|
194 | m->m_len += iphlen; |
---|
195 | |
---|
196 | ip = mtod(m, struct ip *); |
---|
197 | timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE; |
---|
198 | rti = find_rti(ifp); |
---|
199 | |
---|
200 | /* |
---|
201 | * In the IGMPv2 specification, there are 3 states and a flag. |
---|
202 | * |
---|
203 | * In Non-Member state, we simply don't have a membership record. |
---|
204 | * In Delaying Member state, our timer is running (inm->inm_timer) |
---|
205 | * In Idle Member state, our timer is not running (inm->inm_timer==0) |
---|
206 | * |
---|
207 | * The flag is inm->inm_state, it is set to IGMP_OTHERMEMBER if |
---|
208 | * we have heard a report from another member, or IGMP_IREPORTEDLAST |
---|
209 | * if I sent the last report. |
---|
210 | */ |
---|
211 | switch (igmp->igmp_type) { |
---|
212 | |
---|
213 | case IGMP_MEMBERSHIP_QUERY: |
---|
214 | ++igmpstat.igps_rcv_queries; |
---|
215 | |
---|
216 | if (ifp->if_flags & IFF_LOOPBACK) |
---|
217 | break; |
---|
218 | |
---|
219 | if (igmp->igmp_code == 0) { |
---|
220 | /* |
---|
221 | * Old router. Remember that the querier on this |
---|
222 | * interface is old, and set the timer to the |
---|
223 | * value in RFC 1112. |
---|
224 | */ |
---|
225 | |
---|
226 | rti->rti_type = IGMP_V1_ROUTER; |
---|
227 | rti->rti_time = 0; |
---|
228 | |
---|
229 | timer = IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ; |
---|
230 | |
---|
231 | if (ip->ip_dst.s_addr != igmp_all_hosts_group || |
---|
232 | igmp->igmp_group.s_addr != 0) { |
---|
233 | ++igmpstat.igps_rcv_badqueries; |
---|
234 | m_freem(m); |
---|
235 | return; |
---|
236 | } |
---|
237 | } else { |
---|
238 | /* |
---|
239 | * New router. Simply do the new validity check. |
---|
240 | */ |
---|
241 | |
---|
242 | if (igmp->igmp_group.s_addr != 0 && |
---|
243 | !IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) { |
---|
244 | ++igmpstat.igps_rcv_badqueries; |
---|
245 | m_freem(m); |
---|
246 | return; |
---|
247 | } |
---|
248 | } |
---|
249 | |
---|
250 | /* |
---|
251 | * - Start the timers in all of our membership records |
---|
252 | * that the query applies to for the interface on |
---|
253 | * which the query arrived excl. those that belong |
---|
254 | * to the "all-hosts" group (224.0.0.1). |
---|
255 | * - Restart any timer that is already running but has |
---|
256 | * a value longer than the requested timeout. |
---|
257 | * - Use the value specified in the query message as |
---|
258 | * the maximum timeout. |
---|
259 | */ |
---|
260 | IN_FIRST_MULTI(step, inm); |
---|
261 | while (inm != NULL) { |
---|
262 | if (inm->inm_ifp == ifp && |
---|
263 | inm->inm_addr.s_addr != igmp_all_hosts_group && |
---|
264 | (igmp->igmp_group.s_addr == 0 || |
---|
265 | igmp->igmp_group.s_addr == inm->inm_addr.s_addr)) { |
---|
266 | if (inm->inm_timer == 0 || |
---|
267 | inm->inm_timer > timer) { |
---|
268 | inm->inm_timer = |
---|
269 | IGMP_RANDOM_DELAY(timer); |
---|
270 | igmp_timers_are_running = 1; |
---|
271 | } |
---|
272 | } |
---|
273 | IN_NEXT_MULTI(step, inm); |
---|
274 | } |
---|
275 | |
---|
276 | break; |
---|
277 | |
---|
278 | case IGMP_V1_MEMBERSHIP_REPORT: |
---|
279 | case IGMP_V2_MEMBERSHIP_REPORT: |
---|
280 | /* |
---|
281 | * For fast leave to work, we have to know that we are the |
---|
282 | * last person to send a report for this group. Reports |
---|
283 | * can potentially get looped back if we are a multicast |
---|
284 | * router, so discard reports sourced by me. |
---|
285 | */ |
---|
286 | IFP_TO_IA(ifp, ia); |
---|
287 | if (ia && ip->ip_src.s_addr == IA_SIN(ia)->sin_addr.s_addr) |
---|
288 | break; |
---|
289 | |
---|
290 | ++igmpstat.igps_rcv_reports; |
---|
291 | |
---|
292 | if (ifp->if_flags & IFF_LOOPBACK) |
---|
293 | break; |
---|
294 | |
---|
295 | if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) { |
---|
296 | ++igmpstat.igps_rcv_badreports; |
---|
297 | m_freem(m); |
---|
298 | return; |
---|
299 | } |
---|
300 | |
---|
301 | /* |
---|
302 | * KLUDGE: if the IP source address of the report has an |
---|
303 | * unspecified (i.e., zero) subnet number, as is allowed for |
---|
304 | * a booting host, replace it with the correct subnet number |
---|
305 | * so that a process-level multicast routing demon can |
---|
306 | * determine which subnet it arrived from. This is necessary |
---|
307 | * to compensate for the lack of any way for a process to |
---|
308 | * determine the arrival interface of an incoming packet. |
---|
309 | */ |
---|
310 | if ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0) |
---|
311 | if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet); |
---|
312 | |
---|
313 | /* |
---|
314 | * If we belong to the group being reported, stop |
---|
315 | * our timer for that group. |
---|
316 | */ |
---|
317 | IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm); |
---|
318 | |
---|
319 | if (inm != NULL) { |
---|
320 | inm->inm_timer = 0; |
---|
321 | ++igmpstat.igps_rcv_ourreports; |
---|
322 | |
---|
323 | inm->inm_state = IGMP_OTHERMEMBER; |
---|
324 | } |
---|
325 | |
---|
326 | break; |
---|
327 | } |
---|
328 | |
---|
329 | /* |
---|
330 | * Pass all valid IGMP packets up to any process(es) listening |
---|
331 | * on a raw IGMP socket. |
---|
332 | */ |
---|
333 | rip_input(m, iphlen); |
---|
334 | } |
---|
335 | |
---|
336 | void |
---|
337 | igmp_joingroup(inm) |
---|
338 | struct in_multi *inm; |
---|
339 | { |
---|
340 | int s = splnet(); |
---|
341 | |
---|
342 | if (inm->inm_addr.s_addr == igmp_all_hosts_group |
---|
343 | || inm->inm_ifp->if_flags & IFF_LOOPBACK) { |
---|
344 | inm->inm_timer = 0; |
---|
345 | inm->inm_state = IGMP_OTHERMEMBER; |
---|
346 | } else { |
---|
347 | inm->inm_rti = find_rti(inm->inm_ifp); |
---|
348 | igmp_sendpkt(inm, inm->inm_rti->rti_type, 0); |
---|
349 | inm->inm_timer = IGMP_RANDOM_DELAY( |
---|
350 | IGMP_MAX_HOST_REPORT_DELAY*PR_FASTHZ); |
---|
351 | inm->inm_state = IGMP_IREPORTEDLAST; |
---|
352 | igmp_timers_are_running = 1; |
---|
353 | } |
---|
354 | splx(s); |
---|
355 | } |
---|
356 | |
---|
357 | void |
---|
358 | igmp_leavegroup(inm) |
---|
359 | struct in_multi *inm; |
---|
360 | { |
---|
361 | if (inm->inm_state == IGMP_IREPORTEDLAST && |
---|
362 | inm->inm_addr.s_addr != igmp_all_hosts_group && |
---|
363 | !(inm->inm_ifp->if_flags & IFF_LOOPBACK) && |
---|
364 | inm->inm_rti->rti_type != IGMP_V1_ROUTER) |
---|
365 | igmp_sendpkt(inm, IGMP_V2_LEAVE_GROUP, igmp_all_rtrs_group); |
---|
366 | } |
---|
367 | |
---|
368 | void |
---|
369 | igmp_fasttimo() |
---|
370 | { |
---|
371 | register struct in_multi *inm; |
---|
372 | struct in_multistep step; |
---|
373 | int s; |
---|
374 | |
---|
375 | /* |
---|
376 | * Quick check to see if any work needs to be done, in order |
---|
377 | * to minimize the overhead of fasttimo processing. |
---|
378 | */ |
---|
379 | |
---|
380 | if (!igmp_timers_are_running) |
---|
381 | return; |
---|
382 | |
---|
383 | s = splnet(); |
---|
384 | igmp_timers_are_running = 0; |
---|
385 | IN_FIRST_MULTI(step, inm); |
---|
386 | while (inm != NULL) { |
---|
387 | if (inm->inm_timer == 0) { |
---|
388 | /* do nothing */ |
---|
389 | } else if (--inm->inm_timer == 0) { |
---|
390 | igmp_sendpkt(inm, inm->inm_rti->rti_type, 0); |
---|
391 | inm->inm_state = IGMP_IREPORTEDLAST; |
---|
392 | } else { |
---|
393 | igmp_timers_are_running = 1; |
---|
394 | } |
---|
395 | IN_NEXT_MULTI(step, inm); |
---|
396 | } |
---|
397 | splx(s); |
---|
398 | } |
---|
399 | |
---|
400 | void |
---|
401 | igmp_slowtimo() |
---|
402 | { |
---|
403 | int s = splnet(); |
---|
404 | register struct router_info *rti = Head; |
---|
405 | |
---|
406 | #ifdef IGMP_DEBUG |
---|
407 | printf("[igmp.c,_slowtimo] -- > entering \n"); |
---|
408 | #endif |
---|
409 | while (rti) { |
---|
410 | if (rti->rti_type == IGMP_V1_ROUTER) { |
---|
411 | rti->rti_time++; |
---|
412 | if (rti->rti_time >= IGMP_AGE_THRESHOLD) { |
---|
413 | rti->rti_type = IGMP_V2_ROUTER; |
---|
414 | } |
---|
415 | } |
---|
416 | rti = rti->rti_next; |
---|
417 | } |
---|
418 | #ifdef IGMP_DEBUG |
---|
419 | printf("[igmp.c,_slowtimo] -- > exiting \n"); |
---|
420 | #endif |
---|
421 | splx(s); |
---|
422 | } |
---|
423 | |
---|
424 | static struct route igmprt; |
---|
425 | |
---|
426 | static void |
---|
427 | igmp_sendpkt(inm, type, addr) |
---|
428 | struct in_multi *inm; |
---|
429 | int type; |
---|
430 | unsigned long addr; |
---|
431 | { |
---|
432 | struct mbuf *m; |
---|
433 | struct igmp *igmp; |
---|
434 | struct ip *ip; |
---|
435 | struct ip_moptions imo; |
---|
436 | |
---|
437 | MGETHDR(m, M_DONTWAIT, MT_HEADER); |
---|
438 | if (m == NULL) |
---|
439 | return; |
---|
440 | |
---|
441 | m->m_pkthdr.rcvif = loif; |
---|
442 | m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN; |
---|
443 | MH_ALIGN(m, IGMP_MINLEN + sizeof(struct ip)); |
---|
444 | m->m_data += sizeof(struct ip); |
---|
445 | m->m_len = IGMP_MINLEN; |
---|
446 | igmp = mtod(m, struct igmp *); |
---|
447 | igmp->igmp_type = type; |
---|
448 | igmp->igmp_code = 0; |
---|
449 | igmp->igmp_group = inm->inm_addr; |
---|
450 | igmp->igmp_cksum = 0; |
---|
451 | igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN); |
---|
452 | |
---|
453 | m->m_data -= sizeof(struct ip); |
---|
454 | m->m_len += sizeof(struct ip); |
---|
455 | ip = mtod(m, struct ip *); |
---|
456 | ip->ip_tos = 0; |
---|
457 | ip->ip_len = sizeof(struct ip) + IGMP_MINLEN; |
---|
458 | ip->ip_off = 0; |
---|
459 | ip->ip_p = IPPROTO_IGMP; |
---|
460 | ip->ip_src.s_addr = INADDR_ANY; |
---|
461 | ip->ip_dst.s_addr = addr ? addr : igmp->igmp_group.s_addr; |
---|
462 | |
---|
463 | imo.imo_multicast_ifp = inm->inm_ifp; |
---|
464 | imo.imo_multicast_ttl = 1; |
---|
465 | imo.imo_multicast_vif = -1; |
---|
466 | /* |
---|
467 | * Request loopback of the report if we are acting as a multicast |
---|
468 | * router, so that the process-level routing demon can hear it. |
---|
469 | */ |
---|
470 | imo.imo_multicast_loop = (ip_mrouter != NULL); |
---|
471 | |
---|
472 | /* |
---|
473 | * XXX |
---|
474 | * Do we have to worry about reentrancy here? Don't think so. |
---|
475 | */ |
---|
476 | ip_output(m, router_alert, &igmprt, 0, &imo); |
---|
477 | |
---|
478 | ++igmpstat.igps_snd_reports; |
---|
479 | } |
---|