]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - net/ipv6/route.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux.git] / net / ipv6 / route.c
index 6e89151693d0d549326bbaa7eff7d755a260d98a..9ece8067a59be9024602f619d35f0b2966abd027 100644 (file)
@@ -102,7 +102,8 @@ static void         ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
                                           struct sk_buff *skb, u32 mtu);
 static void            rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
                                        struct sk_buff *skb);
-static int rt6_score_route(struct fib6_info *rt, int oif, int strict);
+static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
+                          int strict);
 static size_t rt6_nlmsg_size(struct fib6_info *rt);
 static int rt6_fill_node(struct net *net, struct sk_buff *skb,
                         struct fib6_info *rt, struct dst_entry *dst,
@@ -446,12 +447,13 @@ struct fib6_info *fib6_multipath_select(const struct net *net,
 
        list_for_each_entry_safe(sibling, next_sibling, &match->fib6_siblings,
                                 fib6_siblings) {
+               const struct fib6_nh *nh = &sibling->fib6_nh;
                int nh_upper_bound;
 
-               nh_upper_bound = atomic_read(&sibling->fib6_nh.fib_nh_upper_bound);
+               nh_upper_bound = atomic_read(&nh->fib_nh_upper_bound);
                if (fl6->mp_hash > nh_upper_bound)
                        continue;
-               if (rt6_score_route(sibling, oif, strict) < 0)
+               if (rt6_score_route(nh, sibling->fib6_flags, oif, strict) < 0)
                        break;
                match = sibling;
                break;
@@ -464,12 +466,34 @@ struct fib6_info *fib6_multipath_select(const struct net *net,
  *     Route lookup. rcu_read_lock() should be held.
  */
 
+static bool __rt6_device_match(struct net *net, const struct fib6_nh *nh,
+                              const struct in6_addr *saddr, int oif, int flags)
+{
+       const struct net_device *dev;
+
+       if (nh->fib_nh_flags & RTNH_F_DEAD)
+               return false;
+
+       dev = nh->fib_nh_dev;
+       if (oif) {
+               if (dev->ifindex == oif)
+                       return true;
+       } else {
+               if (ipv6_chk_addr(net, saddr, dev,
+                                 flags & RT6_LOOKUP_F_IFACE))
+                       return true;
+       }
+
+       return false;
+}
+
 static inline struct fib6_info *rt6_device_match(struct net *net,
                                                 struct fib6_info *rt,
                                                    const struct in6_addr *saddr,
                                                    int oif,
                                                    int flags)
 {
+       const struct fib6_nh *nh;
        struct fib6_info *sprt;
 
        if (!oif && ipv6_addr_any(saddr) &&
@@ -477,19 +501,9 @@ static inline struct fib6_info *rt6_device_match(struct net *net,
                return rt;
 
        for (sprt = rt; sprt; sprt = rcu_dereference(sprt->fib6_next)) {
-               const struct net_device *dev = sprt->fib6_nh.fib_nh_dev;
-
-               if (sprt->fib6_nh.fib_nh_flags & RTNH_F_DEAD)
-                       continue;
-
-               if (oif) {
-                       if (dev->ifindex == oif)
-                               return sprt;
-               } else {
-                       if (ipv6_chk_addr(net, saddr, dev,
-                                         flags & RT6_LOOKUP_F_IFACE))
-                               return sprt;
-               }
+               nh = &sprt->fib6_nh;
+               if (__rt6_device_match(net, nh, saddr, oif, flags))
+                       return sprt;
        }
 
        if (oif && flags & RT6_LOOKUP_F_IFACE)
@@ -517,7 +531,7 @@ static void rt6_probe_deferred(struct work_struct *w)
        kfree(work);
 }
 
-static void rt6_probe(struct fib6_info *rt)
+static void rt6_probe(struct fib6_nh *fib6_nh)
 {
        struct __rt6_probe_work *work = NULL;
        const struct in6_addr *nh_gw;
@@ -533,11 +547,11 @@ static void rt6_probe(struct fib6_info *rt)
         * Router Reachability Probe MUST be rate-limited
         * to no more than one per minute.
         */
-       if (!rt || !rt->fib6_nh.fib_nh_has_gw)
+       if (fib6_nh->fib_nh_gw_family)
                return;
 
-       nh_gw = &rt->fib6_nh.fib_nh_gw6;
-       dev = rt->fib6_nh.fib_nh_dev;
+       nh_gw = &fib6_nh->fib_nh_gw6;
+       dev = fib6_nh->fib_nh_dev;
        rcu_read_lock_bh();
        idev = __in6_dev_get(dev);
        neigh = __ipv6_neigh_lookup_noref(dev, nh_gw);
@@ -554,13 +568,13 @@ static void rt6_probe(struct fib6_info *rt)
                                __neigh_set_probe_once(neigh);
                }
                write_unlock(&neigh->lock);
-       } else if (time_after(jiffies, rt->last_probe +
+       } else if (time_after(jiffies, fib6_nh->last_probe +
                                       idev->cnf.rtr_probe_interval)) {
                work = kmalloc(sizeof(*work), GFP_ATOMIC);
        }
 
        if (work) {
-               rt->last_probe = jiffies;
+               fib6_nh->last_probe = jiffies;
                INIT_WORK(&work->work, rt6_probe_deferred);
                work->target = *nh_gw;
                dev_hold(dev);
@@ -572,7 +586,7 @@ static void rt6_probe(struct fib6_info *rt)
        rcu_read_unlock_bh();
 }
 #else
-static inline void rt6_probe(struct fib6_info *rt)
+static inline void rt6_probe(struct fib6_nh *fib6_nh)
 {
 }
 #endif
@@ -580,27 +594,14 @@ static inline void rt6_probe(struct fib6_info *rt)
 /*
  * Default Router Selection (RFC 2461 6.3.6)
  */
-static inline int rt6_check_dev(struct fib6_info *rt, int oif)
-{
-       const struct net_device *dev = rt->fib6_nh.fib_nh_dev;
-
-       if (!oif || dev->ifindex == oif)
-               return 2;
-       return 0;
-}
-
-static inline enum rt6_nud_state rt6_check_neigh(struct fib6_info *rt)
+static enum rt6_nud_state rt6_check_neigh(const struct fib6_nh *fib6_nh)
 {
        enum rt6_nud_state ret = RT6_NUD_FAIL_HARD;
        struct neighbour *neigh;
 
-       if (rt->fib6_flags & RTF_NONEXTHOP ||
-           !rt->fib6_nh.fib_nh_has_gw)
-               return RT6_NUD_SUCCEED;
-
        rcu_read_lock_bh();
-       neigh = __ipv6_neigh_lookup_noref(rt->fib6_nh.fib_nh_dev,
-                                         &rt->fib6_nh.fib_nh_gw6);
+       neigh = __ipv6_neigh_lookup_noref(fib6_nh->fib_nh_dev,
+                                         &fib6_nh->fib_nh_gw6);
        if (neigh) {
                read_lock(&neigh->lock);
                if (neigh->nud_state & NUD_VALID)
@@ -621,43 +622,44 @@ static inline enum rt6_nud_state rt6_check_neigh(struct fib6_info *rt)
        return ret;
 }
 
-static int rt6_score_route(struct fib6_info *rt, int oif, int strict)
+static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
+                          int strict)
 {
-       int m;
+       int m = 0;
+
+       if (!oif || nh->fib_nh_dev->ifindex == oif)
+               m = 2;
 
-       m = rt6_check_dev(rt, oif);
        if (!m && (strict & RT6_LOOKUP_F_IFACE))
                return RT6_NUD_FAIL_HARD;
 #ifdef CONFIG_IPV6_ROUTER_PREF
-       m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->fib6_flags)) << 2;
+       m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(fib6_flags)) << 2;
 #endif
-       if (strict & RT6_LOOKUP_F_REACHABLE) {
-               int n = rt6_check_neigh(rt);
+       if ((strict & RT6_LOOKUP_F_REACHABLE) &&
+           !(fib6_flags & RTF_NONEXTHOP) && nh->fib_nh_gw_family) {
+               int n = rt6_check_neigh(nh);
                if (n < 0)
                        return n;
        }
        return m;
 }
 
-static struct fib6_info *find_match(struct fib6_info *rt, int oif, int strict,
-                                  int *mpri, struct fib6_info *match,
-                                  bool *do_rr)
+static bool find_match(struct fib6_nh *nh, u32 fib6_flags,
+                      int oif, int strict, int *mpri, bool *do_rr)
 {
-       int m;
        bool match_do_rr = false;
+       bool rc = false;
+       int m;
 
-       if (rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD)
+       if (nh->fib_nh_flags & RTNH_F_DEAD)
                goto out;
 
-       if (ip6_ignore_linkdown(rt->fib6_nh.fib_nh_dev) &&
-           rt->fib6_nh.fib_nh_flags & RTNH_F_LINKDOWN &&
+       if (ip6_ignore_linkdown(nh->fib_nh_dev) &&
+           nh->fib_nh_flags & RTNH_F_LINKDOWN &&
            !(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE))
                goto out;
 
-       if (fib6_check_expired(rt))
-               goto out;
-
-       m = rt6_score_route(rt, oif, strict);
+       m = rt6_score_route(nh, fib6_flags, oif, strict);
        if (m == RT6_NUD_FAIL_DO_RR) {
                match_do_rr = true;
                m = 0; /* lowest valid score */
@@ -666,53 +668,64 @@ static struct fib6_info *find_match(struct fib6_info *rt, int oif, int strict,
        }
 
        if (strict & RT6_LOOKUP_F_REACHABLE)
-               rt6_probe(rt);
+               rt6_probe(nh);
 
        /* note that m can be RT6_NUD_FAIL_PROBE at this point */
        if (m > *mpri) {
                *do_rr = match_do_rr;
                *mpri = m;
-               match = rt;
+               rc = true;
        }
 out:
-       return match;
+       return rc;
 }
 
-static struct fib6_info *find_rr_leaf(struct fib6_node *fn,
-                                    struct fib6_info *leaf,
-                                    struct fib6_info *rr_head,
-                                    u32 metric, int oif, int strict,
-                                    bool *do_rr)
+static void __find_rr_leaf(struct fib6_info *rt_start,
+                          struct fib6_info *nomatch, u32 metric,
+                          struct fib6_info **match, struct fib6_info **cont,
+                          int oif, int strict, bool *do_rr, int *mpri)
 {
-       struct fib6_info *rt, *match, *cont;
-       int mpri = -1;
+       struct fib6_info *rt;
 
-       match = NULL;
-       cont = NULL;
-       for (rt = rr_head; rt; rt = rcu_dereference(rt->fib6_next)) {
-               if (rt->fib6_metric != metric) {
-                       cont = rt;
-                       break;
+       for (rt = rt_start;
+            rt && rt != nomatch;
+            rt = rcu_dereference(rt->fib6_next)) {
+               struct fib6_nh *nh;
+
+               if (cont && rt->fib6_metric != metric) {
+                       *cont = rt;
+                       return;
                }
 
-               match = find_match(rt, oif, strict, &mpri, match, do_rr);
+               if (fib6_check_expired(rt))
+                       continue;
+
+               nh = &rt->fib6_nh;
+               if (find_match(nh, rt->fib6_flags, oif, strict, mpri, do_rr))
+                       *match = rt;
        }
+}
 
-       for (rt = leaf; rt && rt != rr_head;
-            rt = rcu_dereference(rt->fib6_next)) {
-               if (rt->fib6_metric != metric) {
-                       cont = rt;
-                       break;
-               }
+static struct fib6_info *find_rr_leaf(struct fib6_node *fn,
+                                     struct fib6_info *leaf,
+                                     struct fib6_info *rr_head,
+                                     u32 metric, int oif, int strict,
+                                     bool *do_rr)
+{
+       struct fib6_info *match = NULL, *cont = NULL;
+       int mpri = -1;
 
-               match = find_match(rt, oif, strict, &mpri, match, do_rr);
-       }
+       __find_rr_leaf(rr_head, NULL, metric, &match, &cont,
+                      oif, strict, do_rr, &mpri);
+
+       __find_rr_leaf(leaf, rr_head, metric, &match, &cont,
+                      oif, strict, do_rr, &mpri);
 
        if (match || !cont)
                return match;
 
-       for (rt = cont; rt; rt = rcu_dereference(rt->fib6_next))
-               match = find_match(rt, oif, strict, &mpri, match, do_rr);
+       __find_rr_leaf(cont, NULL, metric, &match, NULL,
+                      oif, strict, do_rr, &mpri);
 
        return match;
 }
@@ -769,7 +782,7 @@ static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn,
 
 static bool rt6_is_gw_or_nonexthop(const struct fib6_info *rt)
 {
-       return (rt->fib6_flags & RTF_NONEXTHOP) || rt->fib6_nh.fib_nh_has_gw;
+       return (rt->fib6_flags & RTF_NONEXTHOP) || rt->fib6_nh.fib_nh_gw_family;
 }
 
 #ifdef CONFIG_IPV6_ROUTE_INFO
@@ -975,7 +988,7 @@ static void ip6_rt_copy_init(struct rt6_info *rt, struct fib6_info *ort)
        rt->rt6i_dst = ort->fib6_dst;
        rt->rt6i_idev = dev ? in6_dev_get(dev) : NULL;
        rt->rt6i_flags = ort->fib6_flags;
-       if (ort->fib6_nh.fib_nh_has_gw) {
+       if (ort->fib6_nh.fib_nh_gw_family) {
                rt->rt6i_gateway = ort->fib6_nh.fib_nh_gw6;
                rt->rt6i_flags |= RTF_GATEWAY;
        }
@@ -1061,36 +1074,37 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
        fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
 restart:
        f6i = rcu_dereference(fn->leaf);
-       if (!f6i) {
+       if (!f6i)
                f6i = net->ipv6.fib6_null_entry;
-       } else {
+       else
                f6i = rt6_device_match(net, f6i, &fl6->saddr,
                                      fl6->flowi6_oif, flags);
-               if (f6i->fib6_nsiblings && fl6->flowi6_oif == 0)
-                       f6i = fib6_multipath_select(net, f6i, fl6,
-                                                   fl6->flowi6_oif, skb,
-                                                   flags);
-       }
+
        if (f6i == net->ipv6.fib6_null_entry) {
                fn = fib6_backtrack(fn, &fl6->saddr);
                if (fn)
                        goto restart;
-       }
 
-       trace_fib6_table_lookup(net, f6i, table, fl6);
+               rt = net->ipv6.ip6_null_entry;
+               dst_hold(&rt->dst);
+               goto out;
+       }
 
+       if (f6i->fib6_nsiblings && fl6->flowi6_oif == 0)
+               f6i = fib6_multipath_select(net, f6i, fl6, fl6->flowi6_oif, skb,
+                                           flags);
        /* Search through exception table */
        rt = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr);
        if (rt) {
                if (ip6_hold_safe(net, &rt))
                        dst_use_noref(&rt->dst, jiffies);
-       } else if (f6i == net->ipv6.fib6_null_entry) {
-               rt = net->ipv6.ip6_null_entry;
-               dst_hold(&rt->dst);
        } else {
                rt = ip6_create_rt_rcu(f6i);
        }
 
+out:
+       trace_fib6_table_lookup(net, f6i, table, fl6);
+
        rcu_read_unlock();
 
        return rt;
@@ -1841,9 +1855,6 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
        rcu_read_lock();
 
        f6i = fib6_table_lookup(net, table, oif, fl6, strict);
-       if (f6i->fib6_nsiblings)
-               f6i = fib6_multipath_select(net, f6i, fl6, oif, skb, strict);
-
        if (f6i == net->ipv6.fib6_null_entry) {
                rt = net->ipv6.ip6_null_entry;
                rcu_read_unlock();
@@ -1851,6 +1862,9 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
                return rt;
        }
 
+       if (f6i->fib6_nsiblings)
+               f6i = fib6_multipath_select(net, f6i, fl6, oif, skb, strict);
+
        /*Search through exception table */
        rt = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr);
        if (rt) {
@@ -1860,7 +1874,7 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
                rcu_read_unlock();
                return rt;
        } else if (unlikely((fl6->flowi6_flags & FLOWI_FLAG_KNOWN_NH) &&
-                           !f6i->fib6_nh.fib_nh_has_gw)) {
+                           !f6i->fib6_nh.fib_nh_gw_family)) {
                /* Create a RTF_CACHE clone which will not be
                 * owned by the fib6 tree.  It is for the special case where
                 * the daddr in the skb during the neighbor look-up is different
@@ -2317,6 +2331,10 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
 
                rcu_read_lock();
                from = rcu_dereference(rt6->from);
+               if (!from) {
+                       rcu_read_unlock();
+                       return;
+               }
                nrt6 = ip6_rt_cache_alloc(from, daddr, saddr);
                if (nrt6) {
                        rt6_do_update_pmtu(nrt6, mtu);
@@ -2393,6 +2411,35 @@ void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst,
                      NULL);
 }
 
+static bool ip6_redirect_nh_match(struct fib6_info *f6i,
+                                 struct fib6_nh *nh,
+                                 struct flowi6 *fl6,
+                                 const struct in6_addr *gw,
+                                 struct rt6_info **ret)
+{
+       if (nh->fib_nh_flags & RTNH_F_DEAD || !nh->fib_nh_gw_family ||
+           fl6->flowi6_oif != nh->fib_nh_dev->ifindex)
+               return false;
+
+       /* rt_cache's gateway might be different from its 'parent'
+        * in the case of an ip redirect.
+        * So we keep searching in the exception table if the gateway
+        * is different.
+        */
+       if (!ipv6_addr_equal(gw, &nh->fib_nh_gw6)) {
+               struct rt6_info *rt_cache;
+
+               rt_cache = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr);
+               if (rt_cache &&
+                   ipv6_addr_equal(gw, &rt_cache->rt6i_gateway)) {
+                       *ret = rt_cache;
+                       return true;
+               }
+               return false;
+       }
+       return true;
+}
+
 /* Handle redirects */
 struct ip6rd_flowi {
        struct flowi6 fl6;
@@ -2406,7 +2453,7 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
                                             int flags)
 {
        struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6;
-       struct rt6_info *ret = NULL, *rt_cache;
+       struct rt6_info *ret = NULL;
        struct fib6_info *rt;
        struct fib6_node *fn;
 
@@ -2424,34 +2471,13 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
        fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
 restart:
        for_each_fib6_node_rt_rcu(fn) {
-               if (rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD)
-                       continue;
                if (fib6_check_expired(rt))
                        continue;
                if (rt->fib6_flags & RTF_REJECT)
                        break;
-               if (!rt->fib6_nh.fib_nh_has_gw)
-                       continue;
-               if (fl6->flowi6_oif != rt->fib6_nh.fib_nh_dev->ifindex)
-                       continue;
-               /* rt_cache's gateway might be different from its 'parent'
-                * in the case of an ip redirect.
-                * So we keep searching in the exception table if the gateway
-                * is different.
-                */
-               if (!ipv6_addr_equal(&rdfl->gateway, &rt->fib6_nh.fib_nh_gw6)) {
-                       rt_cache = rt6_find_cached_rt(rt,
-                                                     &fl6->daddr,
-                                                     &fl6->saddr);
-                       if (rt_cache &&
-                           ipv6_addr_equal(&rdfl->gateway,
-                                           &rt_cache->rt6i_gateway)) {
-                               ret = rt_cache;
-                               break;
-                       }
-                       continue;
-               }
-               break;
+               if (ip6_redirect_nh_match(rt, &rt->fib6_nh, fl6,
+                                         &rdfl->gateway, &ret))
+                       goto out;
        }
 
        if (!rt)
@@ -2964,7 +2990,7 @@ int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
                        goto out;
 
                fib6_nh->fib_nh_gw6 = cfg->fc_gateway;
-               fib6_nh->fib_nh_has_gw = 1;
+               fib6_nh->fib_nh_gw_family = AF_INET6;
        }
 
        err = -ENODEV;
@@ -3476,7 +3502,7 @@ static struct fib6_info *rt6_get_route_info(struct net *net,
                if (rt->fib6_nh.fib_nh_dev->ifindex != ifindex)
                        continue;
                if (!(rt->fib6_flags & RTF_ROUTEINFO) ||
-                   !rt->fib6_nh.fib_nh_has_gw)
+                   !rt->fib6_nh.fib_nh_gw_family)
                        continue;
                if (!ipv6_addr_equal(&rt->fib6_nh.fib_nh_gw6, gwaddr))
                        continue;
@@ -3807,7 +3833,7 @@ static int fib6_clean_tohost(struct fib6_info *rt, void *arg)
        struct in6_addr *gateway = (struct in6_addr *)arg;
 
        if (((rt->fib6_flags & RTF_RA_ROUTER) == RTF_RA_ROUTER) &&
-           rt->fib6_nh.fib_nh_has_gw &&
+           rt->fib6_nh.fib_nh_gw_family &&
            ipv6_addr_equal(gateway, &rt->fib6_nh.fib_nh_gw6)) {
                return -1;
        }