#include #include #include #include #include #include #include #include static ATOMIC_NOTIFIER_HEAD(fib_chain); int call_fib_notifier(struct notifier_block *nb, struct net *net, enum fib_event_type event_type, struct fib_notifier_info *info) { int err; info->net = net; 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) { int err; info->net = net; err = atomic_notifier_call_chain(&fib_chain, event_type, info); return notifier_to_errno(err); } EXPORT_SYMBOL(call_fib_notifiers); static unsigned int fib_seq_sum(void) { struct fib_notifier_ops *ops; unsigned int fib_seq = 0; struct net *net; rtnl_lock(); down_read(&net_rwsem); for_each_net(net) { rcu_read_lock(); list_for_each_entry_rcu(ops, &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(); } up_read(&net_rwsem); rtnl_unlock(); return fib_seq; } static int fib_net_dump(struct net *net, struct notifier_block *nb) { struct fib_notifier_ops *ops; list_for_each_entry_rcu(ops, &net->fib_notifier_ops, list) { int err; if (!try_module_get(ops->owner)) continue; err = ops->fib_dump(net, nb); module_put(ops->owner); if (err) return err; } return 0; } static bool fib_dump_is_consistent(struct notifier_block *nb, void (*cb)(struct notifier_block *nb), unsigned int fib_seq) { atomic_notifier_chain_register(&fib_chain, nb); if (fib_seq == fib_seq_sum()) return true; atomic_notifier_chain_unregister(&fib_chain, nb); if (cb) cb(nb); return false; } #define FIB_DUMP_MAX_RETRIES 5 int register_fib_notifier(struct notifier_block *nb, void (*cb)(struct notifier_block *nb)) { int retries = 0; int err; do { unsigned int fib_seq = fib_seq_sum(); struct net *net; rcu_read_lock(); for_each_net_rcu(net) { err = fib_net_dump(net, nb); if (err) goto err_fib_net_dump; } rcu_read_unlock(); if (fib_dump_is_consistent(nb, cb, fib_seq)) return 0; } while (++retries < FIB_DUMP_MAX_RETRIES); return -EBUSY; err_fib_net_dump: rcu_read_unlock(); return err; } EXPORT_SYMBOL(register_fib_notifier); int unregister_fib_notifier(struct notifier_block *nb) { return atomic_notifier_chain_unregister(&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_ops *o; list_for_each_entry(o, &net->fib_notifier_ops, list) if (ops->family == o->family) return -EEXIST; list_add_tail_rcu(&ops->list, &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) { INIT_LIST_HEAD(&net->fib_notifier_ops); return 0; } static void __net_exit fib_notifier_net_exit(struct net *net) { WARN_ON_ONCE(!list_empty(&net->fib_notifier_ops)); } static struct pernet_operations fib_notifier_net_ops = { .init = fib_notifier_net_init, .exit = fib_notifier_net_exit, }; static int __init fib_notifier_init(void) { return register_pernet_subsys(&fib_notifier_net_ops); } subsys_initcall(fib_notifier_init);