From: Ido Schimmel <ido...@mellanox.com>

Listeners of the FIB notification chain are expected to be able to take
and release a reference on notified IPv6 routes. This is needed in the
case of drivers capable of offloading these routes to a capable device.

Since notifications are sent in an atomic context, these drivers need to
take a reference on the route, prepare a work item to offload the route
and release the reference at the end of the work.

Currently, rt6i_ref is used to indicate in how many FIB nodes a route
appears. Different code paths rely on rt6i_ref being 0 to indicate the
route is no longer used by the FIB.

For example, whenever a route is deleted or replaced, fib6_purge_rt() is
run to make sure the route is no longer present in intermediate nodes. A
BUG_ON() at the end of the function is executed in case the reference
count isn't 1, as it's only supposed to appear in the non-intermediate
node from which it's going to be deleted.

Instead of changing the semantics of rt6i_ref, a new reference count is
added, so that external users could also take a reference on routes
without modifying rt6i_ref.

To make sure external users don't release routes used by the FIB, the
reference count is set to 1 upon creation of a route and decremented by
the FIB upon rt6_release().

The reference count is atomic, as it's not protected by any locks and
placed in the 40 bytes hole after the existing rt6i_ref.

rt6_free_pcpu() is exported so that modules could invoke rt6_put().
Similar to commit b423cb10807b ("ipv4: fib: Export free_fib_info()").

Signed-off-by: Ido Schimmel <ido...@mellanox.com>
Signed-off-by: Jiri Pirko <j...@mellanox.com>
---
 include/net/ip6_fib.h | 17 +++++++++++++++++
 net/ipv6/ip6_fib.c    | 10 ++++------
 net/ipv6/route.c      |  4 ++++
 3 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 0b30521..e8ecd08 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -119,6 +119,7 @@ struct rt6_info {
        unsigned int                    rt6i_nsiblings;
 
        atomic_t                        rt6i_ref;
+       refcount_t                      rt6i_extref;
 
        /* These are in a separate cache line. */
        struct rt6key                   rt6i_dst ____cacheline_aligned_in_smp;
@@ -187,6 +188,22 @@ static inline void ip6_rt_put(struct rt6_info *rt)
        dst_release(&rt->dst);
 }
 
+void rt6_free_pcpu(struct rt6_info *non_pcpu_rt);
+
+static inline void rt6_get(struct rt6_info *rt)
+{
+       refcount_inc(&rt->rt6i_extref);
+}
+
+static inline void rt6_put(struct rt6_info *rt)
+{
+       if (refcount_dec_and_test(&rt->rt6i_extref)) {
+               rt6_free_pcpu(rt);
+               dst_dev_put(&rt->dst);
+               dst_release(&rt->dst);
+       }
+}
+
 enum fib6_walk_state {
 #ifdef CONFIG_IPV6_SUBTREES
        FWS_S,
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index 719c1048..99ca785 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -154,7 +154,7 @@ static void node_free(struct fib6_node *fn)
        kmem_cache_free(fib6_node_kmem, fn);
 }
 
-static void rt6_free_pcpu(struct rt6_info *non_pcpu_rt)
+void rt6_free_pcpu(struct rt6_info *non_pcpu_rt)
 {
        int cpu;
 
@@ -177,14 +177,12 @@ static void rt6_free_pcpu(struct rt6_info *non_pcpu_rt)
        free_percpu(non_pcpu_rt->rt6i_pcpu);
        non_pcpu_rt->rt6i_pcpu = NULL;
 }
+EXPORT_SYMBOL_GPL(rt6_free_pcpu);
 
 static void rt6_release(struct rt6_info *rt)
 {
-       if (atomic_dec_and_test(&rt->rt6i_ref)) {
-               rt6_free_pcpu(rt);
-               dst_dev_put(&rt->dst);
-               dst_release(&rt->dst);
-       }
+       if (atomic_dec_and_test(&rt->rt6i_ref))
+               rt6_put(rt);
 }
 
 static void fib6_link_table(struct net *net, struct fib6_table *tb)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 924e02d..cabe0c6 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -345,6 +345,10 @@ static void rt6_info_init(struct rt6_info *rt)
        memset(dst + 1, 0, sizeof(*rt) - sizeof(*dst));
        INIT_LIST_HEAD(&rt->rt6i_siblings);
        INIT_LIST_HEAD(&rt->rt6i_uncached);
+       /* Make sure route can't be released as long as it's used by
+        * the FIB.
+        */
+       refcount_set(&rt->rt6i_extref, 1);
 }
 
 /* allocate dst with ip6_dst_ops */
-- 
2.9.3

Reply via email to