]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - net/ipv6/ip6_fib.c
multicast: do not restore deleted record source filter mode to new one
[mirror_ubuntu-bionic-kernel.git] / net / ipv6 / ip6_fib.c
index f5285f4e1d08acb60d42fb6fd10a0c38a239324f..217683d40f129e1414e9c36ceadc5ec8e71d6a86 100644 (file)
@@ -640,6 +640,11 @@ static struct fib6_node *fib6_add_1(struct net *net,
                        if (!(fn->fn_flags & RTN_RTINFO)) {
                                RCU_INIT_POINTER(fn->leaf, NULL);
                                rt6_release(leaf);
+                       /* remove null_entry in the root node */
+                       } else if (fn->fn_flags & RTN_TL_ROOT &&
+                                  rcu_access_pointer(fn->leaf) ==
+                                  net->ipv6.ip6_null_entry) {
+                               RCU_INIT_POINTER(fn->leaf, NULL);
                        }
 
                        return fn;
@@ -1221,8 +1226,14 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
                }
 
                if (!rcu_access_pointer(fn->leaf)) {
-                       atomic_inc(&rt->rt6i_ref);
-                       rcu_assign_pointer(fn->leaf, rt);
+                       if (fn->fn_flags & RTN_TL_ROOT) {
+                               /* put back null_entry for root node */
+                               rcu_assign_pointer(fn->leaf,
+                                           info->nl_net->ipv6.ip6_null_entry);
+                       } else {
+                               atomic_inc(&rt->rt6i_ref);
+                               rcu_assign_pointer(fn->leaf, rt);
+                       }
                }
                fn = sn;
        }
@@ -1241,23 +1252,28 @@ out:
                 * If fib6_add_1 has cleared the old leaf pointer in the
                 * super-tree leaf node we have to find a new one for it.
                 */
-               struct rt6_info *pn_leaf = rcu_dereference_protected(pn->leaf,
-                                           lockdep_is_held(&table->tb6_lock));
-               if (pn != fn && pn_leaf == rt) {
-                       pn_leaf = NULL;
-                       RCU_INIT_POINTER(pn->leaf, NULL);
-                       atomic_dec(&rt->rt6i_ref);
-               }
-               if (pn != fn && !pn_leaf && !(pn->fn_flags & RTN_RTINFO)) {
-                       pn_leaf = fib6_find_prefix(info->nl_net, table, pn);
-#if RT6_DEBUG >= 2
-                       if (!pn_leaf) {
-                               WARN_ON(!pn_leaf);
-                               pn_leaf = info->nl_net->ipv6.ip6_null_entry;
+               if (pn != fn) {
+                       struct rt6_info *pn_leaf =
+                               rcu_dereference_protected(pn->leaf,
+                                   lockdep_is_held(&table->tb6_lock));
+                       if (pn_leaf == rt) {
+                               pn_leaf = NULL;
+                               RCU_INIT_POINTER(pn->leaf, NULL);
+                               atomic_dec(&rt->rt6i_ref);
                        }
+                       if (!pn_leaf && !(pn->fn_flags & RTN_RTINFO)) {
+                               pn_leaf = fib6_find_prefix(info->nl_net, table,
+                                                          pn);
+#if RT6_DEBUG >= 2
+                               if (!pn_leaf) {
+                                       WARN_ON(!pn_leaf);
+                                       pn_leaf =
+                                           info->nl_net->ipv6.ip6_null_entry;
+                               }
 #endif
-                       atomic_inc(&pn_leaf->rt6i_ref);
-                       rcu_assign_pointer(pn->leaf, pn_leaf);
+                               atomic_inc(&pn_leaf->rt6i_ref);
+                               rcu_assign_pointer(pn->leaf, pn_leaf);
+                       }
                }
 #endif
                goto failure;
@@ -1265,13 +1281,17 @@ out:
        return err;
 
 failure:
-       /* fn->leaf could be NULL if fn is an intermediate node and we
-        * failed to add the new route to it in both subtree creation
-        * failure and fib6_add_rt2node() failure case.
-        * In both cases, fib6_repair_tree() should be called to fix
-        * fn->leaf.
+       /* fn->leaf could be NULL and fib6_repair_tree() needs to be called if:
+        * 1. fn is an intermediate node and we failed to add the new
+        * route to it in both subtree creation failure and fib6_add_rt2node()
+        * failure case.
+        * 2. fn is the root node in the table and we fail to add the first
+        * default route to it.
         */
-       if (fn && !(fn->fn_flags & (RTN_RTINFO|RTN_ROOT)))
+       if (fn &&
+           (!(fn->fn_flags & (RTN_RTINFO|RTN_ROOT)) ||
+            (fn->fn_flags & RTN_TL_ROOT &&
+             !rcu_access_pointer(fn->leaf))))
                fib6_repair_tree(info->nl_net, table, fn);
        /* Always release dst as dst->__refcnt is guaranteed
         * to be taken before entering this function
@@ -1526,6 +1546,12 @@ static struct fib6_node *fib6_repair_tree(struct net *net,
        struct fib6_walker *w;
        int iter = 0;
 
+       /* Set fn->leaf to null_entry for root node. */
+       if (fn->fn_flags & RTN_TL_ROOT) {
+               rcu_assign_pointer(fn->leaf, net->ipv6.ip6_null_entry);
+               return fn;
+       }
+
        for (;;) {
                struct fib6_node *fn_r = rcu_dereference_protected(fn->right,
                                            lockdep_is_held(&table->tb6_lock));
@@ -1680,10 +1706,15 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
        }
        read_unlock(&net->ipv6.fib6_walker_lock);
 
-       /* If it was last route, expunge its radix tree node */
+       /* If it was last route, call fib6_repair_tree() to:
+        * 1. For root node, put back null_entry as how the table was created.
+        * 2. For other nodes, expunge its radix tree node.
+        */
        if (!rcu_access_pointer(fn->leaf)) {
-               fn->fn_flags &= ~RTN_RTINFO;
-               net->ipv6.rt6_stats->fib_route_nodes--;
+               if (!(fn->fn_flags & RTN_TL_ROOT)) {
+                       fn->fn_flags &= ~RTN_RTINFO;
+                       net->ipv6.rt6_stats->fib_route_nodes--;
+               }
                fn = fib6_repair_tree(net, table, fn);
        }