Ido Schimmel | 04b1d4e | 2017-08-03 13:28:11 +0200 | [diff] [blame] | 1 | #include <linux/rtnetlink.h> |
| 2 | #include <linux/notifier.h> |
| 3 | #include <linux/rcupdate.h> |
| 4 | #include <linux/kernel.h> |
Ido Schimmel | 864150d | 2017-09-01 12:15:17 +0300 | [diff] [blame] | 5 | #include <linux/module.h> |
Ido Schimmel | 04b1d4e | 2017-08-03 13:28:11 +0200 | [diff] [blame] | 6 | #include <linux/init.h> |
| 7 | #include <net/net_namespace.h> |
| 8 | #include <net/fib_notifier.h> |
| 9 | |
| 10 | static ATOMIC_NOTIFIER_HEAD(fib_chain); |
| 11 | |
| 12 | int call_fib_notifier(struct notifier_block *nb, struct net *net, |
| 13 | enum fib_event_type event_type, |
| 14 | struct fib_notifier_info *info) |
| 15 | { |
| 16 | info->net = net; |
| 17 | return nb->notifier_call(nb, event_type, info); |
| 18 | } |
| 19 | EXPORT_SYMBOL(call_fib_notifier); |
| 20 | |
| 21 | int call_fib_notifiers(struct net *net, enum fib_event_type event_type, |
| 22 | struct fib_notifier_info *info) |
| 23 | { |
| 24 | info->net = net; |
| 25 | return atomic_notifier_call_chain(&fib_chain, event_type, info); |
| 26 | } |
| 27 | EXPORT_SYMBOL(call_fib_notifiers); |
| 28 | |
| 29 | static unsigned int fib_seq_sum(void) |
| 30 | { |
| 31 | struct fib_notifier_ops *ops; |
| 32 | unsigned int fib_seq = 0; |
| 33 | struct net *net; |
| 34 | |
| 35 | rtnl_lock(); |
| 36 | for_each_net(net) { |
Ido Schimmel | 864150d | 2017-09-01 12:15:17 +0300 | [diff] [blame] | 37 | list_for_each_entry(ops, &net->fib_notifier_ops, list) { |
| 38 | if (!try_module_get(ops->owner)) |
| 39 | continue; |
Ido Schimmel | 04b1d4e | 2017-08-03 13:28:11 +0200 | [diff] [blame] | 40 | fib_seq += ops->fib_seq_read(net); |
Ido Schimmel | 864150d | 2017-09-01 12:15:17 +0300 | [diff] [blame] | 41 | module_put(ops->owner); |
| 42 | } |
Ido Schimmel | 04b1d4e | 2017-08-03 13:28:11 +0200 | [diff] [blame] | 43 | } |
| 44 | rtnl_unlock(); |
| 45 | |
| 46 | return fib_seq; |
| 47 | } |
| 48 | |
| 49 | static int fib_net_dump(struct net *net, struct notifier_block *nb) |
| 50 | { |
| 51 | struct fib_notifier_ops *ops; |
| 52 | |
| 53 | list_for_each_entry_rcu(ops, &net->fib_notifier_ops, list) { |
Ido Schimmel | 864150d | 2017-09-01 12:15:17 +0300 | [diff] [blame] | 54 | int err; |
Ido Schimmel | 04b1d4e | 2017-08-03 13:28:11 +0200 | [diff] [blame] | 55 | |
Ido Schimmel | 864150d | 2017-09-01 12:15:17 +0300 | [diff] [blame] | 56 | if (!try_module_get(ops->owner)) |
| 57 | continue; |
| 58 | err = ops->fib_dump(net, nb); |
| 59 | module_put(ops->owner); |
Ido Schimmel | 04b1d4e | 2017-08-03 13:28:11 +0200 | [diff] [blame] | 60 | if (err) |
| 61 | return err; |
| 62 | } |
| 63 | |
| 64 | return 0; |
| 65 | } |
| 66 | |
| 67 | static bool fib_dump_is_consistent(struct notifier_block *nb, |
| 68 | void (*cb)(struct notifier_block *nb), |
| 69 | unsigned int fib_seq) |
| 70 | { |
| 71 | atomic_notifier_chain_register(&fib_chain, nb); |
| 72 | if (fib_seq == fib_seq_sum()) |
| 73 | return true; |
| 74 | atomic_notifier_chain_unregister(&fib_chain, nb); |
| 75 | if (cb) |
| 76 | cb(nb); |
| 77 | return false; |
| 78 | } |
| 79 | |
| 80 | #define FIB_DUMP_MAX_RETRIES 5 |
| 81 | int register_fib_notifier(struct notifier_block *nb, |
| 82 | void (*cb)(struct notifier_block *nb)) |
| 83 | { |
| 84 | int retries = 0; |
| 85 | int err; |
| 86 | |
| 87 | do { |
| 88 | unsigned int fib_seq = fib_seq_sum(); |
| 89 | struct net *net; |
| 90 | |
| 91 | rcu_read_lock(); |
| 92 | for_each_net_rcu(net) { |
| 93 | err = fib_net_dump(net, nb); |
| 94 | if (err) |
| 95 | goto err_fib_net_dump; |
| 96 | } |
| 97 | rcu_read_unlock(); |
| 98 | |
| 99 | if (fib_dump_is_consistent(nb, cb, fib_seq)) |
| 100 | return 0; |
| 101 | } while (++retries < FIB_DUMP_MAX_RETRIES); |
| 102 | |
| 103 | return -EBUSY; |
| 104 | |
| 105 | err_fib_net_dump: |
| 106 | rcu_read_unlock(); |
| 107 | return err; |
| 108 | } |
| 109 | EXPORT_SYMBOL(register_fib_notifier); |
| 110 | |
| 111 | int unregister_fib_notifier(struct notifier_block *nb) |
| 112 | { |
| 113 | return atomic_notifier_chain_unregister(&fib_chain, nb); |
| 114 | } |
| 115 | EXPORT_SYMBOL(unregister_fib_notifier); |
| 116 | |
| 117 | static int __fib_notifier_ops_register(struct fib_notifier_ops *ops, |
| 118 | struct net *net) |
| 119 | { |
| 120 | struct fib_notifier_ops *o; |
| 121 | |
| 122 | list_for_each_entry(o, &net->fib_notifier_ops, list) |
| 123 | if (ops->family == o->family) |
| 124 | return -EEXIST; |
| 125 | list_add_tail_rcu(&ops->list, &net->fib_notifier_ops); |
| 126 | return 0; |
| 127 | } |
| 128 | |
| 129 | struct fib_notifier_ops * |
| 130 | fib_notifier_ops_register(const struct fib_notifier_ops *tmpl, struct net *net) |
| 131 | { |
| 132 | struct fib_notifier_ops *ops; |
| 133 | int err; |
| 134 | |
| 135 | ops = kmemdup(tmpl, sizeof(*ops), GFP_KERNEL); |
| 136 | if (!ops) |
| 137 | return ERR_PTR(-ENOMEM); |
| 138 | |
| 139 | err = __fib_notifier_ops_register(ops, net); |
| 140 | if (err) |
| 141 | goto err_register; |
| 142 | |
| 143 | return ops; |
| 144 | |
| 145 | err_register: |
| 146 | kfree(ops); |
| 147 | return ERR_PTR(err); |
| 148 | } |
| 149 | EXPORT_SYMBOL(fib_notifier_ops_register); |
| 150 | |
| 151 | void fib_notifier_ops_unregister(struct fib_notifier_ops *ops) |
| 152 | { |
| 153 | list_del_rcu(&ops->list); |
| 154 | kfree_rcu(ops, rcu); |
| 155 | } |
| 156 | EXPORT_SYMBOL(fib_notifier_ops_unregister); |
| 157 | |
| 158 | static int __net_init fib_notifier_net_init(struct net *net) |
| 159 | { |
| 160 | INIT_LIST_HEAD(&net->fib_notifier_ops); |
| 161 | return 0; |
| 162 | } |
| 163 | |
| 164 | static struct pernet_operations fib_notifier_net_ops = { |
| 165 | .init = fib_notifier_net_init, |
| 166 | }; |
| 167 | |
| 168 | static int __init fib_notifier_init(void) |
| 169 | { |
| 170 | return register_pernet_subsys(&fib_notifier_net_ops); |
| 171 | } |
| 172 | |
| 173 | subsys_initcall(fib_notifier_init); |