Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
[linux-2.6-microblaze.git] / net / ipv6 / route.c
index affb51c..0253b70 100644 (file)
@@ -3757,6 +3757,7 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
 
        if (rt->fib6_nsiblings && cfg->fc_delete_all_nh) {
                struct fib6_info *sibling, *next_sibling;
+               struct fib6_node *fn;
 
                /* prefer to send a single notification with all hops */
                skb = nlmsg_new(rt6_nlmsg_size(rt), gfp_any());
@@ -3772,12 +3773,32 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
                                info->skip_notify = 1;
                }
 
+               /* 'rt' points to the first sibling route. If it is not the
+                * leaf, then we do not need to send a notification. Otherwise,
+                * we need to check if the last sibling has a next route or not
+                * and emit a replace or delete notification, respectively.
+                */
                info->skip_notify_kernel = 1;
-               call_fib6_multipath_entry_notifiers(net,
-                                                   FIB_EVENT_ENTRY_DEL,
-                                                   rt,
-                                                   rt->fib6_nsiblings,
-                                                   NULL);
+               fn = rcu_dereference_protected(rt->fib6_node,
+                                           lockdep_is_held(&table->tb6_lock));
+               if (rcu_access_pointer(fn->leaf) == rt) {
+                       struct fib6_info *last_sibling, *replace_rt;
+
+                       last_sibling = list_last_entry(&rt->fib6_siblings,
+                                                      struct fib6_info,
+                                                      fib6_siblings);
+                       replace_rt = rcu_dereference_protected(
+                                           last_sibling->fib6_next,
+                                           lockdep_is_held(&table->tb6_lock));
+                       if (replace_rt)
+                               call_fib6_entry_notifiers_replace(net,
+                                                                 replace_rt);
+                       else
+                               call_fib6_multipath_entry_notifiers(net,
+                                                      FIB_EVENT_ENTRY_DEL,
+                                                      rt, rt->fib6_nsiblings,
+                                                      NULL);
+               }
                list_for_each_entry_safe(sibling, next_sibling,
                                         &rt->fib6_siblings,
                                         fib6_siblings) {
@@ -5025,12 +5046,37 @@ static void ip6_route_mpath_notify(struct fib6_info *rt,
                inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags);
 }
 
+static bool ip6_route_mpath_should_notify(const struct fib6_info *rt)
+{
+       bool rt_can_ecmp = rt6_qualify_for_ecmp(rt);
+       bool should_notify = false;
+       struct fib6_info *leaf;
+       struct fib6_node *fn;
+
+       rcu_read_lock();
+       fn = rcu_dereference(rt->fib6_node);
+       if (!fn)
+               goto out;
+
+       leaf = rcu_dereference(fn->leaf);
+       if (!leaf)
+               goto out;
+
+       if (rt == leaf ||
+           (rt_can_ecmp && rt->fib6_metric == leaf->fib6_metric &&
+            rt6_qualify_for_ecmp(leaf)))
+               should_notify = true;
+out:
+       rcu_read_unlock();
+
+       return should_notify;
+}
+
 static int ip6_route_multipath_add(struct fib6_config *cfg,
                                   struct netlink_ext_ack *extack)
 {
        struct fib6_info *rt_notif = NULL, *rt_last = NULL;
        struct nl_info *info = &cfg->fc_nlinfo;
-       enum fib_event_type event_type;
        struct fib6_config r_cfg;
        struct rtnexthop *rtnh;
        struct fib6_info *rt;
@@ -5155,13 +5201,27 @@ static int ip6_route_multipath_add(struct fib6_config *cfg,
                nhn++;
        }
 
-       event_type = replace ? FIB_EVENT_ENTRY_REPLACE : FIB_EVENT_ENTRY_ADD;
-       err = call_fib6_multipath_entry_notifiers(info->nl_net, event_type,
-                                                 rt_notif, nhn - 1, extack);
-       if (err) {
-               /* Delete all the siblings that were just added */
-               err_nh = NULL;
-               goto add_errout;
+       /* An in-kernel notification should only be sent in case the new
+        * multipath route is added as the first route in the node, or if
+        * it was appended to it. We pass 'rt_notif' since it is the first
+        * sibling and might allow us to skip some checks in the replace case.
+        */
+       if (ip6_route_mpath_should_notify(rt_notif)) {
+               enum fib_event_type fib_event;
+
+               if (rt_notif->fib6_nsiblings != nhn - 1)
+                       fib_event = FIB_EVENT_ENTRY_APPEND;
+               else
+                       fib_event = FIB_EVENT_ENTRY_REPLACE;
+
+               err = call_fib6_multipath_entry_notifiers(info->nl_net,
+                                                         fib_event, rt_notif,
+                                                         nhn - 1, extack);
+               if (err) {
+                       /* Delete all the siblings that were just added */
+                       err_nh = NULL;
+                       goto add_errout;
+               }
        }
 
        /* success ... tell user about new route */