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