igmp.c revision 139823
1228753Smm/*-
2228753Smm * Copyright (c) 1988 Stephen Deering.
3228753Smm * Copyright (c) 1992, 1993
4228753Smm *	The Regents of the University of California.  All rights reserved.
5228753Smm *
6228753Smm * This code is derived from software contributed to Berkeley by
7228753Smm * Stephen Deering of Stanford University.
8228753Smm *
9228753Smm * Redistribution and use in source and binary forms, with or without
10228753Smm * modification, are permitted provided that the following conditions
11228753Smm * are met:
12228753Smm * 1. Redistributions of source code must retain the above copyright
13228753Smm *    notice, this list of conditions and the following disclaimer.
14228753Smm * 2. Redistributions in binary form must reproduce the above copyright
15228753Smm *    notice, this list of conditions and the following disclaimer in the
16228753Smm *    documentation and/or other materials provided with the distribution.
17228753Smm * 4. Neither the name of the University nor the names of its contributors
18228753Smm *    may be used to endorse or promote products derived from this software
19228753Smm *    without specific prior written permission.
20228753Smm *
21228753Smm * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22228753Smm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23228753Smm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24228753Smm * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25228753Smm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26228753Smm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27228763Smm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28228753Smm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29228753Smm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30228753Smm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31228753Smm * SUCH DAMAGE.
32228753Smm *
33228753Smm *	@(#)igmp.c	8.1 (Berkeley) 7/19/93
34228753Smm * $FreeBSD: head/sys/netinet/igmp.c 139823 2005-01-07 01:45:51Z imp $
35228753Smm */
36228753Smm
37228753Smm/*
38228753Smm * Internet Group Management Protocol (IGMP) routines.
39228753Smm *
40228753Smm * Written by Steve Deering, Stanford, May 1988.
41228753Smm * Modified by Rosen Sharma, Stanford, Aug 1994.
42228753Smm * Modified by Bill Fenner, Xerox PARC, Feb 1995.
43228753Smm * Modified to fully comply to IGMPv2 by Bill Fenner, Oct 1995.
44228753Smm *
45228753Smm * MULTICAST Revision: 3.5.1.4
46228753Smm */
47228753Smm
48228753Smm#include "opt_mac.h"
49228753Smm
50228753Smm#include <sys/param.h>
51228753Smm#include <sys/systm.h>
52228753Smm#include <sys/mac.h>
53228753Smm#include <sys/malloc.h>
54228753Smm#include <sys/mbuf.h>
55228753Smm#include <sys/socket.h>
56228753Smm#include <sys/protosw.h>
57228753Smm#include <sys/kernel.h>
58228753Smm#include <sys/sysctl.h>
59228753Smm
60228753Smm#include <net/if.h>
61228753Smm#include <net/route.h>
62228753Smm
63228753Smm#include <netinet/in.h>
64228753Smm#include <netinet/in_var.h>
65228753Smm#include <netinet/in_systm.h>
66228753Smm#include <netinet/ip.h>
67228753Smm#include <netinet/ip_var.h>
68228753Smm#include <netinet/igmp.h>
69228753Smm#include <netinet/igmp_var.h>
70228753Smm
71228753Smm#include <machine/in_cksum.h>
72228753Smm
73228753Smmstatic MALLOC_DEFINE(M_IGMP, "igmp", "igmp state");
74228753Smm
75228753Smmstatic struct router_info	*find_rti(struct ifnet *ifp);
76228753Smmstatic void	igmp_sendpkt(struct in_multi *, int, unsigned long);
77228753Smm
78228753Smmstatic struct igmpstat igmpstat;
79228753Smm
80228753SmmSYSCTL_STRUCT(_net_inet_igmp, IGMPCTL_STATS, stats, CTLFLAG_RW, &igmpstat,
81228753Smm    igmpstat, "");
82228753Smm
83228753Smm/*
84228753Smm * igmp_mtx protects all mutable global variables in igmp.c, as well as
85228753Smm * the data fields in struct router_info.  In general, a router_info
86228753Smm * structure will be valid as long as the referencing struct in_multi is
87228753Smm * valid, so no reference counting is used.  We allow unlocked reads of
88228753Smm * router_info data when accessed via an in_multi read-only.
89228753Smm */
90228753Smmstatic struct mtx igmp_mtx;
91228753Smmstatic SLIST_HEAD(, router_info) router_info_head;
92228753Smmstatic int igmp_timers_are_running;
93228753Smm
94228753Smm/*
95228753Smm * XXXRW: can we define these such that these can be made const?  In any
96228753Smm * case, these shouldn't be changed after igmp_init() and therefore don't
97228753Smm * need locking.
98228753Smm */
99228753Smmstatic u_long igmp_all_hosts_group;
100228753Smmstatic u_long igmp_all_rtrs_group;
101228753Smm
102228753Smmstatic struct mbuf *router_alert;
103228753Smmstatic struct route igmprt;
104228753Smm
105228753Smm#ifdef IGMP_DEBUG
106228753Smm#define	IGMP_PRINTF(x)	printf(x)
107228753Smm#else
108228753Smm#define	IGMP_PRINTF(x)
109228753Smm#endif
110228753Smm
111228753Smmvoid
112228753Smmigmp_init(void)
113228753Smm{
114228753Smm	struct ipoption *ra;
115228753Smm
116228753Smm	/*
117228753Smm	 * To avoid byte-swapping the same value over and over again.
118228753Smm	 */
119228753Smm	igmp_all_hosts_group = htonl(INADDR_ALLHOSTS_GROUP);
120228753Smm	igmp_all_rtrs_group = htonl(INADDR_ALLRTRS_GROUP);
121228753Smm
122228753Smm	igmp_timers_are_running = 0;
123228753Smm
124228753Smm	/*
125228753Smm	 * Construct a Router Alert option to use in outgoing packets
126228753Smm	 */
127228753Smm	MGET(router_alert, M_DONTWAIT, MT_DATA);
128228753Smm	ra = mtod(router_alert, struct ipoption *);
129228753Smm	ra->ipopt_dst.s_addr = 0;
130228753Smm	ra->ipopt_list[0] = IPOPT_RA;	/* Router Alert Option */
131228753Smm	ra->ipopt_list[1] = 0x04;	/* 4 bytes long */
132228753Smm	ra->ipopt_list[2] = 0x00;
133228753Smm	ra->ipopt_list[3] = 0x00;
134228753Smm	router_alert->m_len = sizeof(ra->ipopt_dst) + ra->ipopt_list[1];
135228753Smm
136228753Smm	mtx_init(&igmp_mtx, "igmp_mtx", NULL, MTX_DEF);
137228753Smm	SLIST_INIT(&router_info_head);
138228753Smm}
139228753Smm
140228753Smmstatic struct router_info *
141228753Smmfind_rti(struct ifnet *ifp)
142228753Smm{
143228753Smm	struct router_info *rti;
144228753Smm
145228753Smm	mtx_assert(&igmp_mtx, MA_OWNED);
146228753Smm	IGMP_PRINTF("[igmp.c, _find_rti] --> entering \n");
147228753Smm	SLIST_FOREACH(rti, &router_info_head, rti_list) {
148228753Smm		if (rti->rti_ifp == ifp) {
149228753Smm			IGMP_PRINTF(
150228753Smm			    "[igmp.c, _find_rti] --> found old entry \n");
151228753Smm			return rti;
152228753Smm		}
153228753Smm	}
154228753Smm	/*
155228753Smm	 * XXXRW: return value of malloc not checked, despite M_NOWAIT.
156228753Smm	 */
157228753Smm	MALLOC(rti, struct router_info *, sizeof *rti, M_IGMP, M_NOWAIT);
158228753Smm	rti->rti_ifp = ifp;
159	rti->rti_type = IGMP_V2_ROUTER;
160	rti->rti_time = 0;
161	SLIST_INSERT_HEAD(&router_info_head, rti, rti_list);
162
163	IGMP_PRINTF("[igmp.c, _find_rti] --> created an entry \n");
164	return rti;
165}
166
167void
168igmp_input(register struct mbuf *m, int off)
169{
170	register int iphlen = off;
171	register struct igmp *igmp;
172	register struct ip *ip;
173	register int igmplen;
174	register struct ifnet *ifp = m->m_pkthdr.rcvif;
175	register int minlen;
176	register struct in_multi *inm;
177	register struct in_ifaddr *ia;
178	struct in_multistep step;
179	struct router_info *rti;
180	int timer; /** timer value in the igmp query header **/
181
182	++igmpstat.igps_rcv_total;
183
184	ip = mtod(m, struct ip *);
185	igmplen = ip->ip_len;
186
187	/*
188	 * Validate lengths
189	 */
190	if (igmplen < IGMP_MINLEN) {
191		++igmpstat.igps_rcv_tooshort;
192		m_freem(m);
193		return;
194	}
195	minlen = iphlen + IGMP_MINLEN;
196	if ((m->m_flags & M_EXT || m->m_len < minlen) &&
197	    (m = m_pullup(m, minlen)) == 0) {
198		++igmpstat.igps_rcv_tooshort;
199		return;
200	}
201
202	/*
203	 * Validate checksum
204	 */
205	m->m_data += iphlen;
206	m->m_len -= iphlen;
207	igmp = mtod(m, struct igmp *);
208	if (in_cksum(m, igmplen)) {
209		++igmpstat.igps_rcv_badsum;
210		m_freem(m);
211		return;
212	}
213	m->m_data -= iphlen;
214	m->m_len += iphlen;
215
216	ip = mtod(m, struct ip *);
217	timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
218	if (timer == 0)
219		timer = 1;
220
221	/*
222	 * In the IGMPv2 specification, there are 3 states and a flag.
223	 *
224	 * In Non-Member state, we simply don't have a membership record.
225	 * In Delaying Member state, our timer is running (inm->inm_timer)
226	 * In Idle Member state, our timer is not running (inm->inm_timer==0)
227	 *
228	 * The flag is inm->inm_state, it is set to IGMP_OTHERMEMBER if
229	 * we have heard a report from another member, or IGMP_IREPORTEDLAST
230	 * if I sent the last report.
231	 */
232	switch (igmp->igmp_type) {
233	case IGMP_MEMBERSHIP_QUERY:
234		++igmpstat.igps_rcv_queries;
235
236		if (ifp->if_flags & IFF_LOOPBACK)
237			break;
238
239		if (igmp->igmp_code == 0) {
240			/*
241			 * Old router.  Remember that the querier on this
242			 * interface is old, and set the timer to the
243			 * value in RFC 1112.
244			 */
245
246			mtx_lock(&igmp_mtx);
247			rti = find_rti(ifp);
248			rti->rti_type = IGMP_V1_ROUTER;
249			rti->rti_time = 0;
250			mtx_unlock(&igmp_mtx);
251
252			timer = IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ;
253
254			if (ip->ip_dst.s_addr != igmp_all_hosts_group ||
255			    igmp->igmp_group.s_addr != 0) {
256				++igmpstat.igps_rcv_badqueries;
257				m_freem(m);
258				return;
259			}
260		} else {
261			/*
262			 * New router.  Simply do the new validity check.
263			 */
264
265			if (igmp->igmp_group.s_addr != 0 &&
266			    !IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) {
267				++igmpstat.igps_rcv_badqueries;
268				m_freem(m);
269				return;
270			}
271		}
272
273		/*
274		 * - Start the timers in all of our membership records
275		 *   that the query applies to for the interface on
276		 *   which the query arrived excl. those that belong
277		 *   to the "all-hosts" group (224.0.0.1).
278		 * - Restart any timer that is already running but has
279		 *   a value longer than the requested timeout.
280		 * - Use the value specified in the query message as
281		 *   the maximum timeout.
282		 */
283		IN_FIRST_MULTI(step, inm);
284		while (inm != NULL) {
285			if (inm->inm_ifp == ifp &&
286			    inm->inm_addr.s_addr != igmp_all_hosts_group &&
287			    (igmp->igmp_group.s_addr == 0 ||
288			     igmp->igmp_group.s_addr == inm->inm_addr.s_addr)) {
289				if (inm->inm_timer == 0 ||
290				    inm->inm_timer > timer) {
291					inm->inm_timer =
292						IGMP_RANDOM_DELAY(timer);
293					igmp_timers_are_running = 1;
294				}
295			}
296			IN_NEXT_MULTI(step, inm);
297		}
298
299		break;
300
301	case IGMP_V1_MEMBERSHIP_REPORT:
302	case IGMP_V2_MEMBERSHIP_REPORT:
303		/*
304		 * For fast leave to work, we have to know that we are the
305		 * last person to send a report for this group.  Reports
306		 * can potentially get looped back if we are a multicast
307		 * router, so discard reports sourced by me.
308		 */
309		IFP_TO_IA(ifp, ia);
310		if (ia && ip->ip_src.s_addr == IA_SIN(ia)->sin_addr.s_addr)
311			break;
312
313		++igmpstat.igps_rcv_reports;
314
315		if (ifp->if_flags & IFF_LOOPBACK)
316			break;
317
318		if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) {
319			++igmpstat.igps_rcv_badreports;
320			m_freem(m);
321			return;
322		}
323
324		/*
325		 * KLUDGE: if the IP source address of the report has an
326		 * unspecified (i.e., zero) subnet number, as is allowed for
327		 * a booting host, replace it with the correct subnet number
328		 * so that a process-level multicast routing daemon can
329		 * determine which subnet it arrived from.  This is necessary
330		 * to compensate for the lack of any way for a process to
331		 * determine the arrival interface of an incoming packet.
332		 */
333		if ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0)
334			if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet);
335
336		/*
337		 * If we belong to the group being reported, stop
338		 * our timer for that group.
339		 */
340		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
341
342		if (inm != NULL) {
343			inm->inm_timer = 0;
344			++igmpstat.igps_rcv_ourreports;
345
346			inm->inm_state = IGMP_OTHERMEMBER;
347		}
348
349		break;
350	}
351
352	/*
353	 * Pass all valid IGMP packets up to any process(es) listening
354	 * on a raw IGMP socket.
355	 */
356	rip_input(m, off);
357}
358
359void
360igmp_joingroup(struct in_multi *inm)
361{
362	int s = splnet();
363
364	if (inm->inm_addr.s_addr == igmp_all_hosts_group
365	    || inm->inm_ifp->if_flags & IFF_LOOPBACK) {
366		inm->inm_timer = 0;
367		inm->inm_state = IGMP_OTHERMEMBER;
368	} else {
369		mtx_lock(&igmp_mtx);
370		inm->inm_rti = find_rti(inm->inm_ifp);
371		mtx_unlock(&igmp_mtx);
372		igmp_sendpkt(inm, inm->inm_rti->rti_type, 0);
373		inm->inm_timer = IGMP_RANDOM_DELAY(
374					IGMP_MAX_HOST_REPORT_DELAY*PR_FASTHZ);
375		inm->inm_state = IGMP_IREPORTEDLAST;
376		igmp_timers_are_running = 1;
377	}
378	splx(s);
379}
380
381void
382igmp_leavegroup(struct in_multi *inm)
383{
384
385	if (inm->inm_state == IGMP_IREPORTEDLAST &&
386	    inm->inm_addr.s_addr != igmp_all_hosts_group &&
387	    !(inm->inm_ifp->if_flags & IFF_LOOPBACK) &&
388	    inm->inm_rti->rti_type != IGMP_V1_ROUTER)
389		igmp_sendpkt(inm, IGMP_V2_LEAVE_GROUP, igmp_all_rtrs_group);
390}
391
392void
393igmp_fasttimo(void)
394{
395	register struct in_multi *inm;
396	struct in_multistep step;
397	int s;
398
399	/*
400	 * Quick check to see if any work needs to be done, in order
401	 * to minimize the overhead of fasttimo processing.
402	 */
403
404	if (!igmp_timers_are_running)
405		return;
406
407	s = splnet();
408	igmp_timers_are_running = 0;
409	IN_FIRST_MULTI(step, inm);
410	while (inm != NULL) {
411		if (inm->inm_timer == 0) {
412			/* do nothing */
413		} else if (--inm->inm_timer == 0) {
414			igmp_sendpkt(inm, inm->inm_rti->rti_type, 0);
415			inm->inm_state = IGMP_IREPORTEDLAST;
416		} else {
417			igmp_timers_are_running = 1;
418		}
419		IN_NEXT_MULTI(step, inm);
420	}
421	splx(s);
422}
423
424void
425igmp_slowtimo(void)
426{
427	int s = splnet();
428	struct router_info *rti;
429
430	IGMP_PRINTF("[igmp.c,_slowtimo] -- > entering \n");
431	mtx_lock(&igmp_mtx);
432	SLIST_FOREACH(rti, &router_info_head, rti_list) {
433		if (rti->rti_type == IGMP_V1_ROUTER) {
434			rti->rti_time++;
435			if (rti->rti_time >= IGMP_AGE_THRESHOLD)
436				rti->rti_type = IGMP_V2_ROUTER;
437		}
438	}
439	mtx_unlock(&igmp_mtx);
440	IGMP_PRINTF("[igmp.c,_slowtimo] -- > exiting \n");
441	splx(s);
442}
443
444static void
445igmp_sendpkt(struct in_multi *inm, int type, unsigned long addr)
446{
447	struct mbuf *m;
448	struct igmp *igmp;
449	struct ip *ip;
450	struct ip_moptions imo;
451
452	MGETHDR(m, M_DONTWAIT, MT_HEADER);
453	if (m == NULL)
454		return;
455
456	m->m_pkthdr.rcvif = loif;
457#ifdef MAC
458	mac_create_mbuf_linklayer(inm->inm_ifp, m);
459#endif
460	m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
461	MH_ALIGN(m, IGMP_MINLEN + sizeof(struct ip));
462	m->m_data += sizeof(struct ip);
463	m->m_len = IGMP_MINLEN;
464	igmp = mtod(m, struct igmp *);
465	igmp->igmp_type = type;
466	igmp->igmp_code = 0;
467	igmp->igmp_group = inm->inm_addr;
468	igmp->igmp_cksum = 0;
469	igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
470
471	m->m_data -= sizeof(struct ip);
472	m->m_len += sizeof(struct ip);
473	ip = mtod(m, struct ip *);
474	ip->ip_tos = 0;
475	ip->ip_len = sizeof(struct ip) + IGMP_MINLEN;
476	ip->ip_off = 0;
477	ip->ip_p = IPPROTO_IGMP;
478	ip->ip_src.s_addr = INADDR_ANY;
479	ip->ip_dst.s_addr = addr ? addr : igmp->igmp_group.s_addr;
480
481	imo.imo_multicast_ifp  = inm->inm_ifp;
482	imo.imo_multicast_ttl  = 1;
483	imo.imo_multicast_vif  = -1;
484	/*
485	 * Request loopback of the report if we are acting as a multicast
486	 * router, so that the process-level routing daemon can hear it.
487	 */
488	imo.imo_multicast_loop = (ip_mrouter != NULL);
489
490	/*
491	 * XXX
492	 * Do we have to worry about reentrancy here?  Don't think so.
493	 */
494	ip_output(m, router_alert, &igmprt, 0, &imo, NULL);
495
496	++igmpstat.igps_snd_reports;
497}
498