]>
Commit | Line | Data |
---|---|---|
96518518 | 1 | /* |
ce6eb0d7 | 2 | * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net> |
96518518 PM |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * Development of this code funded by Astaro AG (http://www.astaro.com/) | |
9 | */ | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/list.h> | |
c50b960c | 15 | #include <linux/log2.h> |
96518518 PM |
16 | #include <linux/jhash.h> |
17 | #include <linux/netlink.h> | |
9d098292 | 18 | #include <linux/workqueue.h> |
cfe4a9dd | 19 | #include <linux/rhashtable.h> |
96518518 PM |
20 | #include <linux/netfilter.h> |
21 | #include <linux/netfilter/nf_tables.h> | |
22 | #include <net/netfilter/nf_tables.h> | |
23 | ||
cfe4a9dd | 24 | /* We target a hash table size of 4, element hint is 75% of final size */ |
5fc6ced9 | 25 | #define NFT_RHASH_ELEMENT_HINT 3 |
96518518 | 26 | |
5fc6ced9 | 27 | struct nft_rhash { |
745f5450 | 28 | struct rhashtable ht; |
9d098292 | 29 | struct delayed_work gc_work; |
745f5450 PM |
30 | }; |
31 | ||
5fc6ced9 | 32 | struct nft_rhash_elem { |
cfe4a9dd | 33 | struct rhash_head node; |
fe2811eb | 34 | struct nft_set_ext ext; |
96518518 PM |
35 | }; |
36 | ||
5fc6ced9 | 37 | struct nft_rhash_cmp_arg { |
bfd6e327 | 38 | const struct nft_set *set; |
8cd8937a | 39 | const u32 *key; |
cc02e457 | 40 | u8 genmask; |
bfd6e327 PM |
41 | }; |
42 | ||
5fc6ced9 | 43 | static inline u32 nft_rhash_key(const void *data, u32 len, u32 seed) |
bfd6e327 | 44 | { |
5fc6ced9 | 45 | const struct nft_rhash_cmp_arg *arg = data; |
bfd6e327 PM |
46 | |
47 | return jhash(arg->key, len, seed); | |
48 | } | |
49 | ||
5fc6ced9 | 50 | static inline u32 nft_rhash_obj(const void *data, u32 len, u32 seed) |
bfd6e327 | 51 | { |
5fc6ced9 | 52 | const struct nft_rhash_elem *he = data; |
bfd6e327 | 53 | |
fe2811eb | 54 | return jhash(nft_set_ext_key(&he->ext), len, seed); |
bfd6e327 PM |
55 | } |
56 | ||
5fc6ced9 PNA |
57 | static inline int nft_rhash_cmp(struct rhashtable_compare_arg *arg, |
58 | const void *ptr) | |
bfd6e327 | 59 | { |
5fc6ced9 PNA |
60 | const struct nft_rhash_cmp_arg *x = arg->key; |
61 | const struct nft_rhash_elem *he = ptr; | |
bfd6e327 | 62 | |
e562d860 | 63 | if (memcmp(nft_set_ext_key(&he->ext), x->key, x->set->klen)) |
bfd6e327 | 64 | return 1; |
9d098292 PM |
65 | if (nft_set_elem_expired(&he->ext)) |
66 | return 1; | |
cc02e457 PM |
67 | if (!nft_set_elem_active(&he->ext, x->genmask)) |
68 | return 1; | |
bfd6e327 PM |
69 | return 0; |
70 | } | |
71 | ||
5fc6ced9 PNA |
72 | static const struct rhashtable_params nft_rhash_params = { |
73 | .head_offset = offsetof(struct nft_rhash_elem, node), | |
74 | .hashfn = nft_rhash_key, | |
75 | .obj_hashfn = nft_rhash_obj, | |
76 | .obj_cmpfn = nft_rhash_cmp, | |
187388bc PNA |
77 | .automatic_shrinking = true, |
78 | }; | |
79 | ||
5fc6ced9 PNA |
80 | static bool nft_rhash_lookup(const struct net *net, const struct nft_set *set, |
81 | const u32 *key, const struct nft_set_ext **ext) | |
96518518 | 82 | { |
5fc6ced9 PNA |
83 | struct nft_rhash *priv = nft_set_priv(set); |
84 | const struct nft_rhash_elem *he; | |
85 | struct nft_rhash_cmp_arg arg = { | |
42a55769 | 86 | .genmask = nft_genmask_cur(net), |
bfd6e327 PM |
87 | .set = set, |
88 | .key = key, | |
89 | }; | |
ce6eb0d7 | 90 | |
5fc6ced9 | 91 | he = rhashtable_lookup_fast(&priv->ht, &arg, nft_rhash_params); |
b2832dd6 PM |
92 | if (he != NULL) |
93 | *ext = &he->ext; | |
ce6eb0d7 | 94 | |
cfe4a9dd | 95 | return !!he; |
96518518 PM |
96 | } |
97 | ||
ba0e4d99 PNA |
98 | static void *nft_rhash_get(const struct net *net, const struct nft_set *set, |
99 | const struct nft_set_elem *elem, unsigned int flags) | |
100 | { | |
101 | struct nft_rhash *priv = nft_set_priv(set); | |
102 | struct nft_rhash_elem *he; | |
103 | struct nft_rhash_cmp_arg arg = { | |
104 | .genmask = nft_genmask_cur(net), | |
105 | .set = set, | |
106 | .key = elem->key.val.data, | |
107 | }; | |
108 | ||
109 | he = rhashtable_lookup_fast(&priv->ht, &arg, nft_rhash_params); | |
110 | if (he != NULL) | |
111 | return he; | |
112 | ||
113 | return ERR_PTR(-ENOENT); | |
114 | } | |
115 | ||
5fc6ced9 PNA |
116 | static bool nft_rhash_update(struct nft_set *set, const u32 *key, |
117 | void *(*new)(struct nft_set *, | |
118 | const struct nft_expr *, | |
119 | struct nft_regs *regs), | |
120 | const struct nft_expr *expr, | |
121 | struct nft_regs *regs, | |
122 | const struct nft_set_ext **ext) | |
22fe54d5 | 123 | { |
5fc6ced9 PNA |
124 | struct nft_rhash *priv = nft_set_priv(set); |
125 | struct nft_rhash_elem *he, *prev; | |
126 | struct nft_rhash_cmp_arg arg = { | |
22fe54d5 PM |
127 | .genmask = NFT_GENMASK_ANY, |
128 | .set = set, | |
129 | .key = key, | |
130 | }; | |
131 | ||
5fc6ced9 | 132 | he = rhashtable_lookup_fast(&priv->ht, &arg, nft_rhash_params); |
22fe54d5 PM |
133 | if (he != NULL) |
134 | goto out; | |
135 | ||
a55e22e9 | 136 | he = new(set, expr, regs); |
22fe54d5 PM |
137 | if (he == NULL) |
138 | goto err1; | |
dab45060 LZ |
139 | |
140 | prev = rhashtable_lookup_get_insert_key(&priv->ht, &arg, &he->node, | |
5fc6ced9 | 141 | nft_rhash_params); |
dab45060 | 142 | if (IS_ERR(prev)) |
22fe54d5 | 143 | goto err2; |
dab45060 LZ |
144 | |
145 | /* Another cpu may race to insert the element with the same key */ | |
146 | if (prev) { | |
147 | nft_set_elem_destroy(set, he, true); | |
148 | he = prev; | |
149 | } | |
150 | ||
22fe54d5 PM |
151 | out: |
152 | *ext = &he->ext; | |
153 | return true; | |
154 | ||
155 | err2: | |
61f9e292 | 156 | nft_set_elem_destroy(set, he, true); |
22fe54d5 PM |
157 | err1: |
158 | return false; | |
159 | } | |
160 | ||
5fc6ced9 PNA |
161 | static int nft_rhash_insert(const struct net *net, const struct nft_set *set, |
162 | const struct nft_set_elem *elem, | |
163 | struct nft_set_ext **ext) | |
96518518 | 164 | { |
5fc6ced9 PNA |
165 | struct nft_rhash *priv = nft_set_priv(set); |
166 | struct nft_rhash_elem *he = elem->priv; | |
167 | struct nft_rhash_cmp_arg arg = { | |
42a55769 | 168 | .genmask = nft_genmask_next(net), |
bfd6e327 | 169 | .set = set, |
7d740264 | 170 | .key = elem->key.val.data, |
bfd6e327 | 171 | }; |
5fc6ced9 | 172 | struct nft_rhash_elem *prev; |
c016c7e4 PNA |
173 | |
174 | prev = rhashtable_lookup_get_insert_key(&priv->ht, &arg, &he->node, | |
5fc6ced9 | 175 | nft_rhash_params); |
c016c7e4 PNA |
176 | if (IS_ERR(prev)) |
177 | return PTR_ERR(prev); | |
178 | if (prev) { | |
179 | *ext = &prev->ext; | |
180 | return -EEXIST; | |
181 | } | |
182 | return 0; | |
96518518 PM |
183 | } |
184 | ||
5fc6ced9 PNA |
185 | static void nft_rhash_activate(const struct net *net, const struct nft_set *set, |
186 | const struct nft_set_elem *elem) | |
96518518 | 187 | { |
5fc6ced9 | 188 | struct nft_rhash_elem *he = elem->priv; |
ce6eb0d7 | 189 | |
42a55769 | 190 | nft_set_elem_change_active(net, set, &he->ext); |
9d098292 | 191 | nft_set_elem_clear_busy(&he->ext); |
20a69341 | 192 | } |
96518518 | 193 | |
5fc6ced9 PNA |
194 | static bool nft_rhash_flush(const struct net *net, |
195 | const struct nft_set *set, void *priv) | |
37df5301 | 196 | { |
5fc6ced9 | 197 | struct nft_rhash_elem *he = priv; |
37df5301 PNA |
198 | |
199 | if (!nft_set_elem_mark_busy(&he->ext) || | |
200 | !nft_is_active(net, &he->ext)) { | |
201 | nft_set_elem_change_active(net, set, &he->ext); | |
202 | return true; | |
203 | } | |
204 | return false; | |
205 | } | |
206 | ||
5fc6ced9 PNA |
207 | static void *nft_rhash_deactivate(const struct net *net, |
208 | const struct nft_set *set, | |
209 | const struct nft_set_elem *elem) | |
20a69341 | 210 | { |
5fc6ced9 PNA |
211 | struct nft_rhash *priv = nft_set_priv(set); |
212 | struct nft_rhash_elem *he; | |
213 | struct nft_rhash_cmp_arg arg = { | |
8eee54be | 214 | .genmask = nft_genmask_next(net), |
bfd6e327 | 215 | .set = set, |
7d740264 | 216 | .key = elem->key.val.data, |
bfd6e327 | 217 | }; |
fa377321 | 218 | |
9d098292 | 219 | rcu_read_lock(); |
5fc6ced9 | 220 | he = rhashtable_lookup_fast(&priv->ht, &arg, nft_rhash_params); |
37df5301 | 221 | if (he != NULL && |
5fc6ced9 | 222 | !nft_rhash_flush(net, set, he)) |
37df5301 PNA |
223 | he = NULL; |
224 | ||
9d098292 | 225 | rcu_read_unlock(); |
8d24c0b4 | 226 | |
cc02e457 PM |
227 | return he; |
228 | } | |
8d24c0b4 | 229 | |
5fc6ced9 PNA |
230 | static void nft_rhash_remove(const struct net *net, |
231 | const struct nft_set *set, | |
232 | const struct nft_set_elem *elem) | |
cc02e457 | 233 | { |
5fc6ced9 PNA |
234 | struct nft_rhash *priv = nft_set_priv(set); |
235 | struct nft_rhash_elem *he = elem->priv; | |
cc02e457 | 236 | |
5fc6ced9 | 237 | rhashtable_remove_fast(&priv->ht, &he->node, nft_rhash_params); |
96518518 PM |
238 | } |
239 | ||
5fc6ced9 PNA |
240 | static void nft_rhash_walk(const struct nft_ctx *ctx, struct nft_set *set, |
241 | struct nft_set_iter *iter) | |
96518518 | 242 | { |
5fc6ced9 PNA |
243 | struct nft_rhash *priv = nft_set_priv(set); |
244 | struct nft_rhash_elem *he; | |
9a776628 | 245 | struct rhashtable_iter hti; |
20a69341 | 246 | struct nft_set_elem elem; |
9a776628 | 247 | int err; |
96518518 | 248 | |
fa803605 | 249 | err = rhashtable_walk_init(&priv->ht, &hti, GFP_ATOMIC); |
9a776628 HX |
250 | iter->err = err; |
251 | if (err) | |
252 | return; | |
88d6ed15 | 253 | |
9a776628 HX |
254 | err = rhashtable_walk_start(&hti); |
255 | if (err && err != -EAGAIN) { | |
256 | iter->err = err; | |
257 | goto out; | |
258 | } | |
259 | ||
260 | while ((he = rhashtable_walk_next(&hti))) { | |
261 | if (IS_ERR(he)) { | |
262 | err = PTR_ERR(he); | |
263 | if (err != -EAGAIN) { | |
264 | iter->err = err; | |
265 | goto out; | |
266 | } | |
d8bdff59 HX |
267 | |
268 | continue; | |
9a776628 HX |
269 | } |
270 | ||
271 | if (iter->count < iter->skip) | |
272 | goto cont; | |
9d098292 PM |
273 | if (nft_set_elem_expired(&he->ext)) |
274 | goto cont; | |
8588ac09 | 275 | if (!nft_set_elem_active(&he->ext, iter->genmask)) |
cc02e457 | 276 | goto cont; |
20a69341 | 277 | |
fe2811eb | 278 | elem.priv = he; |
9a776628 HX |
279 | |
280 | iter->err = iter->fn(ctx, set, iter, &elem); | |
281 | if (iter->err < 0) | |
282 | goto out; | |
20a69341 | 283 | |
20a69341 | 284 | cont: |
9a776628 | 285 | iter->count++; |
96518518 | 286 | } |
9a776628 HX |
287 | |
288 | out: | |
289 | rhashtable_walk_stop(&hti); | |
290 | rhashtable_walk_exit(&hti); | |
96518518 PM |
291 | } |
292 | ||
5fc6ced9 | 293 | static void nft_rhash_gc(struct work_struct *work) |
9d098292 | 294 | { |
3dd0673a | 295 | struct nft_set *set; |
5fc6ced9 PNA |
296 | struct nft_rhash_elem *he; |
297 | struct nft_rhash *priv; | |
9d098292 PM |
298 | struct nft_set_gc_batch *gcb = NULL; |
299 | struct rhashtable_iter hti; | |
300 | int err; | |
301 | ||
5fc6ced9 | 302 | priv = container_of(work, struct nft_rhash, gc_work.work); |
9d098292 PM |
303 | set = nft_set_container_of(priv); |
304 | ||
8f6fd83c | 305 | err = rhashtable_walk_init(&priv->ht, &hti, GFP_KERNEL); |
9d098292 PM |
306 | if (err) |
307 | goto schedule; | |
308 | ||
309 | err = rhashtable_walk_start(&hti); | |
310 | if (err && err != -EAGAIN) | |
311 | goto out; | |
312 | ||
313 | while ((he = rhashtable_walk_next(&hti))) { | |
314 | if (IS_ERR(he)) { | |
315 | if (PTR_ERR(he) != -EAGAIN) | |
316 | goto out; | |
317 | continue; | |
318 | } | |
319 | ||
320 | if (!nft_set_elem_expired(&he->ext)) | |
321 | continue; | |
322 | if (nft_set_elem_mark_busy(&he->ext)) | |
323 | continue; | |
324 | ||
325 | gcb = nft_set_gc_batch_check(set, gcb, GFP_ATOMIC); | |
326 | if (gcb == NULL) | |
327 | goto out; | |
5fc6ced9 | 328 | rhashtable_remove_fast(&priv->ht, &he->node, nft_rhash_params); |
3dd0673a | 329 | atomic_dec(&set->nelems); |
9d098292 PM |
330 | nft_set_gc_batch_add(gcb, he); |
331 | } | |
332 | out: | |
333 | rhashtable_walk_stop(&hti); | |
334 | rhashtable_walk_exit(&hti); | |
335 | ||
336 | nft_set_gc_batch_complete(gcb); | |
337 | schedule: | |
338 | queue_delayed_work(system_power_efficient_wq, &priv->gc_work, | |
339 | nft_set_gc_interval(set)); | |
340 | } | |
341 | ||
347b408d PNA |
342 | static unsigned int nft_rhash_privsize(const struct nlattr * const nla[], |
343 | const struct nft_set_desc *desc) | |
20a69341 | 344 | { |
5fc6ced9 | 345 | return sizeof(struct nft_rhash); |
cfe4a9dd TG |
346 | } |
347 | ||
5fc6ced9 PNA |
348 | static int nft_rhash_init(const struct nft_set *set, |
349 | const struct nft_set_desc *desc, | |
350 | const struct nlattr * const tb[]) | |
96518518 | 351 | { |
5fc6ced9 PNA |
352 | struct nft_rhash *priv = nft_set_priv(set); |
353 | struct rhashtable_params params = nft_rhash_params; | |
9d098292 | 354 | int err; |
fa377321 | 355 | |
5fc6ced9 | 356 | params.nelem_hint = desc->size ?: NFT_RHASH_ELEMENT_HINT; |
45d84751 | 357 | params.key_len = set->klen; |
96518518 | 358 | |
9d098292 PM |
359 | err = rhashtable_init(&priv->ht, ¶ms); |
360 | if (err < 0) | |
361 | return err; | |
362 | ||
5fc6ced9 | 363 | INIT_DEFERRABLE_WORK(&priv->gc_work, nft_rhash_gc); |
9d098292 PM |
364 | if (set->flags & NFT_SET_TIMEOUT) |
365 | queue_delayed_work(system_power_efficient_wq, &priv->gc_work, | |
366 | nft_set_gc_interval(set)); | |
367 | return 0; | |
96518518 PM |
368 | } |
369 | ||
5fc6ced9 | 370 | static void nft_rhash_elem_destroy(void *ptr, void *arg) |
96518518 | 371 | { |
68ad546a | 372 | nft_set_elem_destroy(arg, ptr, true); |
6b6f302c | 373 | } |
97defe1e | 374 | |
5fc6ced9 | 375 | static void nft_rhash_destroy(const struct nft_set *set) |
6b6f302c | 376 | { |
5fc6ced9 | 377 | struct nft_rhash *priv = nft_set_priv(set); |
745f5450 | 378 | |
9d098292 | 379 | cancel_delayed_work_sync(&priv->gc_work); |
5fc6ced9 | 380 | rhashtable_free_and_destroy(&priv->ht, nft_rhash_elem_destroy, |
61edafbb | 381 | (void *)set); |
96518518 PM |
382 | } |
383 | ||
2111515a PNA |
384 | static u32 nft_hash_buckets(u32 size) |
385 | { | |
386 | return roundup_pow_of_two(size * 4 / 3); | |
387 | } | |
388 | ||
5fc6ced9 PNA |
389 | static bool nft_rhash_estimate(const struct nft_set_desc *desc, u32 features, |
390 | struct nft_set_estimate *est) | |
c50b960c | 391 | { |
6c03ae21 PNA |
392 | est->size = ~0; |
393 | est->lookup = NFT_SET_CLASS_O_1; | |
394 | est->space = NFT_SET_CLASS_O_N; | |
395 | ||
396 | return true; | |
397 | } | |
398 | ||
399 | struct nft_hash { | |
400 | u32 seed; | |
401 | u32 buckets; | |
402 | struct hlist_head table[]; | |
403 | }; | |
404 | ||
405 | struct nft_hash_elem { | |
406 | struct hlist_node node; | |
407 | struct nft_set_ext ext; | |
408 | }; | |
409 | ||
410 | static bool nft_hash_lookup(const struct net *net, const struct nft_set *set, | |
411 | const u32 *key, const struct nft_set_ext **ext) | |
412 | { | |
413 | struct nft_hash *priv = nft_set_priv(set); | |
414 | u8 genmask = nft_genmask_cur(net); | |
415 | const struct nft_hash_elem *he; | |
416 | u32 hash; | |
417 | ||
418 | hash = jhash(key, set->klen, priv->seed); | |
419 | hash = reciprocal_scale(hash, priv->buckets); | |
420 | hlist_for_each_entry_rcu(he, &priv->table[hash], node) { | |
421 | if (!memcmp(nft_set_ext_key(&he->ext), key, set->klen) && | |
422 | nft_set_elem_active(&he->ext, genmask)) { | |
423 | *ext = &he->ext; | |
424 | return true; | |
425 | } | |
426 | } | |
427 | return false; | |
428 | } | |
429 | ||
ba0e4d99 PNA |
430 | static void *nft_hash_get(const struct net *net, const struct nft_set *set, |
431 | const struct nft_set_elem *elem, unsigned int flags) | |
432 | { | |
433 | struct nft_hash *priv = nft_set_priv(set); | |
434 | u8 genmask = nft_genmask_cur(net); | |
435 | struct nft_hash_elem *he; | |
436 | u32 hash; | |
437 | ||
438 | hash = jhash(elem->key.val.data, set->klen, priv->seed); | |
439 | hash = reciprocal_scale(hash, priv->buckets); | |
440 | hlist_for_each_entry_rcu(he, &priv->table[hash], node) { | |
441 | if (!memcmp(nft_set_ext_key(&he->ext), elem->key.val.data, set->klen) && | |
442 | nft_set_elem_active(&he->ext, genmask)) | |
443 | return he; | |
444 | } | |
445 | return ERR_PTR(-ENOENT); | |
446 | } | |
447 | ||
446a8268 PNA |
448 | /* nft_hash_select_ops() makes sure key size can be either 2 or 4 bytes . */ |
449 | static inline u32 nft_hash_key(const u32 *key, u32 klen) | |
450 | { | |
451 | if (klen == 4) | |
452 | return *key; | |
453 | ||
454 | return *(u16 *)key; | |
455 | } | |
456 | ||
457 | static bool nft_hash_lookup_fast(const struct net *net, | |
458 | const struct nft_set *set, | |
459 | const u32 *key, const struct nft_set_ext **ext) | |
460 | { | |
461 | struct nft_hash *priv = nft_set_priv(set); | |
462 | u8 genmask = nft_genmask_cur(net); | |
463 | const struct nft_hash_elem *he; | |
464 | u32 hash, k1, k2; | |
465 | ||
466 | k1 = nft_hash_key(key, set->klen); | |
467 | hash = jhash_1word(k1, priv->seed); | |
468 | hash = reciprocal_scale(hash, priv->buckets); | |
469 | hlist_for_each_entry_rcu(he, &priv->table[hash], node) { | |
470 | k2 = nft_hash_key(nft_set_ext_key(&he->ext)->data, set->klen); | |
471 | if (k1 == k2 && | |
472 | nft_set_elem_active(&he->ext, genmask)) { | |
473 | *ext = &he->ext; | |
474 | return true; | |
475 | } | |
476 | } | |
477 | return false; | |
478 | } | |
479 | ||
6c03ae21 PNA |
480 | static int nft_hash_insert(const struct net *net, const struct nft_set *set, |
481 | const struct nft_set_elem *elem, | |
482 | struct nft_set_ext **ext) | |
483 | { | |
484 | struct nft_hash_elem *this = elem->priv, *he; | |
485 | struct nft_hash *priv = nft_set_priv(set); | |
486 | u8 genmask = nft_genmask_next(net); | |
487 | u32 hash; | |
488 | ||
489 | hash = jhash(nft_set_ext_key(&this->ext), set->klen, priv->seed); | |
490 | hash = reciprocal_scale(hash, priv->buckets); | |
491 | hlist_for_each_entry(he, &priv->table[hash], node) { | |
492 | if (!memcmp(nft_set_ext_key(&this->ext), | |
493 | nft_set_ext_key(&he->ext), set->klen) && | |
494 | nft_set_elem_active(&he->ext, genmask)) { | |
495 | *ext = &he->ext; | |
496 | return -EEXIST; | |
497 | } | |
498 | } | |
499 | hlist_add_head_rcu(&this->node, &priv->table[hash]); | |
500 | return 0; | |
501 | } | |
502 | ||
503 | static void nft_hash_activate(const struct net *net, const struct nft_set *set, | |
504 | const struct nft_set_elem *elem) | |
505 | { | |
506 | struct nft_hash_elem *he = elem->priv; | |
507 | ||
508 | nft_set_elem_change_active(net, set, &he->ext); | |
509 | } | |
510 | ||
511 | static bool nft_hash_flush(const struct net *net, | |
512 | const struct nft_set *set, void *priv) | |
513 | { | |
514 | struct nft_hash_elem *he = priv; | |
515 | ||
516 | nft_set_elem_change_active(net, set, &he->ext); | |
517 | return true; | |
518 | } | |
519 | ||
520 | static void *nft_hash_deactivate(const struct net *net, | |
521 | const struct nft_set *set, | |
522 | const struct nft_set_elem *elem) | |
523 | { | |
524 | struct nft_hash *priv = nft_set_priv(set); | |
525 | struct nft_hash_elem *this = elem->priv, *he; | |
526 | u8 genmask = nft_genmask_next(net); | |
527 | u32 hash; | |
528 | ||
529 | hash = jhash(nft_set_ext_key(&this->ext), set->klen, priv->seed); | |
530 | hash = reciprocal_scale(hash, priv->buckets); | |
531 | hlist_for_each_entry(he, &priv->table[hash], node) { | |
532 | if (!memcmp(nft_set_ext_key(&this->ext), &elem->key.val, | |
7f4dae2d | 533 | set->klen) && |
6c03ae21 PNA |
534 | nft_set_elem_active(&he->ext, genmask)) { |
535 | nft_set_elem_change_active(net, set, &he->ext); | |
536 | return he; | |
537 | } | |
538 | } | |
539 | return NULL; | |
540 | } | |
541 | ||
542 | static void nft_hash_remove(const struct net *net, | |
543 | const struct nft_set *set, | |
544 | const struct nft_set_elem *elem) | |
545 | { | |
546 | struct nft_hash_elem *he = elem->priv; | |
547 | ||
548 | hlist_del_rcu(&he->node); | |
549 | } | |
550 | ||
551 | static void nft_hash_walk(const struct nft_ctx *ctx, struct nft_set *set, | |
552 | struct nft_set_iter *iter) | |
553 | { | |
554 | struct nft_hash *priv = nft_set_priv(set); | |
555 | struct nft_hash_elem *he; | |
556 | struct nft_set_elem elem; | |
557 | int i; | |
558 | ||
559 | for (i = 0; i < priv->buckets; i++) { | |
560 | hlist_for_each_entry_rcu(he, &priv->table[i], node) { | |
561 | if (iter->count < iter->skip) | |
562 | goto cont; | |
563 | if (!nft_set_elem_active(&he->ext, iter->genmask)) | |
564 | goto cont; | |
565 | ||
566 | elem.priv = he; | |
567 | ||
568 | iter->err = iter->fn(ctx, set, iter, &elem); | |
569 | if (iter->err < 0) | |
570 | return; | |
571 | cont: | |
572 | iter->count++; | |
573 | } | |
574 | } | |
575 | } | |
576 | ||
577 | static unsigned int nft_hash_privsize(const struct nlattr * const nla[], | |
578 | const struct nft_set_desc *desc) | |
579 | { | |
580 | return sizeof(struct nft_hash) + | |
581 | nft_hash_buckets(desc->size) * sizeof(struct hlist_head); | |
582 | } | |
583 | ||
584 | static int nft_hash_init(const struct nft_set *set, | |
585 | const struct nft_set_desc *desc, | |
586 | const struct nlattr * const tb[]) | |
587 | { | |
588 | struct nft_hash *priv = nft_set_priv(set); | |
589 | ||
590 | priv->buckets = nft_hash_buckets(desc->size); | |
591 | get_random_bytes(&priv->seed, sizeof(priv->seed)); | |
592 | ||
593 | return 0; | |
594 | } | |
595 | ||
596 | static void nft_hash_destroy(const struct nft_set *set) | |
597 | { | |
598 | struct nft_hash *priv = nft_set_priv(set); | |
599 | struct nft_hash_elem *he; | |
600 | struct hlist_node *next; | |
601 | int i; | |
602 | ||
603 | for (i = 0; i < priv->buckets; i++) { | |
604 | hlist_for_each_entry_safe(he, next, &priv->table[i], node) { | |
605 | hlist_del_rcu(&he->node); | |
606 | nft_set_elem_destroy(set, he, true); | |
607 | } | |
608 | } | |
609 | } | |
c50b960c | 610 | |
6c03ae21 PNA |
611 | static bool nft_hash_estimate(const struct nft_set_desc *desc, u32 features, |
612 | struct nft_set_estimate *est) | |
613 | { | |
614 | est->size = sizeof(struct nft_hash) + | |
615 | nft_hash_buckets(desc->size) * sizeof(struct hlist_head) + | |
616 | desc->size * sizeof(struct nft_hash_elem); | |
55af753c | 617 | est->lookup = NFT_SET_CLASS_O_1; |
0b5a7874 | 618 | est->space = NFT_SET_CLASS_O_N; |
c50b960c PM |
619 | |
620 | return true; | |
621 | } | |
622 | ||
2b664957 | 623 | static struct nft_set_type nft_hash_type; |
5fc6ced9 | 624 | static struct nft_set_ops nft_rhash_ops __read_mostly = { |
2b664957 | 625 | .type = &nft_hash_type, |
5fc6ced9 PNA |
626 | .privsize = nft_rhash_privsize, |
627 | .elemsize = offsetof(struct nft_rhash_elem, ext), | |
628 | .estimate = nft_rhash_estimate, | |
629 | .init = nft_rhash_init, | |
630 | .destroy = nft_rhash_destroy, | |
631 | .insert = nft_rhash_insert, | |
632 | .activate = nft_rhash_activate, | |
633 | .deactivate = nft_rhash_deactivate, | |
634 | .flush = nft_rhash_flush, | |
635 | .remove = nft_rhash_remove, | |
636 | .lookup = nft_rhash_lookup, | |
637 | .update = nft_rhash_update, | |
638 | .walk = nft_rhash_walk, | |
ba0e4d99 | 639 | .get = nft_rhash_get, |
7286ff7f | 640 | .features = NFT_SET_MAP | NFT_SET_OBJECT | NFT_SET_TIMEOUT, |
2b664957 PNA |
641 | }; |
642 | ||
6c03ae21 PNA |
643 | static struct nft_set_ops nft_hash_ops __read_mostly = { |
644 | .type = &nft_hash_type, | |
645 | .privsize = nft_hash_privsize, | |
646 | .elemsize = offsetof(struct nft_hash_elem, ext), | |
647 | .estimate = nft_hash_estimate, | |
648 | .init = nft_hash_init, | |
649 | .destroy = nft_hash_destroy, | |
650 | .insert = nft_hash_insert, | |
651 | .activate = nft_hash_activate, | |
652 | .deactivate = nft_hash_deactivate, | |
653 | .flush = nft_hash_flush, | |
654 | .remove = nft_hash_remove, | |
655 | .lookup = nft_hash_lookup, | |
656 | .walk = nft_hash_walk, | |
ba0e4d99 | 657 | .get = nft_hash_get, |
6c03ae21 PNA |
658 | .features = NFT_SET_MAP | NFT_SET_OBJECT, |
659 | }; | |
660 | ||
446a8268 PNA |
661 | static struct nft_set_ops nft_hash_fast_ops __read_mostly = { |
662 | .type = &nft_hash_type, | |
663 | .privsize = nft_hash_privsize, | |
664 | .elemsize = offsetof(struct nft_hash_elem, ext), | |
665 | .estimate = nft_hash_estimate, | |
666 | .init = nft_hash_init, | |
667 | .destroy = nft_hash_destroy, | |
668 | .insert = nft_hash_insert, | |
669 | .activate = nft_hash_activate, | |
670 | .deactivate = nft_hash_deactivate, | |
671 | .flush = nft_hash_flush, | |
672 | .remove = nft_hash_remove, | |
673 | .lookup = nft_hash_lookup_fast, | |
674 | .walk = nft_hash_walk, | |
ba0e4d99 | 675 | .get = nft_hash_get, |
446a8268 PNA |
676 | .features = NFT_SET_MAP | NFT_SET_OBJECT, |
677 | }; | |
678 | ||
6c03ae21 PNA |
679 | static const struct nft_set_ops * |
680 | nft_hash_select_ops(const struct nft_ctx *ctx, const struct nft_set_desc *desc, | |
681 | u32 flags) | |
682 | { | |
446a8268 PNA |
683 | if (desc->size) { |
684 | switch (desc->klen) { | |
446a8268 PNA |
685 | case 4: |
686 | return &nft_hash_fast_ops; | |
687 | default: | |
688 | return &nft_hash_ops; | |
689 | } | |
690 | } | |
6c03ae21 PNA |
691 | |
692 | return &nft_rhash_ops; | |
693 | } | |
694 | ||
2b664957 | 695 | static struct nft_set_type nft_hash_type __read_mostly = { |
6c03ae21 | 696 | .select_ops = nft_hash_select_ops, |
20a69341 | 697 | .owner = THIS_MODULE, |
96518518 PM |
698 | }; |
699 | ||
700 | static int __init nft_hash_module_init(void) | |
701 | { | |
2b664957 | 702 | return nft_register_set(&nft_hash_type); |
96518518 PM |
703 | } |
704 | ||
705 | static void __exit nft_hash_module_exit(void) | |
706 | { | |
2b664957 | 707 | nft_unregister_set(&nft_hash_type); |
96518518 PM |
708 | } |
709 | ||
710 | module_init(nft_hash_module_init); | |
711 | module_exit(nft_hash_module_exit); | |
712 | ||
713 | MODULE_LICENSE("GPL"); | |
714 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); | |
20a69341 | 715 | MODULE_ALIAS_NFT_SET(); |