Commit | Line | Data |
---|---|---|
04b1d4e5 IS |
1 | #include <linux/rtnetlink.h> |
2 | #include <linux/notifier.h> | |
3 | #include <linux/rcupdate.h> | |
4 | #include <linux/kernel.h> | |
864150df | 5 | #include <linux/module.h> |
04b1d4e5 IS |
6 | #include <linux/init.h> |
7 | #include <net/net_namespace.h> | |
3dd97a08 | 8 | #include <net/netns/generic.h> |
04b1d4e5 IS |
9 | #include <net/fib_notifier.h> |
10 | ||
3dd97a08 JP |
11 | static unsigned int fib_notifier_net_id; |
12 | ||
13 | struct fib_notifier_net { | |
14 | struct list_head fib_notifier_ops; | |
7c550daf | 15 | struct atomic_notifier_head fib_chain; |
3dd97a08 JP |
16 | }; |
17 | ||
7c550daf | 18 | int call_fib_notifier(struct notifier_block *nb, |
04b1d4e5 IS |
19 | enum fib_event_type event_type, |
20 | struct fib_notifier_info *info) | |
21 | { | |
c30d9356 DA |
22 | int err; |
23 | ||
c30d9356 DA |
24 | err = nb->notifier_call(nb, event_type, info); |
25 | return notifier_to_errno(err); | |
04b1d4e5 IS |
26 | } |
27 | EXPORT_SYMBOL(call_fib_notifier); | |
28 | ||
29 | int call_fib_notifiers(struct net *net, enum fib_event_type event_type, | |
30 | struct fib_notifier_info *info) | |
31 | { | |
7c550daf | 32 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
c30d9356 DA |
33 | int err; |
34 | ||
7c550daf | 35 | err = atomic_notifier_call_chain(&fn_net->fib_chain, event_type, info); |
c30d9356 | 36 | return notifier_to_errno(err); |
04b1d4e5 IS |
37 | } |
38 | EXPORT_SYMBOL(call_fib_notifiers); | |
39 | ||
7c550daf | 40 | static unsigned int fib_seq_sum(struct net *net) |
04b1d4e5 | 41 | { |
7c550daf | 42 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
04b1d4e5 IS |
43 | struct fib_notifier_ops *ops; |
44 | unsigned int fib_seq = 0; | |
04b1d4e5 IS |
45 | |
46 | rtnl_lock(); | |
7c550daf JP |
47 | rcu_read_lock(); |
48 | list_for_each_entry_rcu(ops, &fn_net->fib_notifier_ops, list) { | |
49 | if (!try_module_get(ops->owner)) | |
50 | continue; | |
51 | fib_seq += ops->fib_seq_read(net); | |
52 | module_put(ops->owner); | |
04b1d4e5 | 53 | } |
7c550daf | 54 | rcu_read_unlock(); |
04b1d4e5 IS |
55 | rtnl_unlock(); |
56 | ||
57 | return fib_seq; | |
58 | } | |
59 | ||
b7a59557 JP |
60 | static int fib_net_dump(struct net *net, struct notifier_block *nb, |
61 | struct netlink_ext_ack *extack) | |
04b1d4e5 | 62 | { |
3dd97a08 | 63 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
04b1d4e5 | 64 | struct fib_notifier_ops *ops; |
7c550daf | 65 | int err = 0; |
04b1d4e5 | 66 | |
7c550daf | 67 | rcu_read_lock(); |
3dd97a08 | 68 | list_for_each_entry_rcu(ops, &fn_net->fib_notifier_ops, list) { |
864150df IS |
69 | if (!try_module_get(ops->owner)) |
70 | continue; | |
b7a59557 | 71 | err = ops->fib_dump(net, nb, extack); |
864150df | 72 | module_put(ops->owner); |
04b1d4e5 | 73 | if (err) |
7c550daf | 74 | goto unlock; |
04b1d4e5 IS |
75 | } |
76 | ||
7c550daf JP |
77 | unlock: |
78 | rcu_read_unlock(); | |
79 | ||
80 | return err; | |
04b1d4e5 IS |
81 | } |
82 | ||
7c550daf | 83 | static bool fib_dump_is_consistent(struct net *net, struct notifier_block *nb, |
04b1d4e5 IS |
84 | void (*cb)(struct notifier_block *nb), |
85 | unsigned int fib_seq) | |
86 | { | |
7c550daf JP |
87 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
88 | ||
89 | atomic_notifier_chain_register(&fn_net->fib_chain, nb); | |
90 | if (fib_seq == fib_seq_sum(net)) | |
04b1d4e5 | 91 | return true; |
7c550daf | 92 | atomic_notifier_chain_unregister(&fn_net->fib_chain, nb); |
04b1d4e5 IS |
93 | if (cb) |
94 | cb(nb); | |
95 | return false; | |
96 | } | |
97 | ||
98 | #define FIB_DUMP_MAX_RETRIES 5 | |
7c550daf | 99 | int register_fib_notifier(struct net *net, struct notifier_block *nb, |
b7a59557 JP |
100 | void (*cb)(struct notifier_block *nb), |
101 | struct netlink_ext_ack *extack) | |
04b1d4e5 IS |
102 | { |
103 | int retries = 0; | |
104 | int err; | |
105 | ||
106 | do { | |
7c550daf JP |
107 | unsigned int fib_seq = fib_seq_sum(net); |
108 | ||
b7a59557 | 109 | err = fib_net_dump(net, nb, extack); |
7c550daf JP |
110 | if (err) |
111 | return err; | |
112 | ||
113 | if (fib_dump_is_consistent(net, nb, cb, fib_seq)) | |
04b1d4e5 IS |
114 | return 0; |
115 | } while (++retries < FIB_DUMP_MAX_RETRIES); | |
116 | ||
117 | return -EBUSY; | |
04b1d4e5 IS |
118 | } |
119 | EXPORT_SYMBOL(register_fib_notifier); | |
120 | ||
7c550daf | 121 | int unregister_fib_notifier(struct net *net, struct notifier_block *nb) |
04b1d4e5 | 122 | { |
7c550daf JP |
123 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
124 | ||
125 | return atomic_notifier_chain_unregister(&fn_net->fib_chain, nb); | |
04b1d4e5 IS |
126 | } |
127 | EXPORT_SYMBOL(unregister_fib_notifier); | |
128 | ||
129 | static int __fib_notifier_ops_register(struct fib_notifier_ops *ops, | |
130 | struct net *net) | |
131 | { | |
3dd97a08 | 132 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
04b1d4e5 IS |
133 | struct fib_notifier_ops *o; |
134 | ||
3dd97a08 | 135 | list_for_each_entry(o, &fn_net->fib_notifier_ops, list) |
04b1d4e5 IS |
136 | if (ops->family == o->family) |
137 | return -EEXIST; | |
3dd97a08 | 138 | list_add_tail_rcu(&ops->list, &fn_net->fib_notifier_ops); |
04b1d4e5 IS |
139 | return 0; |
140 | } | |
141 | ||
142 | struct fib_notifier_ops * | |
143 | fib_notifier_ops_register(const struct fib_notifier_ops *tmpl, struct net *net) | |
144 | { | |
145 | struct fib_notifier_ops *ops; | |
146 | int err; | |
147 | ||
148 | ops = kmemdup(tmpl, sizeof(*ops), GFP_KERNEL); | |
149 | if (!ops) | |
150 | return ERR_PTR(-ENOMEM); | |
151 | ||
152 | err = __fib_notifier_ops_register(ops, net); | |
153 | if (err) | |
154 | goto err_register; | |
155 | ||
156 | return ops; | |
157 | ||
158 | err_register: | |
159 | kfree(ops); | |
160 | return ERR_PTR(err); | |
161 | } | |
162 | EXPORT_SYMBOL(fib_notifier_ops_register); | |
163 | ||
164 | void fib_notifier_ops_unregister(struct fib_notifier_ops *ops) | |
165 | { | |
166 | list_del_rcu(&ops->list); | |
167 | kfree_rcu(ops, rcu); | |
168 | } | |
169 | EXPORT_SYMBOL(fib_notifier_ops_unregister); | |
170 | ||
171 | static int __net_init fib_notifier_net_init(struct net *net) | |
172 | { | |
3dd97a08 JP |
173 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
174 | ||
175 | INIT_LIST_HEAD(&fn_net->fib_notifier_ops); | |
7c550daf | 176 | ATOMIC_INIT_NOTIFIER_HEAD(&fn_net->fib_chain); |
04b1d4e5 IS |
177 | return 0; |
178 | } | |
179 | ||
0b6f5955 VA |
180 | static void __net_exit fib_notifier_net_exit(struct net *net) |
181 | { | |
3dd97a08 JP |
182 | struct fib_notifier_net *fn_net = net_generic(net, fib_notifier_net_id); |
183 | ||
184 | WARN_ON_ONCE(!list_empty(&fn_net->fib_notifier_ops)); | |
0b6f5955 VA |
185 | } |
186 | ||
04b1d4e5 IS |
187 | static struct pernet_operations fib_notifier_net_ops = { |
188 | .init = fib_notifier_net_init, | |
0b6f5955 | 189 | .exit = fib_notifier_net_exit, |
3dd97a08 JP |
190 | .id = &fib_notifier_net_id, |
191 | .size = sizeof(struct fib_notifier_net), | |
04b1d4e5 IS |
192 | }; |
193 | ||
194 | static int __init fib_notifier_init(void) | |
195 | { | |
196 | return register_pernet_subsys(&fib_notifier_net_ops); | |
197 | } | |
198 | ||
199 | subsys_initcall(fib_notifier_init); |