xref: /netbsd-src/sys/netinet/igmp.c (revision 145523e8345fc150f5004eba38bc0699c052bd61)
1*145523e8Smaxv /*	$NetBSD: igmp.c,v 1.70 2020/05/15 06:34:34 maxv Exp $	*/
2118d2b1dSitojun 
3118d2b1dSitojun /*
4118d2b1dSitojun  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
5118d2b1dSitojun  * All rights reserved.
6118d2b1dSitojun  *
7118d2b1dSitojun  * Redistribution and use in source and binary forms, with or without
8118d2b1dSitojun  * modification, are permitted provided that the following conditions
9118d2b1dSitojun  * are met:
10118d2b1dSitojun  * 1. Redistributions of source code must retain the above copyright
11118d2b1dSitojun  *    notice, this list of conditions and the following disclaimer.
12118d2b1dSitojun  * 2. Redistributions in binary form must reproduce the above copyright
13118d2b1dSitojun  *    notice, this list of conditions and the following disclaimer in the
14118d2b1dSitojun  *    documentation and/or other materials provided with the distribution.
15118d2b1dSitojun  * 3. Neither the name of the project nor the names of its contributors
16118d2b1dSitojun  *    may be used to endorse or promote products derived from this software
17118d2b1dSitojun  *    without specific prior written permission.
18118d2b1dSitojun  *
19118d2b1dSitojun  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20118d2b1dSitojun  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21118d2b1dSitojun  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22118d2b1dSitojun  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23118d2b1dSitojun  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24118d2b1dSitojun  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25118d2b1dSitojun  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26118d2b1dSitojun  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27118d2b1dSitojun  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28118d2b1dSitojun  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29118d2b1dSitojun  * SUCH DAMAGE.
30118d2b1dSitojun  */
31cf92afd6Scgd 
32aa7f3b23Shpeyerl /*
33f49ddb8bSmycroft  * Internet Group Management Protocol (IGMP) routines.
34aa7f3b23Shpeyerl  *
35f49ddb8bSmycroft  * Written by Steve Deering, Stanford, May 1988.
36f49ddb8bSmycroft  * Modified by Rosen Sharma, Stanford, Aug 1994.
37f49ddb8bSmycroft  * Modified by Bill Fenner, Xerox PARC, Feb 1995.
38aa7f3b23Shpeyerl  *
39f49ddb8bSmycroft  * MULTICAST Revision: 1.3
40aa7f3b23Shpeyerl  */
41aa7f3b23Shpeyerl 
42ea1cd7ebSlukem #include <sys/cdefs.h>
43*145523e8Smaxv __KERNEL_RCSID(0, "$NetBSD: igmp.c,v 1.70 2020/05/15 06:34:34 maxv Exp $");
44ea1cd7ebSlukem 
451c4a50f1Spooka #ifdef _KERNEL_OPT
4654ea0747Sscottr #include "opt_mrouting.h"
479e8d969cSozaki-r #include "opt_net_mpsafe.h"
481c4a50f1Spooka #endif
4954ea0747Sscottr 
50aa7f3b23Shpeyerl #include <sys/param.h>
51aa7f3b23Shpeyerl #include <sys/mbuf.h>
52aa7f3b23Shpeyerl #include <sys/socket.h>
5315e29e98Sad #include <sys/socketvar.h>
5414d9cd33Schristos #include <sys/systm.h>
55d49a7f6aSrmind #include <sys/cprng.h>
5683dd1069Sthorpej #include <sys/sysctl.h>
57aa7f3b23Shpeyerl 
58aa7f3b23Shpeyerl #include <net/if.h>
5933326077Sthorpej #include <net/net_stats.h>
60aa7f3b23Shpeyerl 
61aa7f3b23Shpeyerl #include <netinet/in.h>
62aa7f3b23Shpeyerl #include <netinet/in_var.h>
63aa7f3b23Shpeyerl #include <netinet/in_systm.h>
64aa7f3b23Shpeyerl #include <netinet/ip.h>
65aa7f3b23Shpeyerl #include <netinet/ip_var.h>
66aa7f3b23Shpeyerl #include <netinet/igmp.h>
67aa7f3b23Shpeyerl #include <netinet/igmp_var.h>
68aa7f3b23Shpeyerl 
69d49a7f6aSrmind /*
70d49a7f6aSrmind  * Per-interface router version information.
71d49a7f6aSrmind  */
72d49a7f6aSrmind typedef struct router_info {
73d49a7f6aSrmind 	LIST_ENTRY(router_info) rti_link;
74d49a7f6aSrmind 	ifnet_t *	rti_ifp;
75d49a7f6aSrmind 	int		rti_type;	/* type of router on this interface */
76d49a7f6aSrmind 	int		rti_age;	/* time since last v1 query */
77d49a7f6aSrmind } router_info_t;
78aa7f3b23Shpeyerl 
79d49a7f6aSrmind /*
80d49a7f6aSrmind  * The router-info list and the timer flag are protected by in_multilock.
81d49a7f6aSrmind  *
82d49a7f6aSrmind  * Lock order:
83d49a7f6aSrmind  *
84d49a7f6aSrmind  *	softnet_lock ->
85d49a7f6aSrmind  *		in_multilock
86d49a7f6aSrmind  */
87d49a7f6aSrmind static struct pool	igmp_rti_pool		__cacheline_aligned;
88d49a7f6aSrmind static LIST_HEAD(, router_info)	rti_head	__cacheline_aligned;
89d49a7f6aSrmind static int		igmp_timers_on		__cacheline_aligned;
90d49a7f6aSrmind static percpu_t *	igmpstat_percpu		__read_mostly;
9183dd1069Sthorpej 
9233326077Sthorpej #define	IGMP_STATINC(x)		_NET_STATINC(igmpstat_percpu, x)
9383dd1069Sthorpej 
94d49a7f6aSrmind static void		igmp_sendpkt(struct in_multi *, int);
9534944823Sperry static int		rti_fill(struct in_multi *);
96d49a7f6aSrmind static router_info_t *	rti_find(struct ifnet *);
97bef80abbSitojun static void		rti_delete(struct ifnet *);
9811281f01Spooka static void		sysctl_net_inet_igmp_setup(struct sysctllog **);
9911281f01Spooka 
100d49a7f6aSrmind /*
101d49a7f6aSrmind  * rti_fill: associate router information with the given multicast group;
102d49a7f6aSrmind  * if there is no router information for the interface, then create it.
103d49a7f6aSrmind  */
104f49ddb8bSmycroft static int
rti_fill(struct in_multi * inm)10551ad03a9Sperry rti_fill(struct in_multi *inm)
106f49ddb8bSmycroft {
107d49a7f6aSrmind 	router_info_t *rti;
108f49ddb8bSmycroft 
109d49a7f6aSrmind 	KASSERT(in_multi_lock_held());
110d49a7f6aSrmind 
11127e17421Smatt 	LIST_FOREACH(rti, &rti_head, rti_link) {
112f49ddb8bSmycroft 		if (rti->rti_ifp == inm->inm_ifp) {
113f49ddb8bSmycroft 			inm->inm_rti = rti;
114d49a7f6aSrmind 			return rti->rti_type == IGMP_v1_ROUTER ?
115d49a7f6aSrmind 			    IGMP_v1_HOST_MEMBERSHIP_REPORT :
116d49a7f6aSrmind 			    IGMP_v2_HOST_MEMBERSHIP_REPORT;
117f49ddb8bSmycroft 		}
118f49ddb8bSmycroft 	}
11927e17421Smatt 	rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
120d49a7f6aSrmind 	if (rti == NULL) {
12127e17421Smatt 		return 0;
122d49a7f6aSrmind 	}
123f49ddb8bSmycroft 	rti->rti_ifp = inm->inm_ifp;
124f49ddb8bSmycroft 	rti->rti_type = IGMP_v2_ROUTER;
12527e17421Smatt 	LIST_INSERT_HEAD(&rti_head, rti, rti_link);
126f49ddb8bSmycroft 	inm->inm_rti = rti;
127d49a7f6aSrmind 	return IGMP_v2_HOST_MEMBERSHIP_REPORT;
128f49ddb8bSmycroft }
129f49ddb8bSmycroft 
130d49a7f6aSrmind /*
131d49a7f6aSrmind  * rti_find: lookup or create router information for the given interface.
132d49a7f6aSrmind  */
133d49a7f6aSrmind static router_info_t *
rti_find(ifnet_t * ifp)134d49a7f6aSrmind rti_find(ifnet_t *ifp)
135f49ddb8bSmycroft {
136d49a7f6aSrmind 	router_info_t *rti;
137d49a7f6aSrmind 
138d49a7f6aSrmind 	KASSERT(in_multi_lock_held());
139f49ddb8bSmycroft 
14027e17421Smatt 	LIST_FOREACH(rti, &rti_head, rti_link) {
141f49ddb8bSmycroft 		if (rti->rti_ifp == ifp)
142d49a7f6aSrmind 			return rti;
143f49ddb8bSmycroft 	}
14427e17421Smatt 	rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
1458cc016b4Stls 	if (rti == NULL) {
14627e17421Smatt 		return NULL;
1478cc016b4Stls 	}
148f49ddb8bSmycroft 	rti->rti_ifp = ifp;
149f49ddb8bSmycroft 	rti->rti_type = IGMP_v2_ROUTER;
15027e17421Smatt 	LIST_INSERT_HEAD(&rti_head, rti, rti_link);
151d49a7f6aSrmind 	return rti;
152aa7f3b23Shpeyerl }
153aa7f3b23Shpeyerl 
154d49a7f6aSrmind /*
155d49a7f6aSrmind  * rti_delete: remove and free the router information entry for the
156d49a7f6aSrmind  * given interface.
157d49a7f6aSrmind  */
158bef80abbSitojun static void
rti_delete(ifnet_t * ifp)159d49a7f6aSrmind rti_delete(ifnet_t *ifp)
160bef80abbSitojun {
161d49a7f6aSrmind 	router_info_t *rti;
162d49a7f6aSrmind 
163d49a7f6aSrmind 	KASSERT(in_multi_lock_held());
164bef80abbSitojun 
165bef80abbSitojun 	LIST_FOREACH(rti, &rti_head, rti_link) {
166bef80abbSitojun 		if (rti->rti_ifp == ifp) {
167bef80abbSitojun 			LIST_REMOVE(rti, rti_link);
168bef80abbSitojun 			pool_put(&igmp_rti_pool, rti);
169d49a7f6aSrmind 			break;
170bef80abbSitojun 		}
171bef80abbSitojun 	}
172bef80abbSitojun }
173bef80abbSitojun 
174aa7f3b23Shpeyerl void
igmp_init(void)17583dd1069Sthorpej igmp_init(void)
17683dd1069Sthorpej {
177d49a7f6aSrmind 	pool_init(&igmp_rti_pool, sizeof(router_info_t), 0, 0, 0,
178fbd53556Spooka 	    "igmppl", NULL, IPL_SOFTNET);
17983dd1069Sthorpej 	igmpstat_percpu = percpu_alloc(sizeof(uint64_t) * IGMP_NSTATS);
180d49a7f6aSrmind 	sysctl_net_inet_igmp_setup(NULL);
181d49a7f6aSrmind 	LIST_INIT(&rti_head);
18283dd1069Sthorpej }
18383dd1069Sthorpej 
18483dd1069Sthorpej void
igmp_input(struct mbuf * m,int off,int proto)18515652348Smaxv igmp_input(struct mbuf *m, int off, int proto)
186aa7f3b23Shpeyerl {
187fe6d4275Sozaki-r 	ifnet_t *ifp;
1888529438fSaugustss 	struct ip *ip = mtod(m, struct ip *);
1898529438fSaugustss 	struct igmp *igmp;
190d49a7f6aSrmind 	u_int minlen, timer;
191f49ddb8bSmycroft 	struct in_multi *inm;
1928529438fSaugustss 	struct in_ifaddr *ia;
19315652348Smaxv 	int ip_len, iphlen;
194fe6d4275Sozaki-r 	struct psref psref;
19514d9cd33Schristos 
19615652348Smaxv 	iphlen = off;
197aa7f3b23Shpeyerl 
19883dd1069Sthorpej 	IGMP_STATINC(IGMP_STAT_RCV_TOTAL);
199aa7f3b23Shpeyerl 
200aa7f3b23Shpeyerl 	/*
201aa7f3b23Shpeyerl 	 * Validate lengths
202aa7f3b23Shpeyerl 	 */
2037eeb5a04Smycroft 	minlen = iphlen + IGMP_MINLEN;
204c00fa8dfSitojun 	ip_len = ntohs(ip->ip_len);
205c00fa8dfSitojun 	if (ip_len < minlen) {
20683dd1069Sthorpej 		IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
207aa7f3b23Shpeyerl 		m_freem(m);
208aa7f3b23Shpeyerl 		return;
209aa7f3b23Shpeyerl 	}
210e21a3d99Smatt 	if (((m->m_flags & M_EXT) && (ip->ip_src.s_addr & IN_CLASSA_NET) == 0)
211e21a3d99Smatt 	    || m->m_len < minlen) {
21224612de5Sliamjfoy 		if ((m = m_pullup(m, minlen)) == NULL) {
21383dd1069Sthorpej 			IGMP_STATINC(IGMP_STAT_RCV_TOOSHORT);
214aa7f3b23Shpeyerl 			return;
215aa7f3b23Shpeyerl 		}
216e21a3d99Smatt 		ip = mtod(m, struct ip *);
217e21a3d99Smatt 	}
218aa7f3b23Shpeyerl 
219aa7f3b23Shpeyerl 	/*
220aa7f3b23Shpeyerl 	 * Validate checksum
221aa7f3b23Shpeyerl 	 */
222aa7f3b23Shpeyerl 	m->m_data += iphlen;
223aa7f3b23Shpeyerl 	m->m_len -= iphlen;
224aa7f3b23Shpeyerl 	igmp = mtod(m, struct igmp *);
22510c252baSthorpej 	/* No need to assert alignment here. */
226c00fa8dfSitojun 	if (in_cksum(m, ip_len - iphlen)) {
22783dd1069Sthorpej 		IGMP_STATINC(IGMP_STAT_RCV_BADSUM);
228aa7f3b23Shpeyerl 		m_freem(m);
229aa7f3b23Shpeyerl 		return;
230aa7f3b23Shpeyerl 	}
231aa7f3b23Shpeyerl 	m->m_data -= iphlen;
232aa7f3b23Shpeyerl 	m->m_len += iphlen;
233aa7f3b23Shpeyerl 
234fe6d4275Sozaki-r 	ifp = m_get_rcvif_psref(m, &psref);
235ca4ea29dSozaki-r 	if (__predict_false(ifp == NULL))
236ca4ea29dSozaki-r 		goto drop;
237ca4ea29dSozaki-r 
238aa7f3b23Shpeyerl 	switch (igmp->igmp_type) {
239aa7f3b23Shpeyerl 
240aa7f3b23Shpeyerl 	case IGMP_HOST_MEMBERSHIP_QUERY:
24183dd1069Sthorpej 		IGMP_STATINC(IGMP_STAT_RCV_QUERIES);
242aa7f3b23Shpeyerl 
24357de85fbSmycroft 		if (ifp->if_flags & IFF_LOOPBACK)
244aa7f3b23Shpeyerl 			break;
245aa7f3b23Shpeyerl 
246f49ddb8bSmycroft 		if (igmp->igmp_code == 0) {
247d49a7f6aSrmind 			struct in_multistep step;
248d49a7f6aSrmind 			router_info_t *rti;
249f49ddb8bSmycroft 
250eb216fd6Smycroft 			if (ip->ip_dst.s_addr != INADDR_ALLHOSTS_GROUP) {
25183dd1069Sthorpej 				IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
252fe6d4275Sozaki-r 				goto drop;
253aa7f3b23Shpeyerl 			}
254aa7f3b23Shpeyerl 
255d49a7f6aSrmind 			in_multi_lock(RW_WRITER);
256d49a7f6aSrmind 			rti = rti_find(ifp);
257d49a7f6aSrmind 			if (rti == NULL) {
258d49a7f6aSrmind 				in_multi_unlock();
259d49a7f6aSrmind 				break;
260d49a7f6aSrmind 			}
261d49a7f6aSrmind 			rti->rti_type = IGMP_v1_ROUTER;
262d49a7f6aSrmind 			rti->rti_age = 0;
263d49a7f6aSrmind 
264aa7f3b23Shpeyerl 			/*
265f49ddb8bSmycroft 			 * Start the timers in all of our membership records
266f49ddb8bSmycroft 			 * for the interface on which the query arrived,
267f49ddb8bSmycroft 			 * except those that are already running and those
268f49ddb8bSmycroft 			 * that belong to a "local" group (224.0.0.X).
269aa7f3b23Shpeyerl 			 */
270d49a7f6aSrmind 
271d49a7f6aSrmind 			inm = in_first_multi(&step);
272aa7f3b23Shpeyerl 			while (inm != NULL) {
273f49ddb8bSmycroft 				if (inm->inm_ifp == ifp &&
274f49ddb8bSmycroft 				    inm->inm_timer == 0 &&
275f49ddb8bSmycroft 				    !IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
276f49ddb8bSmycroft 					inm->inm_state = IGMP_DELAYING_MEMBER;
277f49ddb8bSmycroft 					inm->inm_timer = IGMP_RANDOM_DELAY(
278f49ddb8bSmycroft 					    IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
279d49a7f6aSrmind 					igmp_timers_on = true;
280aa7f3b23Shpeyerl 				}
281d49a7f6aSrmind 				inm = in_next_multi(&step);
282aa7f3b23Shpeyerl 			}
283d49a7f6aSrmind 			in_multi_unlock();
284f49ddb8bSmycroft 		} else {
285d49a7f6aSrmind 			struct in_multistep step;
286d49a7f6aSrmind 
287eb216fd6Smycroft 			if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
28883dd1069Sthorpej 				IGMP_STATINC(IGMP_STAT_RCV_BADQUERIES);
289fe6d4275Sozaki-r 				goto drop;
290f49ddb8bSmycroft 			}
291f49ddb8bSmycroft 
292f49ddb8bSmycroft 			timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
293fb7871d6Shwr 			if (timer == 0)
294fb7871d6Shwr 				timer = 1;
295f49ddb8bSmycroft 
296f49ddb8bSmycroft 			/*
297f49ddb8bSmycroft 			 * Start the timers in all of our membership records
298f49ddb8bSmycroft 			 * for the interface on which the query arrived,
299f49ddb8bSmycroft 			 * except those that are already running and those
300f49ddb8bSmycroft 			 * that belong to a "local" group (224.0.0.X).  For
301f49ddb8bSmycroft 			 * timers already running, check if they need to be
302f49ddb8bSmycroft 			 * reset.
303f49ddb8bSmycroft 			 */
304d49a7f6aSrmind 			in_multi_lock(RW_WRITER);
305d49a7f6aSrmind 			inm = in_first_multi(&step);
306f49ddb8bSmycroft 			while (inm != NULL) {
307f49ddb8bSmycroft 				if (inm->inm_ifp == ifp &&
308f49ddb8bSmycroft 				    !IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
309eb216fd6Smycroft 				    (ip->ip_dst.s_addr == INADDR_ALLHOSTS_GROUP ||
31062a6cce9Smycroft 				     in_hosteq(ip->ip_dst, inm->inm_addr))) {
311f49ddb8bSmycroft 					switch (inm->inm_state) {
312f49ddb8bSmycroft 					case IGMP_DELAYING_MEMBER:
313f49ddb8bSmycroft 						if (inm->inm_timer <= timer)
314f49ddb8bSmycroft 							break;
315f49ddb8bSmycroft 						/* FALLTHROUGH */
316f49ddb8bSmycroft 					case IGMP_IDLE_MEMBER:
317f49ddb8bSmycroft 					case IGMP_LAZY_MEMBER:
318f49ddb8bSmycroft 					case IGMP_AWAKENING_MEMBER:
319f49ddb8bSmycroft 						inm->inm_state =
320f49ddb8bSmycroft 						    IGMP_DELAYING_MEMBER;
321f49ddb8bSmycroft 						inm->inm_timer =
322f49ddb8bSmycroft 						    IGMP_RANDOM_DELAY(timer);
323d49a7f6aSrmind 						igmp_timers_on = true;
324f49ddb8bSmycroft 						break;
325f49ddb8bSmycroft 					case IGMP_SLEEPING_MEMBER:
326f49ddb8bSmycroft 						inm->inm_state =
327f49ddb8bSmycroft 						    IGMP_AWAKENING_MEMBER;
328f49ddb8bSmycroft 						break;
329f49ddb8bSmycroft 					}
330f49ddb8bSmycroft 				}
331d49a7f6aSrmind 				inm = in_next_multi(&step);
332f49ddb8bSmycroft 			}
333d49a7f6aSrmind 			in_multi_unlock();
334f49ddb8bSmycroft 		}
335aa7f3b23Shpeyerl 
336aa7f3b23Shpeyerl 		break;
337aa7f3b23Shpeyerl 
338f49ddb8bSmycroft 	case IGMP_v1_HOST_MEMBERSHIP_REPORT:
33983dd1069Sthorpej 		IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
340aa7f3b23Shpeyerl 
34157de85fbSmycroft 		if (ifp->if_flags & IFF_LOOPBACK)
342aa7f3b23Shpeyerl 			break;
343aa7f3b23Shpeyerl 
344eb216fd6Smycroft 		if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
34562a6cce9Smycroft 		    !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
34683dd1069Sthorpej 			IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
347fe6d4275Sozaki-r 			goto drop;
348aa7f3b23Shpeyerl 		}
349aa7f3b23Shpeyerl 
350aa7f3b23Shpeyerl 		/*
351aa7f3b23Shpeyerl 		 * KLUDGE: if the IP source address of the report has an
352aa7f3b23Shpeyerl 		 * unspecified (i.e., zero) subnet number, as is allowed for
353aa7f3b23Shpeyerl 		 * a booting host, replace it with the correct subnet number
354f49ddb8bSmycroft 		 * so that a process-level multicast routing daemon can
355aa7f3b23Shpeyerl 		 * determine which subnet it arrived from.  This is necessary
356aa7f3b23Shpeyerl 		 * to compensate for the lack of any way for a process to
357aa7f3b23Shpeyerl 		 * determine the arrival interface of an incoming packet.
358aa7f3b23Shpeyerl 		 */
359eb216fd6Smycroft 		if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
360a403cbd4Sozaki-r 			int s = pserialize_read_enter();
3614133a8ecSozaki-r 			ia = in_get_ia_from_ifp(ifp); /* XXX */
362f49ddb8bSmycroft 			if (ia)
363eb216fd6Smycroft 				ip->ip_src.s_addr = ia->ia_subnet;
364a403cbd4Sozaki-r 			pserialize_read_exit(s);
365aa7f3b23Shpeyerl 		}
366aa7f3b23Shpeyerl 
367aa7f3b23Shpeyerl 		/*
368aa7f3b23Shpeyerl 		 * If we belong to the group being reported, stop
369aa7f3b23Shpeyerl 		 * our timer for that group.
370aa7f3b23Shpeyerl 		 */
371d49a7f6aSrmind 		in_multi_lock(RW_WRITER);
372d49a7f6aSrmind 		inm = in_lookup_multi(igmp->igmp_group, ifp);
373aa7f3b23Shpeyerl 		if (inm != NULL) {
374aa7f3b23Shpeyerl 			inm->inm_timer = 0;
37583dd1069Sthorpej 			IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
376f49ddb8bSmycroft 
377f49ddb8bSmycroft 			switch (inm->inm_state) {
378f49ddb8bSmycroft 			case IGMP_IDLE_MEMBER:
379f49ddb8bSmycroft 			case IGMP_LAZY_MEMBER:
380f49ddb8bSmycroft 			case IGMP_AWAKENING_MEMBER:
381f49ddb8bSmycroft 			case IGMP_SLEEPING_MEMBER:
382f49ddb8bSmycroft 				inm->inm_state = IGMP_SLEEPING_MEMBER;
383f49ddb8bSmycroft 				break;
384f49ddb8bSmycroft 			case IGMP_DELAYING_MEMBER:
385f49ddb8bSmycroft 				if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
386f49ddb8bSmycroft 					inm->inm_state = IGMP_LAZY_MEMBER;
387f49ddb8bSmycroft 				else
388f49ddb8bSmycroft 					inm->inm_state = IGMP_SLEEPING_MEMBER;
389f49ddb8bSmycroft 				break;
390f49ddb8bSmycroft 			}
391aa7f3b23Shpeyerl 		}
392d49a7f6aSrmind 		in_multi_unlock();
393aa7f3b23Shpeyerl 		break;
394f49ddb8bSmycroft 
395a403cbd4Sozaki-r 	case IGMP_v2_HOST_MEMBERSHIP_REPORT: {
396a403cbd4Sozaki-r 		int s = pserialize_read_enter();
397f49ddb8bSmycroft #ifdef MROUTING
398f49ddb8bSmycroft 		/*
399f49ddb8bSmycroft 		 * Make sure we don't hear our own membership report.  Fast
400f49ddb8bSmycroft 		 * leave requires knowing that we are the only member of a
401f49ddb8bSmycroft 		 * group.
402f49ddb8bSmycroft 		 */
4034133a8ecSozaki-r 		ia = in_get_ia_from_ifp(ifp);	/* XXX */
404a403cbd4Sozaki-r 		if (ia && in_hosteq(ip->ip_src, ia->ia_addr.sin_addr)) {
405a403cbd4Sozaki-r 			pserialize_read_exit(s);
406f49ddb8bSmycroft 			break;
407a403cbd4Sozaki-r 		}
408f49ddb8bSmycroft #endif
409f49ddb8bSmycroft 
41083dd1069Sthorpej 		IGMP_STATINC(IGMP_STAT_RCV_REPORTS);
411f49ddb8bSmycroft 
412a403cbd4Sozaki-r 		if (ifp->if_flags & IFF_LOOPBACK) {
413a403cbd4Sozaki-r 			pserialize_read_exit(s);
414f49ddb8bSmycroft 			break;
415a403cbd4Sozaki-r 		}
416f49ddb8bSmycroft 
417eb216fd6Smycroft 		if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
41862a6cce9Smycroft 		    !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
41983dd1069Sthorpej 			IGMP_STATINC(IGMP_STAT_RCV_BADREPORTS);
420a403cbd4Sozaki-r 			pserialize_read_exit(s);
421fe6d4275Sozaki-r 			goto drop;
422f49ddb8bSmycroft 		}
423f49ddb8bSmycroft 
424f49ddb8bSmycroft 		/*
425f49ddb8bSmycroft 		 * KLUDGE: if the IP source address of the report has an
426f49ddb8bSmycroft 		 * unspecified (i.e., zero) subnet number, as is allowed for
427f49ddb8bSmycroft 		 * a booting host, replace it with the correct subnet number
428f49ddb8bSmycroft 		 * so that a process-level multicast routing daemon can
429f49ddb8bSmycroft 		 * determine which subnet it arrived from.  This is necessary
430f49ddb8bSmycroft 		 * to compensate for the lack of any way for a process to
431f49ddb8bSmycroft 		 * determine the arrival interface of an incoming packet.
432f49ddb8bSmycroft 		 */
433eb216fd6Smycroft 		if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
434f49ddb8bSmycroft #ifndef MROUTING
4354133a8ecSozaki-r 			ia = in_get_ia_from_ifp(ifp); /* XXX */
436f49ddb8bSmycroft #endif
437f49ddb8bSmycroft 			if (ia)
438eb216fd6Smycroft 				ip->ip_src.s_addr = ia->ia_subnet;
439f49ddb8bSmycroft 		}
440a403cbd4Sozaki-r 		pserialize_read_exit(s);
441f49ddb8bSmycroft 
442f49ddb8bSmycroft 		/*
443f49ddb8bSmycroft 		 * If we belong to the group being reported, stop
444f49ddb8bSmycroft 		 * our timer for that group.
445f49ddb8bSmycroft 		 */
446d49a7f6aSrmind 		in_multi_lock(RW_WRITER);
447d49a7f6aSrmind 		inm = in_lookup_multi(igmp->igmp_group, ifp);
448f49ddb8bSmycroft 		if (inm != NULL) {
449f49ddb8bSmycroft 			inm->inm_timer = 0;
45083dd1069Sthorpej 			IGMP_STATINC(IGMP_STAT_RCV_OURREPORTS);
451f49ddb8bSmycroft 
452f49ddb8bSmycroft 			switch (inm->inm_state) {
453f49ddb8bSmycroft 			case IGMP_DELAYING_MEMBER:
454f49ddb8bSmycroft 			case IGMP_IDLE_MEMBER:
455f49ddb8bSmycroft 			case IGMP_AWAKENING_MEMBER:
456f49ddb8bSmycroft 				inm->inm_state = IGMP_LAZY_MEMBER;
457f49ddb8bSmycroft 				break;
458f49ddb8bSmycroft 			case IGMP_LAZY_MEMBER:
459f49ddb8bSmycroft 			case IGMP_SLEEPING_MEMBER:
460f49ddb8bSmycroft 				break;
461f49ddb8bSmycroft 			}
462f49ddb8bSmycroft 		}
463d49a7f6aSrmind 		in_multi_unlock();
464f49ddb8bSmycroft 		break;
465a403cbd4Sozaki-r 	    }
466aa7f3b23Shpeyerl 	}
467fe6d4275Sozaki-r 	m_put_rcvif_psref(ifp, &psref);
468aa7f3b23Shpeyerl 
469aa7f3b23Shpeyerl 	/*
470aa7f3b23Shpeyerl 	 * Pass all valid IGMP packets up to any process(es) listening
471aa7f3b23Shpeyerl 	 * on a raw IGMP socket.
472aa7f3b23Shpeyerl 	 */
473a1b205bfSknakahara 	/*
474a1b205bfSknakahara 	 * Currently, igmp_input() is always called holding softnet_lock
475a1b205bfSknakahara 	 * by ipintr()(!NET_MPSAFE) or PR_INPUT_WRAP()(NET_MPSAFE).
476a1b205bfSknakahara 	 */
477a1b205bfSknakahara 	KASSERT(mutex_owned(softnet_lock));
478118d2b1dSitojun 	rip_input(m, iphlen, proto);
479118d2b1dSitojun 	return;
480fe6d4275Sozaki-r 
481fe6d4275Sozaki-r drop:
482fe6d4275Sozaki-r 	m_put_rcvif_psref(ifp, &psref);
483fe6d4275Sozaki-r 	m_freem(m);
484fe6d4275Sozaki-r 	return;
485aa7f3b23Shpeyerl }
486aa7f3b23Shpeyerl 
48727e17421Smatt int
igmp_joingroup(struct in_multi * inm)48851ad03a9Sperry igmp_joingroup(struct in_multi *inm)
489aa7f3b23Shpeyerl {
490d49a7f6aSrmind 	KASSERT(in_multi_lock_held());
491f49ddb8bSmycroft 	inm->inm_state = IGMP_IDLE_MEMBER;
492f49ddb8bSmycroft 
493f49ddb8bSmycroft 	if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
494f49ddb8bSmycroft 	    (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0) {
495d49a7f6aSrmind 		int report_type;
496d49a7f6aSrmind 
49727e17421Smatt 		report_type = rti_fill(inm);
4981ad35fccSchristos 		if (report_type == 0) {
49927e17421Smatt 			return ENOMEM;
5001ad35fccSchristos 		}
50127e17421Smatt 		igmp_sendpkt(inm, report_type);
502f49ddb8bSmycroft 		inm->inm_state = IGMP_DELAYING_MEMBER;
503f49ddb8bSmycroft 		inm->inm_timer = IGMP_RANDOM_DELAY(
504f49ddb8bSmycroft 		    IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
505d49a7f6aSrmind 		igmp_timers_on = true;
506f49ddb8bSmycroft 	} else
507f49ddb8bSmycroft 		inm->inm_timer = 0;
508d49a7f6aSrmind 
50927e17421Smatt 	return 0;
510aa7f3b23Shpeyerl }
511aa7f3b23Shpeyerl 
512aa7f3b23Shpeyerl void
igmp_leavegroup(struct in_multi * inm)51351ad03a9Sperry igmp_leavegroup(struct in_multi *inm)
514aa7f3b23Shpeyerl {
515d49a7f6aSrmind 	KASSERT(in_multi_lock_held());
516f49ddb8bSmycroft 
517f49ddb8bSmycroft 	switch (inm->inm_state) {
518f49ddb8bSmycroft 	case IGMP_DELAYING_MEMBER:
519f49ddb8bSmycroft 	case IGMP_IDLE_MEMBER:
520f49ddb8bSmycroft 		if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
521f49ddb8bSmycroft 		    (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0)
522f49ddb8bSmycroft 			if (inm->inm_rti->rti_type != IGMP_v1_ROUTER)
523f49ddb8bSmycroft 				igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
524f49ddb8bSmycroft 		break;
525f49ddb8bSmycroft 	case IGMP_LAZY_MEMBER:
526f49ddb8bSmycroft 	case IGMP_AWAKENING_MEMBER:
527f49ddb8bSmycroft 	case IGMP_SLEEPING_MEMBER:
528f49ddb8bSmycroft 		break;
529f49ddb8bSmycroft 	}
530aa7f3b23Shpeyerl }
531aa7f3b23Shpeyerl 
532aa7f3b23Shpeyerl void
igmp_fasttimo(void)53351ad03a9Sperry igmp_fasttimo(void)
534aa7f3b23Shpeyerl {
5358529438fSaugustss 	struct in_multi *inm;
536aa7f3b23Shpeyerl 	struct in_multistep step;
537aa7f3b23Shpeyerl 
538aa7f3b23Shpeyerl 	/*
539aa7f3b23Shpeyerl 	 * Quick check to see if any work needs to be done, in order
540aa7f3b23Shpeyerl 	 * to minimize the overhead of fasttimo processing.
541aa7f3b23Shpeyerl 	 */
542d49a7f6aSrmind 	if (!igmp_timers_on) {
543aa7f3b23Shpeyerl 		return;
544d49a7f6aSrmind 	}
545aa7f3b23Shpeyerl 
546d49a7f6aSrmind 	/* XXX: Needed for ip_output(). */
547cead3b88Sozaki-r 	SOFTNET_LOCK_UNLESS_NET_MPSAFE();
54815e29e98Sad 
549d49a7f6aSrmind 	in_multi_lock(RW_WRITER);
550d49a7f6aSrmind 	igmp_timers_on = false;
551d49a7f6aSrmind 	inm = in_first_multi(&step);
552aa7f3b23Shpeyerl 	while (inm != NULL) {
553aa7f3b23Shpeyerl 		if (inm->inm_timer == 0) {
554aa7f3b23Shpeyerl 			/* do nothing */
555aa7f3b23Shpeyerl 		} else if (--inm->inm_timer == 0) {
556f49ddb8bSmycroft 			if (inm->inm_state == IGMP_DELAYING_MEMBER) {
557f49ddb8bSmycroft 				if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
558f49ddb8bSmycroft 					igmp_sendpkt(inm,
559f49ddb8bSmycroft 					    IGMP_v1_HOST_MEMBERSHIP_REPORT);
560f49ddb8bSmycroft 				else
561f49ddb8bSmycroft 					igmp_sendpkt(inm,
562f49ddb8bSmycroft 					    IGMP_v2_HOST_MEMBERSHIP_REPORT);
563f49ddb8bSmycroft 				inm->inm_state = IGMP_IDLE_MEMBER;
564f49ddb8bSmycroft 			}
565aa7f3b23Shpeyerl 		} else {
566d49a7f6aSrmind 			igmp_timers_on = true;
567aa7f3b23Shpeyerl 		}
568d49a7f6aSrmind 		inm = in_next_multi(&step);
569aa7f3b23Shpeyerl 	}
570d49a7f6aSrmind 	in_multi_unlock();
571cead3b88Sozaki-r 	SOFTNET_UNLOCK_UNLESS_NET_MPSAFE();
572aa7f3b23Shpeyerl }
573aa7f3b23Shpeyerl 
574f49ddb8bSmycroft void
igmp_slowtimo(void)57551ad03a9Sperry igmp_slowtimo(void)
576aa7f3b23Shpeyerl {
577d49a7f6aSrmind 	router_info_t *rti;
578f49ddb8bSmycroft 
579d49a7f6aSrmind 	in_multi_lock(RW_WRITER);
58027e17421Smatt 	LIST_FOREACH(rti, &rti_head, rti_link) {
581f49ddb8bSmycroft 		if (rti->rti_type == IGMP_v1_ROUTER &&
582f49ddb8bSmycroft 		    ++rti->rti_age >= IGMP_AGE_THRESHOLD) {
583f49ddb8bSmycroft 			rti->rti_type = IGMP_v2_ROUTER;
584f49ddb8bSmycroft 		}
585f49ddb8bSmycroft 	}
586d49a7f6aSrmind 	in_multi_unlock();
587f49ddb8bSmycroft }
588f49ddb8bSmycroft 
589d49a7f6aSrmind /*
590d49a7f6aSrmind  * igmp_sendpkt: construct an IGMP packet, given the multicast structure
591d49a7f6aSrmind  * and the type, and send the datagram.
592d49a7f6aSrmind  */
593d49a7f6aSrmind static void
igmp_sendpkt(struct in_multi * inm,int type)59451ad03a9Sperry igmp_sendpkt(struct in_multi *inm, int type)
595f49ddb8bSmycroft {
596f49ddb8bSmycroft 	struct mbuf *m;
597f49ddb8bSmycroft 	struct igmp *igmp;
598f49ddb8bSmycroft 	struct ip *ip;
599f49ddb8bSmycroft 	struct ip_moptions imo;
600d49a7f6aSrmind 
601d49a7f6aSrmind 	KASSERT(in_multi_lock_held());
602aa7f3b23Shpeyerl 
603aa7f3b23Shpeyerl 	MGETHDR(m, M_DONTWAIT, MT_HEADER);
604aa7f3b23Shpeyerl 	if (m == NULL)
605aa7f3b23Shpeyerl 		return;
6069008abb1Smaxv 	KASSERT(max_linkhdr + sizeof(struct ip) + IGMP_MINLEN <= MHLEN);
607d49a7f6aSrmind 
608aa7f3b23Shpeyerl 	m->m_data += max_linkhdr;
609aa7f3b23Shpeyerl 	m->m_len = sizeof(struct ip) + IGMP_MINLEN;
610aa7f3b23Shpeyerl 	m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;
611aa7f3b23Shpeyerl 
612aa7f3b23Shpeyerl 	ip = mtod(m, struct ip *);
613aa7f3b23Shpeyerl 	ip->ip_tos = 0;
614c00fa8dfSitojun 	ip->ip_len = htons(sizeof(struct ip) + IGMP_MINLEN);
615c00fa8dfSitojun 	ip->ip_off = htons(0);
616*145523e8Smaxv 	ip->ip_ttl = IP_DEFAULT_MULTICAST_TTL;
617aa7f3b23Shpeyerl 	ip->ip_p = IPPROTO_IGMP;
61862a6cce9Smycroft 	ip->ip_src = zeroin_addr;
619aa7f3b23Shpeyerl 	ip->ip_dst = inm->inm_addr;
620aa7f3b23Shpeyerl 
621de3a00eeSbrezak 	m->m_data += sizeof(struct ip);
622de3a00eeSbrezak 	m->m_len -= sizeof(struct ip);
623de3a00eeSbrezak 	igmp = mtod(m, struct igmp *);
624f49ddb8bSmycroft 	igmp->igmp_type = type;
625aa7f3b23Shpeyerl 	igmp->igmp_code = 0;
626aa7f3b23Shpeyerl 	igmp->igmp_group = inm->inm_addr;
627aa7f3b23Shpeyerl 	igmp->igmp_cksum = 0;
628aa7f3b23Shpeyerl 	igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
629de3a00eeSbrezak 	m->m_data -= sizeof(struct ip);
630de3a00eeSbrezak 	m->m_len += sizeof(struct ip);
631aa7f3b23Shpeyerl 
63243c5ab37Sozaki-r 	imo.imo_multicast_if_index = if_get_index(inm->inm_ifp);
633f49ddb8bSmycroft 	imo.imo_multicast_ttl = 1;
63485bbba5eSmaxv 
635aa7f3b23Shpeyerl 	/*
636aa7f3b23Shpeyerl 	 * Request loopback of the report if we are acting as a multicast
637aa7f3b23Shpeyerl 	 * router, so that the process-level routing demon can hear it.
638aa7f3b23Shpeyerl 	 */
639aa7f3b23Shpeyerl #ifdef MROUTING
640d49a7f6aSrmind 	extern struct socket *ip_mrouter;
641f49ddb8bSmycroft 	imo.imo_multicast_loop = (ip_mrouter != NULL);
642f49ddb8bSmycroft #else
643f49ddb8bSmycroft 	imo.imo_multicast_loop = 0;
644d49a7f6aSrmind #endif
645f49ddb8bSmycroft 
646d49a7f6aSrmind 	/*
647d49a7f6aSrmind 	 * Note: IP_IGMP_MCAST indicates that in_multilock is held.
648d49a7f6aSrmind 	 * The caller must still acquire softnet_lock for ip_output().
649d49a7f6aSrmind 	 */
6509e8d969cSozaki-r #ifndef NET_MPSAFE
651d49a7f6aSrmind 	KASSERT(mutex_owned(softnet_lock));
6529e8d969cSozaki-r #endif
653d49a7f6aSrmind 	ip_output(m, NULL, NULL, IP_IGMP_MCAST, &imo, NULL);
65483dd1069Sthorpej 	IGMP_STATINC(IGMP_STAT_SND_REPORTS);
655aa7f3b23Shpeyerl }
656bef80abbSitojun 
657bef80abbSitojun void
igmp_purgeif(ifnet_t * ifp)658d49a7f6aSrmind igmp_purgeif(ifnet_t *ifp)
659bef80abbSitojun {
660d49a7f6aSrmind 	in_multi_lock(RW_WRITER);
661d49a7f6aSrmind 	rti_delete(ifp);
662d49a7f6aSrmind 	in_multi_unlock();
663bef80abbSitojun }
66483dd1069Sthorpej 
66583dd1069Sthorpej static int
sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS)66683dd1069Sthorpej sysctl_net_inet_igmp_stats(SYSCTLFN_ARGS)
66783dd1069Sthorpej {
668d49a7f6aSrmind 	return NETSTAT_SYSCTL(igmpstat_percpu, IGMP_NSTATS);
66983dd1069Sthorpej }
67083dd1069Sthorpej 
67111281f01Spooka static void
sysctl_net_inet_igmp_setup(struct sysctllog ** clog)67211281f01Spooka sysctl_net_inet_igmp_setup(struct sysctllog **clog)
67383dd1069Sthorpej {
67483dd1069Sthorpej 	sysctl_createv(clog, 0, NULL, NULL,
67583dd1069Sthorpej 			CTLFLAG_PERMANENT,
67683dd1069Sthorpej 			CTLTYPE_NODE, "inet", NULL,
67783dd1069Sthorpej 			NULL, 0, NULL, 0,
67883dd1069Sthorpej 			CTL_NET, PF_INET, CTL_EOL);
67983dd1069Sthorpej 	sysctl_createv(clog, 0, NULL, NULL,
68083dd1069Sthorpej 			CTLFLAG_PERMANENT,
68183dd1069Sthorpej 			CTLTYPE_NODE, "igmp",
68283dd1069Sthorpej 			SYSCTL_DESCR("Internet Group Management Protocol"),
68383dd1069Sthorpej 			NULL, 0, NULL, 0,
68483dd1069Sthorpej 			CTL_NET, PF_INET, IPPROTO_IGMP, CTL_EOL);
68583dd1069Sthorpej 	sysctl_createv(clog, 0, NULL, NULL,
68683dd1069Sthorpej 			CTLFLAG_PERMANENT,
68783dd1069Sthorpej 			CTLTYPE_STRUCT, "stats",
68883dd1069Sthorpej 			SYSCTL_DESCR("IGMP statistics"),
68983dd1069Sthorpej 			sysctl_net_inet_igmp_stats, 0, NULL, 0,
69083dd1069Sthorpej 			CTL_NET, PF_INET, IPPROTO_IGMP, CTL_CREATE, CTL_EOL);
69183dd1069Sthorpej }
692