]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
ipv6: Handle route deletion notification
authorIdo Schimmel <idosch@mellanox.com>
Mon, 23 Dec 2019 13:28:17 +0000 (15:28 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 25 Dec 2019 06:37:30 +0000 (22:37 -0800)
For the purpose of route offload, when a single route is deleted, it is
only of interest if it is the first route in the node or if it is
sibling to such a route.

In the first case, distinguish between several possibilities:

1. Route is the last route in the node. Emit a delete notification

2. Route is followed by a non-multipath route. Emit a replace
notification for the non-multipath route.

3. Route is followed by a multipath route. Emit a replace notification
for the multipath route.

In the second case, only emit a delete notification to ensure the route
is no longer used as a valid nexthop.

Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Reviewed-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: David Ahern <dsahern@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/ip6_fib.h
net/ipv6/ip6_fib.c

index f1535f172935399ca897b2f2d36261a5c7149c9d..b579faea41e9c4c3448f49bf3096f0c8ebe4b0d1 100644 (file)
@@ -487,6 +487,7 @@ int call_fib6_multipath_entry_notifiers(struct net *net,
                                        struct fib6_info *rt,
                                        unsigned int nsiblings,
                                        struct netlink_ext_ack *extack);
+int call_fib6_entry_notifiers_replace(struct net *net, struct fib6_info *rt);
 void fib6_rt_update(struct net *net, struct fib6_info *rt,
                    struct nl_info *info);
 void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
index 51cf848e38f0cfd7819155b49a40d27de5d944f4..67ddee539f77a76f0c3bf304824a4fbc7e0113a8 100644 (file)
@@ -415,6 +415,18 @@ int call_fib6_multipath_entry_notifiers(struct net *net,
        return call_fib6_notifiers(net, event_type, &info.info);
 }
 
+int call_fib6_entry_notifiers_replace(struct net *net, struct fib6_info *rt)
+{
+       struct fib6_entry_notifier_info info = {
+               .rt = rt,
+               .nsiblings = rt->fib6_nsiblings,
+       };
+
+       rt->fib6_table->fib_seq++;
+       return call_fib6_notifiers(net, FIB_EVENT_ENTRY_REPLACE_TMP,
+                                  &info.info);
+}
+
 struct fib6_dump_arg {
        struct net *net;
        struct notifier_block *nb;
@@ -1910,13 +1922,29 @@ static struct fib6_node *fib6_repair_tree(struct net *net,
 static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
                           struct fib6_info __rcu **rtp, struct nl_info *info)
 {
+       struct fib6_info *leaf, *replace_rt = NULL;
        struct fib6_walker *w;
        struct fib6_info *rt = rcu_dereference_protected(*rtp,
                                    lockdep_is_held(&table->tb6_lock));
        struct net *net = info->nl_net;
+       bool notify_del = false;
 
        RT6_TRACE("fib6_del_route\n");
 
+       /* If the deleted route is the first in the node and it is not part of
+        * a multipath route, then we need to replace it with the next route
+        * in the node, if exists.
+        */
+       leaf = rcu_dereference_protected(fn->leaf,
+                                        lockdep_is_held(&table->tb6_lock));
+       if (leaf == rt && !rt->fib6_nsiblings) {
+               if (rcu_access_pointer(rt->fib6_next))
+                       replace_rt = rcu_dereference_protected(rt->fib6_next,
+                                           lockdep_is_held(&table->tb6_lock));
+               else
+                       notify_del = true;
+       }
+
        /* Unlink it */
        *rtp = rt->fib6_next;
        rt->fib6_node = NULL;
@@ -1934,6 +1962,14 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
        if (rt->fib6_nsiblings) {
                struct fib6_info *sibling, *next_sibling;
 
+               /* The route is deleted from a multipath route. If this
+                * multipath route is the first route in the node, then we need
+                * to emit a delete notification. Otherwise, we need to skip
+                * the notification.
+                */
+               if (rt->fib6_metric == leaf->fib6_metric &&
+                   rt6_qualify_for_ecmp(leaf))
+                       notify_del = true;
                list_for_each_entry_safe(sibling, next_sibling,
                                         &rt->fib6_siblings, fib6_siblings)
                        sibling->fib6_nsiblings--;
@@ -1969,8 +2005,14 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
 
        fib6_purge_rt(rt, fn, net);
 
-       if (!info->skip_notify_kernel)
+       if (!info->skip_notify_kernel) {
+               if (notify_del)
+                       call_fib6_entry_notifiers(net, FIB_EVENT_ENTRY_DEL_TMP,
+                                                 rt, NULL);
+               else if (replace_rt)
+                       call_fib6_entry_notifiers_replace(net, replace_rt);
                call_fib6_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, rt, NULL);
+       }
        if (!info->skip_notify)
                inet6_rt_notify(RTM_DELROUTE, rt, info, 0);