Commit b349e0b5 authored by David S. Miller's avatar David S. Miller

Merge branch 'net-Allow-FIB-notifiers-to-fail-add-and-replace'

David Ahern says:

====================
net: Allow FIB notifiers to fail add and replace

I wanted to revisit how resource overload is handled for hardware offload
of FIB entries and rules. At the moment, the in-kernel fib notifier can
tell a driver about a route or rule add, replace, and delete, but the
notifier can not affect the action. Specifically, in the case of mlxsw
if a route or rule add is going to overflow the ASIC resources the only
recourse is to abort hardware offload. Aborting offload is akin to taking
down the switch as the path from data plane to the control plane simply
can not support the traffic bandwidth of the front panel ports. Further,
the current state of FIB notifiers is inconsistent with other resources
where a driver can affect a user request - e.g., enslavement of a port
into a bridge or a VRF.

As a result of the work done over the past 3+ years, I believe we are
at a point where we can bring consistency to the stack and offloads,
and reliably allow the FIB notifiers to fail a request, pushing an error
along with a suitable error message back to the user. Rather than
aborting offload when the switch is out of resources, userspace is simply
prevented from adding more routes and has a clear indication of why.

This set does not resolve the corner case where rules or routes not
supported by the device are installed prior to the driver getting loaded
and registering for FIB notifications. In that case, hardware offload has
not been established and it can refuse to offload anything, sending
errors back to userspace via extack. Since conceptually the driver owns
the netdevices associated with its asic, this corner case mainly applies
to unsupported rules and any races during the bringup phase.

Patch 1 fixes call_fib_notifiers to extract the errno from the encoded
response from handlers.

Patches 2-5 allow the call to call_fib_notifiers to fail the add or
replace of a route or rule.

Patch 6 adds a simple resource controller to netdevsim to illustrate
how a FIB resource controller can limit the number of route entries.

Changes since RFC
- correct return code for call_fib_notifier
- dropped patch 6 exporting devlink symbols
- limited example resource controller to init_net only
- updated Kconfig for netdevsim to use MAY_USE_DEVLINK
- updated cover letter regarding startup case noted by Ido
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 6e2135ce 37923ed6
...@@ -500,6 +500,7 @@ source "drivers/net/hyperv/Kconfig" ...@@ -500,6 +500,7 @@ source "drivers/net/hyperv/Kconfig"
config NETDEVSIM config NETDEVSIM
tristate "Simulated networking device" tristate "Simulated networking device"
depends on DEBUG_FS depends on DEBUG_FS
depends on MAY_USE_DEVLINK
help help
This driver is a developer testing tool and software model that can This driver is a developer testing tool and software model that can
be used to test various control path networking APIs, especially be used to test various control path networking APIs, especially
......
...@@ -9,3 +9,7 @@ ifeq ($(CONFIG_BPF_SYSCALL),y) ...@@ -9,3 +9,7 @@ ifeq ($(CONFIG_BPF_SYSCALL),y)
netdevsim-objs += \ netdevsim-objs += \
bpf.o bpf.o
endif endif
ifneq ($(CONFIG_NET_DEVLINK),)
netdevsim-objs += devlink.o fib.o
endif
/*
* Copyright (c) 2018 Cumulus Networks. All rights reserved.
* Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <linux/device.h>
#include <net/devlink.h>
#include <net/netns/generic.h>
#include "netdevsim.h"
static unsigned int nsim_devlink_id;
/* place holder until devlink and namespaces is sorted out */
static struct net *nsim_devlink_net(struct devlink *devlink)
{
return &init_net;
}
/* IPv4
*/
static u64 nsim_ipv4_fib_resource_occ_get(struct devlink *devlink)
{
struct net *net = nsim_devlink_net(devlink);
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB, false);
}
static struct devlink_resource_ops nsim_ipv4_fib_res_ops = {
.occ_get = nsim_ipv4_fib_resource_occ_get,
};
static u64 nsim_ipv4_fib_rules_res_occ_get(struct devlink *devlink)
{
struct net *net = nsim_devlink_net(devlink);
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB_RULES, false);
}
static struct devlink_resource_ops nsim_ipv4_fib_rules_res_ops = {
.occ_get = nsim_ipv4_fib_rules_res_occ_get,
};
/* IPv6
*/
static u64 nsim_ipv6_fib_resource_occ_get(struct devlink *devlink)
{
struct net *net = nsim_devlink_net(devlink);
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB, false);
}
static struct devlink_resource_ops nsim_ipv6_fib_res_ops = {
.occ_get = nsim_ipv6_fib_resource_occ_get,
};
static u64 nsim_ipv6_fib_rules_res_occ_get(struct devlink *devlink)
{
struct net *net = nsim_devlink_net(devlink);
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB_RULES, false);
}
static struct devlink_resource_ops nsim_ipv6_fib_rules_res_ops = {
.occ_get = nsim_ipv6_fib_rules_res_occ_get,
};
static int devlink_resources_register(struct devlink *devlink)
{
struct devlink_resource_size_params params = {
.size_max = (u64)-1,
.size_granularity = 1,
.unit = DEVLINK_RESOURCE_UNIT_ENTRY
};
struct net *net = nsim_devlink_net(devlink);
int err;
u64 n;
/* Resources for IPv4 */
err = devlink_resource_register(devlink, "IPv4", (u64)-1,
NSIM_RESOURCE_IPV4,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&params, NULL);
if (err) {
pr_err("Failed to register IPv4 top resource\n");
goto out;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB, true);
err = devlink_resource_register(devlink, "fib", n,
NSIM_RESOURCE_IPV4_FIB,
NSIM_RESOURCE_IPV4,
&params, &nsim_ipv4_fib_res_ops);
if (err) {
pr_err("Failed to register IPv4 FIB resource\n");
return err;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB_RULES, true);
err = devlink_resource_register(devlink, "fib-rules", n,
NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV4,
&params, &nsim_ipv4_fib_rules_res_ops);
if (err) {
pr_err("Failed to register IPv4 FIB rules resource\n");
return err;
}
/* Resources for IPv6 */
err = devlink_resource_register(devlink, "IPv6", (u64)-1,
NSIM_RESOURCE_IPV6,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&params, NULL);
if (err) {
pr_err("Failed to register IPv6 top resource\n");
goto out;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB, true);
err = devlink_resource_register(devlink, "fib", n,
NSIM_RESOURCE_IPV6_FIB,
NSIM_RESOURCE_IPV6,
&params, &nsim_ipv6_fib_res_ops);
if (err) {
pr_err("Failed to register IPv6 FIB resource\n");
return err;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB_RULES, true);
err = devlink_resource_register(devlink, "fib-rules", n,
NSIM_RESOURCE_IPV6_FIB_RULES,
NSIM_RESOURCE_IPV6,
&params, &nsim_ipv6_fib_rules_res_ops);
if (err) {
pr_err("Failed to register IPv6 FIB rules resource\n");
return err;
}
out:
return err;
}
static int nsim_devlink_reload(struct devlink *devlink)
{
enum nsim_resource_id res_ids[] = {
NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
};
struct net *net = nsim_devlink_net(devlink);
int i;
for (i = 0; i < ARRAY_SIZE(res_ids); ++i) {
int err;
u64 val;
err = devlink_resource_size_get(devlink, res_ids[i], &val);
if (!err) {
err = nsim_fib_set_max(net, res_ids[i], val);
if (err)
return err;
}
}
return 0;
}
static void nsim_devlink_net_reset(struct net *net)
{
enum nsim_resource_id res_ids[] = {
NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
};
int i;
for (i = 0; i < ARRAY_SIZE(res_ids); ++i) {
if (nsim_fib_set_max(net, res_ids[i], (u64)-1)) {
pr_err("Failed to reset limit for resource %u\n",
res_ids[i]);
}
}
}
static const struct devlink_ops nsim_devlink_ops = {
.reload = nsim_devlink_reload,
};
/* once devlink / namespace issues are sorted out
* this needs to be net in which a devlink instance
* is to be created. e.g., dev_net(ns->netdev)
*/
static struct net *nsim_to_net(struct netdevsim *ns)
{
return &init_net;
}
void nsim_devlink_teardown(struct netdevsim *ns)
{
if (ns->devlink) {
struct net *net = nsim_to_net(ns);
bool *reg_devlink = net_generic(net, nsim_devlink_id);
devlink_unregister(ns->devlink);
devlink_free(ns->devlink);
ns->devlink = NULL;
nsim_devlink_net_reset(net);
*reg_devlink = true;
}
}
void nsim_devlink_setup(struct netdevsim *ns)
{
struct net *net = nsim_to_net(ns);
bool *reg_devlink = net_generic(net, nsim_devlink_id);
struct devlink *devlink;
int err = -ENOMEM;
/* only one device per namespace controls devlink */
if (!*reg_devlink) {
ns->devlink = NULL;
return;
}
devlink = devlink_alloc(&nsim_devlink_ops, 0);
if (!devlink)
return;
err = devlink_register(devlink, &ns->dev);
if (err)
goto err_devlink_free;
err = devlink_resources_register(devlink);
if (err)
goto err_dl_unregister;
ns->devlink = devlink;
*reg_devlink = false;
return;
err_dl_unregister:
devlink_unregister(devlink);
err_devlink_free:
devlink_free(devlink);
}
/* Initialize per network namespace state */
static int __net_init nsim_devlink_netns_init(struct net *net)
{
bool *reg_devlink = net_generic(net, nsim_devlink_id);
*reg_devlink = true;
return 0;
}
static struct pernet_operations nsim_devlink_net_ops __net_initdata = {
.init = nsim_devlink_netns_init,
.id = &nsim_devlink_id,
.size = sizeof(bool),
};
void nsim_devlink_exit(void)
{
unregister_pernet_subsys(&nsim_devlink_net_ops);
nsim_fib_exit();
}
int nsim_devlink_init(void)
{
int err;
err = nsim_fib_init();
if (err)
goto err_out;
err = register_pernet_subsys(&nsim_devlink_net_ops);
if (err)
nsim_fib_exit();
err_out:
return err;
}
/*
* Copyright (c) 2018 Cumulus Networks. All rights reserved.
* Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <net/fib_notifier.h>
#include <net/ip_fib.h>
#include <net/ip6_fib.h>
#include <net/fib_rules.h>
#include <net/netns/generic.h>
#include "netdevsim.h"
struct nsim_fib_entry {
u64 max;
u64 num;
};
struct nsim_per_fib_data {
struct nsim_fib_entry fib;
struct nsim_fib_entry rules;
};
struct nsim_fib_data {
struct nsim_per_fib_data ipv4;
struct nsim_per_fib_data ipv6;
};
static unsigned int nsim_fib_net_id;
u64 nsim_fib_get_val(struct net *net, enum nsim_resource_id res_id, bool max)
{
struct nsim_fib_data *fib_data = net_generic(net, nsim_fib_net_id);
struct nsim_fib_entry *entry;
switch (res_id) {
case NSIM_RESOURCE_IPV4_FIB:
entry = &fib_data->ipv4.fib;
break;
case NSIM_RESOURCE_IPV4_FIB_RULES:
entry = &fib_data->ipv4.rules;
break;
case NSIM_RESOURCE_IPV6_FIB:
entry = &fib_data->ipv6.fib;
break;
case NSIM_RESOURCE_IPV6_FIB_RULES:
entry = &fib_data->ipv6.rules;
break;
default:
return 0;
}
return max ? entry->max : entry->num;
}
int nsim_fib_set_max(struct net *net, enum nsim_resource_id res_id, u64 val)
{
struct nsim_fib_data *fib_data = net_generic(net, nsim_fib_net_id);
struct nsim_fib_entry *entry;
int err = 0;
switch (res_id) {
case NSIM_RESOURCE_IPV4_FIB:
entry = &fib_data->ipv4.fib;
break;
case NSIM_RESOURCE_IPV4_FIB_RULES:
entry = &fib_data->ipv4.rules;
break;
case NSIM_RESOURCE_IPV6_FIB:
entry = &fib_data->ipv6.fib;
break;
case NSIM_RESOURCE_IPV6_FIB_RULES:
entry = &fib_data->ipv6.rules;
break;
default:
return 0;
}
/* not allowing a new max to be less than curren occupancy
* --> no means of evicting entries
*/
if (val < entry->num)
err = -EINVAL;
else
entry->max = val;
return err;
}
static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
struct netlink_ext_ack *extack)
{
int err = 0;
if (add) {
if (entry->num < entry->max) {
entry->num++;
} else {
err = -ENOSPC;
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
}
} else {
entry->num--;
}
return err;
}
static int nsim_fib_rule_event(struct fib_notifier_info *info, bool add)
{
struct nsim_fib_data *data = net_generic(info->net, nsim_fib_net_id);
struct netlink_ext_ack *extack = info->extack;
int err = 0;
switch (info->family) {
case AF_INET:
err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
break;
case AF_INET6:
err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
break;
}
return err;
}
static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
struct netlink_ext_ack *extack)
{
int err = 0;
if (add) {
if (entry->num < entry->max) {
entry->num++;
} else {
err = -ENOSPC;
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
}
} else {
entry->num--;
}
return err;
}
static int nsim_fib_event(struct fib_notifier_info *info, bool add)
{
struct nsim_fib_data *data = net_generic(info->net, nsim_fib_net_id);
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);
break;
case AF_INET6:
err = nsim_fib_account(&data->ipv6.fib, add, extack);
break;
}
return err;
}
static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
void *ptr)
{
struct fib_notifier_info *info = ptr;
int err = 0;
switch (event) {
case FIB_EVENT_RULE_ADD: /* fall through */
case FIB_EVENT_RULE_DEL:
err = nsim_fib_rule_event(info, event == FIB_EVENT_RULE_ADD);
break;
case FIB_EVENT_ENTRY_ADD: /* fall through */
case FIB_EVENT_ENTRY_DEL:
err = nsim_fib_event(info, event == FIB_EVENT_ENTRY_ADD);
break;
}
return notifier_from_errno(err);
}
/* inconsistent dump, trying again */
static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
{
struct nsim_fib_data *data;
struct net *net;
rcu_read_lock();
for_each_net_rcu(net) {
data = net_generic(net, nsim_fib_net_id);
data->ipv4.fib.num = 0ULL;
data->ipv4.rules.num = 0ULL;
data->ipv6.fib.num = 0ULL;
data->ipv6.rules.num = 0ULL;
}
rcu_read_unlock();
}
static struct notifier_block nsim_fib_nb = {
.notifier_call = nsim_fib_event_nb,
};
/* Initialize per network namespace state */
static int __net_init nsim_fib_netns_init(struct net *net)
{
struct nsim_fib_data *data = net_generic(net, nsim_fib_net_id);
data->ipv4.fib.max = (u64)-1;
data->ipv4.rules.max = (u64)-1;
data->ipv6.fib.max = (u64)-1;
data->ipv6.rules.max = (u64)-1;
return 0;
}
static struct pernet_operations nsim_fib_net_ops __net_initdata = {
.init = nsim_fib_netns_init,
.id = &nsim_fib_net_id,
.size = sizeof(struct nsim_fib_data),
};
void nsim_fib_exit(void)
{
unregister_pernet_subsys(&nsim_fib_net_ops);
unregister_fib_notifier(&nsim_fib_nb);
}
int nsim_fib_init(void)
{
int err;
err = register_pernet_subsys(&nsim_fib_net_ops);
if (err < 0) {
pr_err("Failed to register pernet subsystem\n");
goto err_out;
}
err = register_fib_notifier(&nsim_fib_nb, nsim_fib_dump_inconsistent);
if (err < 0) {
pr_err("Failed to register fib notifier\n");
goto err_out;
}
err_out:
return err;
}
...@@ -167,6 +167,8 @@ static int nsim_init(struct net_device *dev) ...@@ -167,6 +167,8 @@ static int nsim_init(struct net_device *dev)
SET_NETDEV_DEV(dev, &ns->dev); SET_NETDEV_DEV(dev, &ns->dev);
nsim_devlink_setup(ns);
return 0; return 0;
err_bpf_uninit: err_bpf_uninit:
...@@ -180,6 +182,7 @@ static void nsim_uninit(struct net_device *dev) ...@@ -180,6 +182,7 @@ static void nsim_uninit(struct net_device *dev)
{ {
struct netdevsim *ns = netdev_priv(dev); struct netdevsim *ns = netdev_priv(dev);
nsim_devlink_teardown(ns);
debugfs_remove_recursive(ns->ddir); debugfs_remove_recursive(ns->ddir);
nsim_bpf_uninit(ns); nsim_bpf_uninit(ns);
} }
...@@ -478,12 +481,18 @@ static int __init nsim_module_init(void) ...@@ -478,12 +481,18 @@ static int __init nsim_module_init(void)
if (err) if (err)
goto err_debugfs_destroy; goto err_debugfs_destroy;
err = rtnl_link_register(&nsim_link_ops); err = nsim_devlink_init();
if (err) if (err)
goto err_unreg_bus; goto err_unreg_bus;
err = rtnl_link_register(&nsim_link_ops);
if (err)
goto err_dl_fini;
return 0; return 0;
err_dl_fini:
nsim_devlink_exit();
err_unreg_bus: err_unreg_bus:
bus_unregister(&nsim_bus); bus_unregister(&nsim_bus);
err_debugfs_destroy: err_debugfs_destroy:
...@@ -494,6 +503,7 @@ static int __init nsim_module_init(void) ...@@ -494,6 +503,7 @@ static int __init nsim_module_init(void)
static void __exit nsim_module_exit(void) static void __exit nsim_module_exit(void)
{ {
rtnl_link_unregister(&nsim_link_ops); rtnl_link_unregister(&nsim_link_ops);
nsim_devlink_exit();
bus_unregister(&nsim_bus); bus_unregister(&nsim_bus);
debugfs_remove_recursive(nsim_ddir); debugfs_remove_recursive(nsim_ddir);
} }
......
...@@ -64,6 +64,9 @@ struct netdevsim { ...@@ -64,6 +64,9 @@ struct netdevsim {
bool bpf_map_accept; bool bpf_map_accept;
struct list_head bpf_bound_maps; struct list_head bpf_bound_maps;
#if IS_ENABLED(CONFIG_NET_DEVLINK)
struct devlink *devlink;
#endif
}; };
extern struct dentry *nsim_ddir; extern struct dentry *nsim_ddir;
...@@ -103,6 +106,46 @@ nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, void *type_data, ...@@ -103,6 +106,46 @@ nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
} }
#endif #endif
#if IS_ENABLED(CONFIG_NET_DEVLINK)
enum nsim_resource_id {
NSIM_RESOURCE_NONE, /* DEVLINK_RESOURCE_ID_PARENT_TOP */
NSIM_RESOURCE_IPV4,
NSIM_RESOURCE_IPV4_FIB,
NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV6,
NSIM_RESOURCE_IPV6_FIB,
NSIM_RESOURCE_IPV6_FIB_RULES,
};
void nsim_devlink_setup(struct netdevsim *ns);
void nsim_devlink_teardown(struct netdevsim *ns);
int nsim_devlink_init(void);
void nsim_devlink_exit(void);
int nsim_fib_init(void);
void nsim_fib_exit(void);
u64 nsim_fib_get_val(struct net *net, enum nsim_resource_id res_id, bool max);
int nsim_fib_set_max(struct net *net, enum nsim_resource_id res_id, u64 val);
#else
static inline void nsim_devlink_setup(struct netdevsim *ns)
{
}
static inline void nsim_devlink_teardown(struct netdevsim *ns)
{
}
static inline int nsim_devlink_init(void)
{
return 0;
}
static inline void nsim_devlink_exit(void)
{
}
#endif
static inline struct netdevsim *to_nsim(struct device *ptr) static inline struct netdevsim *to_nsim(struct device *ptr)
{ {
return container_of(ptr, struct netdevsim, dev); return container_of(ptr, struct netdevsim, dev);
......
...@@ -13,16 +13,22 @@ int call_fib_notifier(struct notifier_block *nb, struct net *net, ...@@ -13,16 +13,22 @@ int call_fib_notifier(struct notifier_block *nb, struct net *net,
enum fib_event_type event_type, enum fib_event_type event_type,
struct fib_notifier_info *info) struct fib_notifier_info *info)
{ {
int err;
info->net = net; info->net = net;
return nb->notifier_call(nb, event_type, info); err = nb->notifier_call(nb, event_type, info);
return notifier_to_errno(err);
} }
EXPORT_SYMBOL(call_fib_notifier); EXPORT_SYMBOL(call_fib_notifier);
int call_fib_notifiers(struct net *net, enum fib_event_type event_type, int call_fib_notifiers(struct net *net, enum fib_event_type event_type,
struct fib_notifier_info *info) struct fib_notifier_info *info)
{ {
int err;
info->net = net; info->net = net;
return atomic_notifier_call_chain(&fib_chain, event_type, info); err = atomic_notifier_call_chain(&fib_chain, event_type, info);
return notifier_to_errno(err);
} }
EXPORT_SYMBOL(call_fib_notifiers); EXPORT_SYMBOL(call_fib_notifiers);
......
...@@ -631,6 +631,11 @@ int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -631,6 +631,11 @@ int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh,
if (err < 0) if (err < 0)
goto errout_free; goto errout_free;
err = call_fib_rule_notifiers(net, FIB_EVENT_RULE_ADD, rule, ops,
extack);
if (err < 0)
goto errout_free;
list_for_each_entry(r, &ops->rules_list, list) { list_for_each_entry(r, &ops->rules_list, list) {
if (r->pref > rule->pref) if (r->pref > rule->pref)
break; break;
...@@ -667,7 +672,6 @@ int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -667,7 +672,6 @@ int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh,
if (rule->tun_id) if (rule->tun_id)
ip_tunnel_need_metadata(); ip_tunnel_need_metadata();
call_fib_rule_notifiers(net, FIB_EVENT_RULE_ADD, rule, ops, extack);
notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid); notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid);
flush_route_cache(ops); flush_route_cache(ops);
rules_ops_put(ops); rules_ops_put(ops);
......
...@@ -1065,6 +1065,9 @@ static int fib_insert_node(struct trie *t, struct key_vector *tp, ...@@ -1065,6 +1065,9 @@ static int fib_insert_node(struct trie *t, struct key_vector *tp,
return -ENOMEM; return -ENOMEM;
} }
/* fib notifier for ADD is sent before calling fib_insert_alias with
* the expectation that the only possible failure ENOMEM
*/
static int fib_insert_alias(struct trie *t, struct key_vector *tp, static int fib_insert_alias(struct trie *t, struct key_vector *tp,
struct key_vector *l, struct fib_alias *new, struct key_vector *l, struct fib_alias *new,
struct fib_alias *fa, t_key key) struct fib_alias *fa, t_key key)
...@@ -1216,8 +1219,13 @@ int fib_table_insert(struct net *net, struct fib_table *tb, ...@@ -1216,8 +1219,13 @@ int fib_table_insert(struct net *net, struct fib_table *tb,
new_fa->tb_id = tb->tb_id; new_fa->tb_id = tb->tb_id;
new_fa->fa_default = -1; new_fa->fa_default = -1;
call_fib_entry_notifiers(net, FIB_EVENT_ENTRY_REPLACE, err = call_fib_entry_notifiers(net,
key, plen, new_fa, extack); FIB_EVENT_ENTRY_REPLACE,
key, plen, new_fa,
extack);
if (err)
goto out_free_new_fa;
rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen,
tb->tb_id, &cfg->fc_nlinfo, nlflags); tb->tb_id, &cfg->fc_nlinfo, nlflags);
...@@ -1263,21 +1271,32 @@ int fib_table_insert(struct net *net, struct fib_table *tb, ...@@ -1263,21 +1271,32 @@ int fib_table_insert(struct net *net, struct fib_table *tb,
new_fa->tb_id = tb->tb_id; new_fa->tb_id = tb->tb_id;
new_fa->fa_default = -1; new_fa->fa_default = -1;
err = call_fib_entry_notifiers(net, event, key, plen, new_fa, extack);
if (err)
goto out_free_new_fa;
/* Insert new entry to the list. */ /* Insert new entry to the list. */
err = fib_insert_alias(t, tp, l, new_fa, fa, key); err = fib_insert_alias(t, tp, l, new_fa, fa, key);
if (err) if (err)
goto out_free_new_fa; goto out_fib_notif;
if (!plen) if (!plen)
tb->tb_num_default++; tb->tb_num_default++;
rt_cache_flush(cfg->fc_nlinfo.nl_net); rt_cache_flush(cfg->fc_nlinfo.nl_net);
call_fib_entry_notifiers(net, event, key, plen, new_fa, extack);
rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, new_fa->tb_id, rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, new_fa->tb_id,
&cfg->fc_nlinfo, nlflags); &cfg->fc_nlinfo, nlflags);
succeeded: succeeded:
return 0; return 0;
out_fib_notif:
/* notifier was sent that entry would be added to trie, but
* the add failed and need to recover. Only failure for
* fib_insert_alias is ENOMEM.
*/
NL_SET_ERR_MSG(extack, "Failed to insert route into trie");
call_fib_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, key,
plen, new_fa, NULL);
out_free_new_fa: out_free_new_fa:
kmem_cache_free(fn_alias_kmem, new_fa); kmem_cache_free(fn_alias_kmem, new_fa);
out: out:
......
...@@ -1007,12 +1007,16 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -1007,12 +1007,16 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
if (err) if (err)
return err; return err;
err = call_fib6_entry_notifiers(info->nl_net,
FIB_EVENT_ENTRY_ADD,
rt, extack);
if (err)
return err;
rcu_assign_pointer(rt->rt6_next, iter); rcu_assign_pointer(rt->rt6_next, iter);
atomic_inc(&rt->rt6i_ref); atomic_inc(&rt->rt6i_ref);
rcu_assign_pointer(rt->rt6i_node, fn); rcu_assign_pointer(rt->rt6i_node, fn);
rcu_assign_pointer(*ins, rt); rcu_assign_pointer(*ins, rt);
call_fib6_entry_notifiers(info->nl_net, FIB_EVENT_ENTRY_ADD,
rt, extack);
if (!info->skip_notify) if (!info->skip_notify)
inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags); inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags);
info->nl_net->ipv6.rt6_stats->fib_rt_entries++; info->nl_net->ipv6.rt6_stats->fib_rt_entries++;
...@@ -1036,12 +1040,16 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -1036,12 +1040,16 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
if (err) if (err)
return err; return err;
err = call_fib6_entry_notifiers(info->nl_net,
FIB_EVENT_ENTRY_REPLACE,
rt, extack);
if (err)
return err;
atomic_inc(&rt->rt6i_ref); atomic_inc(&rt->rt6i_ref);
rcu_assign_pointer(rt->rt6i_node, fn); rcu_assign_pointer(rt->rt6i_node, fn);
rt->rt6_next = iter->rt6_next; rt->rt6_next = iter->rt6_next;
rcu_assign_pointer(*ins, rt); rcu_assign_pointer(*ins, rt);
call_fib6_entry_notifiers(info->nl_net, FIB_EVENT_ENTRY_REPLACE,
rt, extack);
if (!info->skip_notify) if (!info->skip_notify)
inet6_rt_notify(RTM_NEWROUTE, rt, info, NLM_F_REPLACE); inet6_rt_notify(RTM_NEWROUTE, rt, info, NLM_F_REPLACE);
if (!(fn->fn_flags & RTN_RTINFO)) { if (!(fn->fn_flags & RTN_RTINFO)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment