diff options
-rw-r--r-- | net/sched/cls_fw.c | 111 |
1 files changed, 77 insertions, 34 deletions
diff --git a/net/sched/cls_fw.c b/net/sched/cls_fw.c index 861b03ccfed0..006b45a67fdd 100644 --- a/net/sched/cls_fw.c +++ b/net/sched/cls_fw.c | |||
@@ -33,17 +33,20 @@ | |||
33 | 33 | ||
34 | struct fw_head { | 34 | struct fw_head { |
35 | u32 mask; | 35 | u32 mask; |
36 | struct fw_filter *ht[HTSIZE]; | 36 | struct fw_filter __rcu *ht[HTSIZE]; |
37 | struct rcu_head rcu; | ||
37 | }; | 38 | }; |
38 | 39 | ||
39 | struct fw_filter { | 40 | struct fw_filter { |
40 | struct fw_filter *next; | 41 | struct fw_filter __rcu *next; |
41 | u32 id; | 42 | u32 id; |
42 | struct tcf_result res; | 43 | struct tcf_result res; |
43 | #ifdef CONFIG_NET_CLS_IND | 44 | #ifdef CONFIG_NET_CLS_IND |
44 | int ifindex; | 45 | int ifindex; |
45 | #endif /* CONFIG_NET_CLS_IND */ | 46 | #endif /* CONFIG_NET_CLS_IND */ |
46 | struct tcf_exts exts; | 47 | struct tcf_exts exts; |
48 | struct tcf_proto *tp; | ||
49 | struct rcu_head rcu; | ||
47 | }; | 50 | }; |
48 | 51 | ||
49 | static u32 fw_hash(u32 handle) | 52 | static u32 fw_hash(u32 handle) |
@@ -56,14 +59,16 @@ static u32 fw_hash(u32 handle) | |||
56 | static int fw_classify(struct sk_buff *skb, const struct tcf_proto *tp, | 59 | static int fw_classify(struct sk_buff *skb, const struct tcf_proto *tp, |
57 | struct tcf_result *res) | 60 | struct tcf_result *res) |
58 | { | 61 | { |
59 | struct fw_head *head = tp->root; | 62 | struct fw_head *head = rcu_dereference_bh(tp->root); |
60 | struct fw_filter *f; | 63 | struct fw_filter *f; |
61 | int r; | 64 | int r; |
62 | u32 id = skb->mark; | 65 | u32 id = skb->mark; |
63 | 66 | ||
64 | if (head != NULL) { | 67 | if (head != NULL) { |
65 | id &= head->mask; | 68 | id &= head->mask; |
66 | for (f = head->ht[fw_hash(id)]; f; f = f->next) { | 69 | |
70 | for (f = rcu_dereference_bh(head->ht[fw_hash(id)]); f; | ||
71 | f = rcu_dereference_bh(f->next)) { | ||
67 | if (f->id == id) { | 72 | if (f->id == id) { |
68 | *res = f->res; | 73 | *res = f->res; |
69 | #ifdef CONFIG_NET_CLS_IND | 74 | #ifdef CONFIG_NET_CLS_IND |
@@ -92,13 +97,14 @@ static int fw_classify(struct sk_buff *skb, const struct tcf_proto *tp, | |||
92 | 97 | ||
93 | static unsigned long fw_get(struct tcf_proto *tp, u32 handle) | 98 | static unsigned long fw_get(struct tcf_proto *tp, u32 handle) |
94 | { | 99 | { |
95 | struct fw_head *head = tp->root; | 100 | struct fw_head *head = rtnl_dereference(tp->root); |
96 | struct fw_filter *f; | 101 | struct fw_filter *f; |
97 | 102 | ||
98 | if (head == NULL) | 103 | if (head == NULL) |
99 | return 0; | 104 | return 0; |
100 | 105 | ||
101 | for (f = head->ht[fw_hash(handle)]; f; f = f->next) { | 106 | f = rtnl_dereference(head->ht[fw_hash(handle)]); |
107 | for (; f; f = rtnl_dereference(f->next)) { | ||
102 | if (f->id == handle) | 108 | if (f->id == handle) |
103 | return (unsigned long)f; | 109 | return (unsigned long)f; |
104 | } | 110 | } |
@@ -114,8 +120,11 @@ static int fw_init(struct tcf_proto *tp) | |||
114 | return 0; | 120 | return 0; |
115 | } | 121 | } |
116 | 122 | ||
117 | static void fw_delete_filter(struct tcf_proto *tp, struct fw_filter *f) | 123 | static void fw_delete_filter(struct rcu_head *head) |
118 | { | 124 | { |
125 | struct fw_filter *f = container_of(head, struct fw_filter, rcu); | ||
126 | struct tcf_proto *tp = f->tp; | ||
127 | |||
119 | tcf_unbind_filter(tp, &f->res); | 128 | tcf_unbind_filter(tp, &f->res); |
120 | tcf_exts_destroy(tp, &f->exts); | 129 | tcf_exts_destroy(tp, &f->exts); |
121 | kfree(f); | 130 | kfree(f); |
@@ -123,7 +132,7 @@ static void fw_delete_filter(struct tcf_proto *tp, struct fw_filter *f) | |||
123 | 132 | ||
124 | static void fw_destroy(struct tcf_proto *tp) | 133 | static void fw_destroy(struct tcf_proto *tp) |
125 | { | 134 | { |
126 | struct fw_head *head = tp->root; | 135 | struct fw_head *head = rtnl_dereference(tp->root); |
127 | struct fw_filter *f; | 136 | struct fw_filter *f; |
128 | int h; | 137 | int h; |
129 | 138 | ||
@@ -131,29 +140,33 @@ static void fw_destroy(struct tcf_proto *tp) | |||
131 | return; | 140 | return; |
132 | 141 | ||
133 | for (h = 0; h < HTSIZE; h++) { | 142 | for (h = 0; h < HTSIZE; h++) { |
134 | while ((f = head->ht[h]) != NULL) { | 143 | while ((f = rtnl_dereference(head->ht[h])) != NULL) { |
135 | head->ht[h] = f->next; | 144 | RCU_INIT_POINTER(head->ht[h], |
136 | fw_delete_filter(tp, f); | 145 | rtnl_dereference(f->next)); |
146 | call_rcu(&f->rcu, fw_delete_filter); | ||
137 | } | 147 | } |
138 | } | 148 | } |
139 | kfree(head); | 149 | RCU_INIT_POINTER(tp->root, NULL); |
150 | kfree_rcu(head, rcu); | ||
140 | } | 151 | } |
141 | 152 | ||
142 | static int fw_delete(struct tcf_proto *tp, unsigned long arg) | 153 | static int fw_delete(struct tcf_proto *tp, unsigned long arg) |
143 | { | 154 | { |
144 | struct fw_head *head = tp->root; | 155 | struct fw_head *head = rtnl_dereference(tp->root); |
145 | struct fw_filter *f = (struct fw_filter *)arg; | 156 | struct fw_filter *f = (struct fw_filter *)arg; |
146 | struct fw_filter **fp; | 157 | struct fw_filter __rcu **fp; |
158 | struct fw_filter *pfp; | ||
147 | 159 | ||
148 | if (head == NULL || f == NULL) | 160 | if (head == NULL || f == NULL) |
149 | goto out; | 161 | goto out; |
150 | 162 | ||
151 | for (fp = &head->ht[fw_hash(f->id)]; *fp; fp = &(*fp)->next) { | 163 | fp = &head->ht[fw_hash(f->id)]; |
152 | if (*fp == f) { | 164 | |
153 | tcf_tree_lock(tp); | 165 | for (pfp = rtnl_dereference(*fp); pfp; |
154 | *fp = f->next; | 166 | fp = &pfp->next, pfp = rtnl_dereference(*fp)) { |
155 | tcf_tree_unlock(tp); | 167 | if (pfp == f) { |
156 | fw_delete_filter(tp, f); | 168 | RCU_INIT_POINTER(*fp, rtnl_dereference(f->next)); |
169 | call_rcu(&f->rcu, fw_delete_filter); | ||
157 | return 0; | 170 | return 0; |
158 | } | 171 | } |
159 | } | 172 | } |
@@ -171,7 +184,7 @@ static int | |||
171 | fw_change_attrs(struct net *net, struct tcf_proto *tp, struct fw_filter *f, | 184 | fw_change_attrs(struct net *net, struct tcf_proto *tp, struct fw_filter *f, |
172 | struct nlattr **tb, struct nlattr **tca, unsigned long base, bool ovr) | 185 | struct nlattr **tb, struct nlattr **tca, unsigned long base, bool ovr) |
173 | { | 186 | { |
174 | struct fw_head *head = tp->root; | 187 | struct fw_head *head = rtnl_dereference(tp->root); |
175 | struct tcf_exts e; | 188 | struct tcf_exts e; |
176 | u32 mask; | 189 | u32 mask; |
177 | int err; | 190 | int err; |
@@ -220,7 +233,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, | |||
220 | struct nlattr **tca, | 233 | struct nlattr **tca, |
221 | unsigned long *arg, bool ovr) | 234 | unsigned long *arg, bool ovr) |
222 | { | 235 | { |
223 | struct fw_head *head = tp->root; | 236 | struct fw_head *head = rtnl_dereference(tp->root); |
224 | struct fw_filter *f = (struct fw_filter *) *arg; | 237 | struct fw_filter *f = (struct fw_filter *) *arg; |
225 | struct nlattr *opt = tca[TCA_OPTIONS]; | 238 | struct nlattr *opt = tca[TCA_OPTIONS]; |
226 | struct nlattr *tb[TCA_FW_MAX + 1]; | 239 | struct nlattr *tb[TCA_FW_MAX + 1]; |
@@ -233,10 +246,42 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, | |||
233 | if (err < 0) | 246 | if (err < 0) |
234 | return err; | 247 | return err; |
235 | 248 | ||
236 | if (f != NULL) { | 249 | if (f) { |
250 | struct fw_filter *pfp, *fnew; | ||
251 | struct fw_filter __rcu **fp; | ||
252 | |||
237 | if (f->id != handle && handle) | 253 | if (f->id != handle && handle) |
238 | return -EINVAL; | 254 | return -EINVAL; |
239 | return fw_change_attrs(net, tp, f, tb, tca, base, ovr); | 255 | |
256 | fnew = kzalloc(sizeof(struct fw_filter), GFP_KERNEL); | ||
257 | if (!fnew) | ||
258 | return -ENOBUFS; | ||
259 | |||
260 | fnew->id = f->id; | ||
261 | fnew->res = f->res; | ||
262 | #ifdef CONFIG_NET_CLS_IND | ||
263 | fnew->ifindex = f->ifindex; | ||
264 | #endif /* CONFIG_NET_CLS_IND */ | ||
265 | fnew->tp = f->tp; | ||
266 | |||
267 | err = fw_change_attrs(net, tp, fnew, tb, tca, base, ovr); | ||
268 | if (err < 0) { | ||
269 | kfree(fnew); | ||
270 | return err; | ||
271 | } | ||
272 | |||
273 | fp = &head->ht[fw_hash(fnew->id)]; | ||
274 | for (pfp = rtnl_dereference(*fp); pfp; | ||
275 | fp = &pfp->next, pfp = rtnl_dereference(*fp)) | ||
276 | if (pfp == f) | ||
277 | break; | ||
278 | |||
279 | RCU_INIT_POINTER(fnew->next, rtnl_dereference(pfp->next)); | ||
280 | rcu_assign_pointer(*fp, fnew); | ||
281 | call_rcu(&f->rcu, fw_delete_filter); | ||
282 | |||
283 | *arg = (unsigned long)fnew; | ||
284 | return err; | ||
240 | } | 285 | } |
241 | 286 | ||
242 | if (!handle) | 287 | if (!handle) |
@@ -252,9 +297,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, | |||
252 | return -ENOBUFS; | 297 | return -ENOBUFS; |
253 | head->mask = mask; | 298 | head->mask = mask; |
254 | 299 | ||
255 | tcf_tree_lock(tp); | 300 | rcu_assign_pointer(tp->root, head); |
256 | tp->root = head; | ||
257 | tcf_tree_unlock(tp); | ||
258 | } | 301 | } |
259 | 302 | ||
260 | f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL); | 303 | f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL); |
@@ -263,15 +306,14 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, | |||
263 | 306 | ||
264 | tcf_exts_init(&f->exts, TCA_FW_ACT, TCA_FW_POLICE); | 307 | tcf_exts_init(&f->exts, TCA_FW_ACT, TCA_FW_POLICE); |
265 | f->id = handle; | 308 | f->id = handle; |
309 | f->tp = tp; | ||
266 | 310 | ||
267 | err = fw_change_attrs(net, tp, f, tb, tca, base, ovr); | 311 | err = fw_change_attrs(net, tp, f, tb, tca, base, ovr); |
268 | if (err < 0) | 312 | if (err < 0) |
269 | goto errout; | 313 | goto errout; |
270 | 314 | ||
271 | f->next = head->ht[fw_hash(handle)]; | 315 | RCU_INIT_POINTER(f->next, head->ht[fw_hash(handle)]); |
272 | tcf_tree_lock(tp); | 316 | rcu_assign_pointer(head->ht[fw_hash(handle)], f); |
273 | head->ht[fw_hash(handle)] = f; | ||
274 | tcf_tree_unlock(tp); | ||
275 | 317 | ||
276 | *arg = (unsigned long)f; | 318 | *arg = (unsigned long)f; |
277 | return 0; | 319 | return 0; |
@@ -283,7 +325,7 @@ errout: | |||
283 | 325 | ||
284 | static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg) | 326 | static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg) |
285 | { | 327 | { |
286 | struct fw_head *head = tp->root; | 328 | struct fw_head *head = rtnl_dereference(tp->root); |
287 | int h; | 329 | int h; |
288 | 330 | ||
289 | if (head == NULL) | 331 | if (head == NULL) |
@@ -295,7 +337,8 @@ static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg) | |||
295 | for (h = 0; h < HTSIZE; h++) { | 337 | for (h = 0; h < HTSIZE; h++) { |
296 | struct fw_filter *f; | 338 | struct fw_filter *f; |
297 | 339 | ||
298 | for (f = head->ht[h]; f; f = f->next) { | 340 | for (f = rtnl_dereference(head->ht[h]); f; |
341 | f = rtnl_dereference(f->next)) { | ||
299 | if (arg->count < arg->skip) { | 342 | if (arg->count < arg->skip) { |
300 | arg->count++; | 343 | arg->count++; |
301 | continue; | 344 | continue; |
@@ -312,7 +355,7 @@ static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg) | |||
312 | static int fw_dump(struct net *net, struct tcf_proto *tp, unsigned long fh, | 355 | static int fw_dump(struct net *net, struct tcf_proto *tp, unsigned long fh, |
313 | struct sk_buff *skb, struct tcmsg *t) | 356 | struct sk_buff *skb, struct tcmsg *t) |
314 | { | 357 | { |
315 | struct fw_head *head = tp->root; | 358 | struct fw_head *head = rtnl_dereference(tp->root); |
316 | struct fw_filter *f = (struct fw_filter *)fh; | 359 | struct fw_filter *f = (struct fw_filter *)fh; |
317 | unsigned char *b = skb_tail_pointer(skb); | 360 | unsigned char *b = skb_tail_pointer(skb); |
318 | struct nlattr *nest; | 361 | struct nlattr *nest; |