| #include <linux/rtnetlink.h> |
| #include <linux/notifier.h> |
| #include <linux/rcupdate.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <net/net_namespace.h> |
| #include <net/netns/generic.h> |
| #include <net/fib_notifier.h> |
| |
| static unsigned int fib_notifier_net_id; |
| |
| struct fib_notifier_net { |
| struct list_head fib_notifier_ops; |
| struct atomic_notifier_head fib_chain; |
| }; |
| |
| int call_fib_notifier(struct notifier_block *nb, |
| enum fib_event_type event_type, |
| struct fib_notifier_info *info) |
| { |
| int err; |
| |
| err = nb->notifier_call(nb, event_type, info); |
| return notifier_to_errno(err); |
| } |
| EXPORT_SYMBOL(call_fib_notifier); |
| |
| int call_fib_notifiers(struct net *net, enum fib_event_type event_type, |
| struct fib_notifier_info *info) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| int err; |
| |
| err = atomic_notifier_call_chain(&fn_net->fib_chain, event_type, info); |
| return notifier_to_errno(err); |
| } |
| EXPORT_SYMBOL(call_fib_notifiers); |
| |
| static unsigned int fib_seq_sum(struct net *net) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| struct fib_notifier_ops *ops; |
| unsigned int fib_seq = 0; |
| |
| rtnl_lock(); |
| rcu_read_lock(); |
| list_for_each_entry_rcu(ops, &fn_net->fib_notifier_ops, list) { |
| if (!try_module_get(ops->owner)) |
| continue; |
| fib_seq += ops->fib_seq_read(net); |
| module_put(ops->owner); |
| } |
| rcu_read_unlock(); |
| rtnl_unlock(); |
| |
| return fib_seq; |
| } |
| |
| static int fib_net_dump(struct net *net, struct notifier_block *nb, |
| struct netlink_ext_ack *extack) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| struct fib_notifier_ops *ops; |
| int err = 0; |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(ops, &fn_net->fib_notifier_ops, list) { |
| if (!try_module_get(ops->owner)) |
| continue; |
| err = ops->fib_dump(net, nb, extack); |
| module_put(ops->owner); |
| if (err) |
| goto unlock; |
| } |
| |
| unlock: |
| rcu_read_unlock(); |
| |
| return err; |
| } |
| |
| static bool fib_dump_is_consistent(struct net *net, struct notifier_block *nb, |
| void (*cb)(struct notifier_block *nb), |
| unsigned int fib_seq) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| |
| atomic_notifier_chain_register(&fn_net->fib_chain, nb); |
| if (fib_seq == fib_seq_sum(net)) |
| return true; |
| atomic_notifier_chain_unregister(&fn_net->fib_chain, nb); |
| if (cb) |
| cb(nb); |
| return false; |
| } |
| |
| #define FIB_DUMP_MAX_RETRIES 5 |
| int register_fib_notifier(struct net *net, struct notifier_block *nb, |
| void (*cb)(struct notifier_block *nb), |
| struct netlink_ext_ack *extack) |
| { |
| int retries = 0; |
| int err; |
| |
| do { |
| unsigned int fib_seq = fib_seq_sum(net); |
| |
| err = fib_net_dump(net, nb, extack); |
| if (err) |
| return err; |
| |
| if (fib_dump_is_consistent(net, nb, cb, fib_seq)) |
| return 0; |
| } while (++retries < FIB_DUMP_MAX_RETRIES); |
| |
| return -EBUSY; |
| } |
| EXPORT_SYMBOL(register_fib_notifier); |
| |
| int unregister_fib_notifier(struct net *net, struct notifier_block *nb) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| |
| return atomic_notifier_chain_unregister(&fn_net->fib_chain, nb); |
| } |
| EXPORT_SYMBOL(unregister_fib_notifier); |
| |
| static int __fib_notifier_ops_register(struct fib_notifier_ops *ops, |
| struct net *net) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| struct fib_notifier_ops *o; |
| |
| list_for_each_entry(o, &fn_net->fib_notifier_ops, list) |
| if (ops->family == o->family) |
| return -EEXIST; |
| list_add_tail_rcu(&ops->list, &fn_net->fib_notifier_ops); |
| return 0; |
| } |
| |
| struct fib_notifier_ops * |
| fib_notifier_ops_register(const struct fib_notifier_ops *tmpl, struct net *net) |
| { |
| struct fib_notifier_ops *ops; |
| int err; |
| |
| ops = kmemdup(tmpl, sizeof(*ops), GFP_KERNEL); |
| if (!ops) |
| return ERR_PTR(-ENOMEM); |
| |
| err = __fib_notifier_ops_register(ops, net); |
| if (err) |
| goto err_register; |
| |
| return ops; |
| |
| err_register: |
| kfree(ops); |
| return ERR_PTR(err); |
| } |
| EXPORT_SYMBOL(fib_notifier_ops_register); |
| |
| void fib_notifier_ops_unregister(struct fib_notifier_ops *ops) |
| { |
| list_del_rcu(&ops->list); |
| kfree_rcu(ops, rcu); |
| } |
| EXPORT_SYMBOL(fib_notifier_ops_unregister); |
| |
| static int __net_init fib_notifier_net_init(struct net *net) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| |
| INIT_LIST_HEAD(&fn_net->fib_notifier_ops); |
| ATOMIC_INIT_NOTIFIER_HEAD(&fn_net->fib_chain); |
| return 0; |
| } |
| |
| static void __net_exit fib_notifier_net_exit(struct net *net) |
| { |
| struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
| |
| WARN_ON_ONCE(!list_empty(&fn_net->fib_notifier_ops)); |
| } |
| |
| static struct pernet_operations fib_notifier_net_ops = { |
| .init = fib_notifier_net_init, |
| .exit = fib_notifier_net_exit, |
| .id = &fib_notifier_net_id, |
| .size = sizeof(struct fib_notifier_net), |
| }; |
| |
| static int __init fib_notifier_init(void) |
| { |
| return register_pernet_subsys(&fib_notifier_net_ops); |
| } |
| |
| subsys_initcall(fib_notifier_init); |