[MCAST] IPv6: Check packet size when process Multicast
[safe/jmp/linux-2.6] / net / ipv6 / mcast.c
index c0ca92e..966b237 100644 (file)
@@ -164,7 +164,7 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
 #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) | (1<<(nbmant))) << \
        (MLDV2_MASK((value) >> (nbmant), nbexp) + (nbexp))))
 
 #define MLDV2_QQIC(value) MLDV2_EXP(0x80, 4, 3, value)
@@ -281,7 +281,7 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
        }
        write_unlock_bh(&ipv6_sk_mc_lock);
 
-       return -ENOENT;
+       return -EADDRNOTAVAIL;
 }
 
 static struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex)
@@ -386,12 +386,16 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
                if (ipv6_addr_equal(&pmc->addr, group))
                        break;
        }
-       if (!pmc)               /* must have a prior join */
+       if (!pmc) {             /* must have a prior join */
+               err = -EINVAL;
                goto done;
+       }
        /* if a source filter was set, must be the same mode as before */
        if (pmc->sflist) {
-               if (pmc->sfmode != omode)
+               if (pmc->sfmode != omode) {
+                       err = -EINVAL;
                        goto done;
+               }
        } else if (pmc->sfmode != omode) {
                /* allow mode switches for empty-set filters */
                ip6_mc_add_src(idev, group, omode, 0, NULL, 0);
@@ -402,7 +406,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
        psl = pmc->sflist;
        if (!add) {
                if (!psl)
-                       goto done;
+                       goto done;      /* err = -EADDRNOTAVAIL */
                rv = !0;
                for (i=0; i<psl->sl_count; i++) {
                        rv = memcmp(&psl->sl_addr[i], source,
@@ -411,7 +415,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
                                break;
                }
                if (rv)         /* source not found */
-                       goto done;
+                       goto done;      /* err = -EADDRNOTAVAIL */
 
                /* special case - (INCLUDE, empty) == LEAVE_GROUP */
                if (psl->sl_count == 1 && omode == MCAST_INCLUDE) {
@@ -488,6 +492,7 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
        struct inet6_dev *idev;
        struct ipv6_pinfo *inet6 = inet6_sk(sk);
        struct ip6_sf_socklist *newpsl, *psl;
+       int leavegroup = 0;
        int i, err;
 
        group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
@@ -503,7 +508,12 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
        if (!idev)
                return -ENODEV;
        dev = idev->dev;
-       err = -EADDRNOTAVAIL;
+
+       err = 0;
+       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)
@@ -511,8 +521,10 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
                if (ipv6_addr_equal(&pmc->addr, group))
                        break;
        }
-       if (!pmc)               /* must have a prior join */
+       if (!pmc) {             /* must have a prior join */
+               err = -EINVAL;
                goto done;
+       }
        if (gsf->gf_numsrc) {
                newpsl = (struct ip6_sf_socklist *)sock_kmalloc(sk,
                                IP6_SFLSIZE(gsf->gf_numsrc), GFP_ATOMIC);
@@ -544,10 +556,13 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
                (void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
        pmc->sflist = newpsl;
        pmc->sfmode = gsf->gf_fmode;
+       err = 0;
 done:
        read_unlock_bh(&idev->lock);
        in6_dev_put(idev);
        dev_put(dev);
+       if (leavegroup)
+               err = ipv6_sock_mc_drop(sk, gsf->gf_interface, group);
        return err;
 }
 
@@ -1072,7 +1087,7 @@ static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs,
 
 int igmp6_event_query(struct sk_buff *skb)
 {
-       struct mld2_query *mlh2 = (struct mld2_query *) skb->h.raw;
+       struct mld2_query *mlh2 = NULL;
        struct ifmcaddr6 *ma;
        struct in6_addr *group;
        unsigned long max_delay;
@@ -1125,6 +1140,13 @@ int igmp6_event_query(struct sk_buff *skb)
                /* clear deleted report items */
                mld_clear_delrec(idev);
        } else if (len >= 28) {
+               int srcs_offset = sizeof(struct mld2_query) - 
+                                 sizeof(struct icmp6hdr);
+               if (!pskb_may_pull(skb, srcs_offset)) {
+                       in6_dev_put(idev);
+                       return -EINVAL;
+               }
+               mlh2 = (struct mld2_query *) skb->h.raw;
                max_delay = (MLDV2_MRC(ntohs(mlh2->mrc))*HZ)/1000;
                if (!max_delay)
                        max_delay = 1;
@@ -1141,7 +1163,15 @@ int igmp6_event_query(struct sk_buff *skb)
                        return 0;
                }
                /* mark sources to include, if group & source-specific */
-               mark = mlh2->nsrcs != 0;
+               if (mlh2->nsrcs != 0) {
+                       if (!pskb_may_pull(skb, srcs_offset + 
+                               mlh2->nsrcs * sizeof(struct in6_addr))) {
+                               in6_dev_put(idev);
+                               return -EINVAL;
+                       }
+                       mlh2 = (struct mld2_query *) skb->h.raw;
+                       mark = 1;
+               }
        } else {
                in6_dev_put(idev);
                return -EINVAL;
@@ -1301,15 +1331,6 @@ static struct sk_buff *mld_newpack(struct net_device *dev, int size)
                return NULL;
 
        skb_reserve(skb, LL_RESERVED_SPACE(dev));
-       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 NULL;
-               }
-       }
 
        if (ipv6_get_lladdr(dev, &addr_buf)) {
                /* <draft-ietf-magma-mld-source-05.txt>:
@@ -1333,6 +1354,30 @@ static struct sk_buff *mld_newpack(struct net_device *dev, int size)
        return skb;
 }
 
+static inline int mld_dev_queue_xmit2(struct sk_buff *skb)
+{
+       struct net_device *dev = skb->dev;
+
+       if (dev->hard_header) {
+               unsigned char ha[MAX_ADDR_LEN];
+               int err;
+
+               ndisc_mc_map(&skb->nh.ipv6h->daddr, ha, dev, 1);
+               err = dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, skb->len);
+               if (err < 0) {
+                       kfree_skb(skb);
+                       return err;
+               }
+       }
+       return dev_queue_xmit(skb);
+}
+
+static inline int mld_dev_queue_xmit(struct sk_buff *skb)
+{
+       return NF_HOOK(PF_INET6, NF_IP6_POST_ROUTING, skb, NULL, skb->dev,
+                      mld_dev_queue_xmit2);
+}
+
 static void mld_sendpack(struct sk_buff *skb)
 {
        struct ipv6hdr *pip6 = skb->nh.ipv6h;
@@ -1350,7 +1395,7 @@ static void mld_sendpack(struct sk_buff *skb)
        pmr->csum = csum_ipv6_magic(&pip6->saddr, &pip6->daddr, mldlen,
                IPPROTO_ICMPV6, csum_partial(skb->h.raw, mldlen, 0));
        err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev,
-               dev_queue_xmit);
+               mld_dev_queue_xmit);
        if (!err) {
                ICMP6_INC_STATS(idev,ICMP6_MIB_OUTMSGS);
                IP6_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS);
@@ -1363,7 +1408,7 @@ static void mld_sendpack(struct sk_buff *skb)
 
 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);
+       return sizeof(struct mld2_grec) + 16 * mld_scount(pmc,type,gdel,sdel);
 }
 
 static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc,
@@ -1656,12 +1701,6 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type)
        }
 
        skb_reserve(skb, LL_RESERVED_SPACE(dev));
-       if (dev->hard_header) {
-               unsigned char ha[MAX_ADDR_LEN];
-               ndisc_mc_map(snd_addr, ha, dev, 1);
-               if (dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, full_len) < 0)
-                       goto out;
-       }
 
        if (ipv6_get_lladdr(dev, &addr_buf)) {
                /* <draft-ietf-magma-mld-source-05.txt>:
@@ -1689,7 +1728,7 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type)
        idev = in6_dev_get(skb->dev);
 
        err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev,
-               dev_queue_xmit);
+               mld_dev_queue_xmit);
        if (!err) {
                if (type == ICMPV6_MGM_REDUCTION)
                        ICMP6_INC_STATS(idev, ICMP6_MIB_OUTGROUPMEMBREDUCTIONS);
@@ -1703,10 +1742,6 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type)
        if (likely(idev != NULL))
                in6_dev_put(idev);
        return;
-
-out:
-       IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);
-       kfree_skb(skb);
 }
 
 static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode,
@@ -1948,7 +1983,7 @@ static void ip6_mc_clear_src(struct ifmcaddr6 *pmc)
        }
        pmc->mca_sources = NULL;
        pmc->mca_sfmode = MCAST_EXCLUDE;
-       pmc->mca_sfcount[MCAST_EXCLUDE] = 0;
+       pmc->mca_sfcount[MCAST_INCLUDE] = 0;
        pmc->mca_sfcount[MCAST_EXCLUDE] = 1;
 }