ipv6: probe routes asynchronous in rt6_probe
[pandora-kernel.git] / net / ipv6 / route.c
index 8d9a93e..f54e3a1 100644 (file)
@@ -283,9 +283,8 @@ static inline struct rt6_info *ip6_dst_alloc(struct net *net,
 
                memset(dst + 1, 0, sizeof(*rt) - sizeof(*dst));
                rt6_init_peer(rt, table ? &table->tb6_peers : net->ipv6.peers);
-               rt->rt6i_genid = rt_genid(net);
+               rt->rt6i_genid = rt_genid_ipv6(net);
                INIT_LIST_HEAD(&rt->rt6i_siblings);
-               rt->rt6i_nsiblings = 0;
        }
        return rt;
 }
@@ -477,6 +476,24 @@ out:
 }
 
 #ifdef CONFIG_IPV6_ROUTER_PREF
+struct __rt6_probe_work {
+       struct work_struct work;
+       struct in6_addr target;
+       struct net_device *dev;
+};
+
+static void rt6_probe_deferred(struct work_struct *w)
+{
+       struct in6_addr mcaddr;
+       struct __rt6_probe_work *work =
+               container_of(w, struct __rt6_probe_work, work);
+
+       addrconf_addr_solict_mult(&work->target, &mcaddr);
+       ndisc_send_ns(work->dev, NULL, &work->target, &mcaddr, NULL);
+       dev_put(work->dev);
+       kfree(w);
+}
+
 static void rt6_probe(struct rt6_info *rt)
 {
        struct neighbour *neigh;
@@ -500,17 +517,23 @@ static void rt6_probe(struct rt6_info *rt)
 
        if (!neigh ||
            time_after(jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) {
-               struct in6_addr mcaddr;
-               struct in6_addr *target;
+               struct __rt6_probe_work *work;
+
+               work = kmalloc(sizeof(*work), GFP_ATOMIC);
 
-               if (neigh) {
+               if (neigh && work)
                        neigh->updated = jiffies;
+
+               if (neigh)
                        write_unlock(&neigh->lock);
-               }
 
-               target = (struct in6_addr *)&rt->rt6i_gateway;
-               addrconf_addr_solict_mult(target, &mcaddr);
-               ndisc_send_ns(rt->dst.dev, NULL, target, &mcaddr, NULL);
+               if (work) {
+                       INIT_WORK(&work->work, rt6_probe_deferred);
+                       work->target = rt->rt6i_gateway;
+                       dev_hold(rt->dst.dev);
+                       work->dev = rt->dst.dev;
+                       schedule_work(&work->work);
+               }
        } else {
 out:
                write_unlock(&neigh->lock);
@@ -852,7 +875,6 @@ static struct rt6_info *rt6_alloc_cow(struct rt6_info *ort,
                        if (ort->rt6i_dst.plen != 128 &&
                            ipv6_addr_equal(&ort->rt6i_dst.addr, daddr))
                                rt->rt6i_flags |= RTF_ANYCAST;
-                       rt->rt6i_gateway = *daddr;
                }
 
                rt->rt6i_flags |= RTF_CACHE;
@@ -1062,7 +1084,7 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
         * DST_OBSOLETE_FORCE_CHK which forces validation calls down
         * into this function always.
         */
-       if (rt->rt6i_genid != rt_genid(dev_net(rt->dst.dev)))
+       if (rt->rt6i_genid != rt_genid_ipv6(dev_net(rt->dst.dev)))
                return NULL;
 
        if (rt->rt6i_node && (rt->rt6i_node->fn_sernum == cookie))
@@ -1157,6 +1179,77 @@ void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, __be32 mtu)
 }
 EXPORT_SYMBOL_GPL(ip6_sk_update_pmtu);
 
+/* Handle redirects */
+struct ip6rd_flowi {
+       struct flowi6 fl6;
+       struct in6_addr gateway;
+};
+
+static struct rt6_info *__ip6_route_redirect(struct net *net,
+                                            struct fib6_table *table,
+                                            struct flowi6 *fl6,
+                                            int flags)
+{
+       struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6;
+       struct rt6_info *rt;
+       struct fib6_node *fn;
+
+       /* Get the "current" route for this destination and
+        * check if the redirect has come from approriate router.
+        *
+        * RFC 4861 specifies that redirects should only be
+        * accepted if they come from the nexthop to the target.
+        * Due to the way the routes are chosen, this notion
+        * is a bit fuzzy and one might need to check all possible
+        * routes.
+        */
+
+       read_lock_bh(&table->tb6_lock);
+       fn = fib6_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
+restart:
+       for (rt = fn->leaf; rt; rt = rt->dst.rt6_next) {
+               if (rt6_check_expired(rt))
+                       continue;
+               if (rt->dst.error)
+                       break;
+               if (!(rt->rt6i_flags & RTF_GATEWAY))
+                       continue;
+               if (fl6->flowi6_oif != rt->dst.dev->ifindex)
+                       continue;
+               if (!ipv6_addr_equal(&rdfl->gateway, &rt->rt6i_gateway))
+                       continue;
+               break;
+       }
+
+       if (!rt)
+               rt = net->ipv6.ip6_null_entry;
+       else if (rt->dst.error) {
+               rt = net->ipv6.ip6_null_entry;
+               goto out;
+       }
+       BACKTRACK(net, &fl6->saddr);
+out:
+       dst_hold(&rt->dst);
+
+       read_unlock_bh(&table->tb6_lock);
+
+       return rt;
+};
+
+static struct dst_entry *ip6_route_redirect(struct net *net,
+                                       const struct flowi6 *fl6,
+                                       const struct in6_addr *gateway)
+{
+       int flags = RT6_LOOKUP_F_HAS_SADDR;
+       struct ip6rd_flowi rdfl;
+
+       rdfl.fl6 = *fl6;
+       rdfl.gateway = *gateway;
+
+       return fib6_rule_lookup(net, &rdfl.fl6,
+                               flags, __ip6_route_redirect);
+}
+
 void ip6_redirect(struct sk_buff *skb, struct net *net, int oif, u32 mark)
 {
        const struct ipv6hdr *iph = (struct ipv6hdr *) skb->data;
@@ -1171,9 +1264,8 @@ void ip6_redirect(struct sk_buff *skb, struct net *net, int oif, u32 mark)
        fl6.saddr = iph->saddr;
        fl6.flowlabel = ip6_flowinfo(iph);
 
-       dst = ip6_route_output(net, NULL, &fl6);
-       if (!dst->error)
-               rt6_do_redirect(dst, NULL, skb);
+       dst = ip6_route_redirect(net, &fl6, &ipv6_hdr(skb)->saddr);
+       rt6_do_redirect(dst, NULL, skb);
        dst_release(dst);
 }
 EXPORT_SYMBOL_GPL(ip6_redirect);
@@ -1193,9 +1285,8 @@ void ip6_redirect_no_header(struct sk_buff *skb, struct net *net, int oif,
        fl6.daddr = msg->dest;
        fl6.saddr = iph->daddr;
 
-       dst = ip6_route_output(net, NULL, &fl6);
-       if (!dst->error)
-               rt6_do_redirect(dst, NULL, skb);
+       dst = ip6_route_redirect(net, &fl6, &iph->saddr);
+       rt6_do_redirect(dst, NULL, skb);
        dst_release(dst);
 }
 
@@ -1270,6 +1361,7 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
        rt->dst.flags |= DST_HOST;
        rt->dst.output  = ip6_output;
        atomic_set(&rt->dst.__refcnt, 1);
+       rt->rt6i_gateway  = fl6->daddr;
        rt->rt6i_dst.addr = fl6->daddr;
        rt->rt6i_dst.plen = 128;
        rt->rt6i_idev     = idev;
@@ -1355,25 +1447,6 @@ out:
        return entries > rt_max_size;
 }
 
-int ip6_dst_hoplimit(struct dst_entry *dst)
-{
-       int hoplimit = dst_metric_raw(dst, RTAX_HOPLIMIT);
-       if (hoplimit == 0) {
-               struct net_device *dev = dst->dev;
-               struct inet6_dev *idev;
-
-               rcu_read_lock();
-               idev = __in6_dev_get(dev);
-               if (idev)
-                       hoplimit = idev->cnf.hop_limit;
-               else
-                       hoplimit = dev_net(dev)->ipv6.devconf_all->hop_limit;
-               rcu_read_unlock();
-       }
-       return hoplimit;
-}
-EXPORT_SYMBOL(ip6_dst_hoplimit);
-
 /*
  *
  */
@@ -1824,7 +1897,10 @@ static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
                        in6_dev_hold(rt->rt6i_idev);
                rt->dst.lastuse = jiffies;
 
-               rt->rt6i_gateway = ort->rt6i_gateway;
+               if (ort->rt6i_flags & RTF_GATEWAY)
+                       rt->rt6i_gateway = ort->rt6i_gateway;
+               else
+                       rt->rt6i_gateway = *dest;
                rt->rt6i_flags = ort->rt6i_flags;
                if ((ort->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF)) ==
                    (RTF_DEFAULT | RTF_ADDRCONF))
@@ -2111,6 +2187,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
        else
                rt->rt6i_flags |= RTF_LOCAL;
 
+       rt->rt6i_gateway  = *addr;
        rt->rt6i_dst.addr = *addr;
        rt->rt6i_dst.plen = 128;
        rt->rt6i_table = fib6_get_table(net, RT6_TABLE_LOCAL);