From 446f739104f4da6207230363848ec2c89dfd858d Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Sat, 14 Dec 2019 17:53:15 +0200 Subject: ipv4: Remove old route notifications and convert listeners Unlike mlxsw, the other listeners to the FIB notification chain do not require any special modifications as they never considered multiple identical routes. This patch removes the old route notifications and converts all the listeners to use the new replace / delete notifications. Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Signed-off-by: David S. Miller --- drivers/net/netdevsim/fib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/net/netdevsim/fib.c') diff --git a/drivers/net/netdevsim/fib.c b/drivers/net/netdevsim/fib.c index 13540dee7364..4e02a4231fcb 100644 --- a/drivers/net/netdevsim/fib.c +++ b/drivers/net/netdevsim/fib.c @@ -177,10 +177,10 @@ static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, event == FIB_EVENT_RULE_ADD); break; + case FIB_EVENT_ENTRY_REPLACE: /* fall through */ case FIB_EVENT_ENTRY_ADD: /* fall through */ case FIB_EVENT_ENTRY_DEL: - err = nsim_fib_event(data, info, - event == FIB_EVENT_ENTRY_ADD); + err = nsim_fib_event(data, info, event != FIB_EVENT_ENTRY_DEL); break; } -- cgit v1.2.3-70-g09d2 From caafb2509fac1432849650826953dd88b7cbe374 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 23 Dec 2019 15:28:20 +0200 Subject: ipv6: Remove old route notifications and convert listeners Now that mlxsw is converted to use the new FIB notifications it is possible to delete the old ones and use the new replace / append / delete notifications. Signed-off-by: Ido Schimmel Reviewed-by: Jiri Pirko Reviewed-by: David Ahern Signed-off-by: David S. Miller --- .../net/ethernet/mellanox/mlxsw/spectrum_router.c | 9 ++-- drivers/net/netdevsim/fib.c | 1 - include/net/fib_notifier.h | 2 - net/ipv6/ip6_fib.c | 61 ++++++---------------- net/ipv6/route.c | 18 +------ 5 files changed, 21 insertions(+), 70 deletions(-) (limited to 'drivers/net/netdevsim/fib.c') diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c index 295cdcb1c4c0..f62e8d67348c 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c @@ -5966,7 +5966,7 @@ static void mlxsw_sp_router_fib6_event_work(struct work_struct *work) mlxsw_sp_span_respin(mlxsw_sp); switch (fib_work->event) { - case FIB_EVENT_ENTRY_REPLACE_TMP: + case FIB_EVENT_ENTRY_REPLACE: err = mlxsw_sp_router_fib6_replace(mlxsw_sp, fib_work->fib6_work.rt_arr, fib_work->fib6_work.nrt6); @@ -5982,7 +5982,7 @@ static void mlxsw_sp_router_fib6_event_work(struct work_struct *work) mlxsw_sp_router_fib_abort(mlxsw_sp); mlxsw_sp_router_fib6_work_fini(&fib_work->fib6_work); break; - case FIB_EVENT_ENTRY_DEL_TMP: + case FIB_EVENT_ENTRY_DEL: mlxsw_sp_router_fib6_del(mlxsw_sp, fib_work->fib6_work.rt_arr, fib_work->fib6_work.nrt6); @@ -6068,9 +6068,9 @@ static int mlxsw_sp_router_fib6_event(struct mlxsw_sp_fib_event_work *fib_work, int err; switch (fib_work->event) { - case FIB_EVENT_ENTRY_REPLACE_TMP: /* fall through */ + case FIB_EVENT_ENTRY_REPLACE: /* fall through */ case FIB_EVENT_ENTRY_APPEND: /* fall through */ - case FIB_EVENT_ENTRY_DEL_TMP: + case FIB_EVENT_ENTRY_DEL: fen6_info = container_of(info, struct fib6_entry_notifier_info, info); err = mlxsw_sp_router_fib6_work_init(&fib_work->fib6_work, @@ -6174,7 +6174,6 @@ static int mlxsw_sp_router_fib_event(struct notifier_block *nb, return notifier_from_errno(err); case FIB_EVENT_ENTRY_ADD: /* fall through */ case FIB_EVENT_ENTRY_REPLACE: /* fall through */ - case FIB_EVENT_ENTRY_REPLACE_TMP: /* fall through */ case FIB_EVENT_ENTRY_APPEND: if (router->aborted) { NL_SET_ERR_MSG_MOD(info->extack, "FIB offload was aborted. Not configuring route"); diff --git a/drivers/net/netdevsim/fib.c b/drivers/net/netdevsim/fib.c index 4e02a4231fcb..b5df308b4e33 100644 --- a/drivers/net/netdevsim/fib.c +++ b/drivers/net/netdevsim/fib.c @@ -178,7 +178,6 @@ static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, break; case FIB_EVENT_ENTRY_REPLACE: /* fall through */ - case FIB_EVENT_ENTRY_ADD: /* fall through */ case FIB_EVENT_ENTRY_DEL: err = nsim_fib_event(data, info, event != FIB_EVENT_ENTRY_DEL); break; diff --git a/include/net/fib_notifier.h b/include/net/fib_notifier.h index b3c54325caec..6d59221ff05a 100644 --- a/include/net/fib_notifier.h +++ b/include/net/fib_notifier.h @@ -23,8 +23,6 @@ enum fib_event_type { FIB_EVENT_NH_DEL, FIB_EVENT_VIF_ADD, FIB_EVENT_VIF_DEL, - FIB_EVENT_ENTRY_REPLACE_TMP, - FIB_EVENT_ENTRY_DEL_TMP, }; struct fib_notifier_ops { diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 67ddee539f77..b1e9a10e1133 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -423,8 +423,7 @@ int call_fib6_entry_notifiers_replace(struct net *net, struct fib6_info *rt) }; rt->fib6_table->fib_seq++; - return call_fib6_notifiers(net, FIB_EVENT_ENTRY_REPLACE_TMP, - &info.info); + return call_fib6_notifiers(net, FIB_EVENT_ENTRY_REPLACE, &info.info); } struct fib6_dump_arg { @@ -435,15 +434,7 @@ struct fib6_dump_arg { static int fib6_rt_dump(struct fib6_info *rt, struct fib6_dump_arg *arg) { - if (rt == arg->net->ipv6.fib6_null_entry) - return 0; - return call_fib6_entry_notifier(arg->nb, FIB_EVENT_ENTRY_ADD, - rt, arg->extack); -} - -static int fib6_rt_dump_tmp(struct fib6_info *rt, struct fib6_dump_arg *arg) -{ - enum fib_event_type fib_event = FIB_EVENT_ENTRY_REPLACE_TMP; + enum fib_event_type fib_event = FIB_EVENT_ENTRY_REPLACE; int err; if (!rt || rt == arg->net->ipv6.fib6_null_entry) @@ -463,19 +454,9 @@ static int fib6_rt_dump_tmp(struct fib6_info *rt, struct fib6_dump_arg *arg) static int fib6_node_dump(struct fib6_walker *w) { - struct fib6_info *rt; - int err = 0; - - err = fib6_rt_dump_tmp(w->leaf, w->args); - if (err) - goto out; + int err; - for_each_fib6_walker_rt(w) { - err = fib6_rt_dump(rt, w->args); - if (err) - break; - } -out: + err = fib6_rt_dump(w->leaf, w->args); w->leaf = NULL; return err; } @@ -1220,25 +1201,21 @@ next_iter: add: nlflags |= NLM_F_CREATE; - if (!info->skip_notify_kernel) { + /* The route should only be notified if it is the first + * route in the node or if it is added as a sibling + * route to the first route in the node. + */ + if (!info->skip_notify_kernel && + (notify_sibling_rt || ins == &fn->leaf)) { enum fib_event_type fib_event; if (notify_sibling_rt) fib_event = FIB_EVENT_ENTRY_APPEND; else - fib_event = FIB_EVENT_ENTRY_REPLACE_TMP; - /* The route should only be notified if it is the first - * route in the node or if it is added as a sibling - * route to the first route in the node. - */ - if (notify_sibling_rt || ins == &fn->leaf) - err = call_fib6_entry_notifiers(info->nl_net, - fib_event, rt, - extack); - + fib_event = FIB_EVENT_ENTRY_REPLACE; err = call_fib6_entry_notifiers(info->nl_net, - FIB_EVENT_ENTRY_ADD, - rt, extack); + fib_event, rt, + extack); if (err) { struct fib6_info *sibling, *next_sibling; @@ -1282,14 +1259,7 @@ add: return -ENOENT; } - if (!info->skip_notify_kernel) { - enum fib_event_type fib_event; - - fib_event = FIB_EVENT_ENTRY_REPLACE_TMP; - if (ins == &fn->leaf) - err = call_fib6_entry_notifiers(info->nl_net, - fib_event, rt, - extack); + if (!info->skip_notify_kernel && ins == &fn->leaf) { err = call_fib6_entry_notifiers(info->nl_net, FIB_EVENT_ENTRY_REPLACE, rt, extack); @@ -2007,11 +1977,10 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, if (!info->skip_notify_kernel) { if (notify_del) - call_fib6_entry_notifiers(net, FIB_EVENT_ENTRY_DEL_TMP, + call_fib6_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, 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); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 646716a47cc9..4b8659e077d3 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3787,15 +3787,10 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) replace_rt); else call_fib6_multipath_entry_notifiers(net, - FIB_EVENT_ENTRY_DEL_TMP, + FIB_EVENT_ENTRY_DEL, rt, rt->fib6_nsiblings, NULL); } - 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) { @@ -5074,7 +5069,6 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, { 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; @@ -5210,7 +5204,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, if (rt_notif->fib6_nsiblings != nhn - 1) fib_event = FIB_EVENT_ENTRY_APPEND; else - fib_event = FIB_EVENT_ENTRY_REPLACE_TMP; + fib_event = FIB_EVENT_ENTRY_REPLACE; err = call_fib6_multipath_entry_notifiers(info->nl_net, fib_event, rt_notif, @@ -5221,14 +5215,6 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, goto add_errout; } } - 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; - } /* success ... tell user about new route */ ip6_route_mpath_notify(rt_notif, rt_last, info, nlflags); -- cgit v1.2.3-70-g09d2 From 48bb9eb47b2708bd3ded16684ddd258e53bd402c Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 14 Jan 2020 13:23:15 +0200 Subject: netdevsim: fib: Add dummy implementation for FIB offload Implement dummy IPv4 and IPv6 FIB "offload" in the driver by storing currently "programmed" routes in a hash table. Each route in the hash table is marked with "trap" indication. The indication is cleared when the route is replaced or when the netdevsim instance is deleted. This will later allow us to test the route offload API on top of netdevsim. v2: * Convert to new fib_alias_hw_flags_set() interface Signed-off-by: Ido Schimmel Reviewed-by: Jiri Pirko Signed-off-by: David S. Miller --- drivers/net/Kconfig | 1 + drivers/net/netdevsim/fib.c | 671 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 663 insertions(+), 9 deletions(-) (limited to 'drivers/net/netdevsim/fib.c') diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 01e2657e4c26..e65ed4d0a7ad 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -549,6 +549,7 @@ source "drivers/net/hyperv/Kconfig" config NETDEVSIM tristate "Simulated networking device" depends on DEBUG_FS + depends on IPV6 || IPV6=n select NET_DEVLINK help This driver is a developer testing tool and software model that can diff --git a/drivers/net/netdevsim/fib.c b/drivers/net/netdevsim/fib.c index b5df308b4e33..8f56289fc2ec 100644 --- a/drivers/net/netdevsim/fib.c +++ b/drivers/net/netdevsim/fib.c @@ -14,6 +14,12 @@ * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. */ +#include +#include +#include +#include +#include +#include #include #include #include @@ -36,6 +42,48 @@ struct nsim_fib_data { struct notifier_block fib_nb; struct nsim_per_fib_data ipv4; struct nsim_per_fib_data ipv6; + struct rhashtable fib_rt_ht; + struct list_head fib_rt_list; + spinlock_t fib_lock; /* Protects hashtable, list and accounting */ + struct devlink *devlink; +}; + +struct nsim_fib_rt_key { + unsigned char addr[sizeof(struct in6_addr)]; + unsigned char prefix_len; + int family; + u32 tb_id; +}; + +struct nsim_fib_rt { + struct nsim_fib_rt_key key; + struct rhash_head ht_node; + struct list_head list; /* Member of fib_rt_list */ +}; + +struct nsim_fib4_rt { + struct nsim_fib_rt common; + struct fib_info *fi; + u8 tos; + u8 type; +}; + +struct nsim_fib6_rt { + struct nsim_fib_rt common; + struct list_head nh_list; + unsigned int nhs; +}; + +struct nsim_fib6_rt_nh { + struct list_head list; /* Member of nh_list */ + struct fib6_info *rt; +}; + +static const struct rhashtable_params nsim_fib_rt_ht_params = { + .key_offset = offsetof(struct nsim_fib_rt, key), + .head_offset = offsetof(struct nsim_fib_rt, ht_node), + .key_len = sizeof(struct nsim_fib_rt_key), + .automatic_shrinking = true, }; u64 nsim_fib_get_val(struct nsim_fib_data *fib_data, @@ -144,18 +192,556 @@ static int nsim_fib_account(struct nsim_fib_entry *entry, bool add, return err; } +static void nsim_fib_rt_init(struct nsim_fib_data *data, + struct nsim_fib_rt *fib_rt, const void *addr, + size_t addr_len, unsigned int prefix_len, + int family, u32 tb_id) +{ + memcpy(fib_rt->key.addr, addr, addr_len); + fib_rt->key.prefix_len = prefix_len; + fib_rt->key.family = family; + fib_rt->key.tb_id = tb_id; + list_add(&fib_rt->list, &data->fib_rt_list); +} + +static void nsim_fib_rt_fini(struct nsim_fib_rt *fib_rt) +{ + list_del(&fib_rt->list); +} + +static struct nsim_fib_rt *nsim_fib_rt_lookup(struct rhashtable *fib_rt_ht, + const void *addr, size_t addr_len, + unsigned int prefix_len, + int family, u32 tb_id) +{ + struct nsim_fib_rt_key key; + + memset(&key, 0, sizeof(key)); + memcpy(key.addr, addr, addr_len); + key.prefix_len = prefix_len; + key.family = family; + key.tb_id = tb_id; + + return rhashtable_lookup_fast(fib_rt_ht, &key, nsim_fib_rt_ht_params); +} + +static struct nsim_fib4_rt * +nsim_fib4_rt_create(struct nsim_fib_data *data, + struct fib_entry_notifier_info *fen_info) +{ + struct nsim_fib4_rt *fib4_rt; + + fib4_rt = kzalloc(sizeof(*fib4_rt), GFP_ATOMIC); + if (!fib4_rt) + return NULL; + + nsim_fib_rt_init(data, &fib4_rt->common, &fen_info->dst, sizeof(u32), + fen_info->dst_len, AF_INET, fen_info->tb_id); + + fib4_rt->fi = fen_info->fi; + fib_info_hold(fib4_rt->fi); + fib4_rt->tos = fen_info->tos; + fib4_rt->type = fen_info->type; + + return fib4_rt; +} + +static void nsim_fib4_rt_destroy(struct nsim_fib4_rt *fib4_rt) +{ + fib_info_put(fib4_rt->fi); + nsim_fib_rt_fini(&fib4_rt->common); + kfree(fib4_rt); +} + +static struct nsim_fib4_rt * +nsim_fib4_rt_lookup(struct rhashtable *fib_rt_ht, + const struct fib_entry_notifier_info *fen_info) +{ + struct nsim_fib_rt *fib_rt; + + fib_rt = nsim_fib_rt_lookup(fib_rt_ht, &fen_info->dst, sizeof(u32), + fen_info->dst_len, AF_INET, + fen_info->tb_id); + if (!fib_rt) + return NULL; + + return container_of(fib_rt, struct nsim_fib4_rt, common); +} + +static void nsim_fib4_rt_hw_flags_set(struct net *net, + const struct nsim_fib4_rt *fib4_rt, + bool trap) +{ + u32 *p_dst = (u32 *) fib4_rt->common.key.addr; + int dst_len = fib4_rt->common.key.prefix_len; + struct fib_rt_info fri; + + fri.fi = fib4_rt->fi; + fri.tb_id = fib4_rt->common.key.tb_id; + fri.dst = cpu_to_be32(*p_dst); + fri.dst_len = dst_len; + fri.tos = fib4_rt->tos; + fri.type = fib4_rt->type; + fri.offload = false; + fri.trap = trap; + fib_alias_hw_flags_set(net, &fri); +} + +static int nsim_fib4_rt_add(struct nsim_fib_data *data, + struct nsim_fib4_rt *fib4_rt, + struct netlink_ext_ack *extack) +{ + struct net *net = devlink_net(data->devlink); + int err; + + err = nsim_fib_account(&data->ipv4.fib, true, extack); + if (err) + return err; + + err = rhashtable_insert_fast(&data->fib_rt_ht, + &fib4_rt->common.ht_node, + nsim_fib_rt_ht_params); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv4 route"); + goto err_fib_dismiss; + } + + nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); + + return 0; + +err_fib_dismiss: + nsim_fib_account(&data->ipv4.fib, false, extack); + return err; +} + +static int nsim_fib4_rt_replace(struct nsim_fib_data *data, + struct nsim_fib4_rt *fib4_rt, + struct nsim_fib4_rt *fib4_rt_old, + struct netlink_ext_ack *extack) +{ + struct net *net = devlink_net(data->devlink); + int err; + + /* We are replacing a route, so no need to change the accounting. */ + err = rhashtable_replace_fast(&data->fib_rt_ht, + &fib4_rt_old->common.ht_node, + &fib4_rt->common.ht_node, + nsim_fib_rt_ht_params); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv4 route"); + return err; + } + + nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); + + nsim_fib4_rt_hw_flags_set(net, fib4_rt_old, false); + nsim_fib4_rt_destroy(fib4_rt_old); + + return 0; +} + +static int nsim_fib4_rt_insert(struct nsim_fib_data *data, + struct fib_entry_notifier_info *fen_info) +{ + struct netlink_ext_ack *extack = fen_info->info.extack; + struct nsim_fib4_rt *fib4_rt, *fib4_rt_old; + int err; + + fib4_rt = nsim_fib4_rt_create(data, fen_info); + if (!fib4_rt) + return -ENOMEM; + + fib4_rt_old = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info); + if (!fib4_rt_old) + err = nsim_fib4_rt_add(data, fib4_rt, extack); + else + err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old, extack); + + if (err) + nsim_fib4_rt_destroy(fib4_rt); + + return err; +} + +static void nsim_fib4_rt_remove(struct nsim_fib_data *data, + const struct fib_entry_notifier_info *fen_info) +{ + struct netlink_ext_ack *extack = fen_info->info.extack; + struct nsim_fib4_rt *fib4_rt; + + fib4_rt = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info); + if (WARN_ON_ONCE(!fib4_rt)) + return; + + rhashtable_remove_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node, + nsim_fib_rt_ht_params); + nsim_fib_account(&data->ipv4.fib, false, extack); + nsim_fib4_rt_destroy(fib4_rt); +} + +static int nsim_fib4_event(struct nsim_fib_data *data, + struct fib_notifier_info *info, + unsigned long event) +{ + struct fib_entry_notifier_info *fen_info; + int err = 0; + + fen_info = container_of(info, struct fib_entry_notifier_info, info); + + if (fen_info->fi->nh) { + NL_SET_ERR_MSG_MOD(info->extack, "IPv4 route with nexthop objects is not supported"); + return 0; + } + + switch (event) { + case FIB_EVENT_ENTRY_REPLACE: + err = nsim_fib4_rt_insert(data, fen_info); + break; + case FIB_EVENT_ENTRY_DEL: + nsim_fib4_rt_remove(data, fen_info); + break; + default: + break; + } + + return err; +} + +static struct nsim_fib6_rt_nh * +nsim_fib6_rt_nh_find(const struct nsim_fib6_rt *fib6_rt, + const struct fib6_info *rt) +{ + struct nsim_fib6_rt_nh *fib6_rt_nh; + + list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) { + if (fib6_rt_nh->rt == rt) + return fib6_rt_nh; + } + + return NULL; +} + +static int nsim_fib6_rt_nh_add(struct nsim_fib6_rt *fib6_rt, + struct fib6_info *rt) +{ + struct nsim_fib6_rt_nh *fib6_rt_nh; + + fib6_rt_nh = kzalloc(sizeof(*fib6_rt_nh), GFP_ATOMIC); + if (!fib6_rt_nh) + return -ENOMEM; + + fib6_info_hold(rt); + fib6_rt_nh->rt = rt; + list_add_tail(&fib6_rt_nh->list, &fib6_rt->nh_list); + fib6_rt->nhs++; + + return 0; +} + +static void nsim_fib6_rt_nh_del(struct nsim_fib6_rt *fib6_rt, + const struct fib6_info *rt) +{ + struct nsim_fib6_rt_nh *fib6_rt_nh; + + fib6_rt_nh = nsim_fib6_rt_nh_find(fib6_rt, rt); + if (WARN_ON_ONCE(!fib6_rt_nh)) + return; + + fib6_rt->nhs--; + list_del(&fib6_rt_nh->list); +#if IS_ENABLED(CONFIG_IPV6) + fib6_info_release(fib6_rt_nh->rt); +#endif + kfree(fib6_rt_nh); +} + +static struct nsim_fib6_rt * +nsim_fib6_rt_create(struct nsim_fib_data *data, + struct fib6_entry_notifier_info *fen6_info) +{ + struct fib6_info *iter, *rt = fen6_info->rt; + struct nsim_fib6_rt *fib6_rt; + int i = 0; + int err; + + fib6_rt = kzalloc(sizeof(*fib6_rt), GFP_ATOMIC); + if (!fib6_rt) + return NULL; + + nsim_fib_rt_init(data, &fib6_rt->common, &rt->fib6_dst.addr, + sizeof(rt->fib6_dst.addr), rt->fib6_dst.plen, AF_INET6, + rt->fib6_table->tb6_id); + + /* We consider a multipath IPv6 route as one entry, but it can be made + * up from several fib6_info structs (one for each nexthop), so we + * add them all to the same list under the entry. + */ + INIT_LIST_HEAD(&fib6_rt->nh_list); + + err = nsim_fib6_rt_nh_add(fib6_rt, rt); + if (err) + goto err_fib_rt_fini; + + if (!fen6_info->nsiblings) + return fib6_rt; + + list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) { + if (i == fen6_info->nsiblings) + break; + + err = nsim_fib6_rt_nh_add(fib6_rt, iter); + if (err) + goto err_fib6_rt_nh_del; + i++; + } + WARN_ON_ONCE(i != fen6_info->nsiblings); + + return fib6_rt; + +err_fib6_rt_nh_del: + list_for_each_entry_continue_reverse(iter, &rt->fib6_siblings, + fib6_siblings) + nsim_fib6_rt_nh_del(fib6_rt, iter); + nsim_fib6_rt_nh_del(fib6_rt, rt); +err_fib_rt_fini: + nsim_fib_rt_fini(&fib6_rt->common); + kfree(fib6_rt); + return ERR_PTR(err); +} + +static void nsim_fib6_rt_destroy(struct nsim_fib6_rt *fib6_rt) +{ + struct nsim_fib6_rt_nh *iter, *tmp; + + list_for_each_entry_safe(iter, tmp, &fib6_rt->nh_list, list) + nsim_fib6_rt_nh_del(fib6_rt, iter->rt); + WARN_ON_ONCE(!list_empty(&fib6_rt->nh_list)); + nsim_fib_rt_fini(&fib6_rt->common); + kfree(fib6_rt); +} + +static struct nsim_fib6_rt * +nsim_fib6_rt_lookup(struct rhashtable *fib_rt_ht, const struct fib6_info *rt) +{ + struct nsim_fib_rt *fib_rt; + + fib_rt = nsim_fib_rt_lookup(fib_rt_ht, &rt->fib6_dst.addr, + sizeof(rt->fib6_dst.addr), + rt->fib6_dst.plen, AF_INET6, + rt->fib6_table->tb6_id); + if (!fib_rt) + return NULL; + + return container_of(fib_rt, struct nsim_fib6_rt, common); +} + +static int nsim_fib6_rt_append(struct nsim_fib_data *data, + struct fib6_entry_notifier_info *fen6_info) +{ + struct fib6_info *iter, *rt = fen6_info->rt; + struct nsim_fib6_rt *fib6_rt; + int i = 0; + int err; + + fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, rt); + if (WARN_ON_ONCE(!fib6_rt)) + return -EINVAL; + + err = nsim_fib6_rt_nh_add(fib6_rt, rt); + if (err) + return err; + rt->trap = true; + + if (!fen6_info->nsiblings) + return 0; + + list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) { + if (i == fen6_info->nsiblings) + break; + + err = nsim_fib6_rt_nh_add(fib6_rt, iter); + if (err) + goto err_fib6_rt_nh_del; + iter->trap = true; + i++; + } + WARN_ON_ONCE(i != fen6_info->nsiblings); + + return 0; + +err_fib6_rt_nh_del: + list_for_each_entry_continue_reverse(iter, &rt->fib6_siblings, + fib6_siblings) { + iter->trap = false; + nsim_fib6_rt_nh_del(fib6_rt, iter); + } + rt->trap = false; + nsim_fib6_rt_nh_del(fib6_rt, rt); + return err; +} + +static void nsim_fib6_rt_hw_flags_set(const struct nsim_fib6_rt *fib6_rt, + bool trap) +{ + struct nsim_fib6_rt_nh *fib6_rt_nh; + + list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) + fib6_info_hw_flags_set(fib6_rt_nh->rt, false, trap); +} + +static int nsim_fib6_rt_add(struct nsim_fib_data *data, + struct nsim_fib6_rt *fib6_rt, + struct netlink_ext_ack *extack) +{ + int err; + + err = nsim_fib_account(&data->ipv6.fib, true, extack); + if (err) + return err; + + err = rhashtable_insert_fast(&data->fib_rt_ht, + &fib6_rt->common.ht_node, + nsim_fib_rt_ht_params); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv6 route"); + goto err_fib_dismiss; + } + + nsim_fib6_rt_hw_flags_set(fib6_rt, true); + + return 0; + +err_fib_dismiss: + nsim_fib_account(&data->ipv6.fib, false, extack); + return err; +} + +static int nsim_fib6_rt_replace(struct nsim_fib_data *data, + struct nsim_fib6_rt *fib6_rt, + struct nsim_fib6_rt *fib6_rt_old, + struct netlink_ext_ack *extack) +{ + int err; + + /* We are replacing a route, so no need to change the accounting. */ + err = rhashtable_replace_fast(&data->fib_rt_ht, + &fib6_rt_old->common.ht_node, + &fib6_rt->common.ht_node, + nsim_fib_rt_ht_params); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv6 route"); + return err; + } + + nsim_fib6_rt_hw_flags_set(fib6_rt, true); + + nsim_fib6_rt_hw_flags_set(fib6_rt_old, false); + nsim_fib6_rt_destroy(fib6_rt_old); + + return 0; +} + +static int nsim_fib6_rt_insert(struct nsim_fib_data *data, + struct fib6_entry_notifier_info *fen6_info) +{ + struct netlink_ext_ack *extack = fen6_info->info.extack; + struct nsim_fib6_rt *fib6_rt, *fib6_rt_old; + int err; + + fib6_rt = nsim_fib6_rt_create(data, fen6_info); + if (!fib6_rt) + return -ENOMEM; + + fib6_rt_old = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt); + if (!fib6_rt_old) + err = nsim_fib6_rt_add(data, fib6_rt, extack); + else + err = nsim_fib6_rt_replace(data, fib6_rt, fib6_rt_old, extack); + + if (err) + nsim_fib6_rt_destroy(fib6_rt); + + return err; +} + +static void +nsim_fib6_rt_remove(struct nsim_fib_data *data, + const struct fib6_entry_notifier_info *fen6_info) +{ + struct netlink_ext_ack *extack = fen6_info->info.extack; + struct nsim_fib6_rt *fib6_rt; + + /* Multipath routes are first added to the FIB trie and only then + * notified. If we vetoed the addition, we will get a delete + * notification for a route we do not have. Therefore, do not warn if + * route was not found. + */ + fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt); + if (!fib6_rt) + return; + + /* If not all the nexthops are deleted, then only reduce the nexthop + * group. + */ + if (fen6_info->nsiblings + 1 != fib6_rt->nhs) { + nsim_fib6_rt_nh_del(fib6_rt, fen6_info->rt); + return; + } + + rhashtable_remove_fast(&data->fib_rt_ht, &fib6_rt->common.ht_node, + nsim_fib_rt_ht_params); + nsim_fib_account(&data->ipv6.fib, false, extack); + nsim_fib6_rt_destroy(fib6_rt); +} + +static int nsim_fib6_event(struct nsim_fib_data *data, + struct fib_notifier_info *info, + unsigned long event) +{ + struct fib6_entry_notifier_info *fen6_info; + int err = 0; + + fen6_info = container_of(info, struct fib6_entry_notifier_info, info); + + if (fen6_info->rt->nh) { + NL_SET_ERR_MSG_MOD(info->extack, "IPv6 route with nexthop objects is not supported"); + return 0; + } + + if (fen6_info->rt->fib6_src.plen) { + NL_SET_ERR_MSG_MOD(info->extack, "IPv6 source-specific route is not supported"); + return 0; + } + + switch (event) { + case FIB_EVENT_ENTRY_REPLACE: + err = nsim_fib6_rt_insert(data, fen6_info); + break; + case FIB_EVENT_ENTRY_APPEND: + err = nsim_fib6_rt_append(data, fen6_info); + break; + case FIB_EVENT_ENTRY_DEL: + nsim_fib6_rt_remove(data, fen6_info); + break; + default: + break; + } + + return err; +} + static int nsim_fib_event(struct nsim_fib_data *data, - struct fib_notifier_info *info, bool add) + struct fib_notifier_info *info, unsigned long event) { - struct netlink_ext_ack *extack = info->extack; int err = 0; switch (info->family) { case AF_INET: - err = nsim_fib_account(&data->ipv4.fib, add, extack); + err = nsim_fib4_event(data, info, event); break; case AF_INET6: - err = nsim_fib_account(&data->ipv6.fib, add, extack); + err = nsim_fib6_event(data, info, event); break; } @@ -170,6 +756,9 @@ static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, struct fib_notifier_info *info = ptr; int err = 0; + /* IPv6 routes can be added via RAs from softIRQ. */ + spin_lock_bh(&data->fib_lock); + switch (event) { case FIB_EVENT_RULE_ADD: /* fall through */ case FIB_EVENT_RULE_DEL: @@ -178,23 +767,74 @@ static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, break; case FIB_EVENT_ENTRY_REPLACE: /* fall through */ + case FIB_EVENT_ENTRY_APPEND: /* fall through */ case FIB_EVENT_ENTRY_DEL: - err = nsim_fib_event(data, info, event != FIB_EVENT_ENTRY_DEL); + err = nsim_fib_event(data, info, event); break; } + spin_unlock_bh(&data->fib_lock); + return notifier_from_errno(err); } +static void nsim_fib4_rt_free(struct nsim_fib_rt *fib_rt, + struct nsim_fib_data *data) +{ + struct devlink *devlink = data->devlink; + struct nsim_fib4_rt *fib4_rt; + + fib4_rt = container_of(fib_rt, struct nsim_fib4_rt, common); + nsim_fib4_rt_hw_flags_set(devlink_net(devlink), fib4_rt, false); + nsim_fib_account(&data->ipv4.fib, false, NULL); + nsim_fib4_rt_destroy(fib4_rt); +} + +static void nsim_fib6_rt_free(struct nsim_fib_rt *fib_rt, + struct nsim_fib_data *data) +{ + struct nsim_fib6_rt *fib6_rt; + + fib6_rt = container_of(fib_rt, struct nsim_fib6_rt, common); + nsim_fib6_rt_hw_flags_set(fib6_rt, false); + nsim_fib_account(&data->ipv6.fib, false, NULL); + nsim_fib6_rt_destroy(fib6_rt); +} + +static void nsim_fib_rt_free(void *ptr, void *arg) +{ + struct nsim_fib_rt *fib_rt = ptr; + struct nsim_fib_data *data = arg; + + switch (fib_rt->key.family) { + case AF_INET: + nsim_fib4_rt_free(fib_rt, data); + break; + case AF_INET6: + nsim_fib6_rt_free(fib_rt, data); + break; + default: + WARN_ON_ONCE(1); + } +} + /* inconsistent dump, trying again */ static void nsim_fib_dump_inconsistent(struct notifier_block *nb) { struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, fib_nb); + struct nsim_fib_rt *fib_rt, *fib_rt_tmp; + + /* The notifier block is still not registered, so we do not need to + * take any locks here. + */ + list_for_each_entry_safe(fib_rt, fib_rt_tmp, &data->fib_rt_list, list) { + rhashtable_remove_fast(&data->fib_rt_ht, &fib_rt->ht_node, + nsim_fib_rt_ht_params); + nsim_fib_rt_free(fib_rt, data); + } - data->ipv4.fib.num = 0ULL; data->ipv4.rules.num = 0ULL; - data->ipv6.fib.num = 0ULL; data->ipv6.rules.num = 0ULL; } @@ -255,6 +895,13 @@ struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return ERR_PTR(-ENOMEM); + data->devlink = devlink; + + spin_lock_init(&data->fib_lock); + INIT_LIST_HEAD(&data->fib_rt_list); + err = rhashtable_init(&data->fib_rt_ht, &nsim_fib_rt_ht_params); + if (err) + goto err_data_free; nsim_fib_set_max_all(data, devlink); @@ -263,7 +910,7 @@ struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, nsim_fib_dump_inconsistent, extack); if (err) { pr_err("Failed to register fib notifier\n"); - goto err_out; + goto err_rhashtable_destroy; } devlink_resource_occ_get_register(devlink, @@ -284,7 +931,10 @@ struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, data); return data; -err_out: +err_rhashtable_destroy: + rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free, + data); +err_data_free: kfree(data); return ERR_PTR(err); } @@ -300,5 +950,8 @@ void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data) devlink_resource_occ_get_unregister(devlink, NSIM_RESOURCE_IPV4_FIB); unregister_fib_notifier(devlink_net(devlink), &data->fib_nb); + rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free, + data); + WARN_ON_ONCE(!list_empty(&data->fib_rt_list)); kfree(data); } -- cgit v1.2.3-70-g09d2 From 41cdc741048b0d04604c02aad9ec19f7d9130b70 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 15 Jan 2020 11:57:41 -0800 Subject: netdevsim: fix nsim_fib6_rt_create() error path It seems nsim_fib6_rt_create() intent was to return either a valid pointer or an embedded error code. BUG: unable to handle page fault for address: fffffffffffffff4 PGD 9870067 P4D 9870067 PUD 9872067 PMD 0 Oops: 0000 [#1] PREEMPT SMP KASAN CPU: 0 PID: 22851 Comm: syz-executor.1 Not tainted 5.5.0-rc5-syzkaller #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011 RIP: 0010:jhash2 include/linux/jhash.h:125 [inline] RIP: 0010:rhashtable_jhash2+0x76/0x2c0 lib/rhashtable.c:963 Code: b9 00 00 00 00 00 fc ff df 48 c1 e8 03 0f b6 14 08 4c 89 f0 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 30 02 00 00 49 8d 7e 04 <41> 8b 06 48 be 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 0f b6 RSP: 0018:ffffc90016127190 EFLAGS: 00010246 RAX: 0000000000000007 RBX: 00000000dfb3ab49 RCX: dffffc0000000000 RDX: 0000000000000000 RSI: ffffffff839ba7c8 RDI: fffffffffffffff8 RBP: ffffc900161271c0 R08: ffff8880951f8640 R09: ffffed1015d0703d R10: ffffed1015d0703c R11: ffff8880ae8381e3 R12: 00000000dfb3ab49 R13: 00000000dfb3ab49 R14: fffffffffffffff4 R15: 0000000000000007 FS: 00007f40bfbc6700(0000) GS:ffff8880ae800000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: fffffffffffffff4 CR3: 0000000093660000 CR4: 00000000001406f0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: rht_key_get_hash include/linux/rhashtable.h:133 [inline] rht_key_hashfn include/linux/rhashtable.h:159 [inline] rht_head_hashfn include/linux/rhashtable.h:174 [inline] __rhashtable_insert_fast.constprop.0+0xe15/0x1180 include/linux/rhashtable.h:723 rhashtable_insert_fast include/linux/rhashtable.h:832 [inline] nsim_fib6_rt_add drivers/net/netdevsim/fib.c:603 [inline] nsim_fib6_rt_insert drivers/net/netdevsim/fib.c:658 [inline] nsim_fib6_event drivers/net/netdevsim/fib.c:719 [inline] nsim_fib_event drivers/net/netdevsim/fib.c:744 [inline] nsim_fib_event_nb+0x1b16/0x2600 drivers/net/netdevsim/fib.c:772 notifier_call_chain+0xc2/0x230 kernel/notifier.c:83 __atomic_notifier_call_chain+0xa6/0x1a0 kernel/notifier.c:173 atomic_notifier_call_chain+0x2e/0x40 kernel/notifier.c:183 call_fib_notifiers+0x173/0x2a0 net/core/fib_notifier.c:35 call_fib6_notifiers+0x4b/0x60 net/ipv6/fib6_notifier.c:22 call_fib6_entry_notifiers+0xfb/0x150 net/ipv6/ip6_fib.c:399 fib6_add_rt2node net/ipv6/ip6_fib.c:1216 [inline] fib6_add+0x20cd/0x3ec0 net/ipv6/ip6_fib.c:1471 __ip6_ins_rt+0x54/0x80 net/ipv6/route.c:1315 ip6_ins_rt+0x96/0xd0 net/ipv6/route.c:1325 __ipv6_dev_ac_inc+0x76f/0xb20 net/ipv6/anycast.c:324 ipv6_sock_ac_join+0x4c1/0x790 net/ipv6/anycast.c:139 do_ipv6_setsockopt.isra.0+0x3908/0x4290 net/ipv6/ipv6_sockglue.c:670 ipv6_setsockopt+0xff/0x180 net/ipv6/ipv6_sockglue.c:944 udpv6_setsockopt+0x68/0xb0 net/ipv6/udp.c:1564 sock_common_setsockopt+0x94/0xd0 net/core/sock.c:3149 __sys_setsockopt+0x261/0x4c0 net/socket.c:2130 __do_sys_setsockopt net/socket.c:2146 [inline] __se_sys_setsockopt net/socket.c:2143 [inline] __x64_sys_setsockopt+0xbe/0x150 net/socket.c:2143 do_syscall_64+0xfa/0x790 arch/x86/entry/common.c:294 entry_SYSCALL_64_after_hwframe+0x49/0xbe RIP: 0033:0x45aff9 Fixes: 48bb9eb47b27 ("netdevsim: fib: Add dummy implementation for FIB offload") Signed-off-by: Eric Dumazet Reported-by: syzbot Cc: Ido Schimmel Reviewed-by: Ido Schimmel Signed-off-by: David S. Miller --- drivers/net/netdevsim/fib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/net/netdevsim/fib.c') diff --git a/drivers/net/netdevsim/fib.c b/drivers/net/netdevsim/fib.c index 8f56289fc2ec..f32d56ac3e80 100644 --- a/drivers/net/netdevsim/fib.c +++ b/drivers/net/netdevsim/fib.c @@ -467,7 +467,7 @@ nsim_fib6_rt_create(struct nsim_fib_data *data, fib6_rt = kzalloc(sizeof(*fib6_rt), GFP_ATOMIC); if (!fib6_rt) - return NULL; + return ERR_PTR(-ENOMEM); nsim_fib_rt_init(data, &fib6_rt->common, &rt->fib6_dst.addr, sizeof(rt->fib6_dst.addr), rt->fib6_dst.plen, AF_INET6, @@ -650,8 +650,8 @@ static int nsim_fib6_rt_insert(struct nsim_fib_data *data, int err; fib6_rt = nsim_fib6_rt_create(data, fen6_info); - if (!fib6_rt) - return -ENOMEM; + if (IS_ERR(fib6_rt)) + return PTR_ERR(fib6_rt); fib6_rt_old = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt); if (!fib6_rt_old) -- cgit v1.2.3-70-g09d2