net: igmp: fix source address check for IGMPv3 reports
[pandora-kernel.git] / net / ipv4 / igmp.c
index c7472ef..96ad44a 100644 (file)
@@ -89,6 +89,7 @@
 #include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
 #include <linux/times.h>
+#include <linux/byteorder/generic.h>
 
 #include <net/net_namespace.h>
 #include <net/arp.h>
          time_before(jiffies, (in_dev)->mr_v2_seen)))
 
 static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im);
-static void igmpv3_del_delrec(struct in_device *in_dev, __be32 multiaddr);
+static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im);
 static void igmpv3_clear_delrec(struct in_device *in_dev);
 static int sf_setstate(struct ip_mc_list *pmc);
 static void sf_markstate(struct ip_mc_list *pmc);
@@ -197,9 +198,14 @@ static void igmp_start_timer(struct ip_mc_list *im, int max_delay)
 static void igmp_gq_start_timer(struct in_device *in_dev)
 {
        int tv = net_random() % in_dev->mr_maxdelay;
+       unsigned long exp = jiffies + tv + 2;
+
+       if (in_dev->mr_gq_running &&
+           time_after_eq(exp, (in_dev->mr_gq_timer).expires))
+               return;
 
        in_dev->mr_gq_running = 1;
-       if (!mod_timer(&in_dev->mr_gq_timer, jiffies+tv+2))
+       if (!mod_timer(&in_dev->mr_gq_timer, exp))
                in_dev_hold(in_dev);
 }
 
@@ -294,9 +300,24 @@ igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted)
        return scount;
 }
 
-#define igmp_skb_size(skb) (*(unsigned int *)((skb)->cb))
+/* source address selection per RFC 3376 section 4.2.13 */
+static __be32 igmpv3_get_srcaddr(struct net_device *dev,
+                                const struct flowi4 *fl4)
+{
+       struct in_device *in_dev = __in_dev_get_rcu(dev);
+
+       if (!in_dev)
+               return htonl(INADDR_ANY);
+
+       for_ifa(in_dev) {
+               if (fl4->saddr == ifa->ifa_local)
+                       return fl4->saddr;
+       } endfor_ifa(in_dev);
 
-static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
+       return htonl(INADDR_ANY);
+}
+
+static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
 {
        struct sk_buff *skb;
        struct rtable *rt;
@@ -304,9 +325,12 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
        struct igmpv3_report *pig;
        struct net *net = dev_net(dev);
        struct flowi4 fl4;
+       int hlen = LL_RESERVED_SPACE(dev);
+       int tlen = dev->needed_tailroom;
+       unsigned int size = mtu;
 
        while (1) {
-               skb = alloc_skb(size + LL_ALLOCATED_SPACE(dev),
+               skb = alloc_skb(size + hlen + tlen,
                                GFP_ATOMIC | __GFP_NOWARN);
                if (skb)
                        break;
@@ -314,7 +338,6 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
                if (size < 256)
                        return NULL;
        }
-       igmp_skb_size(skb) = size;
 
        rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0,
                                   0, 0,
@@ -327,7 +350,8 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
        skb_dst_set(skb, &rt->dst);
        skb->dev = dev;
 
-       skb_reserve(skb, LL_RESERVED_SPACE(dev));
+       skb_reserve(skb, hlen);
+       skb_tailroom_reserve(skb, mtu, tlen);
 
        skb_reset_network_header(skb);
        pip = ip_hdr(skb);
@@ -339,10 +363,10 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
        pip->frag_off = htons(IP_DF);
        pip->ttl      = 1;
        pip->daddr    = fl4.daddr;
-       pip->saddr    = fl4.saddr;
+       pip->saddr    = igmpv3_get_srcaddr(dev, &fl4);
        pip->protocol = IPPROTO_IGMP;
        pip->tot_len  = 0;      /* filled in later */
-       ip_select_ident(pip, &rt->dst, NULL);
+       ip_select_ident(skb, NULL);
        ((u8*)&pip[1])[0] = IPOPT_RA;
        ((u8*)&pip[1])[1] = 4;
        ((u8*)&pip[1])[2] = 0;
@@ -396,8 +420,7 @@ static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc,
        return skb;
 }
 
-#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? igmp_skb_size(skb) - (skb)->len : \
-       skb_tailroom(skb)) : 0)
+#define AVAILABLE(skb) ((skb) ? skb_availroom(skb) : 0)
 
 static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,
        int type, int gdeleted, int sdeleted)
@@ -647,6 +670,7 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
        __be32  group = pmc ? pmc->multiaddr : 0;
        struct flowi4 fl4;
        __be32  dst;
+       int hlen, tlen;
 
        if (type == IGMPV3_HOST_MEMBERSHIP_REPORT)
                return igmpv3_send_report(in_dev, pmc);
@@ -661,7 +685,9 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
        if (IS_ERR(rt))
                return -1;
 
-       skb = alloc_skb(IGMP_SIZE+LL_ALLOCATED_SPACE(dev), GFP_ATOMIC);
+       hlen = LL_RESERVED_SPACE(dev);
+       tlen = dev->needed_tailroom;
+       skb = alloc_skb(IGMP_SIZE + hlen + tlen, GFP_ATOMIC);
        if (skb == NULL) {
                ip_rt_put(rt);
                return -1;
@@ -669,7 +695,7 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
 
        skb_dst_set(skb, &rt->dst);
 
-       skb_reserve(skb, LL_RESERVED_SPACE(dev));
+       skb_reserve(skb, hlen);
 
        skb_reset_network_header(skb);
        iph = ip_hdr(skb);
@@ -683,7 +709,7 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
        iph->daddr    = dst;
        iph->saddr    = fl4.saddr;
        iph->protocol = IPPROTO_IGMP;
-       ip_select_ident(iph, &rt->dst, NULL);
+       ip_select_ident(skb, NULL);
        ((u8*)&iph[1])[0] = IPOPT_RA;
        ((u8*)&iph[1])[1] = 4;
        ((u8*)&iph[1])[2] = 0;
@@ -705,7 +731,7 @@ static void igmp_gq_timer_expire(unsigned long data)
 
        in_dev->mr_gq_running = 0;
        igmpv3_send_report(in_dev, NULL);
-       __in_dev_put(in_dev);
+       in_dev_put(in_dev);
 }
 
 static void igmp_ifc_timer_expire(unsigned long data)
@@ -717,7 +743,7 @@ static void igmp_ifc_timer_expire(unsigned long data)
                in_dev->mr_ifc_count--;
                igmp_ifc_start_timer(in_dev, IGMP_Unsolicited_Report_Interval);
        }
-       __in_dev_put(in_dev);
+       in_dev_put(in_dev);
 }
 
 static void igmp_ifc_event(struct in_device *in_dev)
@@ -875,6 +901,8 @@ static void igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
                 * to be intended in a v3 query.
                 */
                max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
+               if (!max_delay)
+                       max_delay = 1;  /* can't mod w/ 0 */
        } else { /* v3 */
                if (!pskb_may_pull(skb, sizeof(struct igmpv3_query)))
                        return;
@@ -1072,10 +1100,14 @@ static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im)
        spin_unlock_bh(&in_dev->mc_tomb_lock);
 }
 
-static void igmpv3_del_delrec(struct in_device *in_dev, __be32 multiaddr)
+/*
+ * restore ip_mc_list deleted records
+ */
+static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im)
 {
        struct ip_mc_list *pmc, *pmc_prev;
-       struct ip_sf_list *psf, *psf_next;
+       struct ip_sf_list *psf;
+       __be32 multiaddr = im->multiaddr;
 
        spin_lock_bh(&in_dev->mc_tomb_lock);
        pmc_prev = NULL;
@@ -1091,16 +1123,27 @@ static void igmpv3_del_delrec(struct in_device *in_dev, __be32 multiaddr)
                        in_dev->mc_tomb = pmc->next;
        }
        spin_unlock_bh(&in_dev->mc_tomb_lock);
+
+       spin_lock_bh(&im->lock);
        if (pmc) {
-               for (psf=pmc->tomb; psf; psf=psf_next) {
-                       psf_next = psf->sf_next;
-                       kfree(psf);
+               im->interface = pmc->interface;
+               im->crcount = in_dev->mr_qrv ?: IGMP_Unsolicited_Report_Count;
+               im->sfmode = pmc->sfmode;
+               if (pmc->sfmode == MCAST_INCLUDE) {
+                       im->tomb = pmc->tomb;
+                       im->sources = pmc->sources;
+                       for (psf = im->sources; psf; psf = psf->sf_next)
+                               psf->sf_crcount = im->crcount;
                }
                in_dev_put(pmc->interface);
                kfree(pmc);
        }
+       spin_unlock_bh(&im->lock);
 }
 
+/*
+ * flush ip_mc_list deleted records
+ */
 static void igmpv3_clear_delrec(struct in_device *in_dev)
 {
        struct ip_mc_list *pmc, *nextpmc;
@@ -1242,10 +1285,10 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
 
        im->next_rcu = in_dev->mc_list;
        in_dev->mc_count++;
-       RCU_INIT_POINTER(in_dev->mc_list, im);
+       rcu_assign_pointer(in_dev->mc_list, im);
 
 #ifdef CONFIG_IP_MULTICAST
-       igmpv3_del_delrec(in_dev, im->multiaddr);
+       igmpv3_del_delrec(in_dev, im);
 #endif
        igmp_group_added(im);
        if (!in_dev->dead)
@@ -1335,8 +1378,12 @@ void ip_mc_remap(struct in_device *in_dev)
 
        ASSERT_RTNL();
 
-       for_each_pmc_rtnl(in_dev, pmc)
+       for_each_pmc_rtnl(in_dev, pmc) {
+#ifdef CONFIG_IP_MULTICAST
+               igmpv3_del_delrec(in_dev, pmc);
+#endif
                igmp_group_added(pmc);
+       }
 }
 
 /* Device going down */
@@ -1357,7 +1404,6 @@ void ip_mc_down(struct in_device *in_dev)
        in_dev->mr_gq_running = 0;
        if (del_timer(&in_dev->mr_gq_timer))
                __in_dev_put(in_dev);
-       igmpv3_clear_delrec(in_dev);
 #endif
 
        ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS);
@@ -1392,8 +1438,12 @@ void ip_mc_up(struct in_device *in_dev)
 
        ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS);
 
-       for_each_pmc_rtnl(in_dev, pmc)
+       for_each_pmc_rtnl(in_dev, pmc) {
+#ifdef CONFIG_IP_MULTICAST
+               igmpv3_del_delrec(in_dev, pmc);
+#endif
                igmp_group_added(pmc);
+       }
 }
 
 /*
@@ -1408,13 +1458,13 @@ void ip_mc_destroy_dev(struct in_device *in_dev)
 
        /* Deactivate timers */
        ip_mc_down(in_dev);
+#ifdef CONFIG_IP_MULTICAST
+       igmpv3_clear_delrec(in_dev);
+#endif
 
        while ((i = rtnl_dereference(in_dev->mc_list)) != NULL) {
                in_dev->mc_list = i->next_rcu;
                in_dev->mc_count--;
-
-               /* We've dropped the groups in ip_mc_down already */
-               ip_mc_clear_src(i);
                ip_ma_put(i);
        }
 }
@@ -1716,7 +1766,8 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode,
        if (err) {
                int j;
 
-               pmc->sfcount[sfmode]--;
+               if (!delta)
+                       pmc->sfcount[sfmode]--;
                for (j=0; j<i; j++)
                        (void) ip_mc_del1_src(pmc, sfmode, &psfsrc[j]);
        } else if (isexclude != (pmc->sfcount[MCAST_EXCLUDE] != 0)) {
@@ -1813,7 +1864,7 @@ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr)
        iml->next_rcu = inet->mc_list;
        iml->sflist = NULL;
        iml->sfmode = MCAST_EXCLUDE;
-       RCU_INIT_POINTER(inet->mc_list, iml);
+       rcu_assign_pointer(inet->mc_list, iml);
        ip_mc_inc_group(in_dev, addr);
        err = 0;
 done:
@@ -1859,6 +1910,10 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
 
        rtnl_lock();
        in_dev = ip_mc_find_dev(net, imr);
+       if (!imr->imr_ifindex && !imr->imr_address.s_addr && !in_dev) {
+               ret = -ENODEV;
+               goto out;
+       }
        ifindex = imr->imr_ifindex;
        for (imlp = &inet->mc_list;
             (iml = rtnl_dereference(*imlp)) != NULL;
@@ -1884,8 +1939,7 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
                kfree_rcu(iml, rcu);
                return 0;
        }
-       if (!in_dev)
-               ret = -ENODEV;
+out:
        rtnl_unlock();
        return ret;
 }
@@ -2000,7 +2054,7 @@ int ip_mc_source(int add, int omode, struct sock *sk, struct
                        atomic_sub(IP_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
                        kfree_rcu(psl, rcu);
                }
-               RCU_INIT_POINTER(pmc->sflist, newpsl);
+               rcu_assign_pointer(pmc->sflist, newpsl);
                psl = newpsl;
        }
        rv = 1; /* > 0 for insert logic below if sl_count is 0 */
@@ -2103,7 +2157,7 @@ int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf, int ifindex)
        } else
                (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
                        0, NULL, 0);
-       RCU_INIT_POINTER(pmc->sflist, newpsl);
+       rcu_assign_pointer(pmc->sflist, newpsl);
        pmc->sfmode = msf->imsf_fmode;
        err = 0;
 done: