]>
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> | |
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) { | |
11bf284f KT |
37 | rcu_read_lock(); |
38 | list_for_each_entry_rcu(ops, &net->fib_notifier_ops, list) { | |
864150df IS |
39 | if (!try_module_get(ops->owner)) |
40 | continue; | |
04b1d4e5 | 41 | fib_seq += ops->fib_seq_read(net); |
864150df IS |
42 | module_put(ops->owner); |
43 | } | |
11bf284f | 44 | rcu_read_unlock(); |
04b1d4e5 IS |
45 | } |
46 | rtnl_unlock(); | |
47 | ||
48 | return fib_seq; | |
49 | } | |
50 | ||
51 | static int fib_net_dump(struct net *net, struct notifier_block *nb) | |
52 | { | |
53 | struct fib_notifier_ops *ops; | |
54 | ||
55 | list_for_each_entry_rcu(ops, &net->fib_notifier_ops, list) { | |
864150df | 56 | int err; |
04b1d4e5 | 57 | |
864150df IS |
58 | if (!try_module_get(ops->owner)) |
59 | continue; | |
60 | err = ops->fib_dump(net, nb); | |
61 | module_put(ops->owner); | |
04b1d4e5 IS |
62 | if (err) |
63 | return err; | |
64 | } | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | static bool fib_dump_is_consistent(struct notifier_block *nb, | |
70 | void (*cb)(struct notifier_block *nb), | |
71 | unsigned int fib_seq) | |
72 | { | |
73 | atomic_notifier_chain_register(&fib_chain, nb); | |
74 | if (fib_seq == fib_seq_sum()) | |
75 | return true; | |
76 | atomic_notifier_chain_unregister(&fib_chain, nb); | |
77 | if (cb) | |
78 | cb(nb); | |
79 | return false; | |
80 | } | |
81 | ||
82 | #define FIB_DUMP_MAX_RETRIES 5 | |
83 | int register_fib_notifier(struct notifier_block *nb, | |
84 | void (*cb)(struct notifier_block *nb)) | |
85 | { | |
86 | int retries = 0; | |
87 | int err; | |
88 | ||
89 | do { | |
90 | unsigned int fib_seq = fib_seq_sum(); | |
91 | struct net *net; | |
92 | ||
93 | rcu_read_lock(); | |
94 | for_each_net_rcu(net) { | |
95 | err = fib_net_dump(net, nb); | |
96 | if (err) | |
97 | goto err_fib_net_dump; | |
98 | } | |
99 | rcu_read_unlock(); | |
100 | ||
101 | if (fib_dump_is_consistent(nb, cb, fib_seq)) | |
102 | return 0; | |
103 | } while (++retries < FIB_DUMP_MAX_RETRIES); | |
104 | ||
105 | return -EBUSY; | |
106 | ||
107 | err_fib_net_dump: | |
108 | rcu_read_unlock(); | |
109 | return err; | |
110 | } | |
111 | EXPORT_SYMBOL(register_fib_notifier); | |
112 | ||
113 | int unregister_fib_notifier(struct notifier_block *nb) | |
114 | { | |
115 | return atomic_notifier_chain_unregister(&fib_chain, nb); | |
116 | } | |
117 | EXPORT_SYMBOL(unregister_fib_notifier); | |
118 | ||
119 | static int __fib_notifier_ops_register(struct fib_notifier_ops *ops, | |
120 | struct net *net) | |
121 | { | |
122 | struct fib_notifier_ops *o; | |
123 | ||
124 | list_for_each_entry(o, &net->fib_notifier_ops, list) | |
125 | if (ops->family == o->family) | |
126 | return -EEXIST; | |
127 | list_add_tail_rcu(&ops->list, &net->fib_notifier_ops); | |
128 | return 0; | |
129 | } | |
130 | ||
131 | struct fib_notifier_ops * | |
132 | fib_notifier_ops_register(const struct fib_notifier_ops *tmpl, struct net *net) | |
133 | { | |
134 | struct fib_notifier_ops *ops; | |
135 | int err; | |
136 | ||
137 | ops = kmemdup(tmpl, sizeof(*ops), GFP_KERNEL); | |
138 | if (!ops) | |
139 | return ERR_PTR(-ENOMEM); | |
140 | ||
141 | err = __fib_notifier_ops_register(ops, net); | |
142 | if (err) | |
143 | goto err_register; | |
144 | ||
145 | return ops; | |
146 | ||
147 | err_register: | |
148 | kfree(ops); | |
149 | return ERR_PTR(err); | |
150 | } | |
151 | EXPORT_SYMBOL(fib_notifier_ops_register); | |
152 | ||
153 | void fib_notifier_ops_unregister(struct fib_notifier_ops *ops) | |
154 | { | |
155 | list_del_rcu(&ops->list); | |
156 | kfree_rcu(ops, rcu); | |
157 | } | |
158 | EXPORT_SYMBOL(fib_notifier_ops_unregister); | |
159 | ||
160 | static int __net_init fib_notifier_net_init(struct net *net) | |
161 | { | |
162 | INIT_LIST_HEAD(&net->fib_notifier_ops); | |
163 | return 0; | |
164 | } | |
165 | ||
0b6f5955 VA |
166 | static void __net_exit fib_notifier_net_exit(struct net *net) |
167 | { | |
168 | WARN_ON_ONCE(!list_empty(&net->fib_notifier_ops)); | |
169 | } | |
170 | ||
04b1d4e5 IS |
171 | static struct pernet_operations fib_notifier_net_ops = { |
172 | .init = fib_notifier_net_init, | |
0b6f5955 | 173 | .exit = fib_notifier_net_exit, |
04b1d4e5 IS |
174 | }; |
175 | ||
176 | static int __init fib_notifier_init(void) | |
177 | { | |
178 | return register_pernet_subsys(&fib_notifier_net_ops); | |
179 | } | |
180 | ||
181 | subsys_initcall(fib_notifier_init); |