]> git.neil.brown.name Git - history.git/commitdiff
[IPV6]: Add MLDv2 support.
authorDavid Stevens <dlstevens@us.ibm.com>
Tue, 15 Apr 2003 18:46:01 +0000 (11:46 -0700)
committerDavid S. Miller <davem@nuts.ninka.net>
Tue, 15 Apr 2003 18:46:01 +0000 (11:46 -0700)
14 files changed:
include/linux/icmpv6.h
include/linux/in.h
include/linux/ip.h
include/linux/tcp.h
include/linux/udp.h
include/net/addrconf.h
include/net/icmp.h
include/net/if_inet6.h
net/ipv6/ip6_input.c
net/ipv6/ip6_output.c
net/ipv6/ipv6_sockglue.c
net/ipv6/mcast.c
net/ipv6/raw.c
net/ipv6/udp.c

index 2e5fcb59b131d37430560b1dc1d6faa9ddd96ee3..604cef611b6c611b6152e11c77f7da8671191c34 100644 (file)
@@ -86,6 +86,19 @@ struct icmp6hdr {
 #define ICMPV6_MGM_REPORT              131
 #define ICMPV6_MGM_REDUCTION           132
 
+/* definitions for MLDv2 */
+
+#define MLD2_MODE_IS_INCLUDE   1
+#define MLD2_MODE_IS_EXCLUDE   2
+#define MLD2_CHANGE_TO_INCLUDE 3
+#define MLD2_CHANGE_TO_EXCLUDE 4
+#define MLD2_ALLOW_NEW_SOURCES 5
+#define MLD2_BLOCK_OLD_SOURCES 6
+
+/* this must be an IANA-assigned value; 206 for testing only */
+#define ICMPV6_MLD2_REPORT             206
+#define MLD2_ALL_MCR_INIT { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0,0,0x16 } } }
+
 /*
  *     Codes for Destination Unreachable
  */
index bd2ec6c01f4f4d18e59330c56b2939235fb449c0..5a5efb354b49be22d9b712dab08f7f5fe5d15cb4 100644 (file)
@@ -90,6 +90,13 @@ struct in_addr {
 #define IP_ADD_SOURCE_MEMBERSHIP       39
 #define IP_DROP_SOURCE_MEMBERSHIP      40
 #define IP_MSFILTER                    41
+#define MCAST_JOIN_GROUP               42
+#define MCAST_BLOCK_SOURCE             43
+#define MCAST_UNBLOCK_SOURCE           44
+#define MCAST_LEAVE_GROUP              45
+#define MCAST_JOIN_SOURCE_GROUP                46
+#define MCAST_LEAVE_SOURCE_GROUP       47
+#define MCAST_MSFILTER                 48
 
 #define MCAST_EXCLUDE  0
 #define MCAST_INCLUDE  1
@@ -131,6 +138,32 @@ struct ip_msfilter {
        (sizeof(struct ip_msfilter) - sizeof(__u32) \
        + (numsrc) * sizeof(__u32))
 
+struct group_req
+{
+       __u32                   gr_interface;   /* interface index */
+       struct sockaddr_storage gr_group;       /* group address */
+};
+
+struct group_source_req
+{
+       __u32                   gsr_interface;  /* interface index */
+       struct sockaddr_storage gsr_group;      /* group address */
+       struct sockaddr_storage gsr_source;     /* source address */
+};
+
+struct group_filter
+{
+       __u32                   gf_interface;   /* interface index */
+       struct sockaddr_storage gf_group;       /* multicast address */
+       __u32                   gf_fmode;       /* filter mode */
+       __u32                   gf_numsrc;      /* number of sources */
+       struct sockaddr_storage gf_slist[1];    /* interface index */
+};
+
+#define GROUP_FILTER_SIZE(numsrc) \
+       (sizeof(struct group_filter) - sizeof(struct sockaddr_storage) \
+       + (numsrc) * sizeof(struct sockaddr_storage))
+
 struct in_pktinfo
 {
        int             ipi_ifindex;
index 5d3212a73ace04514a769dbabafb0a2a14cbaa77..dca25a95d267be9ce8e2b7f07bfcf7607a85f985 100644 (file)
@@ -79,6 +79,7 @@
 #define        IPOPT_TS_PRESPEC        3               /* specified modules only */
 
 #ifdef __KERNEL__
+#include <linux/config.h>
 #include <linux/types.h>
 #include <net/sock.h>
 #include <linux/igmp.h>
index da4be9ce631d07e2ff47314d2f417bd87d53f82d..f5d9de777530d737109b835e20bacf9416235415 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef _LINUX_TCP_H
 #define _LINUX_TCP_H
 
+#include <linux/config.h>
 #include <linux/skbuff.h>
 #include <asm/byteorder.h>
 #include <net/sock.h>
index 2cd62b5b79f31a45545be28a14af68299b1bbb0a..0d87073be1b4cba1fe81e9a9e753628a04e3b5a5 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef _LINUX_UDP_H
 #define _LINUX_UDP_H
 
+#include <linux/config.h>
 #include <asm/byteorder.h>
 #include <net/sock.h>
 #include <linux/ip.h>
index 3a63c42022cf604980a5470e1935b31f5038de2f..ff3cf48bf7eea76aa4dbd814704d1d0f4c34a11b 100644 (file)
@@ -76,49 +76,38 @@ extern void                 addrconf_leave_solict(struct net_device *dev,
 /*
  *     multicast prototypes (mcast.c)
  */
-extern int                     ipv6_sock_mc_join(struct sock *sk, 
-                                                 int ifindex, 
-                                                 struct in6_addr *addr);
-extern int                     ipv6_sock_mc_drop(struct sock *sk,
-                                                 int ifindex, 
-                                                 struct in6_addr *addr);
-extern void                    ipv6_sock_mc_close(struct sock *sk);
-extern int                     inet6_mc_check(struct sock *sk, struct in6_addr *addr);
-
-extern int                     ipv6_dev_mc_inc(struct net_device *dev,
-                                               struct in6_addr *addr);
-extern int                     ipv6_dev_mc_dec(struct net_device *dev,
-                                               struct in6_addr *addr);
-extern void                    ipv6_mc_up(struct inet6_dev *idev);
-extern void                    ipv6_mc_down(struct inet6_dev *idev);
-extern void                    ipv6_mc_init_dev(struct inet6_dev *idev);
-extern void                    ipv6_mc_destroy_dev(struct inet6_dev *idev);
-extern void                    addrconf_dad_failure(struct inet6_ifaddr *ifp);
-
-extern int                     ipv6_chk_mcast_addr(struct net_device *dev,
-                                                   struct in6_addr *addr);
-
-extern void                    addrconf_prefix_rcv(struct net_device *dev,
-                                                   u8 *opt, int len);
+extern int ipv6_sock_mc_join(struct sock *sk, int ifindex, 
+                 struct in6_addr *addr);
+extern int ipv6_sock_mc_drop(struct sock *sk, int ifindex, 
+                 struct in6_addr *addr);
+extern void ipv6_sock_mc_close(struct sock *sk);
+extern int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr,
+               struct in6_addr *src_addr);
+
+extern int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr);
+extern int ipv6_dev_mc_dec(struct net_device *dev, struct in6_addr *addr);
+extern void ipv6_mc_up(struct inet6_dev *idev);
+extern void ipv6_mc_down(struct inet6_dev *idev);
+extern void ipv6_mc_init_dev(struct inet6_dev *idev);
+extern void ipv6_mc_destroy_dev(struct inet6_dev *idev);
+extern void addrconf_dad_failure(struct inet6_ifaddr *ifp);
+
+extern int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group,
+               struct in6_addr *src_addr);
+
+extern void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len);
 
 /*
  *     anycast prototypes (anycast.c)
  */
-extern int                     ipv6_sock_ac_join(struct sock *sk, 
-                                                 int ifindex, 
-                                                 struct in6_addr *addr);
-extern int                     ipv6_sock_ac_drop(struct sock *sk,
-                                                 int ifindex, 
-                                                 struct in6_addr *addr);
-extern void                    ipv6_sock_ac_close(struct sock *sk);
-extern int                     inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex);
-
-extern int                     ipv6_dev_ac_inc(struct net_device *dev,
-                                               struct in6_addr *addr);
-extern int                     ipv6_dev_ac_dec(struct net_device *dev,
-                                               struct in6_addr *addr);
-extern int                     ipv6_chk_acast_addr(struct net_device *dev,
-                                               struct in6_addr *addr);
+extern int ipv6_sock_ac_join(struct sock *sk,int ifindex,struct in6_addr *addr);
+extern int ipv6_sock_ac_drop(struct sock *sk,int ifindex,struct in6_addr *addr);
+extern void ipv6_sock_ac_close(struct sock *sk);
+extern int inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex);
+
+extern int ipv6_dev_ac_inc(struct net_device *dev, struct in6_addr *addr);
+extern int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr);
+extern int ipv6_chk_acast_addr(struct net_device *dev, struct in6_addr *addr);
 
 
 /* Device notifier */
index 81c6a6c84e02d5d97cc8354642f457e107c72442..edefb1387c95c71b69fa3ba9367fef297488c688 100644 (file)
@@ -18,6 +18,7 @@
 #ifndef _ICMP_H
 #define        _ICMP_H
 
+#include <linux/config.h>
 #include <linux/icmp.h>
 #include <linux/skbuff.h>
 
index edf4185f9e7e4741d16d4fdd801dc75f4f6c6c9f..59d5a888c921aed371845f8669f85d726af2a631 100644 (file)
@@ -52,27 +52,58 @@ struct inet6_ifaddr
        int                     dead;
 };
 
+struct ip6_sf_socklist
+{
+       unsigned int            sl_max;
+       unsigned int            sl_count;
+       struct in6_addr         sl_addr[0];
+};
+
+#define IP6_SFLSIZE(count)     (sizeof(struct ip6_sf_socklist) + \
+       (count) * sizeof(struct in6_addr))
+
+#define IP6_SFBLOCK    10      /* allocate this many at once */
+
 struct ipv6_mc_socklist
 {
        struct in6_addr         addr;
        int                     ifindex;
        struct ipv6_mc_socklist *next;
+       unsigned int            sfmode;         /* MCAST_{INCLUDE,EXCLUDE} */
+       struct ip6_sf_socklist  *sflist;
+};
+
+struct ip6_sf_list
+{
+       struct ip6_sf_list      *sf_next;
+       struct in6_addr         sf_addr;
+       unsigned long           sf_count[2];    /* include/exclude counts */
+       unsigned char           sf_gsresp;      /* include in g & s response? */
+       unsigned char           sf_oldin;       /* change state */
+       unsigned char           sf_crcount;     /* retrans. left to send */
 };
 
 #define MAF_TIMER_RUNNING      0x01
 #define MAF_LAST_REPORTER      0x02
 #define MAF_LOADED             0x04
+#define MAF_NOREPORT           0x08
+#define MAF_GSQUERY            0x10
 
 struct ifmcaddr6
 {
        struct in6_addr         mca_addr;
        struct inet6_dev        *idev;
        struct ifmcaddr6        *next;
+       struct ip6_sf_list      *mca_sources;
+       struct ip6_sf_list      *mca_tomb;
+       unsigned int            mca_sfmode;
+       unsigned long           mca_sfcount[2];
        struct timer_list       mca_timer;
        unsigned                mca_flags;
        int                     mca_users;
        atomic_t                mca_refcnt;
        spinlock_t              mca_lock;
+       unsigned char           mca_crcount;
 };
 
 /* Anycast stuff */
@@ -126,7 +157,18 @@ struct inet6_dev
        struct net_device               *dev;
 
        struct inet6_ifaddr     *addr_list;
+
        struct ifmcaddr6        *mc_list;
+       struct ifmcaddr6        *mc_tomb;
+       rwlock_t                mc_lock;
+       unsigned long           mc_v1_seen;
+       unsigned long           mc_maxdelay;
+       unsigned char           mc_qrv;
+       unsigned char           mc_gq_running;
+       unsigned char           mc_ifc_count;
+       struct timer_list       mc_gq_timer;    /* general query timer */
+       struct timer_list       mc_ifc_timer;   /* interface change timer */
+
        struct ifacaddr6        *ac_list;
        rwlock_t                lock;
        atomic_t                refcnt;
index dad093996eaa1781481b30f92bcfa3b3decd54be..c737d2f3d486c62b210ce7f24bc4848040b9db0d 100644 (file)
@@ -217,7 +217,7 @@ int ip6_mc_input(struct sk_buff *skb)
        IP6_INC_STATS_BH(Ip6InMcastPkts);
 
        hdr = skb->nh.ipv6h;
-       if (ipv6_chk_mcast_addr(skb->dev, &hdr->daddr))
+       if (ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, &hdr->saddr))
                deliver = 1;
 
        /*
index bacdf3cfdfe30ace53e52fc92ad98ce4a225a18d..a3e58a770886a4aea34ab9796bead63dde1faab3 100644 (file)
@@ -110,7 +110,8 @@ int ip6_output(struct sk_buff *skb)
                struct ipv6_pinfo* np = skb->sk ? inet6_sk(skb->sk) : NULL;
 
                if (!(dev->flags & IFF_LOOPBACK) && (!np || np->mc_loop) &&
-                   ipv6_chk_mcast_addr(dev, &skb->nh.ipv6h->daddr)) {
+                   ipv6_chk_mcast_addr(dev, &skb->nh.ipv6h->daddr,
+                               &skb->nh.ipv6h->saddr)) {
                        struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
 
                        /* Do not check for IFF_ALLMULTI; multicast routing
index e67785753d3d6f22f5a7f3db560791dc97625e8f..4ab428df1864054f45bca7ffcb11782d1ede120b 100644 (file)
  *     o       Return -EINVAL for setsockopt of short lengths
  *     o       Truncate getsockopt returns
  *     o       Return an optlen of the truncated length if need be
+ *
+ *     Changes:
+ *     David L Stevens <dlstevens@us.ibm.com>:
+ *             - added multicast source filtering API for MLDv2
  */
 
 #include <linux/module.h>
@@ -375,6 +379,89 @@ done:
                        retv = ipv6_sock_ac_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
                break;
        }
+       case MCAST_JOIN_GROUP:
+       case MCAST_LEAVE_GROUP:
+       {
+               struct group_req greq;
+               struct sockaddr_in6 *psin6;
+
+               retv = -EFAULT;
+               if (copy_from_user(&greq, optval, sizeof(struct group_req)))
+                       break;
+               if (greq.gr_group.ss_family != AF_INET6) {
+                       retv = -EADDRNOTAVAIL;
+                       break;
+               }
+               psin6 = (struct sockaddr_in6 *)&greq.gr_group;
+               if (optname == IPV6_ADD_MEMBERSHIP)
+                       retv = ipv6_sock_mc_join(sk, greq.gr_interface,
+                               &psin6->sin6_addr);
+               else
+                       retv = ipv6_sock_mc_drop(sk, greq.gr_interface,
+                               &psin6->sin6_addr);
+       }
+       case MCAST_JOIN_SOURCE_GROUP:
+       case MCAST_LEAVE_SOURCE_GROUP:
+       case MCAST_BLOCK_SOURCE:
+       case MCAST_UNBLOCK_SOURCE:
+       {
+               struct group_source_req greqs;
+               int omode, add;
+
+               if (optlen != sizeof(struct group_source_req))
+                       goto e_inval;
+               if (copy_from_user(&greqs, optval, sizeof(greqs))) {
+                       retv = -EFAULT;
+                       break;
+               }
+               if (greqs.gsr_group.ss_family != AF_INET6) {
+                       retv = -EADDRNOTAVAIL;
+                       break;
+               }
+               if (optname == MCAST_BLOCK_SOURCE) {
+                       omode = MCAST_EXCLUDE;
+                       add = 1;
+               } else if (optname == MCAST_UNBLOCK_SOURCE) {
+                       omode = MCAST_EXCLUDE;
+                       add = 0;
+               } else if (optname == MCAST_JOIN_SOURCE_GROUP) {
+                       struct sockaddr_in6 *psin6;
+
+                       psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
+                       retv = ipv6_sock_mc_join(sk, greqs.gsr_interface,
+                               &psin6->sin6_addr);
+                       if (retv)
+                               break;
+                       omode = MCAST_INCLUDE;
+                       add = 1;
+               } else /*IP_DROP_SOURCE_MEMBERSHIP */ {
+                       omode = MCAST_INCLUDE;
+                       add = 0;
+               }
+               retv = ip6_mc_source(add, omode, sk, &greqs);
+               break;
+       }
+       case MCAST_MSFILTER:
+       {
+               struct group_filter *gsf;
+
+               if (optlen < GROUP_FILTER_SIZE(0))
+                       goto e_inval;
+               gsf = (struct group_filter *)kmalloc(optlen,GFP_KERNEL);
+               if (gsf == 0) {
+                       retv = -ENOBUFS;
+                       break;
+               }
+               retv = -EFAULT;
+               if (copy_from_user(gsf, optval, optlen)) {
+                       kfree(gsf);
+                       break;
+               }
+               retv = ip6_mc_msfilter(sk, gsf);
+               kfree(gsf);
+
+               break;
+       }
        case IPV6_ROUTER_ALERT:
                retv = ip6_ra_control(sk, val, NULL);
                break;
@@ -448,6 +535,21 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname, char *optval,
                        return -ENOTCONN;
                val = sk->family;
                break;
+       case MCAST_MSFILTER:
+       {
+               struct group_filter gsf;
+               int err;
+
+               if (len < GROUP_FILTER_SIZE(0))
+                       return -EINVAL;
+               if (copy_from_user(&gsf, optval, GROUP_FILTER_SIZE(0)))
+                       return -EFAULT;
+               lock_sock(sk);
+               err = ip6_mc_msfget(sk, &gsf,
+                       (struct group_filter *)optval, optlen);
+               release_sock(sk);
+               return err;
+       }
 
        case IPV6_PKTOPTIONS:
        {
index 6ab60641f03f31462f01eb818c5a849ee2ac8fb2..176963371c72739a1bfc887cb9183ad8dfe5f512 100644 (file)
@@ -24,6 +24,8 @@
  *     YOSHIFUJI Hideaki @USAGI:
  *             - Ignore Queries for invalid addresses.
  *             - MLD for link-local addresses.
+ *     David L Stevens <dlstevens@us.ibm.com>:
+ *             - MLDv2 support
  */
 
 #include <linux/config.h>
@@ -35,6 +37,7 @@
 #include <linux/sockios.h>
 #include <linux/jiffies.h>
 #include <linux/net.h>
+#include <linux/in.h>
 #include <linux/in6.h>
 #include <linux/netdevice.h>
 #include <linux/if_arp.h>
 #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;
+       __u16           grec_nsrcs;
+       struct in6_addr grec_mca;
+       struct in6_addr grec_src[0];
+};
+
+struct mld2_report {
+       __u8    type;
+       __u8    resv1;
+       __u16   csum;
+       __u16   resv2;
+       __u16   ngrec;
+       struct mld2_grec grec[0];
+};
+
+struct mld2_query {
+       __u8 type;
+       __u8 code;
+       __u16 csum;
+       __u16 mrc;
+       __u16 resv1;
+       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;
+       __u16 nsrcs;
+       struct in6_addr srcs[0];
+};
+
+struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT;
+
 /* Big mc list lock for all the sockets */
 static rwlock_t ipv6_sk_mc_lock = RW_LOCK_UNLOCKED;
 
@@ -72,7 +127,37 @@ 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);
+int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+       int sfcount, struct in6_addr *psfsrc, int delta);
+int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+       int sfcount, struct in6_addr *psfsrc, int delta);
+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) ((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+nbexp))) << \
+       (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)
 
 /*
  *     socket join on multicast group
@@ -113,6 +198,8 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr)
        }
 
        mc_lst->ifindex = dev->ifindex;
+       mc_lst->sfmode = MCAST_EXCLUDE;
+       mc_lst->sflist = 0;
 
        /*
         *      now add/increase the group membership on the device
@@ -154,6 +241,12 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
                        write_unlock_bh(&ipv6_sk_mc_lock);
 
                        if ((dev = dev_get_by_index(mc_lst->ifindex)) != NULL) {
+                               struct inet6_dev *idev = in6_dev_get(dev);
+
+                               if (idev) {
+                                       (void) ip6_mc_leave_src(sk,mc_lst,idev);
+                                       in6_dev_put(idev);
+                               }
                                ipv6_dev_mc_dec(dev, &mc_lst->addr);
                                dev_put(dev);
                        }
@@ -166,6 +259,40 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
        return -ENOENT;
 }
 
+struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex)
+{
+       struct net_device *dev = 0;
+       struct inet6_dev *idev = 0;
+
+       if (ifindex == 0) {
+               struct rt6_info *rt;
+
+               rt = rt6_lookup(group, NULL, 0, 0);
+               if (rt) {
+                       dev = rt->rt6i_dev;
+                       dev_hold(dev);
+                       dst_release(&rt->u.dst);
+               }
+       } else
+               dev = dev_get_by_index(ifindex);
+
+       if (!dev)
+               return 0;
+       idev = in6_dev_get(dev);
+       if (!idev) {
+               dev_put(dev);
+               return 0;
+       }
+       read_lock_bh(&idev->lock);
+       if (idev->dead) {
+               read_unlock_bh(&idev->lock);
+               in6_dev_put(idev);
+               dev_put(dev);
+               return 0;
+       }
+       return idev;
+}
+
 void ipv6_sock_mc_close(struct sock *sk)
 {
        struct ipv6_pinfo *np = inet6_sk(sk);
@@ -180,6 +307,12 @@ void ipv6_sock_mc_close(struct sock *sk)
 
                dev = dev_get_by_index(mc_lst->ifindex);
                if (dev) {
+                       struct inet6_dev *idev = in6_dev_get(dev);
+
+                       if (idev) {
+                               (void) ip6_mc_leave_src(sk, mc_lst, idev);
+                               in6_dev_put(idev);
+                       }
                        ipv6_dev_mc_dec(dev, &mc_lst->addr);
                        dev_put(dev);
                }
@@ -191,21 +324,291 @@ void ipv6_sock_mc_close(struct sock *sk)
        write_unlock_bh(&ipv6_sk_mc_lock);
 }
 
-int inet6_mc_check(struct sock *sk, struct in6_addr *addr)
+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 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_type(group) & IPV6_ADDR_MULTICAST))
+               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 (pmc->ifindex != pgsr->gsr_interface)
+                       continue;
+               if (ipv6_addr_cmp(&pmc->addr, group) == 0)
+                       break;
+       }
+       if (!pmc)               /* must have a prior join */
+               goto done;
+       /* if a source filter was set, must be the same mode as before */
+       if (pmc->sflist) {
+               if (pmc->sfmode != omode)
+                       goto done;
+       } else if (pmc->sfmode != omode) {
+               /* allow mode switches for empty-set filters */
+               ip6_mc_del_src(idev, group, pmc->sfmode, 0, 0, 0);
+               pmc->sfmode = omode;
+               ip6_mc_del_src(idev, group, pmc->sfmode, 0, 0, 0);
+       }
+
+       psl = pmc->sflist;
+       if (!add) {
+               if (!psl)
+                       goto done;
+               rv = !0;
+               for (i=0; i<psl->sl_count; i++) {
+                       rv = memcmp(&psl->sl_addr, group,
+                               sizeof(struct in6_addr));
+                       if (rv >= 0)
+                               break;
+               }
+               if (!rv)        /* source not found */
+                       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 == psl->sl_max) {
+               struct ip6_sf_socklist *newpsl;
+               int count = IP6_SFBLOCK;
+
+               if (psl)
+                       count += psl->sl_max;
+               newpsl = (struct ip6_sf_socklist *)sock_kmalloc(sk,
+                       IP6_SFLSIZE(count), GFP_ATOMIC);
+               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, group, 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:
+       read_unlock_bh(&idev->lock);
+       in6_dev_put(idev);
+       dev_put(dev);
+       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;
+       int i, err;
+
+       group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
+
+       if (!(ipv6_addr_type(group) & IPV6_ADDR_MULTICAST))
+               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;
+       err = -EADDRNOTAVAIL;
+
+       for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
+               if (pmc->ifindex != gsf->gf_interface)
+                       continue;
+               if (ipv6_addr_cmp(&pmc->addr, group) == 0)
+                       break;
+       }
+       if (!pmc)               /* must have a prior join */
+               goto done;
+       if (gsf->gf_numsrc) {
+               newpsl = (struct ip6_sf_socklist *)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
+               newpsl = 0;
+       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, 0, 0);
+       pmc->sflist = newpsl;
+       pmc->sfmode = gsf->gf_fmode;
+done:
+       read_unlock_bh(&idev->lock);
+       in6_dev_put(idev);
+       dev_put(dev);
+       return err;
+}
+
+int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
+       struct group_filter *optval, int *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_type(group) & IPV6_ADDR_MULTICAST))
+               return -EINVAL;
+
+       idev = ip6_mc_find_dev(group, gsf->gf_interface);
+
+       if (!idev)
+               return -ENODEV;
+
+       dev = idev->dev;
+
+       err = -EADDRNOTAVAIL;
+
+       for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
+               if (pmc->ifindex != gsf->gf_interface)
+                       continue;
+               if (ipv6_addr_cmp(group, &pmc->addr) == 0)
+                       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((void *)optval, gsf, GROUP_FILTER_SIZE(0))) {
+               return -EFAULT;
+       }
+       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((void *)&optval->gf_slist[i], &ss, sizeof(ss)))
+                       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 = 0;
 
        read_lock(&ipv6_sk_mc_lock);
        for (mc = np->ipv6_mc_list; mc; mc = mc->next) {
-               if (ipv6_addr_cmp(&mc->addr, addr) == 0) {
-                       read_unlock(&ipv6_sk_mc_lock);
-                       return 1;
+               if (ipv6_addr_cmp(&mc->addr, mc_addr) == 0)
+                       break;
+       }
+       if (!mc) {
+               read_unlock(&ipv6_sk_mc_lock);
+               return 0;
+       }
+       psl = mc->sflist;
+       if (!psl) {
+               rv = mc->sfmode == MCAST_EXCLUDE;
+       } else {
+               int i;
+
+               for (i=0; i<psl->sl_count; i++) {
+                       if (ipv6_addr_cmp(&psl->sl_addr[i], src_addr) == 0)
+                               break;
                }
+               rv = (mc->sfmode == MCAST_INCLUDE && i < psl->sl_count);
+               rv |= (mc->sfmode == MCAST_EXCLUDE && i >= psl->sl_count);
        }
        read_unlock(&ipv6_sk_mc_lock);
 
-       return 0;
+       return rv;
 }
 
 static void ma_put(struct ifmcaddr6 *mc)
@@ -216,7 +619,7 @@ static void ma_put(struct ifmcaddr6 *mc)
        }
 }
 
-static int igmp6_group_added(struct ifmcaddr6 *mc)
+static void igmp6_group_added(struct ifmcaddr6 *mc)
 {
        struct net_device *dev = mc->idev->dev;
        char buf[MAX_ADDR_LEN];
@@ -229,12 +632,20 @@ static int igmp6_group_added(struct ifmcaddr6 *mc)
        }
        spin_unlock_bh(&mc->mca_lock);
 
-       if (dev->flags&IFF_UP)
+       if (!(dev->flags & IFF_UP) || (mc->mca_flags & MAF_NOREPORT))
+               return;
+
+       if (MLD_V1_SEEN(mc->idev)) {
                igmp6_join_group(mc);
-       return 0;
+               return;
+       }
+       /* else v2 */
+
+       mc->mca_crcount = mc->idev->mc_qrv;
+       mld_ifc_event(mc->idev);
 }
 
-static int igmp6_group_dropped(struct ifmcaddr6 *mc)
+static void igmp6_group_dropped(struct ifmcaddr6 *mc)
 {
        struct net_device *dev = mc->idev->dev;
        char buf[MAX_ADDR_LEN];
@@ -247,9 +658,122 @@ static int igmp6_group_dropped(struct ifmcaddr6 *mc)
        }
        spin_unlock_bh(&mc->mca_lock);
 
+       if (mc->mca_flags & MAF_NOREPORT)
+               goto done;
+
        if (dev->flags&IFF_UP)
                igmp6_leave_group(mc);
-       return 0;
+
+       spin_lock_bh(&mc->mca_lock);
+       if (del_timer(&mc->mca_timer))
+               atomic_dec(&mc->mca_refcnt);
+       spin_unlock_bh(&mc->mca_lock);
+
+done:
+       ip6_mc_clear_src(mc);
+}
+
+/*
+ * 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 = (struct ifmcaddr6 *)kmalloc(sizeof(*pmc), GFP_ATOMIC);
+       if (!pmc)
+               return;
+       memset(pmc, 0, sizeof(*pmc));
+       spin_lock_bh(&im->mca_lock);
+       pmc->mca_lock = SPIN_LOCK_UNLOCKED;
+       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 = 0;
+               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 = 0;
+       for (pmc=idev->mc_tomb; pmc; pmc=pmc->next) {
+               if (ipv6_addr_cmp(&pmc->mca_addr, pmca) == 0)
+                       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 = 0;
+       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 = 0;
+               spin_unlock_bh(&pmc->mca_lock);
+               for (psf=pmc->mca_tomb; psf; psf=psf_next) {
+                       psf_next = psf->sf_next;
+                       kfree(psf);
+               }
+       }
+       read_unlock_bh(&idev->lock);
 }
 
 
@@ -277,6 +801,8 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr)
                if (ipv6_addr_cmp(&mc->mca_addr, addr) == 0) {
                        mc->mca_users++;
                        write_unlock_bh(&idev->lock);
+                       ip6_mc_add_src(idev, &mc->mca_addr, MCAST_EXCLUDE, 0,
+                               0, 0);
                        in6_dev_put(idev);
                        return 0;
                }
@@ -305,10 +831,19 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr)
        atomic_set(&mc->mca_refcnt, 2);
        mc->mca_lock = SPIN_LOCK_UNLOCKED;
 
+       /* 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;
@@ -353,25 +888,57 @@ int ipv6_dev_mc_dec(struct net_device *dev, struct in6_addr *addr)
 /*
  *     check if the interface/address pair is valid
  */
-int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *addr)
+int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group,
+       struct in6_addr *src_addr)
 {
        struct inet6_dev *idev;
        struct ifmcaddr6 *mc;
+       int rv = 0;
 
        idev = in6_dev_get(dev);
        if (idev) {
                read_lock_bh(&idev->lock);
                for (mc = idev->mc_list; mc; mc=mc->next) {
-                       if (ipv6_addr_cmp(&mc->mca_addr, addr) == 0) {
-                               read_unlock_bh(&idev->lock);
-                               in6_dev_put(idev);
-                               return 1;
+                       if (ipv6_addr_cmp(&mc->mca_addr, group) == 0)
+                               break;
+               }
+               if (mc) {
+                       struct ip6_sf_list *psf;
+
+                       spin_lock_bh(&mc->mca_lock);
+                       for (psf=mc->mca_sources; psf; psf=psf->sf_next) {
+                               if (ipv6_addr_cmp(&psf->sf_addr, src_addr) == 0)
+                                       break;
                        }
+                       if (psf)
+                               rv = psf->sf_count[MCAST_INCLUDE] ||
+                                       psf->sf_count[MCAST_EXCLUDE] !=
+                                       mc->mca_sfcount[MCAST_EXCLUDE];
+                       else
+                               rv = mc->mca_sfcount[MCAST_EXCLUDE] != 0;
+                       spin_unlock_bh(&mc->mca_lock);
                }
                read_unlock_bh(&idev->lock);
                in6_dev_put(idev);
        }
-       return 0;
+       return rv;
+}
+
+static void mld_gq_start_timer(struct inet6_dev *idev)
+{
+       int tv = net_random() % idev->mc_maxdelay;
+
+       idev->mc_gq_running = 1;
+       if (!mod_timer(&idev->mc_gq_timer, jiffies+tv+2))
+               atomic_inc(&idev->refcnt);
+}
+
+static void mld_ifc_start_timer(struct inet6_dev *idev, int delay)
+{
+       int tv = net_random() % delay;
+
+       if (!mod_timer(&idev->mc_ifc_timer, jiffies+tv+2))
+               atomic_inc(&idev->refcnt);
 }
 
 /*
@@ -386,7 +953,6 @@ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime)
        if (ipv6_addr_type(&ma->mca_addr)&(IPV6_ADDR_LINKLOCAL|IPV6_ADDR_LOOPBACK))
                return;
 
-       spin_lock(&ma->mca_lock);
        if (del_timer(&ma->mca_timer)) {
                atomic_dec(&ma->mca_refcnt);
                delay = ma->mca_timer.expires - jiffies;
@@ -398,60 +964,137 @@ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime)
                else
                        delay = 1;
        }
-
        ma->mca_timer.expires = jiffies + delay;
        if (!mod_timer(&ma->mca_timer, jiffies + delay))
                atomic_inc(&ma->mca_refcnt);
-       spin_unlock(&ma->mca_lock);
+}
+
+static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs,
+       struct in6_addr *srcs)
+{
+       struct ip6_sf_list *psf;
+       int i, scount;
+
+       scount = 0;
+       for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+               if (scount == nsrcs)
+                       break;
+               for (i=0; i<nsrcs; i++)
+                       if (ipv6_addr_cmp(&srcs[i], &psf->sf_addr) == 0) {
+                               psf->sf_gsresp = 1;
+                               scount++;
+                               break;
+                       }
+       }
 }
 
 int igmp6_event_query(struct sk_buff *skb)
 {
+       struct mld2_query *mlh2 = (struct mld2_query *) skb->h.raw;
        struct ifmcaddr6 *ma;
-       struct in6_addr *addrp;
-       unsigned long resptime;
+       struct in6_addr *group;
+       unsigned long max_delay;
        struct inet6_dev *idev;
        struct icmp6hdr *hdr;
-       int addr_type;
+       int group_type;
+       int mark = 0;
+       int len;
 
        if (!pskb_may_pull(skb, sizeof(struct in6_addr)))
                return -EINVAL;
 
-       hdr = (struct icmp6hdr*) skb->h.raw;
+       len = ntohs(skb->nh.ipv6h->payload_len);
 
        /* Drop queries with not link local source */
        if (!(ipv6_addr_type(&skb->nh.ipv6h->saddr)&IPV6_ADDR_LINKLOCAL))
                return -EINVAL;
 
-       resptime = ntohs(hdr->icmp6_maxdelay);
-       /* Translate milliseconds to jiffies */
-       resptime = (resptime<<10)/(1024000/HZ);
-
-       addrp = (struct in6_addr *) (hdr + 1);
-       addr_type = ipv6_addr_type(addrp);
-
-       if (addr_type != IPV6_ADDR_ANY &&
-           !(addr_type&IPV6_ADDR_MULTICAST))
-               return -EINVAL;
-
        idev = in6_dev_get(skb->dev);
 
        if (idev == NULL)
                return 0;
 
-       read_lock(&idev->lock);
-       if (addr_type == IPV6_ADDR_ANY) {
-               for (ma = idev->mc_list; ma; ma=ma->next)
-                       igmp6_group_queried(ma, resptime);
+       hdr = (struct icmp6hdr *) skb->h.raw;
+       group = (struct in6_addr *) (hdr + 1);
+       group_type = ipv6_addr_type(group);
+
+       if (group_type != IPV6_ADDR_ANY &&
+           !(group_type&IPV6_ADDR_MULTICAST)) {
+               in6_dev_put(idev);
+               return -EINVAL;
+       }
+
+       if (len == 24) {
+               int switchback;
+               /* MLDv1 router present */
+
+               /* Translate milliseconds to jiffies */
+               max_delay = ntohs(hdr->icmp6_maxdelay)*(HZ/10);
+
+               switchback = (idev->mc_qrv + 1) * max_delay;
+               idev->mc_v1_seen = jiffies + switchback;
+
+               /* cancel the interface change timer */
+               idev->mc_ifc_count = 0;
+               if (del_timer(&idev->mc_ifc_timer))
+                       atomic_dec(&idev->refcnt);
+               /* clear deleted report items */
+               mld_clear_delrec(idev);
+       } else if (len >= 28) {
+               max_delay = MLDV2_MRC(ntohs(mlh2->mrc))*(HZ/10);
+               idev->mc_maxdelay = max_delay;
+               if (mlh2->qrv)
+                       idev->mc_qrv = mlh2->qrv;
+               if (group_type == IPV6_ADDR_ANY) { /* general query */
+                       if (mlh2->nsrcs) {
+                               in6_dev_put(idev);
+                               return -EINVAL; /* no sources allowed */
+                       }
+                       mld_gq_start_timer(idev);
+                       in6_dev_put(idev);
+                       return 0;
+               }
+               /* mark sources to include, if group & source-specific */
+               mark = mlh2->nsrcs != 0;
+       } else {
+               in6_dev_put(idev);
+               return -EINVAL;
+       }
+
+       read_lock_bh(&idev->lock);
+       if (group_type == IPV6_ADDR_ANY) {
+               for (ma = idev->mc_list; ma; ma=ma->next) {
+                       spin_lock_bh(&ma->mca_lock);
+                       igmp6_group_queried(ma, max_delay);
+                       spin_unlock_bh(&ma->mca_lock);
+               }
        } else {
                for (ma = idev->mc_list; ma; ma=ma->next) {
-                       if (ipv6_addr_cmp(addrp, &ma->mca_addr) == 0) {
-                               igmp6_group_queried(ma, resptime);
-                               break;
+                       if (group_type != IPV6_ADDR_ANY &&
+                           ipv6_addr_cmp(group, &ma->mca_addr) != 0)
+                               continue;
+                       spin_lock_bh(&ma->mca_lock);
+                       if (ma->mca_flags & MAF_TIMER_RUNNING) {
+                               /* gsquery <- gsquery && mark */
+                               if (!mark)
+                                       ma->mca_flags &= ~MAF_GSQUERY;
+                       } else {
+                               /* gsquery <- mark */
+                               if (mark)
+                                       ma->mca_flags |= MAF_GSQUERY;
+                               else
+                                       ma->mca_flags &= ~MAF_GSQUERY;
                        }
+                       if (ma->mca_flags & MAF_GSQUERY)
+                               mld_marksources(ma, ntohs(mlh2->nsrcs),
+                                       mlh2->srcs);
+                       igmp6_group_queried(ma, max_delay);
+                       spin_unlock_bh(&ma->mca_lock);
+                       if (group_type != IPV6_ADDR_ANY)
+                               break;
                }
        }
-       read_unlock(&idev->lock);
+       read_unlock_bh(&idev->lock);
        in6_dev_put(idev);
 
        return 0;
@@ -491,7 +1134,7 @@ int igmp6_event_report(struct sk_buff *skb)
         *      Cancel the timer for this group
         */
 
-       read_lock(&idev->lock);
+       read_lock_bh(&idev->lock);
        for (ma = idev->mc_list; ma; ma=ma->next) {
                if (ipv6_addr_cmp(&ma->mca_addr, addrp) == 0) {
                        spin_lock(&ma->mca_lock);
@@ -502,11 +1145,376 @@ int igmp6_event_report(struct sk_buff *skb)
                        break;
                }
        }
-       read_unlock(&idev->lock);
+       read_unlock_bh(&idev->lock);
        in6_dev_put(idev);
        return 0;
 }
 
+static int is_in(struct ifmcaddr6 *pmc, struct ip6_sf_list *psf, int type,
+       int gdeleted, int sdeleted)
+{
+       switch (type) {
+       case MLD2_MODE_IS_INCLUDE:
+       case MLD2_MODE_IS_EXCLUDE:
+               if (gdeleted || sdeleted)
+                       return 0;
+               return !((pmc->mca_flags & MAF_GSQUERY) && !psf->sf_gsresp);
+       case MLD2_CHANGE_TO_INCLUDE:
+               if (gdeleted || sdeleted)
+                       return 0;
+               return psf->sf_count[MCAST_INCLUDE] != 0;
+       case MLD2_CHANGE_TO_EXCLUDE:
+               if (gdeleted || sdeleted)
+                       return 0;
+               if (pmc->mca_sfcount[MCAST_EXCLUDE] == 0 ||
+                   psf->sf_count[MCAST_INCLUDE])
+                       return 0;
+               return pmc->mca_sfcount[MCAST_EXCLUDE] ==
+                       psf->sf_count[MCAST_EXCLUDE];
+       case MLD2_ALLOW_NEW_SOURCES:
+               if (gdeleted || !psf->sf_crcount)
+                       return 0;
+               return (pmc->mca_sfmode == MCAST_INCLUDE) ^ sdeleted;
+       case MLD2_BLOCK_OLD_SOURCES:
+               if (pmc->mca_sfmode == MCAST_INCLUDE)
+                       return gdeleted || (psf->sf_crcount && sdeleted);
+               return psf->sf_crcount && !gdeleted && !sdeleted;
+       }
+       return 0;
+}
+
+static int
+mld_scount(struct ifmcaddr6 *pmc, int type, int gdeleted, int sdeleted)
+{
+       struct ip6_sf_list *psf;
+       int scount = 0;
+
+       for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+               if (!is_in(pmc, psf, type, gdeleted, sdeleted))
+                       continue;
+               scount++;
+       }
+       return scount;
+}
+
+static struct sk_buff *mld_newpack(struct net_device *dev, int size)
+{
+       struct sock *sk = igmp6_socket->sk;
+       struct sk_buff *skb;
+       struct mld2_report *pmr;
+       struct in6_addr addr_buf;
+       int err;
+       u8 ra[8] = { IPPROTO_ICMPV6, 0,
+                    IPV6_TLV_ROUTERALERT, 2, 0, 0,
+                    IPV6_TLV_PADN, 0 };
+
+       skb = sock_alloc_send_skb(sk, size + dev->hard_header_len+15, 0, &err);
+
+       if (skb == 0)
+               return 0;
+
+       skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
+       if (dev->hard_header) {
+               unsigned char ha[MAX_ADDR_LEN];
+
+               ndisc_mc_map(&mld2_all_mcr, ha, dev, 1);
+               if (dev->hard_header(skb, dev, ETH_P_IPV6,ha,NULL,size) < 0) {
+                       kfree_skb(skb);
+                       return 0;
+               }
+       }
+
+       if (ipv6_get_lladdr(dev, &addr_buf)) {
+               /* <draft-ietf-magma-mld-source-02.txt>:
+                * use unspecified address as the source address 
+                * when a valid link-local address is not available.
+                */
+               memset(&addr_buf, 0, sizeof(addr_buf));
+       }
+
+       ip6_nd_hdr(sk, skb, dev, &addr_buf, &mld2_all_mcr, NEXTHDR_HOP, 0);
+
+       memcpy(skb_put(skb, sizeof(ra)), ra, sizeof(ra));
+
+       pmr =(struct mld2_report *)skb_put(skb, sizeof(*pmr));
+       skb->h.raw = (unsigned char *)pmr;
+       pmr->type = ICMPV6_MLD2_REPORT;
+       pmr->resv1 = 0;
+       pmr->csum = 0;
+       pmr->resv2 = 0;
+       pmr->ngrec = 0;
+       return skb;
+}
+
+static void mld_sendpack(struct sk_buff *skb)
+{
+       struct ipv6hdr *pip6 = skb->nh.ipv6h;
+       struct mld2_report *pmr = (struct mld2_report *)skb->h.raw;
+       int payload_len, mldlen;
+
+       payload_len = skb->tail - (unsigned char *)skb->nh.ipv6h -
+               sizeof(struct ipv6hdr);
+       mldlen = skb->tail - skb->h.raw;
+       pip6->payload_len = htons(payload_len);
+
+       pmr->csum = csum_ipv6_magic(&pip6->saddr, &pip6->daddr, mldlen,
+               IPPROTO_ICMPV6, csum_partial(skb->h.raw, mldlen, 0));
+       dev_queue_xmit(skb);
+       ICMP6_INC_STATS(Icmp6OutMsgs);
+}
+
+static int grec_size(struct ifmcaddr6 *pmc, int type, int gdel, int sdel)
+{
+       return sizeof(struct mld2_grec) + 4*mld_scount(pmc,type,gdel,sdel);
+}
+
+static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc,
+       int type, struct mld2_grec **ppgr)
+{
+       struct net_device *dev = pmc->idev->dev;
+       struct mld2_report *pmr;
+       struct mld2_grec *pgr;
+
+       if (!skb)
+               skb = mld_newpack(dev, dev->mtu);
+       if (!skb)
+               return 0;
+       pgr = (struct mld2_grec *)skb_put(skb, sizeof(struct mld2_grec));
+       pgr->grec_type = type;
+       pgr->grec_auxwords = 0;
+       pgr->grec_nsrcs = 0;
+       pgr->grec_mca = pmc->mca_addr;  /* structure copy */
+       pmr = (struct mld2_report *)skb->h.raw;
+       pmr->ngrec = htons(ntohs(pmr->ngrec)+1);
+       *ppgr = pgr;
+       return skb;
+}
+
+#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? (skb)->dev->mtu - (skb)->len : \
+       skb_tailroom(skb)) : 0)
+
+static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
+       int type, int gdeleted, int sdeleted)
+{
+       struct net_device *dev = pmc->idev->dev;
+       struct mld2_report *pmr;
+       struct mld2_grec *pgr = 0;
+       struct ip6_sf_list *psf, *psf_next, *psf_prev, *psf_list;
+       int scount, first, isquery, truncate;
+
+       if (pmc->mca_flags & MAF_NOREPORT)
+               return skb;
+
+       isquery = type == MLD2_MODE_IS_INCLUDE ||
+                 type == MLD2_MODE_IS_EXCLUDE;
+       truncate = type == MLD2_MODE_IS_EXCLUDE ||
+                   type == MLD2_CHANGE_TO_EXCLUDE;
+
+       psf_list = sdeleted ? pmc->mca_tomb : pmc->mca_sources;
+
+       if (!psf_list) {
+               if (type == MLD2_ALLOW_NEW_SOURCES ||
+                   type == MLD2_BLOCK_OLD_SOURCES)
+                       return skb;
+               if (pmc->mca_crcount || isquery)
+                       skb = add_grhead(skb, pmc, type, &pgr);
+               return skb;
+       }
+       pmr = skb ? (struct mld2_report *)skb->h.raw : 0;
+
+       /* EX and TO_EX get a fresh packet, if needed */
+       if (truncate) {
+               if (pmr && pmr->ngrec &&
+                   AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) {
+                       if (skb)
+                               mld_sendpack(skb);
+                       skb = mld_newpack(dev, dev->mtu);
+               }
+       }
+       first = 1;
+       scount = 0;
+       psf_prev = 0;
+       for (psf=psf_list; psf; psf=psf_next) {
+               struct in6_addr *psrc;
+
+               psf_next = psf->sf_next;
+
+               if (!is_in(pmc, psf, type, gdeleted, sdeleted)) {
+                       psf_prev = psf;
+                       continue;
+               }
+
+               /* clear marks on query responses */
+               if (isquery)
+                       psf->sf_gsresp = 0;
+
+               if (AVAILABLE(skb) < sizeof(*psrc) +
+                   first*sizeof(struct mld2_grec)) {
+                       if (truncate && !first)
+                               break;   /* truncate these */
+                       if (pgr)
+                               pgr->grec_nsrcs = htons(scount);
+                       if (skb)
+                               mld_sendpack(skb);
+                       skb = mld_newpack(dev, dev->mtu);
+                       first = 1;
+                       scount = 0;
+               }
+               if (first) {
+                       skb = add_grhead(skb, pmc, type, &pgr);
+                       first = 0;
+               }
+               psrc = (struct in6_addr *)skb_put(skb, sizeof(*psrc));
+               *psrc = psf->sf_addr;
+               scount++;
+               if ((type == MLD2_ALLOW_NEW_SOURCES ||
+                    type == MLD2_BLOCK_OLD_SOURCES) && psf->sf_crcount) {
+                       psf->sf_crcount--;
+                       if ((sdeleted || gdeleted) && psf->sf_crcount == 0) {
+                               if (psf_prev)
+                                       psf_prev->sf_next = psf->sf_next;
+                               else
+                                       pmc->mca_tomb = psf->sf_next;
+                               kfree(psf);
+                               continue;
+                       }
+               }
+               psf_prev = psf;
+       }
+       if (pgr)
+               pgr->grec_nsrcs = htons(scount);
+
+       if (isquery)
+               pmc->mca_flags &= ~MAF_GSQUERY; /* clear query state */
+       return skb;
+}
+
+static void mld_send_report(struct inet6_dev *idev, struct ifmcaddr6 *pmc)
+{
+       struct sk_buff *skb = 0;
+       int type;
+
+       if (!pmc) {
+               read_lock_bh(&idev->lock);
+               for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+                       if (pmc->mca_flags & MAF_NOREPORT)
+                               continue;
+                       spin_lock_bh(&pmc->mca_lock);
+                       if (pmc->mca_sfcount[MCAST_EXCLUDE])
+                               type = MLD2_MODE_IS_EXCLUDE;
+                       else
+                               type = MLD2_MODE_IS_INCLUDE;
+                       skb = add_grec(skb, pmc, type, 0, 0);
+                       spin_unlock_bh(&pmc->mca_lock);
+               }
+               read_unlock_bh(&idev->lock);
+       } else {
+               spin_lock_bh(&pmc->mca_lock);
+               if (pmc->mca_sfcount[MCAST_EXCLUDE])
+                       type = MLD2_MODE_IS_EXCLUDE;
+               else
+                       type = MLD2_MODE_IS_INCLUDE;
+               skb = add_grec(skb, pmc, type, 0, 0);
+               spin_unlock_bh(&pmc->mca_lock);
+       }
+       if (skb)
+               mld_sendpack(skb);
+}
+
+/*
+ * remove zero-count source records from a source filter list
+ */
+static void mld_clear_zeros(struct ip6_sf_list **ppsf)
+{
+       struct ip6_sf_list *psf_prev, *psf_next, *psf;
+
+       psf_prev = 0;
+       for (psf=*ppsf; psf; psf = psf_next) {
+               psf_next = psf->sf_next;
+               if (psf->sf_crcount == 0) {
+                       if (psf_prev)
+                               psf_prev->sf_next = psf->sf_next;
+                       else
+                               *ppsf = psf->sf_next;
+                       kfree(psf);
+               } else
+                       psf_prev = psf;
+       }
+}
+
+static void mld_send_cr(struct inet6_dev *idev)
+{
+       struct ifmcaddr6 *pmc, *pmc_prev, *pmc_next;
+       struct sk_buff *skb = 0;
+       int type, dtype;
+
+       read_lock_bh(&idev->lock);
+       write_lock_bh(&idev->mc_lock);
+
+       /* deleted MCA's */
+       pmc_prev = 0;
+       for (pmc=idev->mc_tomb; pmc; pmc=pmc_next) {
+               pmc_next = pmc->next;
+               if (pmc->mca_sfmode == MCAST_INCLUDE) {
+                       type = MLD2_BLOCK_OLD_SOURCES;
+                       dtype = MLD2_BLOCK_OLD_SOURCES;
+                       skb = add_grec(skb, pmc, type, 1, 0);
+                       skb = add_grec(skb, pmc, dtype, 1, 1);
+               }
+               if (pmc->mca_crcount) {
+                       pmc->mca_crcount--;
+                       if (pmc->mca_sfmode == MCAST_EXCLUDE) {
+                               type = MLD2_CHANGE_TO_INCLUDE;
+                               skb = add_grec(skb, pmc, type, 1, 0);
+                       }
+                       if (pmc->mca_crcount == 0) {
+                               mld_clear_zeros(&pmc->mca_tomb);
+                               mld_clear_zeros(&pmc->mca_sources);
+                       }
+               }
+               if (pmc->mca_crcount == 0 && !pmc->mca_tomb &&
+                   !pmc->mca_sources) {
+                       if (pmc_prev)
+                               pmc_prev->next = pmc_next;
+                       else
+                               idev->mc_tomb = pmc_next;
+                       in6_dev_put(pmc->idev);
+                       kfree(pmc);
+               } else
+                       pmc_prev = pmc;
+       }
+       write_unlock_bh(&idev->mc_lock);
+
+       /* change recs */
+       for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+               spin_lock_bh(&pmc->mca_lock);
+               if (pmc->mca_sfcount[MCAST_EXCLUDE]) {
+                       type = MLD2_BLOCK_OLD_SOURCES;
+                       dtype = MLD2_ALLOW_NEW_SOURCES;
+               } else {
+                       type = MLD2_ALLOW_NEW_SOURCES;
+                       dtype = MLD2_BLOCK_OLD_SOURCES;
+               }
+               skb = add_grec(skb, pmc, type, 0, 0);
+               skb = add_grec(skb, pmc, dtype, 0, 1);  /* deleted sources */
+
+               /* filter mode changes */
+               if (pmc->mca_crcount) {
+                       pmc->mca_crcount--;
+                       if (pmc->mca_sfmode == MCAST_EXCLUDE)
+                               type = MLD2_CHANGE_TO_EXCLUDE;
+                       else
+                               type = MLD2_CHANGE_TO_INCLUDE;
+                       skb = add_grec(skb, pmc, type, 0, 0);
+               }
+               spin_unlock_bh(&pmc->mca_lock);
+       }
+       read_unlock_bh(&idev->lock);
+       if (!skb)
+               return;
+       (void) mld_sendpack(skb);
+}
+
 static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type)
 {
        struct sock *sk = igmp6_socket->sk;
@@ -579,12 +1587,251 @@ out:
        kfree_skb(skb);
 }
 
+static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode,
+       struct in6_addr *psfsrc)
+{
+       struct ip6_sf_list *psf, *psf_prev;
+       int rv = 0;
+
+       psf_prev = 0;
+       for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+               if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0)
+                       break;
+               psf_prev = psf;
+       }
+       if (!psf || psf->sf_count[sfmode] == 0) {
+               /* source filter not found, or count wrong =>  bug */
+               return -ESRCH;
+       }
+       psf->sf_count[sfmode]--;
+       if (!psf->sf_count[MCAST_INCLUDE] && !psf->sf_count[MCAST_EXCLUDE]) {
+               struct inet6_dev *idev = pmc->idev;
+
+               /* no more filters for this source */
+               if (psf_prev)
+                       psf_prev->sf_next = psf->sf_next;
+               else
+                       pmc->mca_sources = psf->sf_next;
+               if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) &&
+                   !MLD_V1_SEEN(idev)) {
+                       psf->sf_crcount = idev->mc_qrv;
+                       psf->sf_next = pmc->mca_tomb;
+                       pmc->mca_tomb = psf;
+                       rv = 1;
+               } else
+                       kfree(psf);
+       }
+       return rv;
+}
+
+int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+       int sfcount, struct in6_addr *psfsrc, int delta)
+{
+       struct ifmcaddr6 *pmc;
+       int     changerec = 0;
+       int     i, err;
+
+       if (!idev)
+               return -ENODEV;
+       read_lock_bh(&idev->lock);
+       for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+               if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0)
+                       break;
+       }
+       if (!pmc) {
+               /* MCA not found?? bug */
+               read_unlock_bh(&idev->lock);
+               return -ESRCH;
+       }
+       spin_lock_bh(&pmc->mca_lock);
+       read_unlock_bh(&idev->lock);
+       sf_markstate(pmc);
+       if (!delta) {
+               if (!pmc->mca_sfcount[sfmode]) {
+                       spin_unlock_bh(&pmc->mca_lock);
+                       return -EINVAL;
+               }
+               pmc->mca_sfcount[sfmode]--;
+       }
+       err = 0;
+       for (i=0; i<sfcount; i++) {
+               int rv = ip6_mc_del1_src(pmc, sfmode, &psfsrc[i]);
+
+               changerec |= rv > 0;
+               if (!err && rv < 0)
+                       err = rv;
+       }
+       if (pmc->mca_sfmode == MCAST_EXCLUDE &&
+           pmc->mca_sfcount[MCAST_EXCLUDE] == 0 &&
+           pmc->mca_sfcount[MCAST_INCLUDE]) {
+               struct ip6_sf_list *psf;
+
+               /* filter mode change */
+               pmc->mca_sfmode = MCAST_INCLUDE;
+               pmc->mca_crcount = idev->mc_qrv;
+               idev->mc_ifc_count = pmc->mca_crcount;
+               for (psf=pmc->mca_sources; psf; psf = psf->sf_next)
+                       psf->sf_crcount = 0;
+               mld_ifc_event(pmc->idev);
+       } else if (sf_setstate(pmc) || changerec)
+               mld_ifc_event(pmc->idev);
+       spin_unlock_bh(&pmc->mca_lock);
+       return err;
+}
+
+/*
+ * Add multicast single-source filter to the interface list
+ */
+static int ip6_mc_add1_src(struct ifmcaddr6 *pmc, int sfmode,
+       struct in6_addr *psfsrc, int delta)
+{
+       struct ip6_sf_list *psf, *psf_prev;
+
+       psf_prev = 0;
+       for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+               if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0)
+                       break;
+               psf_prev = psf;
+       }
+       if (!psf) {
+               psf = (struct ip6_sf_list *)kmalloc(sizeof(*psf), GFP_ATOMIC);
+               if (!psf)
+                       return -ENOBUFS;
+               memset(psf, 0, sizeof(*psf));
+               psf->sf_addr = *psfsrc;
+               if (psf_prev) {
+                       psf_prev->sf_next = psf;
+               } else
+                       pmc->mca_sources = psf;
+       }
+       psf->sf_count[sfmode]++;
+       return 0;
+}
+
+static void sf_markstate(struct ifmcaddr6 *pmc)
+{
+       struct ip6_sf_list *psf;
+       int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE];
+
+       for (psf=pmc->mca_sources; psf; psf=psf->sf_next)
+               if (pmc->mca_sfcount[MCAST_EXCLUDE]) {
+                       psf->sf_oldin = mca_xcount ==
+                               psf->sf_count[MCAST_EXCLUDE] &&
+                               !psf->sf_count[MCAST_INCLUDE];
+               } else
+                       psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0;
+}
+
+static int sf_setstate(struct ifmcaddr6 *pmc)
+{
+       struct ip6_sf_list *psf;
+       int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE];
+       int qrv = pmc->idev->mc_qrv;
+       int new_in, rv;
+
+       rv = 0;
+       for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+               if (pmc->mca_sfcount[MCAST_EXCLUDE]) {
+                       new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] &&
+                               !psf->sf_count[MCAST_INCLUDE];
+               } else
+                       new_in = psf->sf_count[MCAST_INCLUDE] != 0;
+               if (new_in != psf->sf_oldin) {
+                       psf->sf_crcount = qrv;
+                       rv++;
+               }
+       }
+       return rv;
+}
+
+/*
+ * Add multicast source filter list to the interface list
+ */
+int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+       int sfcount, struct in6_addr *psfsrc, int delta)
+{
+       struct ifmcaddr6 *pmc;
+       int     isexclude;
+       int     i, err;
+
+       if (!idev)
+               return -ENODEV;
+       read_lock_bh(&idev->lock);
+       for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+               if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0)
+                       break;
+       }
+       if (!pmc) {
+               /* MCA not found?? bug */
+               read_unlock_bh(&idev->lock);
+               return -ESRCH;
+       }
+       spin_lock_bh(&pmc->mca_lock);
+       read_unlock_bh(&idev->lock);
+
+       sf_markstate(pmc);
+       isexclude = pmc->mca_sfmode == MCAST_EXCLUDE;
+       if (!delta)
+               pmc->mca_sfcount[sfmode]++;
+       err = 0;
+       for (i=0; i<sfcount; i++) {
+               err = ip6_mc_add1_src(pmc, sfmode, &psfsrc[i], delta);
+               if (err)
+                       break;
+       }
+       if (err) {
+               int j;
+
+               pmc->mca_sfcount[sfmode]--;
+               for (j=0; j<i; j++)
+                       (void) ip6_mc_del1_src(pmc, sfmode, &psfsrc[i]);
+       } else if (isexclude != (pmc->mca_sfcount[MCAST_EXCLUDE] != 0)) {
+               struct inet6_dev *idev = pmc->idev;
+               struct ip6_sf_list *psf;
+
+               /* filter mode change */
+               if (pmc->mca_sfcount[MCAST_EXCLUDE])
+                       pmc->mca_sfmode = MCAST_EXCLUDE;
+               else if (pmc->mca_sfcount[MCAST_INCLUDE])
+                       pmc->mca_sfmode = MCAST_INCLUDE;
+               /* else no filters; keep old mode for reports */
+
+               pmc->mca_crcount = idev->mc_qrv;
+               idev->mc_ifc_count = pmc->mca_crcount;
+               for (psf=pmc->mca_sources; psf; psf = psf->sf_next)
+                       psf->sf_crcount = 0;
+               mld_ifc_event(idev);
+       } else if (sf_setstate(pmc))
+               mld_ifc_event(idev);
+       spin_unlock_bh(&pmc->mca_lock);
+       return err;
+}
+
+static void ip6_mc_clear_src(struct ifmcaddr6 *pmc)
+{
+       struct ip6_sf_list *psf, *nextpsf;
+
+       for (psf=pmc->mca_tomb; psf; psf=nextpsf) {
+               nextpsf = psf->sf_next;
+               kfree(psf);
+       }
+       pmc->mca_tomb = 0;
+       for (psf=pmc->mca_sources; psf; psf=nextpsf) {
+               nextpsf = psf->sf_next;
+               kfree(psf);
+       }
+       pmc->mca_sources = 0;
+       pmc->mca_sfmode = MCAST_EXCLUDE;
+       pmc->mca_sfcount[MCAST_EXCLUDE] = 0;
+       pmc->mca_sfcount[MCAST_EXCLUDE] = 1;
+}
+
+
 static void igmp6_join_group(struct ifmcaddr6 *ma)
 {
        unsigned long delay;
 
-       if (IPV6_ADDR_MC_SCOPE(&ma->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL ||
-           ipv6_addr_is_ll_all_nodes(&ma->mca_addr))
+       if (ma->mca_flags & MAF_NOREPORT)
                return;
 
        igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT);
@@ -603,26 +1850,71 @@ static void igmp6_join_group(struct ifmcaddr6 *ma)
        spin_unlock_bh(&ma->mca_lock);
 }
 
+int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
+       struct inet6_dev *idev)
+{
+       int err;
+
+       if (iml->sflist == 0) {
+               /* any-source empty exclude case */
+               return ip6_mc_del_src(idev, &iml->addr, iml->sfmode, 0, 0, 0);
+       }
+       err = ip6_mc_del_src(idev, &iml->addr, iml->sfmode,
+               iml->sflist->sl_count, iml->sflist->sl_addr, 0);
+       sock_kfree_s(sk, iml->sflist, IP6_SFLSIZE(iml->sflist->sl_max));
+       iml->sflist = 0;
+       return err;
+}
+
 static void igmp6_leave_group(struct ifmcaddr6 *ma)
 {
-       if (IPV6_ADDR_MC_SCOPE(&ma->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL ||
-           ipv6_addr_is_ll_all_nodes(&ma->mca_addr))
-               return;
+       if (MLD_V1_SEEN(ma->idev)) {
+               if (ma->mca_flags & MAF_LAST_REPORTER)
+                       igmp6_send(&ma->mca_addr, ma->idev->dev,
+                               ICMPV6_MGM_REDUCTION);
+       } else {
+               mld_add_delrec(ma->idev, ma);
+               mld_ifc_event(ma->idev);
+       }
+}
 
-       if (ma->mca_flags & MAF_LAST_REPORTER)
-               igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REDUCTION);
+static void mld_gq_timer_expire(unsigned long data)
+{
+       struct inet6_dev *idev = (struct inet6_dev *)data;
 
-       spin_lock_bh(&ma->mca_lock);
-       if (del_timer(&ma->mca_timer))
-               atomic_dec(&ma->mca_refcnt);
-       spin_unlock_bh(&ma->mca_lock);
+       idev->mc_gq_running = 0;
+       mld_send_report(idev, 0);
+}
+
+static void mld_ifc_timer_expire(unsigned long data)
+{
+       struct inet6_dev *idev = (struct inet6_dev *)data;
+
+       mld_send_cr(idev);
+       if (idev->mc_ifc_count) {
+               idev->mc_ifc_count--;
+               if (idev->mc_ifc_count)
+                       mld_ifc_start_timer(idev, idev->mc_maxdelay);
+       }
+}
+
+static void mld_ifc_event(struct inet6_dev *idev)
+{
+       if (MLD_V1_SEEN(idev))
+               return;
+       idev->mc_ifc_count = idev->mc_qrv;
+       mld_ifc_start_timer(idev, 1);
 }
 
+
 static void igmp6_timer_handler(unsigned long data)
 {
        struct ifmcaddr6 *ma = (struct ifmcaddr6 *) data;
 
-       igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT);
+       if (MLD_V1_SEEN(ma->idev))
+               igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT);
+       else
+               mld_send_report(ma->idev, ma);
 
        spin_lock(&ma->mca_lock);
        ma->mca_flags |=  MAF_LAST_REPORTER;
@@ -640,9 +1932,18 @@ void ipv6_mc_down(struct inet6_dev *idev)
        /* Withdraw multicast list */
 
        read_lock_bh(&idev->lock);
+       idev->mc_ifc_count = 0;
+       if (del_timer(&idev->mc_ifc_timer))
+               atomic_dec(&idev->refcnt);
+       idev->mc_gq_running = 0;
+       if (del_timer(&idev->mc_gq_timer))
+               atomic_dec(&idev->refcnt);
+
        for (i = idev->mc_list; i; i=i->next)
                igmp6_group_dropped(i);
        read_unlock_bh(&idev->lock);
+
+       mld_clear_delrec(idev);
 }
 
 
@@ -666,6 +1967,22 @@ void ipv6_mc_init_dev(struct inet6_dev *idev)
 {
        struct in6_addr maddr;
 
+       write_lock_bh(&idev->lock);
+       idev->mc_lock = RW_LOCK_UNLOCKED;
+       idev->mc_gq_running = 0;
+       init_timer(&idev->mc_gq_timer);
+       idev->mc_gq_timer.data = (unsigned long) idev;
+       idev->mc_gq_timer.function = &mld_gq_timer_expire;
+       idev->mc_tomb = 0;
+       idev->mc_ifc_count = 0;
+       init_timer(&idev->mc_ifc_timer);
+       idev->mc_ifc_timer.data = (unsigned long) idev;
+       idev->mc_ifc_timer.function = &mld_ifc_timer_expire;
+       idev->mc_qrv = MLD_QRV_DEFAULT;
+       idev->mc_maxdelay = IGMP6_UNSOLICITED_IVAL;
+       idev->mc_v1_seen = 0;
+       write_unlock_bh(&idev->lock);
+
        /* Add all-nodes address. */
        ipv6_addr_all_nodes(&maddr);
        ipv6_dev_mc_inc(idev->dev, &maddr);
@@ -744,6 +2061,113 @@ static int igmp6_read_proc(char *buffer, char **start, off_t offset,
        }
        *eof = 1;
 
+done:
+       read_unlock(&dev_base_lock);
+
+       *start=buffer+(offset-begin);
+       len-=(offset-begin);
+       if(len>length)
+               len=length;
+       if (len<0)
+               len=0;
+       return len;
+}
+
+static int ip6_mcf_read_proc(char *buffer, char **start, off_t offset,
+                          int length, int *eof, void *data)
+{
+       off_t pos=0, begin=0;
+       int len=0;
+       int first=1;
+       struct net_device *dev;
+       
+       read_lock(&dev_base_lock);
+       for (dev=dev_base; dev; dev=dev->next) {
+               struct inet6_dev *idev = in6_dev_get(dev);
+               struct ifmcaddr6 *imc;
+
+               if (idev == NULL)
+                       continue;
+
+               read_lock_bh(&idev->lock);
+
+               for (imc=idev->mc_list; imc; imc=imc->next) {
+                       struct ip6_sf_list *psf;
+                       unsigned long icount, xcount, i;
+
+                       spin_lock_bh(&imc->mca_lock);
+                       icount = imc->mca_sfcount[MCAST_INCLUDE];
+                       xcount = imc->mca_sfcount[MCAST_EXCLUDE];
+                       for (psf=imc->mca_sources; psf; psf=psf->sf_next) {
+                               if (first) {
+                                       len += sprintf(buffer+len, "%3s %6s "
+                                               "%32s %32s %6s %6s\n", "Idx",
+                                               "Device", "Multicast Address",
+                                               "Source Address", "INC", "EXC");
+                                       first = 0;
+                               }
+                               len += sprintf(buffer+len,"%3d %6.6s ",
+                                       dev->ifindex, dev->name);
+
+                               for (i=0; i<16; i++)
+                                       len += sprintf(buffer+len, "%02x",
+                                               imc->mca_addr.s6_addr[i]);
+                               buffer[len++] = ' ';
+                               for (i=0; i<16; i++)
+                                       len += sprintf(buffer+len, "%02x",
+                                               psf->sf_addr.s6_addr[i]);
+                               len += sprintf(buffer+len, " %6lu %6lu\n",
+                                       psf->sf_count[MCAST_INCLUDE],
+                                       psf->sf_count[MCAST_EXCLUDE]);
+                               pos = begin+len;
+                               if (pos < offset) {
+                                       len=0;
+                                       begin=pos;
+                               }
+                               if (pos > offset+length) {
+                                       spin_unlock_bh(&imc->mca_lock);
+                                       read_unlock_bh(&idev->lock);
+                                       in6_dev_put(idev);
+                                       goto done;
+                               }
+                               icount -= psf->sf_count[MCAST_INCLUDE];
+                               xcount -= psf->sf_count[MCAST_EXCLUDE];
+                       }
+                       if (icount > 0 || xcount > 0) {
+                               if (first) {
+                                       len += sprintf(buffer+len, "%3s %6s "
+                                               "%32s %32s %6s %6s\n", "Idx",
+                                               "Device", "Multicast Address",
+                                               "Source Address", "INC", "EXC");
+                                       first = 0;
+                               }
+                               len += sprintf(buffer+len,"%3d %6.6s ",
+                                       dev->ifindex, dev->name);
+
+                               for (i=0; i<16; i++)
+                                       len += sprintf(buffer+len, "%02x",
+                                               imc->mca_addr.s6_addr[i]);
+                               len += sprintf(buffer+len, " %32s %6lu %6lu\n",
+                                       "NONE", icount, xcount);
+                               pos = begin+len;
+                               if (pos < offset) {
+                                       len=0;
+                                       begin=pos;
+                               }
+                               if (pos > offset+length) {
+                                       spin_unlock_bh(&imc->mca_lock);
+                                       read_unlock_bh(&idev->lock);
+                                       in6_dev_put(idev);
+                                       goto done;
+                               }
+                       }
+                       spin_unlock_bh(&imc->mca_lock);
+               }
+               read_unlock_bh(&idev->lock);
+               in6_dev_put(idev);
+       }
+       *eof = 1;
+
 done:
        read_unlock(&dev_base_lock);
 
@@ -780,6 +2204,7 @@ int __init igmp6_init(struct net_proto_family *ops)
        np->hop_limit = 1;
 #ifdef CONFIG_PROC_FS
        create_proc_read_entry("net/igmp6", 0, 0, igmp6_read_proc, NULL);
+       create_proc_read_entry("net/mcfilter6", 0, 0, ip6_mcf_read_proc, NULL);
 #endif
 
        return 0;
index 2615647db3166bc70bf8213ad259b5dcf3445bbe..b211cacdfee5e49084358753f01a1254388414ee 100644 (file)
@@ -99,7 +99,7 @@ struct sock *__raw_v6_lookup(struct sock *sk, unsigned short num,
                                if (!ipv6_addr_cmp(&np->rcv_saddr, loc_addr))
                                        break;
                                if ((addr_type & IPV6_ADDR_MULTICAST) &&
-                                   inet6_mc_check(s, loc_addr))
+                                   inet6_mc_check(s, loc_addr, rmt_addr))
                                        break;
                                continue;
                        }
index 173aa214e97ff15323914395a2bbbf97937412aa..a25394f3f6ffc74f0718589f21d511defa7e8f46 100644 (file)
@@ -592,7 +592,7 @@ static struct sock *udp_v6_mcast_next(struct sock *sk,
                                if (!ipv6_addr_cmp(&np->rcv_saddr, loc_addr))
                                        return s;
                        }
-                       if(!inet6_mc_check(s, loc_addr))
+                       if(!inet6_mc_check(s, loc_addr, rmt_addr))
                                continue;
                        return s;
                }