ipv6: prevent fib6_run_gc() contention
[pandora-kernel.git] / net / ipv6 / ndisc.c
index 9da6e02..39836da 100644 (file)
@@ -370,17 +370,14 @@ static int ndisc_constructor(struct neighbour *neigh)
        struct neigh_parms *parms;
        int is_multicast = ipv6_addr_is_multicast(addr);
 
-       rcu_read_lock();
        in6_dev = in6_dev_get(dev);
        if (in6_dev == NULL) {
-               rcu_read_unlock();
                return -EINVAL;
        }
 
        parms = in6_dev->nd_parms;
        __neigh_parms_put(neigh->parms);
        neigh->parms = neigh_parms_clone(parms);
-       rcu_read_unlock();
 
        neigh->type = is_multicast ? RTN_MULTICAST : RTN_UNICAST;
        if (!dev->header_ops) {
@@ -449,8 +446,9 @@ struct sk_buff *ndisc_build_skb(struct net_device *dev,
        struct sock *sk = net->ipv6.ndisc_sk;
        struct sk_buff *skb;
        struct icmp6hdr *hdr;
+       int hlen = LL_RESERVED_SPACE(dev);
+       int tlen = dev->needed_tailroom;
        int len;
-       int err;
        u8 *opt;
 
        if (!dev->addr_len)
@@ -460,18 +458,16 @@ struct sk_buff *ndisc_build_skb(struct net_device *dev,
        if (llinfo)
                len += ndisc_opt_addr_space(dev);
 
-       skb = sock_alloc_send_skb(sk,
-                                 (MAX_HEADER + sizeof(struct ipv6hdr) +
-                                  len + LL_ALLOCATED_SPACE(dev)),
-                                 1, &err);
+       skb = alloc_skb((MAX_HEADER + sizeof(struct ipv6hdr) +
+                        len + hlen + tlen), GFP_ATOMIC);
        if (!skb) {
                ND_PRINTK0(KERN_ERR
-                          "ICMPv6 ND: %s() failed to allocate an skb, err=%d.\n",
-                          __func__, err);
+                          "ICMPv6 ND: %s() failed to allocate an skb.\n",
+                          __func__);
                return NULL;
        }
 
-       skb_reserve(skb, LL_RESERVED_SPACE(dev));
+       skb_reserve(skb, hlen);
        ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
 
        skb->transport_header = skb->tail;
@@ -495,6 +491,11 @@ struct sk_buff *ndisc_build_skb(struct net_device *dev,
                                           csum_partial(hdr,
                                                        len, 0));
 
+       /* Manually assign socket ownership as we avoid calling
+        * sock_alloc_send_pskb() to bypass wmem buffer limits
+        */
+       skb_set_owner_w(skb, sk);
+
        return skb;
 }
 
@@ -533,7 +534,8 @@ void ndisc_send_skb(struct sk_buff *skb,
 
        skb_dst_set(skb, dst);
 
-       idev = in6_dev_get(dst->dev);
+       rcu_read_lock();
+       idev = __in6_dev_get(dst->dev);
        IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len);
 
        err = NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, skb, NULL, dst->dev,
@@ -543,8 +545,7 @@ void ndisc_send_skb(struct sk_buff *skb,
                ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS);
        }
 
-       if (likely(idev != NULL))
-               in6_dev_put(idev);
+       rcu_read_unlock();
 }
 
 EXPORT_SYMBOL(ndisc_send_skb);
@@ -609,7 +610,7 @@ static void ndisc_send_unsol_na(struct net_device *dev)
 {
        struct inet6_dev *idev;
        struct inet6_ifaddr *ifa;
-       struct in6_addr mcaddr;
+       struct in6_addr mcaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
 
        idev = in6_dev_get(dev);
        if (!idev)
@@ -617,7 +618,6 @@ static void ndisc_send_unsol_na(struct net_device *dev)
 
        read_lock_bh(&idev->lock);
        list_for_each_entry(ifa, &idev->addr_list, if_list) {
-               addrconf_addr_solict_mult(&ifa->addr, &mcaddr);
                ndisc_send_na(dev, NULL, &mcaddr, &ifa->addr,
                              /*router=*/ !!idev->cnf.forwarding,
                              /*solicited=*/ false, /*override=*/ true,
@@ -1039,7 +1039,7 @@ static void ndisc_recv_rs(struct sk_buff *skb)
        if (skb->len < sizeof(*rs_msg))
                return;
 
-       idev = in6_dev_get(skb->dev);
+       idev = __in6_dev_get(skb->dev);
        if (!idev) {
                if (net_ratelimit())
                        ND_PRINTK1("ICMP6 RS: can't find in6 device\n");
@@ -1080,7 +1080,7 @@ static void ndisc_recv_rs(struct sk_buff *skb)
                neigh_release(neigh);
        }
 out:
-       in6_dev_put(idev);
+       return;
 }
 
 static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)
@@ -1179,7 +1179,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
         *      set the RA_RECV flag in the interface
         */
 
-       in6_dev = in6_dev_get(skb->dev);
+       in6_dev = __in6_dev_get(skb->dev);
        if (in6_dev == NULL) {
                ND_PRINTK0(KERN_ERR
                           "ICMPv6 RA: can't find inet6 device for %s.\n",
@@ -1188,7 +1188,6 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        }
 
        if (!ndisc_parse_options(opt, optlen, &ndopts)) {
-               in6_dev_put(in6_dev);
                ND_PRINTK2(KERN_WARNING
                           "ICMP6 RA: invalid ND options\n");
                return;
@@ -1225,6 +1224,9 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        if (!in6_dev->cnf.accept_ra_defrtr)
                goto skip_defrtr;
 
+       if (ipv6_chk_addr(dev_net(in6_dev->dev), &ipv6_hdr(skb)->saddr, NULL, 0))
+               goto skip_defrtr;
+
        lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime);
 
 #ifdef CONFIG_IPV6_ROUTER_PREF
@@ -1255,7 +1257,6 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                        ND_PRINTK0(KERN_ERR
                                   "ICMPv6 RA: %s() failed to add default route.\n",
                                   __func__);
-                       in6_dev_put(in6_dev);
                        return;
                }
 
@@ -1265,7 +1266,6 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                                   "ICMPv6 RA: %s() got default router without neighbour.\n",
                                   __func__);
                        dst_release(&rt->dst);
-                       in6_dev_put(in6_dev);
                        return;
                }
                neigh->flags |= NTF_ROUTER;
@@ -1277,7 +1277,14 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                rt->rt6i_expires = jiffies + (HZ * lifetime);
 
        if (ra_msg->icmph.icmp6_hop_limit) {
-               in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit;
+               /* Only set hop_limit on the interface if it is higher than
+                * the current hop_limit.
+                */
+               if (in6_dev->cnf.hop_limit < ra_msg->icmph.icmp6_hop_limit) {
+                       in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit;
+               } else {
+                       ND_PRINTK2(KERN_WARNING "RA: Got route advertisement with lower hop_limit than current\n");
+               }
                if (rt)
                        dst_metric_set(&rt->dst, RTAX_HOPLIMIT,
                                       ra_msg->icmph.icmp6_hop_limit);
@@ -1349,6 +1356,9 @@ skip_linkparms:
                goto out;
 
 #ifdef CONFIG_IPV6_ROUTE_INFO
+       if (ipv6_chk_addr(dev_net(in6_dev->dev), &ipv6_hdr(skb)->saddr, NULL, 0))
+               goto skip_routeinfo;
+
        if (in6_dev->cnf.accept_ra_rtr_pref && ndopts.nd_opts_ri) {
                struct nd_opt_hdr *p;
                for (p = ndopts.nd_opts_ri;
@@ -1366,6 +1376,8 @@ skip_linkparms:
                                      &ipv6_hdr(skb)->saddr);
                }
        }
+
+skip_routeinfo:
 #endif
 
 #ifdef CONFIG_IPV6_NDISC_NODETYPE
@@ -1422,7 +1434,6 @@ out:
                dst_release(&rt->dst);
        else if (neigh)
                neigh_release(neigh);
-       in6_dev_put(in6_dev);
 }
 
 static void ndisc_redirect_rcv(struct sk_buff *skb)
@@ -1481,13 +1492,11 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                return;
        }
 
-       in6_dev = in6_dev_get(skb->dev);
+       in6_dev = __in6_dev_get(skb->dev);
        if (!in6_dev)
                return;
-       if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects) {
-               in6_dev_put(in6_dev);
+       if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects)
                return;
-       }
 
        /* RFC2461 8.1:
         *      The IP source address of the Redirect MUST be the same as the current
@@ -1497,7 +1506,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
        if (!ndisc_parse_options((u8*)(dest + 1), optlen, &ndopts)) {
                ND_PRINTK2(KERN_WARNING
                           "ICMPv6 Redirect: invalid ND options\n");
-               in6_dev_put(in6_dev);
                return;
        }
        if (ndopts.nd_opts_tgt_lladdr) {
@@ -1506,7 +1514,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                if (!lladdr) {
                        ND_PRINTK2(KERN_WARNING
                                   "ICMPv6 Redirect: invalid link-layer address length\n");
-                       in6_dev_put(in6_dev);
                        return;
                }
        }
@@ -1518,7 +1525,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                             on_link);
                neigh_release(neigh);
        }
-       in6_dev_put(in6_dev);
 }
 
 void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
@@ -1537,6 +1543,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        struct inet6_dev *idev;
        struct flowi6 fl6;
        u8 *opt;
+       int hlen, tlen;
        int rd_len;
        int err;
        u8 ha_buf[MAX_ADDR_LEN], *ha = NULL;
@@ -1575,7 +1582,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        }
        if (!rt->rt6i_peer)
                rt6_bind_peer(rt, 1);
-       if (inet_peer_xrlim_allow(rt->rt6i_peer, 1*HZ))
+       if (!inet_peer_xrlim_allow(rt->rt6i_peer, 1*HZ))
                goto release;
 
        if (dev->addr_len) {
@@ -1594,9 +1601,11 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        rd_len &= ~0x7;
        len += rd_len;
 
+       hlen = LL_RESERVED_SPACE(dev);
+       tlen = dev->needed_tailroom;
        buff = sock_alloc_send_skb(sk,
                                   (MAX_HEADER + sizeof(struct ipv6hdr) +
-                                   len + LL_ALLOCATED_SPACE(dev)),
+                                   len + hlen + tlen),
                                   1, &err);
        if (buff == NULL) {
                ND_PRINTK0(KERN_ERR
@@ -1605,7 +1614,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
                goto release;
        }
 
-       skb_reserve(buff, LL_RESERVED_SPACE(dev));
+       skb_reserve(buff, hlen);
        ip6_nd_hdr(sk, buff, dev, &saddr_buf, &ipv6_hdr(skb)->saddr,
                   IPPROTO_ICMPV6, len);
 
@@ -1651,7 +1660,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
                                             csum_partial(icmph, len, 0));
 
        skb_dst_set(buff, dst);
-       idev = in6_dev_get(dst->dev);
+       rcu_read_lock();
+       idev = __in6_dev_get(dst->dev);
        IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len);
        err = NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, buff, NULL, dst->dev,
                      dst_output);
@@ -1660,8 +1670,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
                ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS);
        }
 
-       if (likely(idev != NULL))
-               in6_dev_put(idev);
+       rcu_read_unlock();
        return;
 
 release:
@@ -1734,11 +1743,11 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
        switch (event) {
        case NETDEV_CHANGEADDR:
                neigh_changeaddr(&nd_tbl, dev);
-               fib6_run_gc(~0UL, net);
+               fib6_run_gc(0, net, false);
                break;
        case NETDEV_DOWN:
                neigh_ifdown(&nd_tbl, dev);
-               fib6_run_gc(~0UL, net);
+               fib6_run_gc(0, net, false);
                break;
        case NETDEV_NOTIFY_PEERS:
                ndisc_send_unsol_na(dev);