mlxsw: spectrum_router: Reflect nexthop status changes
authorIdo Schimmel <idosch@mellanox.com>
Wed, 8 Feb 2017 10:16:40 +0000 (11:16 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 8 Feb 2017 20:25:18 +0000 (15:25 -0500)
When a packet hits a multipath route in the device's routing table, a
hash is computed over its headers, which is then used to select the
appropriate nexthop from the device's adjacency table.

There are situations in which the kernel removes a nexthop from a
multipath route (e.g., no carrier) and the device should do the same.

Upon the reception of NH_{ADD,DEL} events, add or remove a nexthop from
the device's adjacency table and refresh all the routes using the
nexthop group. If all the nexthops of a multipath route are invalid,
then any packet hitting the route would be trapped to the CPU for
forwarding.

If all the nexthops are DEAD, then the kernel would remove the route
entirely. On the other hand, if all the nexthops are merely LINKDOWN,
then the kernel would keep the route and forward any incoming packet
using a different route.

While the last case might sound like a problem, it's expected that a
routing daemon running in user space would remove such a route from the
FIB as it's dumped with the DEAD flag set.

Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c

index 136a50cffa850f75da08d6803de394f18333fcc9..1c68b402152418c298262434681a9dafca6ee9b6 100644 (file)
@@ -1180,6 +1180,14 @@ static void mlxsw_sp_nexthop_remove(struct mlxsw_sp *mlxsw_sp,
                               mlxsw_sp_nexthop_ht_params);
 }
 
+static struct mlxsw_sp_nexthop *
+mlxsw_sp_nexthop_lookup(struct mlxsw_sp *mlxsw_sp,
+                       struct mlxsw_sp_nexthop_key key)
+{
+       return rhashtable_lookup_fast(&mlxsw_sp->router.nexthop_ht, &key,
+                                     mlxsw_sp_nexthop_ht_params);
+}
+
 static int mlxsw_sp_adj_index_mass_update_vr(struct mlxsw_sp *mlxsw_sp,
                                             struct mlxsw_sp_vr *vr,
                                             u32 adj_index, u16 ecmp_size,
@@ -1417,7 +1425,7 @@ static int mlxsw_sp_nexthop_neigh_init(struct mlxsw_sp *mlxsw_sp,
        u8 nud_state, dead;
        int err;
 
-       if (!nh->nh_grp->gateway)
+       if (!nh->nh_grp->gateway || nh->neigh_entry)
                return 0;
 
        /* Take a reference of neigh here ensuring that neigh would
@@ -1527,6 +1535,39 @@ static void mlxsw_sp_nexthop_fini(struct mlxsw_sp *mlxsw_sp,
        mlxsw_sp_nexthop_remove(mlxsw_sp, nh);
 }
 
+static void mlxsw_sp_nexthop_event(struct mlxsw_sp *mlxsw_sp,
+                                  unsigned long event, struct fib_nh *fib_nh)
+{
+       struct mlxsw_sp_nexthop_key key;
+       struct mlxsw_sp_nexthop *nh;
+       struct mlxsw_sp_rif *r;
+
+       if (mlxsw_sp->router.aborted)
+               return;
+
+       key.fib_nh = fib_nh;
+       nh = mlxsw_sp_nexthop_lookup(mlxsw_sp, key);
+       if (WARN_ON_ONCE(!nh))
+               return;
+
+       r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, fib_nh->nh_dev);
+       if (!r)
+               return;
+
+       switch (event) {
+       case FIB_EVENT_NH_ADD:
+               nh->r = r;
+               mlxsw_sp_nexthop_neigh_init(mlxsw_sp, nh);
+               break;
+       case FIB_EVENT_NH_DEL:
+               mlxsw_sp_nexthop_neigh_fini(mlxsw_sp, nh);
+               nh->r = NULL;
+               break;
+       }
+
+       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
+}
+
 static struct mlxsw_sp_nexthop_group *
 mlxsw_sp_nexthop_group_create(struct mlxsw_sp *mlxsw_sp, struct fib_info *fi)
 {
@@ -2085,7 +2126,10 @@ static void __mlxsw_sp_router_fini(struct mlxsw_sp *mlxsw_sp)
 
 struct mlxsw_sp_fib_event_work {
        struct work_struct work;
-       struct fib_entry_notifier_info fen_info;
+       union {
+               struct fib_entry_notifier_info fen_info;
+               struct fib_nh_notifier_info fnh_info;
+       };
        struct mlxsw_sp *mlxsw_sp;
        unsigned long event;
 };
@@ -2114,6 +2158,12 @@ static void mlxsw_sp_router_fib_event_work(struct work_struct *work)
        case FIB_EVENT_RULE_DEL:
                mlxsw_sp_router_fib4_abort(mlxsw_sp);
                break;
+       case FIB_EVENT_NH_ADD: /* fall through */
+       case FIB_EVENT_NH_DEL:
+               mlxsw_sp_nexthop_event(mlxsw_sp, fib_work->event,
+                                      fib_work->fnh_info.fib_nh);
+               fib_info_put(fib_work->fnh_info.fib_nh->nh_parent);
+               break;
        }
        rtnl_unlock();
        kfree(fib_work);
@@ -2147,6 +2197,11 @@ static int mlxsw_sp_router_fib_event(struct notifier_block *nb,
                 */
                fib_info_hold(fib_work->fen_info.fi);
                break;
+       case FIB_EVENT_NH_ADD: /* fall through */
+       case FIB_EVENT_NH_DEL:
+               memcpy(&fib_work->fnh_info, ptr, sizeof(fib_work->fnh_info));
+               fib_info_hold(fib_work->fnh_info.fib_nh->nh_parent);
+               break;
        }
 
        mlxsw_core_schedule_work(&fib_work->work);