]>
Commit | Line | Data |
---|---|---|
09c434b8 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1da177e4 LT |
2 | /* |
3 | * net/sched/cls_tcindex.c Packet classifier for skb->tc_index | |
4 | * | |
5 | * Written 1998,1999 by Werner Almesberger, EPFL ICA | |
6 | */ | |
7 | ||
1da177e4 LT |
8 | #include <linux/module.h> |
9 | #include <linux/types.h> | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/skbuff.h> | |
12 | #include <linux/errno.h> | |
5a0e3ad6 | 13 | #include <linux/slab.h> |
304e0242 | 14 | #include <linux/refcount.h> |
1da177e4 | 15 | #include <net/act_api.h> |
dc5fc579 | 16 | #include <net/netlink.h> |
1da177e4 | 17 | #include <net/pkt_cls.h> |
1abf2720 | 18 | #include <net/sch_generic.h> |
1da177e4 | 19 | |
1da177e4 LT |
20 | /* |
21 | * Passing parameters to the root seems to be done more awkwardly than really | |
22 | * necessary. At least, u32 doesn't seem to use such dirty hacks. To be | |
23 | * verified. FIXME. | |
24 | */ | |
25 | ||
26 | #define PERFECT_HASH_THRESHOLD 64 /* use perfect hash if not bigger */ | |
27 | #define DEFAULT_HASH_SIZE 64 /* optimized for diffserv */ | |
28 | ||
29 | ||
304e0242 CW |
30 | struct tcindex_data; |
31 | ||
1da177e4 LT |
32 | struct tcindex_filter_result { |
33 | struct tcf_exts exts; | |
34 | struct tcf_result res; | |
304e0242 | 35 | struct tcindex_data *p; |
aaa908ff | 36 | struct rcu_work rwork; |
1da177e4 LT |
37 | }; |
38 | ||
39 | struct tcindex_filter { | |
40 | u16 key; | |
41 | struct tcindex_filter_result result; | |
331b7292 | 42 | struct tcindex_filter __rcu *next; |
aaa908ff | 43 | struct rcu_work rwork; |
1da177e4 LT |
44 | }; |
45 | ||
46 | ||
47 | struct tcindex_data { | |
48 | struct tcindex_filter_result *perfect; /* perfect hash; NULL if none */ | |
331b7292 JF |
49 | struct tcindex_filter __rcu **h; /* imperfect hash; */ |
50 | struct tcf_proto *tp; | |
1da177e4 | 51 | u16 mask; /* AND key with mask */ |
331b7292 JF |
52 | u32 shift; /* shift ANDed key to the right */ |
53 | u32 hash; /* hash table size; 0 if undefined */ | |
54 | u32 alloc_hash; /* allocated size */ | |
55 | u32 fall_through; /* 0: only classify if explicit match */ | |
304e0242 | 56 | refcount_t refcnt; /* a temporary refcnt for perfect hash */ |
3d210534 | 57 | struct rcu_work rwork; |
1da177e4 LT |
58 | }; |
59 | ||
5a7a5555 | 60 | static inline int tcindex_filter_is_set(struct tcindex_filter_result *r) |
1da177e4 | 61 | { |
6fc6d06e | 62 | return tcf_exts_has_actions(&r->exts) || r->res.classid; |
1da177e4 LT |
63 | } |
64 | ||
304e0242 CW |
65 | static void tcindex_data_get(struct tcindex_data *p) |
66 | { | |
67 | refcount_inc(&p->refcnt); | |
68 | } | |
69 | ||
70 | static void tcindex_data_put(struct tcindex_data *p) | |
71 | { | |
72 | if (refcount_dec_and_test(&p->refcnt)) { | |
73 | kfree(p->perfect); | |
74 | kfree(p->h); | |
75 | kfree(p); | |
76 | } | |
77 | } | |
78 | ||
5a7a5555 JHS |
79 | static struct tcindex_filter_result *tcindex_lookup(struct tcindex_data *p, |
80 | u16 key) | |
1da177e4 | 81 | { |
331b7292 JF |
82 | if (p->perfect) { |
83 | struct tcindex_filter_result *f = p->perfect + key; | |
84 | ||
85 | return tcindex_filter_is_set(f) ? f : NULL; | |
86 | } else if (p->h) { | |
87 | struct tcindex_filter __rcu **fp; | |
88 | struct tcindex_filter *f; | |
1da177e4 | 89 | |
331b7292 JF |
90 | fp = &p->h[key % p->hash]; |
91 | for (f = rcu_dereference_bh_rtnl(*fp); | |
92 | f; | |
93 | fp = &f->next, f = rcu_dereference_bh_rtnl(*fp)) | |
1da177e4 LT |
94 | if (f->key == key) |
95 | return &f->result; | |
96 | } | |
97 | ||
98 | return NULL; | |
99 | } | |
100 | ||
101 | ||
dc7f9f6e | 102 | static int tcindex_classify(struct sk_buff *skb, const struct tcf_proto *tp, |
1da177e4 LT |
103 | struct tcf_result *res) |
104 | { | |
2f9a220e | 105 | struct tcindex_data *p = rcu_dereference_bh(tp->root); |
1da177e4 LT |
106 | struct tcindex_filter_result *f; |
107 | int key = (skb->tc_index & p->mask) >> p->shift; | |
108 | ||
aa767bfe SH |
109 | pr_debug("tcindex_classify(skb %p,tp %p,res %p),p %p\n", |
110 | skb, tp, res, p); | |
1da177e4 LT |
111 | |
112 | f = tcindex_lookup(p, key); | |
113 | if (!f) { | |
1abf2720 JP |
114 | struct Qdisc *q = tcf_block_q(tp->chain->block); |
115 | ||
1da177e4 LT |
116 | if (!p->fall_through) |
117 | return -1; | |
1abf2720 | 118 | res->classid = TC_H_MAKE(TC_H_MAJ(q->handle), key); |
1da177e4 | 119 | res->class = 0; |
aa767bfe | 120 | pr_debug("alg 0x%x\n", res->classid); |
1da177e4 LT |
121 | return 0; |
122 | } | |
123 | *res = f->res; | |
aa767bfe | 124 | pr_debug("map 0x%x\n", res->classid); |
1da177e4 LT |
125 | |
126 | return tcf_exts_exec(skb, &f->exts, res); | |
127 | } | |
128 | ||
129 | ||
8113c095 | 130 | static void *tcindex_get(struct tcf_proto *tp, u32 handle) |
1da177e4 | 131 | { |
331b7292 | 132 | struct tcindex_data *p = rtnl_dereference(tp->root); |
1da177e4 LT |
133 | struct tcindex_filter_result *r; |
134 | ||
aa767bfe | 135 | pr_debug("tcindex_get(tp %p,handle 0x%08x)\n", tp, handle); |
1da177e4 | 136 | if (p->perfect && handle >= p->alloc_hash) |
8113c095 | 137 | return NULL; |
1da177e4 | 138 | r = tcindex_lookup(p, handle); |
8113c095 | 139 | return r && tcindex_filter_is_set(r) ? r : NULL; |
1da177e4 LT |
140 | } |
141 | ||
1da177e4 LT |
142 | static int tcindex_init(struct tcf_proto *tp) |
143 | { | |
144 | struct tcindex_data *p; | |
145 | ||
aa767bfe SH |
146 | pr_debug("tcindex_init(tp %p)\n", tp); |
147 | p = kzalloc(sizeof(struct tcindex_data), GFP_KERNEL); | |
1da177e4 LT |
148 | if (!p) |
149 | return -ENOMEM; | |
150 | ||
1da177e4 LT |
151 | p->mask = 0xffff; |
152 | p->hash = DEFAULT_HASH_SIZE; | |
153 | p->fall_through = 1; | |
a8eab6d3 | 154 | refcount_set(&p->refcnt, 1); /* Paired with tcindex_destroy_work() */ |
1da177e4 | 155 | |
331b7292 | 156 | rcu_assign_pointer(tp->root, p); |
1da177e4 LT |
157 | return 0; |
158 | } | |
159 | ||
f2b75105 CW |
160 | static void __tcindex_destroy_rexts(struct tcindex_filter_result *r) |
161 | { | |
162 | tcf_exts_destroy(&r->exts); | |
163 | tcf_exts_put_net(&r->exts); | |
304e0242 | 164 | tcindex_data_put(r->p); |
f2b75105 CW |
165 | } |
166 | ||
27ce4f05 CW |
167 | static void tcindex_destroy_rexts_work(struct work_struct *work) |
168 | { | |
169 | struct tcindex_filter_result *r; | |
170 | ||
aaa908ff CW |
171 | r = container_of(to_rcu_work(work), |
172 | struct tcindex_filter_result, | |
173 | rwork); | |
27ce4f05 | 174 | rtnl_lock(); |
f2b75105 | 175 | __tcindex_destroy_rexts(r); |
27ce4f05 CW |
176 | rtnl_unlock(); |
177 | } | |
178 | ||
f2b75105 CW |
179 | static void __tcindex_destroy_fexts(struct tcindex_filter *f) |
180 | { | |
181 | tcf_exts_destroy(&f->result.exts); | |
182 | tcf_exts_put_net(&f->result.exts); | |
183 | kfree(f); | |
184 | } | |
185 | ||
27ce4f05 CW |
186 | static void tcindex_destroy_fexts_work(struct work_struct *work) |
187 | { | |
aaa908ff CW |
188 | struct tcindex_filter *f = container_of(to_rcu_work(work), |
189 | struct tcindex_filter, | |
190 | rwork); | |
27ce4f05 CW |
191 | |
192 | rtnl_lock(); | |
f2b75105 | 193 | __tcindex_destroy_fexts(f); |
27ce4f05 | 194 | rtnl_unlock(); |
ed7aa879 AS |
195 | } |
196 | ||
571acf21 | 197 | static int tcindex_delete(struct tcf_proto *tp, void *arg, bool *last, |
12db03b6 | 198 | bool rtnl_held, struct netlink_ext_ack *extack) |
1da177e4 | 199 | { |
331b7292 | 200 | struct tcindex_data *p = rtnl_dereference(tp->root); |
8113c095 | 201 | struct tcindex_filter_result *r = arg; |
331b7292 | 202 | struct tcindex_filter __rcu **walk; |
1da177e4 LT |
203 | struct tcindex_filter *f = NULL; |
204 | ||
8113c095 | 205 | pr_debug("tcindex_delete(tp %p,arg %p),p %p\n", tp, arg, p); |
1da177e4 LT |
206 | if (p->perfect) { |
207 | if (!r->res.class) | |
208 | return -ENOENT; | |
209 | } else { | |
210 | int i; | |
1da177e4 | 211 | |
331b7292 JF |
212 | for (i = 0; i < p->hash; i++) { |
213 | walk = p->h + i; | |
214 | for (f = rtnl_dereference(*walk); f; | |
215 | walk = &f->next, f = rtnl_dereference(*walk)) { | |
216 | if (&f->result == r) | |
1da177e4 | 217 | goto found; |
331b7292 JF |
218 | } |
219 | } | |
1da177e4 LT |
220 | return -ENOENT; |
221 | ||
222 | found: | |
331b7292 | 223 | rcu_assign_pointer(*walk, rtnl_dereference(f->next)); |
1da177e4 LT |
224 | } |
225 | tcf_unbind_filter(tp, &r->res); | |
ed7aa879 AS |
226 | /* all classifiers are required to call tcf_exts_destroy() after rcu |
227 | * grace period, since converted-to-rcu actions are relying on that | |
228 | * in cleanup() callback | |
229 | */ | |
f2b75105 CW |
230 | if (f) { |
231 | if (tcf_exts_get_net(&f->result.exts)) | |
aaa908ff | 232 | tcf_queue_work(&f->rwork, tcindex_destroy_fexts_work); |
f2b75105 CW |
233 | else |
234 | __tcindex_destroy_fexts(f); | |
235 | } else { | |
304e0242 CW |
236 | tcindex_data_get(p); |
237 | ||
f2b75105 | 238 | if (tcf_exts_get_net(&r->exts)) |
aaa908ff | 239 | tcf_queue_work(&r->rwork, tcindex_destroy_rexts_work); |
f2b75105 CW |
240 | else |
241 | __tcindex_destroy_rexts(r); | |
242 | } | |
763dbf63 WC |
243 | |
244 | *last = false; | |
1da177e4 LT |
245 | return 0; |
246 | } | |
247 | ||
3d210534 | 248 | static void tcindex_destroy_work(struct work_struct *work) |
331b7292 | 249 | { |
3d210534 CW |
250 | struct tcindex_data *p = container_of(to_rcu_work(work), |
251 | struct tcindex_data, | |
252 | rwork); | |
331b7292 | 253 | |
304e0242 | 254 | tcindex_data_put(p); |
1da177e4 LT |
255 | } |
256 | ||
257 | static inline int | |
258 | valid_perfect_hash(struct tcindex_data *p) | |
259 | { | |
260 | return p->hash > (p->mask >> p->shift); | |
261 | } | |
262 | ||
6fa8c014 PM |
263 | static const struct nla_policy tcindex_policy[TCA_TCINDEX_MAX + 1] = { |
264 | [TCA_TCINDEX_HASH] = { .type = NLA_U32 }, | |
265 | [TCA_TCINDEX_MASK] = { .type = NLA_U16 }, | |
266 | [TCA_TCINDEX_SHIFT] = { .type = NLA_U32 }, | |
267 | [TCA_TCINDEX_FALL_THROUGH] = { .type = NLA_U32 }, | |
268 | [TCA_TCINDEX_CLASSID] = { .type = NLA_U32 }, | |
269 | }; | |
270 | ||
14215108 | 271 | static int tcindex_filter_result_init(struct tcindex_filter_result *r, |
304e0242 | 272 | struct tcindex_data *p, |
14215108 | 273 | struct net *net) |
bf63ac73 CW |
274 | { |
275 | memset(r, 0, sizeof(*r)); | |
304e0242 | 276 | r->p = p; |
14215108 CW |
277 | return tcf_exts_init(&r->exts, net, TCA_TCINDEX_ACT, |
278 | TCA_TCINDEX_POLICE); | |
bf63ac73 CW |
279 | } |
280 | ||
f5051bce PS |
281 | static void tcindex_free_perfect_hash(struct tcindex_data *cp); |
282 | ||
3d210534 | 283 | static void tcindex_partial_destroy_work(struct work_struct *work) |
331b7292 | 284 | { |
3d210534 CW |
285 | struct tcindex_data *p = container_of(to_rcu_work(work), |
286 | struct tcindex_data, | |
287 | rwork); | |
331b7292 | 288 | |
b1be2e8c | 289 | rtnl_lock(); |
f5051bce PS |
290 | if (p->perfect) |
291 | tcindex_free_perfect_hash(p); | |
331b7292 | 292 | kfree(p); |
b1be2e8c | 293 | rtnl_unlock(); |
331b7292 JF |
294 | } |
295 | ||
b9a24bb7 WC |
296 | static void tcindex_free_perfect_hash(struct tcindex_data *cp) |
297 | { | |
298 | int i; | |
299 | ||
300 | for (i = 0; i < cp->hash; i++) | |
301 | tcf_exts_destroy(&cp->perfect[i].exts); | |
302 | kfree(cp->perfect); | |
303 | } | |
304 | ||
033b228e | 305 | static int tcindex_alloc_perfect_hash(struct net *net, struct tcindex_data *cp) |
b9a24bb7 WC |
306 | { |
307 | int i, err = 0; | |
308 | ||
309 | cp->perfect = kcalloc(cp->hash, sizeof(struct tcindex_filter_result), | |
3f2db250 | 310 | GFP_KERNEL | __GFP_NOWARN); |
b9a24bb7 WC |
311 | if (!cp->perfect) |
312 | return -ENOMEM; | |
313 | ||
314 | for (i = 0; i < cp->hash; i++) { | |
14215108 | 315 | err = tcf_exts_init(&cp->perfect[i].exts, net, |
b9a24bb7 WC |
316 | TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE); |
317 | if (err < 0) | |
318 | goto errout; | |
304e0242 | 319 | cp->perfect[i].p = cp; |
b9a24bb7 WC |
320 | } |
321 | ||
322 | return 0; | |
323 | ||
324 | errout: | |
325 | tcindex_free_perfect_hash(cp); | |
326 | return err; | |
327 | } | |
328 | ||
1da177e4 | 329 | static int |
c1b52739 BL |
330 | tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base, |
331 | u32 handle, struct tcindex_data *p, | |
332 | struct tcindex_filter_result *r, struct nlattr **tb, | |
695176bf | 333 | struct nlattr *est, u32 flags, struct netlink_ext_ack *extack) |
1da177e4 | 334 | { |
1da177e4 | 335 | struct tcindex_filter_result new_filter_result, *old_r = r; |
b9a24bb7 | 336 | struct tcindex_data *cp = NULL, *oldp; |
1da177e4 | 337 | struct tcindex_filter *f = NULL; /* make gcc behave */ |
1db817e7 | 338 | struct tcf_result cr = {}; |
b9a24bb7 | 339 | int err, balloc = 0; |
1da177e4 LT |
340 | struct tcf_exts e; |
341 | ||
14215108 | 342 | err = tcf_exts_init(&e, net, TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE); |
1da177e4 LT |
343 | if (err < 0) |
344 | return err; | |
695176bf | 345 | err = tcf_exts_validate(net, tp, tb, est, &e, flags, extack); |
b9a24bb7 WC |
346 | if (err < 0) |
347 | goto errout; | |
10297b99 | 348 | |
02c5e844 | 349 | err = -ENOMEM; |
331b7292 JF |
350 | /* tcindex_data attributes must look atomic to classifier/lookup so |
351 | * allocate new tcindex data and RCU assign it onto root. Keeping | |
352 | * perfect hash and hash pointers from old data. | |
353 | */ | |
a57a65ba | 354 | cp = kzalloc(sizeof(*cp), GFP_KERNEL); |
02c5e844 | 355 | if (!cp) |
44b75e43 | 356 | goto errout; |
331b7292 JF |
357 | |
358 | cp->mask = p->mask; | |
359 | cp->shift = p->shift; | |
360 | cp->hash = p->hash; | |
361 | cp->alloc_hash = p->alloc_hash; | |
362 | cp->fall_through = p->fall_through; | |
363 | cp->tp = tp; | |
304e0242 | 364 | refcount_set(&cp->refcnt, 1); /* Paired with tcindex_destroy_work() */ |
331b7292 | 365 | |
599be01e CW |
366 | if (tb[TCA_TCINDEX_HASH]) |
367 | cp->hash = nla_get_u32(tb[TCA_TCINDEX_HASH]); | |
368 | ||
369 | if (tb[TCA_TCINDEX_MASK]) | |
370 | cp->mask = nla_get_u16(tb[TCA_TCINDEX_MASK]); | |
371 | ||
bcd0cf19 | 372 | if (tb[TCA_TCINDEX_SHIFT]) { |
599be01e | 373 | cp->shift = nla_get_u32(tb[TCA_TCINDEX_SHIFT]); |
bcd0cf19 ED |
374 | if (cp->shift > 16) { |
375 | err = -EINVAL; | |
376 | goto errout; | |
377 | } | |
378 | } | |
599be01e CW |
379 | if (!cp->hash) { |
380 | /* Hash not specified, use perfect hash if the upper limit | |
381 | * of the hashing index is below the threshold. | |
382 | */ | |
383 | if ((cp->mask >> cp->shift) < PERFECT_HASH_THRESHOLD) | |
384 | cp->hash = (cp->mask >> cp->shift) + 1; | |
385 | else | |
386 | cp->hash = DEFAULT_HASH_SIZE; | |
387 | } | |
388 | ||
331b7292 | 389 | if (p->perfect) { |
6e056569 WC |
390 | int i; |
391 | ||
033b228e | 392 | if (tcindex_alloc_perfect_hash(net, cp) < 0) |
331b7292 | 393 | goto errout; |
0d1c3530 | 394 | cp->alloc_hash = cp->hash; |
599be01e | 395 | for (i = 0; i < min(cp->hash, p->hash); i++) |
b9a24bb7 | 396 | cp->perfect[i].res = p->perfect[i].res; |
44b75e43 | 397 | balloc = 1; |
331b7292 JF |
398 | } |
399 | cp->h = p->h; | |
400 | ||
304e0242 | 401 | err = tcindex_filter_result_init(&new_filter_result, cp, net); |
b9a24bb7 | 402 | if (err < 0) |
52b5ae50 | 403 | goto errout_alloc; |
1da177e4 | 404 | if (old_r) |
1db817e7 | 405 | cr = r->res; |
1da177e4 | 406 | |
1da177e4 | 407 | err = -EBUSY; |
331b7292 | 408 | |
1da177e4 LT |
409 | /* Hash already allocated, make sure that we still meet the |
410 | * requirements for the allocated hash. | |
411 | */ | |
331b7292 JF |
412 | if (cp->perfect) { |
413 | if (!valid_perfect_hash(cp) || | |
414 | cp->hash > cp->alloc_hash) | |
44b75e43 | 415 | goto errout_alloc; |
331b7292 | 416 | } else if (cp->h && cp->hash != cp->alloc_hash) { |
44b75e43 | 417 | goto errout_alloc; |
331b7292 | 418 | } |
1da177e4 LT |
419 | |
420 | err = -EINVAL; | |
6fa8c014 | 421 | if (tb[TCA_TCINDEX_FALL_THROUGH]) |
331b7292 | 422 | cp->fall_through = nla_get_u32(tb[TCA_TCINDEX_FALL_THROUGH]); |
1da177e4 | 423 | |
68f6a7c6 | 424 | if (!cp->perfect && !cp->h) |
331b7292 | 425 | cp->alloc_hash = cp->hash; |
1da177e4 LT |
426 | |
427 | /* Note: this could be as restrictive as if (handle & ~(mask >> shift)) | |
428 | * but then, we'd fail handles that may become valid after some future | |
429 | * mask change. While this is extremely unlikely to ever matter, | |
430 | * the check below is safer (and also more backwards-compatible). | |
431 | */ | |
331b7292 JF |
432 | if (cp->perfect || valid_perfect_hash(cp)) |
433 | if (handle >= cp->alloc_hash) | |
44b75e43 | 434 | goto errout_alloc; |
1da177e4 LT |
435 | |
436 | ||
437 | err = -ENOMEM; | |
331b7292 JF |
438 | if (!cp->perfect && !cp->h) { |
439 | if (valid_perfect_hash(cp)) { | |
033b228e | 440 | if (tcindex_alloc_perfect_hash(net, cp) < 0) |
44b75e43 | 441 | goto errout_alloc; |
1da177e4 LT |
442 | balloc = 1; |
443 | } else { | |
331b7292 JF |
444 | struct tcindex_filter __rcu **hash; |
445 | ||
446 | hash = kcalloc(cp->hash, | |
447 | sizeof(struct tcindex_filter *), | |
448 | GFP_KERNEL); | |
449 | ||
450 | if (!hash) | |
44b75e43 | 451 | goto errout_alloc; |
331b7292 JF |
452 | |
453 | cp->h = hash; | |
1da177e4 LT |
454 | balloc = 2; |
455 | } | |
456 | } | |
457 | ||
331b7292 JF |
458 | if (cp->perfect) |
459 | r = cp->perfect + handle; | |
1da177e4 | 460 | else |
331b7292 | 461 | r = tcindex_lookup(cp, handle) ? : &new_filter_result; |
1da177e4 LT |
462 | |
463 | if (r == &new_filter_result) { | |
0da974f4 | 464 | f = kzalloc(sizeof(*f), GFP_KERNEL); |
1da177e4 LT |
465 | if (!f) |
466 | goto errout_alloc; | |
6e056569 | 467 | f->key = handle; |
6e056569 | 468 | f->next = NULL; |
304e0242 | 469 | err = tcindex_filter_result_init(&f->result, cp, net); |
b9a24bb7 WC |
470 | if (err < 0) { |
471 | kfree(f); | |
472 | goto errout_alloc; | |
473 | } | |
10297b99 | 474 | } |
1da177e4 | 475 | |
add93b61 | 476 | if (tb[TCA_TCINDEX_CLASSID]) { |
1db817e7 CW |
477 | cr.classid = nla_get_u32(tb[TCA_TCINDEX_CLASSID]); |
478 | tcf_bind_filter(tp, &cr, base); | |
10297b99 | 479 | } |
1da177e4 | 480 | |
b9a24bb7 | 481 | if (old_r && old_r != r) { |
304e0242 | 482 | err = tcindex_filter_result_init(old_r, cp, net); |
b9a24bb7 WC |
483 | if (err < 0) { |
484 | kfree(f); | |
485 | goto errout_alloc; | |
486 | } | |
487 | } | |
1da177e4 | 488 | |
331b7292 | 489 | oldp = p; |
1db817e7 | 490 | r->res = cr; |
2df8bee5 HL |
491 | tcf_exts_change(&r->exts, &e); |
492 | ||
331b7292 | 493 | rcu_assign_pointer(tp->root, cp); |
1da177e4 LT |
494 | |
495 | if (r == &new_filter_result) { | |
331b7292 JF |
496 | struct tcindex_filter *nfp; |
497 | struct tcindex_filter __rcu **fp; | |
1da177e4 | 498 | |
008369dc | 499 | f->result.res = r->res; |
9b0d4446 | 500 | tcf_exts_change(&f->result.exts, &r->exts); |
331b7292 | 501 | |
69301eaa | 502 | fp = cp->h + (handle % cp->hash); |
331b7292 JF |
503 | for (nfp = rtnl_dereference(*fp); |
504 | nfp; | |
505 | fp = &nfp->next, nfp = rtnl_dereference(*fp)) | |
506 | ; /* nothing */ | |
507 | ||
508 | rcu_assign_pointer(*fp, f); | |
1db817e7 CW |
509 | } else { |
510 | tcf_exts_destroy(&new_filter_result.exts); | |
10297b99 | 511 | } |
1da177e4 | 512 | |
331b7292 | 513 | if (oldp) |
3d210534 | 514 | tcf_queue_work(&oldp->rwork, tcindex_partial_destroy_work); |
1da177e4 LT |
515 | return 0; |
516 | ||
517 | errout_alloc: | |
518 | if (balloc == 1) | |
b9a24bb7 | 519 | tcindex_free_perfect_hash(cp); |
1da177e4 | 520 | else if (balloc == 2) |
331b7292 | 521 | kfree(cp->h); |
b9a24bb7 | 522 | tcf_exts_destroy(&new_filter_result.exts); |
1da177e4 | 523 | errout: |
331b7292 | 524 | kfree(cp); |
18d0264f | 525 | tcf_exts_destroy(&e); |
1da177e4 LT |
526 | return err; |
527 | } | |
528 | ||
529 | static int | |
c1b52739 | 530 | tcindex_change(struct net *net, struct sk_buff *in_skb, |
af4c6641 | 531 | struct tcf_proto *tp, unsigned long base, u32 handle, |
695176bf CW |
532 | struct nlattr **tca, void **arg, u32 flags, |
533 | struct netlink_ext_ack *extack) | |
1da177e4 | 534 | { |
add93b61 PM |
535 | struct nlattr *opt = tca[TCA_OPTIONS]; |
536 | struct nlattr *tb[TCA_TCINDEX_MAX + 1]; | |
331b7292 | 537 | struct tcindex_data *p = rtnl_dereference(tp->root); |
8113c095 | 538 | struct tcindex_filter_result *r = *arg; |
cee63723 | 539 | int err; |
1da177e4 | 540 | |
aa767bfe | 541 | pr_debug("tcindex_change(tp %p,handle 0x%08x,tca %p,arg %p),opt %p," |
8113c095 | 542 | "p %p,r %p,*arg %p\n", |
c5efcf17 | 543 | tp, handle, tca, arg, opt, p, r, *arg); |
1da177e4 LT |
544 | |
545 | if (!opt) | |
546 | return 0; | |
547 | ||
8cb08174 JB |
548 | err = nla_parse_nested_deprecated(tb, TCA_TCINDEX_MAX, opt, |
549 | tcindex_policy, NULL); | |
cee63723 PM |
550 | if (err < 0) |
551 | return err; | |
1da177e4 | 552 | |
c1b52739 | 553 | return tcindex_set_parms(net, tp, base, handle, p, r, tb, |
695176bf | 554 | tca[TCA_RATE], flags, extack); |
1da177e4 LT |
555 | } |
556 | ||
12db03b6 VB |
557 | static void tcindex_walk(struct tcf_proto *tp, struct tcf_walker *walker, |
558 | bool rtnl_held) | |
1da177e4 | 559 | { |
331b7292 | 560 | struct tcindex_data *p = rtnl_dereference(tp->root); |
aa767bfe | 561 | struct tcindex_filter *f, *next; |
1da177e4 LT |
562 | int i; |
563 | ||
aa767bfe | 564 | pr_debug("tcindex_walk(tp %p,walker %p),p %p\n", tp, walker, p); |
1da177e4 LT |
565 | if (p->perfect) { |
566 | for (i = 0; i < p->hash; i++) { | |
567 | if (!p->perfect[i].res.class) | |
568 | continue; | |
569 | if (walker->count >= walker->skip) { | |
8113c095 | 570 | if (walker->fn(tp, p->perfect + i, walker) < 0) { |
1da177e4 LT |
571 | walker->stop = 1; |
572 | return; | |
573 | } | |
574 | } | |
575 | walker->count++; | |
576 | } | |
577 | } | |
578 | if (!p->h) | |
579 | return; | |
580 | for (i = 0; i < p->hash; i++) { | |
331b7292 JF |
581 | for (f = rtnl_dereference(p->h[i]); f; f = next) { |
582 | next = rtnl_dereference(f->next); | |
1da177e4 | 583 | if (walker->count >= walker->skip) { |
8113c095 | 584 | if (walker->fn(tp, &f->result, walker) < 0) { |
1da177e4 LT |
585 | walker->stop = 1; |
586 | return; | |
587 | } | |
588 | } | |
589 | walker->count++; | |
590 | } | |
591 | } | |
592 | } | |
593 | ||
12db03b6 | 594 | static void tcindex_destroy(struct tcf_proto *tp, bool rtnl_held, |
715df5ec | 595 | struct netlink_ext_ack *extack) |
1da177e4 | 596 | { |
331b7292 | 597 | struct tcindex_data *p = rtnl_dereference(tp->root); |
51dcb69d | 598 | int i; |
1da177e4 | 599 | |
aa767bfe | 600 | pr_debug("tcindex_destroy(tp %p),p %p\n", tp, p); |
51dcb69d CW |
601 | |
602 | if (p->perfect) { | |
603 | for (i = 0; i < p->hash; i++) { | |
604 | struct tcindex_filter_result *r = p->perfect + i; | |
605 | ||
304e0242 CW |
606 | /* tcf_queue_work() does not guarantee the ordering we |
607 | * want, so we have to take this refcnt temporarily to | |
608 | * ensure 'p' is freed after all tcindex_filter_result | |
609 | * here. Imperfect hash does not need this, because it | |
610 | * uses linked lists rather than an array. | |
611 | */ | |
612 | tcindex_data_get(p); | |
613 | ||
51dcb69d CW |
614 | tcf_unbind_filter(tp, &r->res); |
615 | if (tcf_exts_get_net(&r->exts)) | |
616 | tcf_queue_work(&r->rwork, | |
617 | tcindex_destroy_rexts_work); | |
618 | else | |
619 | __tcindex_destroy_rexts(r); | |
620 | } | |
621 | } | |
622 | ||
623 | for (i = 0; p->h && i < p->hash; i++) { | |
624 | struct tcindex_filter *f, *next; | |
625 | bool last; | |
626 | ||
627 | for (f = rtnl_dereference(p->h[i]); f; f = next) { | |
628 | next = rtnl_dereference(f->next); | |
629 | tcindex_delete(tp, &f->result, &last, rtnl_held, NULL); | |
630 | } | |
631 | } | |
331b7292 | 632 | |
3d210534 | 633 | tcf_queue_work(&p->rwork, tcindex_destroy_work); |
1da177e4 LT |
634 | } |
635 | ||
636 | ||
8113c095 | 637 | static int tcindex_dump(struct net *net, struct tcf_proto *tp, void *fh, |
12db03b6 | 638 | struct sk_buff *skb, struct tcmsg *t, bool rtnl_held) |
1da177e4 | 639 | { |
331b7292 | 640 | struct tcindex_data *p = rtnl_dereference(tp->root); |
8113c095 | 641 | struct tcindex_filter_result *r = fh; |
4b3550ef | 642 | struct nlattr *nest; |
1da177e4 | 643 | |
8113c095 | 644 | pr_debug("tcindex_dump(tp %p,fh %p,skb %p,t %p),p %p,r %p\n", |
6ea3b446 | 645 | tp, fh, skb, t, p, r); |
aa767bfe | 646 | pr_debug("p->perfect %p p->h %p\n", p->perfect, p->h); |
4b3550ef | 647 | |
ae0be8de | 648 | nest = nla_nest_start_noflag(skb, TCA_OPTIONS); |
4b3550ef PM |
649 | if (nest == NULL) |
650 | goto nla_put_failure; | |
651 | ||
1da177e4 LT |
652 | if (!fh) { |
653 | t->tcm_handle = ~0; /* whatever ... */ | |
1b34ec43 DM |
654 | if (nla_put_u32(skb, TCA_TCINDEX_HASH, p->hash) || |
655 | nla_put_u16(skb, TCA_TCINDEX_MASK, p->mask) || | |
656 | nla_put_u32(skb, TCA_TCINDEX_SHIFT, p->shift) || | |
657 | nla_put_u32(skb, TCA_TCINDEX_FALL_THROUGH, p->fall_through)) | |
658 | goto nla_put_failure; | |
4b3550ef | 659 | nla_nest_end(skb, nest); |
1da177e4 LT |
660 | } else { |
661 | if (p->perfect) { | |
331b7292 | 662 | t->tcm_handle = r - p->perfect; |
1da177e4 LT |
663 | } else { |
664 | struct tcindex_filter *f; | |
331b7292 | 665 | struct tcindex_filter __rcu **fp; |
1da177e4 LT |
666 | int i; |
667 | ||
668 | t->tcm_handle = 0; | |
669 | for (i = 0; !t->tcm_handle && i < p->hash; i++) { | |
331b7292 JF |
670 | fp = &p->h[i]; |
671 | for (f = rtnl_dereference(*fp); | |
672 | !t->tcm_handle && f; | |
673 | fp = &f->next, f = rtnl_dereference(*fp)) { | |
1da177e4 LT |
674 | if (&f->result == r) |
675 | t->tcm_handle = f->key; | |
676 | } | |
677 | } | |
678 | } | |
aa767bfe | 679 | pr_debug("handle = %d\n", t->tcm_handle); |
1b34ec43 DM |
680 | if (r->res.class && |
681 | nla_put_u32(skb, TCA_TCINDEX_CLASSID, r->res.classid)) | |
682 | goto nla_put_failure; | |
1da177e4 | 683 | |
5da57f42 | 684 | if (tcf_exts_dump(skb, &r->exts) < 0) |
add93b61 | 685 | goto nla_put_failure; |
4b3550ef | 686 | nla_nest_end(skb, nest); |
1da177e4 | 687 | |
5da57f42 | 688 | if (tcf_exts_dump_stats(skb, &r->exts) < 0) |
add93b61 | 689 | goto nla_put_failure; |
1da177e4 | 690 | } |
10297b99 | 691 | |
1da177e4 LT |
692 | return skb->len; |
693 | ||
add93b61 | 694 | nla_put_failure: |
6ea3b446 | 695 | nla_nest_cancel(skb, nest); |
1da177e4 LT |
696 | return -1; |
697 | } | |
698 | ||
2e24cd75 CW |
699 | static void tcindex_bind_class(void *fh, u32 classid, unsigned long cl, |
700 | void *q, unsigned long base) | |
07d79fc7 CW |
701 | { |
702 | struct tcindex_filter_result *r = fh; | |
703 | ||
2e24cd75 CW |
704 | if (r && r->res.classid == classid) { |
705 | if (cl) | |
706 | __tcf_bind_filter(q, &r->res, base); | |
707 | else | |
708 | __tcf_unbind_filter(q, &r->res); | |
709 | } | |
07d79fc7 CW |
710 | } |
711 | ||
2eb9d75c | 712 | static struct tcf_proto_ops cls_tcindex_ops __read_mostly = { |
1da177e4 LT |
713 | .kind = "tcindex", |
714 | .classify = tcindex_classify, | |
715 | .init = tcindex_init, | |
716 | .destroy = tcindex_destroy, | |
717 | .get = tcindex_get, | |
1da177e4 LT |
718 | .change = tcindex_change, |
719 | .delete = tcindex_delete, | |
720 | .walk = tcindex_walk, | |
721 | .dump = tcindex_dump, | |
07d79fc7 | 722 | .bind_class = tcindex_bind_class, |
1da177e4 LT |
723 | .owner = THIS_MODULE, |
724 | }; | |
725 | ||
726 | static int __init init_tcindex(void) | |
727 | { | |
728 | return register_tcf_proto_ops(&cls_tcindex_ops); | |
729 | } | |
730 | ||
10297b99 | 731 | static void __exit exit_tcindex(void) |
1da177e4 LT |
732 | { |
733 | unregister_tcf_proto_ops(&cls_tcindex_ops); | |
734 | } | |
735 | ||
736 | module_init(init_tcindex) | |
737 | module_exit(exit_tcindex) | |
738 | MODULE_LICENSE("GPL"); |