Newer
Older
*
* $Id: mcast.c,v 1.40 2002/02/08 03:57:19 davem Exp $
*
* Based on linux/ipv4/igmp.c and linux/ipv4/ip_sockglue.c
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
/* Changes:
*
* yoshfuji : fix format of router-alert option
* YOSHIFUJI Hideaki @USAGI:
* Fixed source address for MLD message based on
* <draft-ietf-magma-mld-source-05.txt>.
* YOSHIFUJI Hideaki @USAGI:
* - Ignore Queries for invalid addresses.
* - MLD for link-local addresses.
* David L Stevens <dlstevens@us.ibm.com>:
* - MLDv2 support
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/jiffies.h>
#include <linux/times.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/route.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <net/net_namespace.h>
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/if_inet6.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include <net/ip6_checksum.h>
/* Set to 3 to get tracing... */
#define MCAST_DEBUG 2
#if MCAST_DEBUG >= 3
#define MDBG(x) printk x
#else
#define MDBG(x)
#endif
/*
* These header formats should be in a separate include file, but icmpv6.h
* doesn't have in6_addr defined in all cases, there is no __u128, and no
* other files reference these.
*
* +-DLS 4/14/03
*/
/* Multicast Listener Discovery version 2 headers */
struct mld2_grec {
__u8 grec_type;
__u8 grec_auxwords;
struct in6_addr grec_mca;
struct in6_addr grec_src[0];
};
struct mld2_report {
__u8 type;
__u8 resv1;
struct mld2_grec grec[0];
};
struct mld2_query {
__u8 type;
__u8 code;
struct in6_addr mca;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 qrv:3,
suppress:1,
resv2:4;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u8 resv2:4,
suppress:1,
qrv:3;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 qqic;
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
struct in6_addr srcs[0];
};
static struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT;
/* Big mc list lock for all the sockets */
static DEFINE_RWLOCK(ipv6_sk_mc_lock);
static struct socket *igmp6_socket;
int __ipv6_dev_mc_dec(struct inet6_dev *idev, struct in6_addr *addr);
static void igmp6_join_group(struct ifmcaddr6 *ma);
static void igmp6_leave_group(struct ifmcaddr6 *ma);
static void igmp6_timer_handler(unsigned long data);
static void mld_gq_timer_expire(unsigned long data);
static void mld_ifc_timer_expire(unsigned long data);
static void mld_ifc_event(struct inet6_dev *idev);
static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *pmc);
static void mld_del_delrec(struct inet6_dev *idev, struct in6_addr *addr);
static void mld_clear_delrec(struct inet6_dev *idev);
static int sf_setstate(struct ifmcaddr6 *pmc);
static void sf_markstate(struct ifmcaddr6 *pmc);
static void ip6_mc_clear_src(struct ifmcaddr6 *pmc);
static int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca,
int sfmode, int sfcount, struct in6_addr *psfsrc,
int delta);
static int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca,
int sfmode, int sfcount, struct in6_addr *psfsrc,
int delta);
static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
struct inet6_dev *idev);
#define IGMP6_UNSOLICITED_IVAL (10*HZ)
#define MLD_QRV_DEFAULT 2
#define MLD_V1_SEEN(idev) (ipv6_devconf.force_mld_version == 1 || \
(idev)->cnf.force_mld_version == 1 || \
((idev)->mc_v1_seen && \
time_before(jiffies, (idev)->mc_v1_seen)))
#define MLDV2_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value))
#define MLDV2_EXP(thresh, nbmant, nbexp, value) \
((value) < (thresh) ? (value) : \
((MLDV2_MASK(value, nbmant) | (1<<(nbmant))) << \
(MLDV2_MASK((value) >> (nbmant), nbexp) + (nbexp))))
#define MLDV2_QQIC(value) MLDV2_EXP(0x80, 4, 3, value)
#define MLDV2_MRC(value) MLDV2_EXP(0x8000, 12, 3, value)
#define IPV6_MLD_MAX_MSF 64
int sysctl_mld_max_msf __read_mostly = IPV6_MLD_MAX_MSF;
/*
* socket join on multicast group
*/
int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr)
{
struct net_device *dev = NULL;
struct ipv6_mc_socklist *mc_lst;
struct ipv6_pinfo *np = inet6_sk(sk);
int err;
if (!ipv6_addr_is_multicast(addr))
return -EINVAL;
read_lock_bh(&ipv6_sk_mc_lock);
for (mc_lst=np->ipv6_mc_list; mc_lst; mc_lst=mc_lst->next) {
if ((ifindex == 0 || mc_lst->ifindex == ifindex) &&
ipv6_addr_equal(&mc_lst->addr, addr)) {
read_unlock_bh(&ipv6_sk_mc_lock);
return -EADDRINUSE;
}
}
read_unlock_bh(&ipv6_sk_mc_lock);
mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL);
if (mc_lst == NULL)
return -ENOMEM;
mc_lst->next = NULL;
ipv6_addr_copy(&mc_lst->addr, addr);
if (ifindex == 0) {
struct rt6_info *rt;
rt = rt6_lookup(&init_net, addr, NULL, 0, 0);
if (rt) {
dev = rt->rt6i_dev;
dev_hold(dev);
dst_release(&rt->u.dst);
}
} else
dev = dev_get_by_index(&init_net, ifindex);
if (dev == NULL) {
sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
return -ENODEV;
}
mc_lst->ifindex = dev->ifindex;
mc_lst->sfmode = MCAST_EXCLUDE;
rwlock_init(&mc_lst->sflock);
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
mc_lst->sflist = NULL;
/*
* now add/increase the group membership on the device
*/
err = ipv6_dev_mc_inc(dev, addr);
if (err) {
sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
dev_put(dev);
return err;
}
write_lock_bh(&ipv6_sk_mc_lock);
mc_lst->next = np->ipv6_mc_list;
np->ipv6_mc_list = mc_lst;
write_unlock_bh(&ipv6_sk_mc_lock);
dev_put(dev);
return 0;
}
/*
* socket leave on multicast group
*/
int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct ipv6_mc_socklist *mc_lst, **lnk;
write_lock_bh(&ipv6_sk_mc_lock);
for (lnk = &np->ipv6_mc_list; (mc_lst = *lnk) !=NULL ; lnk = &mc_lst->next) {
if ((ifindex == 0 || mc_lst->ifindex == ifindex) &&
ipv6_addr_equal(&mc_lst->addr, addr)) {
struct net_device *dev;
*lnk = mc_lst->next;
write_unlock_bh(&ipv6_sk_mc_lock);
if ((dev = dev_get_by_index(&init_net, mc_lst->ifindex)) != NULL) {
(void) ip6_mc_leave_src(sk, mc_lst, idev);
if (idev) {
__ipv6_dev_mc_dec(idev, &mc_lst->addr);
in6_dev_put(idev);
}
dev_put(dev);
} else
(void) ip6_mc_leave_src(sk, mc_lst, NULL);
sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
return 0;
}
}
write_unlock_bh(&ipv6_sk_mc_lock);

David L Stevens
committed
return -EADDRNOTAVAIL;
}
static struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex)
{
struct net_device *dev = NULL;
struct inet6_dev *idev = NULL;
if (ifindex == 0) {
struct rt6_info *rt;
rt = rt6_lookup(&init_net, group, NULL, 0, 0);
if (rt) {
dev = rt->rt6i_dev;
dev_hold(dev);
dst_release(&rt->u.dst);
}
} else
dev = dev_get_by_index(&init_net, ifindex);
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
if (!dev)
return NULL;
idev = in6_dev_get(dev);
if (!idev) {
dev_put(dev);
return NULL;
}
read_lock_bh(&idev->lock);
if (idev->dead) {
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
dev_put(dev);
return NULL;
}
return idev;
}
void ipv6_sock_mc_close(struct sock *sk)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct ipv6_mc_socklist *mc_lst;
write_lock_bh(&ipv6_sk_mc_lock);
while ((mc_lst = np->ipv6_mc_list) != NULL) {
struct net_device *dev;
np->ipv6_mc_list = mc_lst->next;
write_unlock_bh(&ipv6_sk_mc_lock);
dev = dev_get_by_index(&init_net, mc_lst->ifindex);
if (dev) {
struct inet6_dev *idev = in6_dev_get(dev);
(void) ip6_mc_leave_src(sk, mc_lst, idev);
if (idev) {
__ipv6_dev_mc_dec(idev, &mc_lst->addr);
in6_dev_put(idev);
}
dev_put(dev);
} else
(void) ip6_mc_leave_src(sk, mc_lst, NULL);
sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
write_lock_bh(&ipv6_sk_mc_lock);
}
write_unlock_bh(&ipv6_sk_mc_lock);
}
int ip6_mc_source(int add, int omode, struct sock *sk,
struct group_source_req *pgsr)
{
struct in6_addr *source, *group;
struct ipv6_mc_socklist *pmc;
struct net_device *dev;
struct inet6_dev *idev;
struct ipv6_pinfo *inet6 = inet6_sk(sk);
struct ip6_sf_socklist *psl;
int i, j, rv;
int pmclocked = 0;
int err;
if (pgsr->gsr_group.ss_family != AF_INET6 ||
pgsr->gsr_source.ss_family != AF_INET6)
return -EINVAL;
source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr;
group = &((struct sockaddr_in6 *)&pgsr->gsr_group)->sin6_addr;
if (!ipv6_addr_is_multicast(group))
return -EINVAL;
idev = ip6_mc_find_dev(group, pgsr->gsr_interface);
if (!idev)
return -ENODEV;
dev = idev->dev;
err = -EADDRNOTAVAIL;
for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface)
continue;
if (ipv6_addr_equal(&pmc->addr, group))
break;
}
if (!pmc) { /* must have a prior join */
err = -EINVAL;
/* if a source filter was set, must be the same mode as before */
if (pmc->sflist) {
if (pmc->sfmode != omode) {
err = -EINVAL;
} else if (pmc->sfmode != omode) {
/* allow mode switches for empty-set filters */
ip6_mc_add_src(idev, group, omode, 0, NULL, 0);
ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
pmc->sfmode = omode;
}
write_lock_bh(&pmc->sflock);
pmclocked = 1;
goto done; /* err = -EADDRNOTAVAIL */
rv = !0;
for (i=0; i<psl->sl_count; i++) {
rv = memcmp(&psl->sl_addr[i], source,
sizeof(struct in6_addr));
if (rv == 0)
break;
}
if (rv) /* source not found */
goto done; /* err = -EADDRNOTAVAIL */
/* special case - (INCLUDE, empty) == LEAVE_GROUP */
if (psl->sl_count == 1 && omode == MCAST_INCLUDE) {
leavegroup = 1;
goto done;
}
/* update the interface filter */
ip6_mc_del_src(idev, group, omode, 1, source, 1);
for (j=i+1; j<psl->sl_count; j++)
psl->sl_addr[j-1] = psl->sl_addr[j];
psl->sl_count--;
err = 0;
goto done;
}
/* else, add a new source to the filter */
if (psl && psl->sl_count >= sysctl_mld_max_msf) {
err = -ENOBUFS;
goto done;
}
if (!psl || psl->sl_count == psl->sl_max) {
struct ip6_sf_socklist *newpsl;
int count = IP6_SFBLOCK;
if (psl)
count += psl->sl_max;
newpsl = sock_kmalloc(sk, IP6_SFLSIZE(count), GFP_ATOMIC);
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
if (!newpsl) {
err = -ENOBUFS;
goto done;
}
newpsl->sl_max = count;
newpsl->sl_count = count - IP6_SFBLOCK;
if (psl) {
for (i=0; i<psl->sl_count; i++)
newpsl->sl_addr[i] = psl->sl_addr[i];
sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max));
}
pmc->sflist = psl = newpsl;
}
rv = 1; /* > 0 for insert logic below if sl_count is 0 */
for (i=0; i<psl->sl_count; i++) {
rv = memcmp(&psl->sl_addr[i], source, sizeof(struct in6_addr));
if (rv == 0)
break;
}
if (rv == 0) /* address already there is an error */
goto done;
for (j=psl->sl_count-1; j>=i; j--)
psl->sl_addr[j+1] = psl->sl_addr[j];
psl->sl_addr[i] = *source;
psl->sl_count++;
err = 0;
/* update the interface list */
ip6_mc_add_src(idev, group, omode, 1, source, 1);
done:
if (pmclocked)
write_unlock_bh(&pmc->sflock);
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
dev_put(dev);
if (leavegroup)
return ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group);
return err;
}
int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
{
struct in6_addr *group;
struct ipv6_mc_socklist *pmc;
struct net_device *dev;
struct inet6_dev *idev;
struct ipv6_pinfo *inet6 = inet6_sk(sk);
struct ip6_sf_socklist *newpsl, *psl;

David L Stevens
committed
int leavegroup = 0;
int i, err;
group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
if (!ipv6_addr_is_multicast(group))
return -EINVAL;
if (gsf->gf_fmode != MCAST_INCLUDE &&
gsf->gf_fmode != MCAST_EXCLUDE)
return -EINVAL;
idev = ip6_mc_find_dev(group, gsf->gf_interface);
if (!idev)
return -ENODEV;
dev = idev->dev;
read_lock_bh(&ipv6_sk_mc_lock);

David L Stevens
committed
if (gsf->gf_fmode == MCAST_INCLUDE && gsf->gf_numsrc == 0) {
leavegroup = 1;
goto done;
}
for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
if (pmc->ifindex != gsf->gf_interface)
continue;
if (ipv6_addr_equal(&pmc->addr, group))
break;
}
if (!pmc) { /* must have a prior join */
err = -EINVAL;
newpsl = sock_kmalloc(sk, IP6_SFLSIZE(gsf->gf_numsrc),
GFP_ATOMIC);
if (!newpsl) {
err = -ENOBUFS;
goto done;
}
newpsl->sl_max = newpsl->sl_count = gsf->gf_numsrc;
for (i=0; i<newpsl->sl_count; ++i) {
struct sockaddr_in6 *psin6;
psin6 = (struct sockaddr_in6 *)&gsf->gf_slist[i];
newpsl->sl_addr[i] = psin6->sin6_addr;
}
err = ip6_mc_add_src(idev, group, gsf->gf_fmode,
newpsl->sl_count, newpsl->sl_addr, 0);
if (err) {
sock_kfree_s(sk, newpsl, IP6_SFLSIZE(newpsl->sl_max));
goto done;
}
} else {
(void) ip6_mc_add_src(idev, group, gsf->gf_fmode, 0, NULL, 0);
}
write_lock_bh(&pmc->sflock);
psl = pmc->sflist;
if (psl) {
(void) ip6_mc_del_src(idev, group, pmc->sfmode,
psl->sl_count, psl->sl_addr, 0);
sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max));
} else
(void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
pmc->sflist = newpsl;
pmc->sfmode = gsf->gf_fmode;
write_unlock_bh(&pmc->sflock);
read_unlock_bh(&ipv6_sk_mc_lock);
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
dev_put(dev);

David L Stevens
committed
if (leavegroup)
err = ipv6_sock_mc_drop(sk, gsf->gf_interface, group);
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
return err;
}
int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
struct group_filter __user *optval, int __user *optlen)
{
int err, i, count, copycount;
struct in6_addr *group;
struct ipv6_mc_socklist *pmc;
struct inet6_dev *idev;
struct net_device *dev;
struct ipv6_pinfo *inet6 = inet6_sk(sk);
struct ip6_sf_socklist *psl;
group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
if (!ipv6_addr_is_multicast(group))
return -EINVAL;
idev = ip6_mc_find_dev(group, gsf->gf_interface);
if (!idev)
return -ENODEV;
dev = idev->dev;
err = -EADDRNOTAVAIL;
/*
* changes to the ipv6_mc_list require the socket lock and
* a read lock on ip6_sk_mc_lock. We have the socket lock,
* so reading the list is safe.
*/
for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
if (pmc->ifindex != gsf->gf_interface)
continue;
if (ipv6_addr_equal(group, &pmc->addr))
break;
}
if (!pmc) /* must have a prior join */
goto done;
gsf->gf_fmode = pmc->sfmode;
psl = pmc->sflist;
count = psl ? psl->sl_count : 0;
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
dev_put(dev);
copycount = count < gsf->gf_numsrc ? count : gsf->gf_numsrc;
gsf->gf_numsrc = count;
if (put_user(GROUP_FILTER_SIZE(copycount), optlen) ||
copy_to_user(optval, gsf, GROUP_FILTER_SIZE(0))) {
return -EFAULT;
}
/* changes to psl require the socket lock, a read lock on
* on ipv6_sk_mc_lock and a write lock on pmc->sflock. We
* have the socket lock, so reading here is safe.
*/
for (i=0; i<copycount; i++) {
struct sockaddr_in6 *psin6;
struct sockaddr_storage ss;
psin6 = (struct sockaddr_in6 *)&ss;
memset(&ss, 0, sizeof(ss));
psin6->sin6_family = AF_INET6;
psin6->sin6_addr = psl->sl_addr[i];
if (copy_to_user(&optval->gf_slist[i], &ss, sizeof(ss)))
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
return -EFAULT;
}
return 0;
done:
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
dev_put(dev);
return err;
}
int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr,
struct in6_addr *src_addr)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct ipv6_mc_socklist *mc;
struct ip6_sf_socklist *psl;
int rv = 1;
read_lock(&ipv6_sk_mc_lock);
for (mc = np->ipv6_mc_list; mc; mc = mc->next) {
if (ipv6_addr_equal(&mc->addr, mc_addr))
break;
}
if (!mc) {
read_unlock(&ipv6_sk_mc_lock);
return 1;
}
read_lock(&mc->sflock);
psl = mc->sflist;
if (!psl) {
rv = mc->sfmode == MCAST_EXCLUDE;
} else {
int i;
for (i=0; i<psl->sl_count; i++) {
if (ipv6_addr_equal(&psl->sl_addr[i], src_addr))
break;
}
if (mc->sfmode == MCAST_INCLUDE && i >= psl->sl_count)
rv = 0;
if (mc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
rv = 0;
}
read_unlock(&mc->sflock);
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
read_unlock(&ipv6_sk_mc_lock);
return rv;
}
static void ma_put(struct ifmcaddr6 *mc)
{
if (atomic_dec_and_test(&mc->mca_refcnt)) {
in6_dev_put(mc->idev);
kfree(mc);
}
}
static void igmp6_group_added(struct ifmcaddr6 *mc)
{
struct net_device *dev = mc->idev->dev;
char buf[MAX_ADDR_LEN];
spin_lock_bh(&mc->mca_lock);
if (!(mc->mca_flags&MAF_LOADED)) {
mc->mca_flags |= MAF_LOADED;
if (ndisc_mc_map(&mc->mca_addr, buf, dev, 0) == 0)
dev_mc_add(dev, buf, dev->addr_len, 0);
}
spin_unlock_bh(&mc->mca_lock);
if (!(dev->flags & IFF_UP) || (mc->mca_flags & MAF_NOREPORT))
return;
if (MLD_V1_SEEN(mc->idev)) {
igmp6_join_group(mc);
return;
}
/* else v2 */
mc->mca_crcount = mc->idev->mc_qrv;
mld_ifc_event(mc->idev);
}
static void igmp6_group_dropped(struct ifmcaddr6 *mc)
{
struct net_device *dev = mc->idev->dev;
char buf[MAX_ADDR_LEN];
spin_lock_bh(&mc->mca_lock);
if (mc->mca_flags&MAF_LOADED) {
mc->mca_flags &= ~MAF_LOADED;
if (ndisc_mc_map(&mc->mca_addr, buf, dev, 0) == 0)
dev_mc_delete(dev, buf, dev->addr_len, 0);
}
if (mc->mca_flags & MAF_NOREPORT)
goto done;
spin_unlock_bh(&mc->mca_lock);
if (!mc->idev->dead)
igmp6_leave_group(mc);
spin_lock_bh(&mc->mca_lock);
if (del_timer(&mc->mca_timer))
atomic_dec(&mc->mca_refcnt);
done:
ip6_mc_clear_src(mc);
spin_unlock_bh(&mc->mca_lock);
}
/*
* deleted ifmcaddr6 manipulation
*/
static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im)
{
struct ifmcaddr6 *pmc;
/* this is an "ifmcaddr6" for convenience; only the fields below
* are actually used. In particular, the refcnt and users are not
* used for management of the delete list. Using the same structure
* for deleted items allows change reports to use common code with
* non-deleted or query-response MCA's.
*/
pmc = kzalloc(sizeof(*pmc), GFP_ATOMIC);
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
spin_lock_bh(&im->mca_lock);
spin_lock_init(&pmc->mca_lock);
pmc->idev = im->idev;
in6_dev_hold(idev);
pmc->mca_addr = im->mca_addr;
pmc->mca_crcount = idev->mc_qrv;
pmc->mca_sfmode = im->mca_sfmode;
if (pmc->mca_sfmode == MCAST_INCLUDE) {
struct ip6_sf_list *psf;
pmc->mca_tomb = im->mca_tomb;
pmc->mca_sources = im->mca_sources;
im->mca_tomb = im->mca_sources = NULL;
for (psf=pmc->mca_sources; psf; psf=psf->sf_next)
psf->sf_crcount = pmc->mca_crcount;
}
spin_unlock_bh(&im->mca_lock);
write_lock_bh(&idev->mc_lock);
pmc->next = idev->mc_tomb;
idev->mc_tomb = pmc;
write_unlock_bh(&idev->mc_lock);
}
static void mld_del_delrec(struct inet6_dev *idev, struct in6_addr *pmca)
{
struct ifmcaddr6 *pmc, *pmc_prev;
struct ip6_sf_list *psf, *psf_next;
write_lock_bh(&idev->mc_lock);
pmc_prev = NULL;
for (pmc=idev->mc_tomb; pmc; pmc=pmc->next) {
if (ipv6_addr_equal(&pmc->mca_addr, pmca))
break;
pmc_prev = pmc;
}
if (pmc) {
if (pmc_prev)
pmc_prev->next = pmc->next;
else
idev->mc_tomb = pmc->next;
}
write_unlock_bh(&idev->mc_lock);
if (pmc) {
for (psf=pmc->mca_tomb; psf; psf=psf_next) {
psf_next = psf->sf_next;
kfree(psf);
}
in6_dev_put(pmc->idev);
kfree(pmc);
}
}
static void mld_clear_delrec(struct inet6_dev *idev)
{
struct ifmcaddr6 *pmc, *nextpmc;
write_lock_bh(&idev->mc_lock);
pmc = idev->mc_tomb;
idev->mc_tomb = NULL;
write_unlock_bh(&idev->mc_lock);
for (; pmc; pmc = nextpmc) {
nextpmc = pmc->next;
ip6_mc_clear_src(pmc);
in6_dev_put(pmc->idev);
kfree(pmc);
}
/* clear dead sources, too */
read_lock_bh(&idev->lock);
for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
struct ip6_sf_list *psf, *psf_next;
spin_lock_bh(&pmc->mca_lock);
psf = pmc->mca_tomb;
pmc->mca_tomb = NULL;
spin_unlock_bh(&pmc->mca_lock);
for (; psf; psf=psf_next) {
psf_next = psf->sf_next;
kfree(psf);
}
}
read_unlock_bh(&idev->lock);
}
/*
* device multicast group inc (add if not found)
*/
int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr)
{
struct ifmcaddr6 *mc;
struct inet6_dev *idev;
idev = in6_dev_get(dev);
if (idev == NULL)
return -EINVAL;
write_lock_bh(&idev->lock);
if (idev->dead) {
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return -ENODEV;
}
for (mc = idev->mc_list; mc; mc = mc->next) {
if (ipv6_addr_equal(&mc->mca_addr, addr)) {
mc->mca_users++;
write_unlock_bh(&idev->lock);
ip6_mc_add_src(idev, &mc->mca_addr, MCAST_EXCLUDE, 0,
NULL, 0);
in6_dev_put(idev);
return 0;
}
}
/*
* not found: create a new one.
*/
mc = kzalloc(sizeof(struct ifmcaddr6), GFP_ATOMIC);
if (mc == NULL) {
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return -ENOMEM;
}
setup_timer(&mc->mca_timer, igmp6_timer_handler, (unsigned long)mc);
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
ipv6_addr_copy(&mc->mca_addr, addr);
mc->idev = idev;
mc->mca_users = 1;
/* mca_stamp should be updated upon changes */
mc->mca_cstamp = mc->mca_tstamp = jiffies;
atomic_set(&mc->mca_refcnt, 2);
spin_lock_init(&mc->mca_lock);
/* initial mode is (EX, empty) */
mc->mca_sfmode = MCAST_EXCLUDE;
mc->mca_sfcount[MCAST_EXCLUDE] = 1;
if (ipv6_addr_is_ll_all_nodes(&mc->mca_addr) ||
IPV6_ADDR_MC_SCOPE(&mc->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL)
mc->mca_flags |= MAF_NOREPORT;
mc->next = idev->mc_list;
idev->mc_list = mc;
write_unlock_bh(&idev->lock);
mld_del_delrec(idev, &mc->mca_addr);
igmp6_group_added(mc);
ma_put(mc);
return 0;
}
/*
* device multicast group del
*/
int __ipv6_dev_mc_dec(struct inet6_dev *idev, struct in6_addr *addr)
{
struct ifmcaddr6 *ma, **map;
write_lock_bh(&idev->lock);
for (map = &idev->mc_list; (ma=*map) != NULL; map = &ma->next) {
if (ipv6_addr_equal(&ma->mca_addr, addr)) {
if (--ma->mca_users == 0) {
*map = ma->next;
write_unlock_bh(&idev->lock);
igmp6_group_dropped(ma);
ma_put(ma);
return 0;
}
write_unlock_bh(&idev->lock);
return 0;
}
}
write_unlock_bh(&idev->lock);
return -ENOENT;
}
int ipv6_dev_mc_dec(struct net_device *dev, struct in6_addr *addr)
{
struct inet6_dev *idev = in6_dev_get(dev);
int err;
if (!idev)
return -ENODEV;
err = __ipv6_dev_mc_dec(idev, addr);
in6_dev_put(idev);
return err;
}
/*
* identify MLD packets for MLD filter exceptions
*/
int ipv6_is_mld(struct sk_buff *skb, int nexthdr)
{
struct icmp6hdr *pic;
if (nexthdr != IPPROTO_ICMPV6)
return 0;
if (!pskb_may_pull(skb, sizeof(struct icmp6hdr)))
return 0;