fib_trie: Make fib_table rcu safe
authorAlexander Duyck <alexander.h.duyck@redhat.com>
Wed, 4 Mar 2015 23:02:44 +0000 (15:02 -0800)
committerDavid S. Miller <davem@davemloft.net>
Thu, 5 Mar 2015 04:35:18 +0000 (23:35 -0500)
The fib_table was wrapped in several places with an
rcu_read_lock/rcu_read_unlock however after looking over the code I found
several spots where the tables were being accessed as just standard
pointers without any protections.  This change fixes that so that all of
the proper protections are in place when accessing the table to take RCU
replacement or removal of the table into account.

Signed-off-by: Alexander Duyck <alexander.h.duyck@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/ip_fib.h
include/net/netns/ipv4.h
net/ipv4/fib_frontend.c
net/ipv4/fib_trie.c

index cba4b7c..825cb28 100644 (file)
@@ -185,6 +185,7 @@ struct fib_table {
        u32                     tb_id;
        int                     tb_default;
        int                     tb_num_default;
+       struct rcu_head         rcu;
        unsigned long           tb_data[0];
 };
 
@@ -206,12 +207,16 @@ void fib_free_table(struct fib_table *tb);
 
 static inline struct fib_table *fib_get_table(struct net *net, u32 id)
 {
+       struct hlist_node *tb_hlist;
        struct hlist_head *ptr;
 
        ptr = id == RT_TABLE_LOCAL ?
                &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] :
                &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX];
-       return hlist_entry(ptr->first, struct fib_table, tb_hlist);
+
+       tb_hlist = rcu_dereference_rtnl(hlist_first_rcu(ptr));
+
+       return hlist_entry(tb_hlist, struct fib_table, tb_hlist);
 }
 
 static inline struct fib_table *fib_new_table(struct net *net, u32 id)
@@ -222,15 +227,19 @@ static inline struct fib_table *fib_new_table(struct net *net, u32 id)
 static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
                             struct fib_result *res)
 {
-       int err = -ENETUNREACH;
+       struct fib_table *tb;
+       int err;
 
        rcu_read_lock();
 
-       if (!fib_table_lookup(fib_get_table(net, RT_TABLE_LOCAL), flp, res,
-                             FIB_LOOKUP_NOREF) ||
-           !fib_table_lookup(fib_get_table(net, RT_TABLE_MAIN), flp, res,
-                             FIB_LOOKUP_NOREF))
-               err = 0;
+       for (err = 0; !err; err = -ENETUNREACH) {
+               tb = fib_get_table(net, RT_TABLE_LOCAL);
+               if (tb && !fib_table_lookup(tb, flp, res, FIB_LOOKUP_NOREF))
+                       break;
+               tb = fib_get_table(net, RT_TABLE_MAIN);
+               if (tb && !fib_table_lookup(tb, flp, res, FIB_LOOKUP_NOREF))
+                       break;
+       }
 
        rcu_read_unlock();
 
@@ -249,28 +258,33 @@ int __fib_lookup(struct net *net, struct flowi4 *flp, struct fib_result *res);
 static inline int fib_lookup(struct net *net, struct flowi4 *flp,
                             struct fib_result *res)
 {
-       if (!net->ipv4.fib_has_custom_rules) {
-               int err = -ENETUNREACH;
-
-               rcu_read_lock();
-
-               res->tclassid = 0;
-               if ((net->ipv4.fib_local &&
-                    !fib_table_lookup(net->ipv4.fib_local, flp, res,
-                                      FIB_LOOKUP_NOREF)) ||
-                   (net->ipv4.fib_main &&
-                    !fib_table_lookup(net->ipv4.fib_main, flp, res,
-                                      FIB_LOOKUP_NOREF)) ||
-                   (net->ipv4.fib_default &&
-                    !fib_table_lookup(net->ipv4.fib_default, flp, res,
-                                      FIB_LOOKUP_NOREF)))
-                       err = 0;
-
-               rcu_read_unlock();
-
-               return err;
+       struct fib_table *tb;
+       int err;
+
+       if (net->ipv4.fib_has_custom_rules)
+               return __fib_lookup(net, flp, res);
+
+       rcu_read_lock();
+
+       res->tclassid = 0;
+
+       for (err = 0; !err; err = -ENETUNREACH) {
+               tb = rcu_dereference_rtnl(net->ipv4.fib_local);
+               if (tb && !fib_table_lookup(tb, flp, res, FIB_LOOKUP_NOREF))
+                       break;
+
+               tb = rcu_dereference_rtnl(net->ipv4.fib_main);
+               if (tb && !fib_table_lookup(tb, flp, res, FIB_LOOKUP_NOREF))
+                       break;
+
+               tb = rcu_dereference_rtnl(net->ipv4.fib_default);
+               if (tb && !fib_table_lookup(tb, flp, res, FIB_LOOKUP_NOREF))
+                       break;
        }
-       return __fib_lookup(net, flp, res);
+
+       rcu_read_unlock();
+
+       return err;
 }
 
 #endif /* CONFIG_IP_MULTIPLE_TABLES */
index 1b26c6c..db1db15 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <linux/uidgid.h>
 #include <net/inet_frag.h>
+#include <linux/rcupdate.h>
 
 struct tcpm_hash_bucket;
 struct ctl_table_header;
@@ -38,9 +39,9 @@ struct netns_ipv4 {
 #ifdef CONFIG_IP_MULTIPLE_TABLES
        struct fib_rules_ops    *rules_ops;
        bool                    fib_has_custom_rules;
-       struct fib_table        *fib_local;
-       struct fib_table        *fib_main;
-       struct fib_table        *fib_default;
+       struct fib_table __rcu  *fib_local;
+       struct fib_table __rcu  *fib_main;
+       struct fib_table __rcu  *fib_default;
 #endif
 #ifdef CONFIG_IP_ROUTE_CLASSID
        int                     fib_num_tclassid_users;
Simple merge
Simple merge