]>
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 | ||
5fc6ced9 PNA |
98 | static bool nft_rhash_update(struct nft_set *set, const u32 *key, |
99 | void *(*new)(struct nft_set *, | |
100 | const struct nft_expr *, | |
101 | struct nft_regs *regs), | |
102 | const struct nft_expr *expr, | |
103 | struct nft_regs *regs, | |
104 | const struct nft_set_ext **ext) | |
22fe54d5 | 105 | { |
5fc6ced9 PNA |
106 | struct nft_rhash *priv = nft_set_priv(set); |
107 | struct nft_rhash_elem *he, *prev; | |
108 | struct nft_rhash_cmp_arg arg = { | |
22fe54d5 PM |
109 | .genmask = NFT_GENMASK_ANY, |
110 | .set = set, | |
111 | .key = key, | |
112 | }; | |
113 | ||
5fc6ced9 | 114 | he = rhashtable_lookup_fast(&priv->ht, &arg, nft_rhash_params); |
22fe54d5 PM |
115 | if (he != NULL) |
116 | goto out; | |
117 | ||
a55e22e9 | 118 | he = new(set, expr, regs); |
22fe54d5 PM |
119 | if (he == NULL) |
120 | goto err1; | |
dab45060 LZ |
121 | |
122 | prev = rhashtable_lookup_get_insert_key(&priv->ht, &arg, &he->node, | |
5fc6ced9 | 123 | nft_rhash_params); |
dab45060 | 124 | if (IS_ERR(prev)) |
22fe54d5 | 125 | goto err2; |
dab45060 LZ |
126 | |
127 | /* Another cpu may race to insert the element with the same key */ | |
128 | if (prev) { | |
129 | nft_set_elem_destroy(set, he, true); | |
130 | he = prev; | |
131 | } | |
132 | ||
22fe54d5 PM |
133 | out: |
134 | *ext = &he->ext; | |
135 | return true; | |
136 | ||
137 | err2: | |
61f9e292 | 138 | nft_set_elem_destroy(set, he, true); |
22fe54d5 PM |
139 | err1: |
140 | return false; | |
141 | } | |
142 | ||
5fc6ced9 PNA |
143 | static int nft_rhash_insert(const struct net *net, const struct nft_set *set, |
144 | const struct nft_set_elem *elem, | |
145 | struct nft_set_ext **ext) | |
96518518 | 146 | { |
5fc6ced9 PNA |
147 | struct nft_rhash *priv = nft_set_priv(set); |
148 | struct nft_rhash_elem *he = elem->priv; | |
149 | struct nft_rhash_cmp_arg arg = { | |
42a55769 | 150 | .genmask = nft_genmask_next(net), |
bfd6e327 | 151 | .set = set, |
7d740264 | 152 | .key = elem->key.val.data, |
bfd6e327 | 153 | }; |
5fc6ced9 | 154 | struct nft_rhash_elem *prev; |
c016c7e4 PNA |
155 | |
156 | prev = rhashtable_lookup_get_insert_key(&priv->ht, &arg, &he->node, | |
5fc6ced9 | 157 | nft_rhash_params); |
c016c7e4 PNA |
158 | if (IS_ERR(prev)) |
159 | return PTR_ERR(prev); | |
160 | if (prev) { | |
161 | *ext = &prev->ext; | |
162 | return -EEXIST; | |
163 | } | |
164 | return 0; | |
96518518 PM |
165 | } |
166 | ||
5fc6ced9 PNA |
167 | static void nft_rhash_activate(const struct net *net, const struct nft_set *set, |
168 | const struct nft_set_elem *elem) | |
96518518 | 169 | { |
5fc6ced9 | 170 | struct nft_rhash_elem *he = elem->priv; |
ce6eb0d7 | 171 | |
42a55769 | 172 | nft_set_elem_change_active(net, set, &he->ext); |
9d098292 | 173 | nft_set_elem_clear_busy(&he->ext); |
20a69341 | 174 | } |
96518518 | 175 | |
5fc6ced9 PNA |
176 | static bool nft_rhash_flush(const struct net *net, |
177 | const struct nft_set *set, void *priv) | |
37df5301 | 178 | { |
5fc6ced9 | 179 | struct nft_rhash_elem *he = priv; |
37df5301 PNA |
180 | |
181 | if (!nft_set_elem_mark_busy(&he->ext) || | |
182 | !nft_is_active(net, &he->ext)) { | |
183 | nft_set_elem_change_active(net, set, &he->ext); | |
184 | return true; | |
185 | } | |
186 | return false; | |
187 | } | |
188 | ||
5fc6ced9 PNA |
189 | static void *nft_rhash_deactivate(const struct net *net, |
190 | const struct nft_set *set, | |
191 | const struct nft_set_elem *elem) | |
20a69341 | 192 | { |
5fc6ced9 PNA |
193 | struct nft_rhash *priv = nft_set_priv(set); |
194 | struct nft_rhash_elem *he; | |
195 | struct nft_rhash_cmp_arg arg = { | |
8eee54be | 196 | .genmask = nft_genmask_next(net), |
bfd6e327 | 197 | .set = set, |
7d740264 | 198 | .key = elem->key.val.data, |
bfd6e327 | 199 | }; |
fa377321 | 200 | |
9d098292 | 201 | rcu_read_lock(); |
5fc6ced9 | 202 | he = rhashtable_lookup_fast(&priv->ht, &arg, nft_rhash_params); |
37df5301 | 203 | if (he != NULL && |
5fc6ced9 | 204 | !nft_rhash_flush(net, set, he)) |
37df5301 PNA |
205 | he = NULL; |
206 | ||
9d098292 | 207 | rcu_read_unlock(); |
8d24c0b4 | 208 | |
cc02e457 PM |
209 | return he; |
210 | } | |
8d24c0b4 | 211 | |
5fc6ced9 PNA |
212 | static void nft_rhash_remove(const struct net *net, |
213 | const struct nft_set *set, | |
214 | const struct nft_set_elem *elem) | |
cc02e457 | 215 | { |
5fc6ced9 PNA |
216 | struct nft_rhash *priv = nft_set_priv(set); |
217 | struct nft_rhash_elem *he = elem->priv; | |
cc02e457 | 218 | |
5fc6ced9 | 219 | rhashtable_remove_fast(&priv->ht, &he->node, nft_rhash_params); |
96518518 PM |
220 | } |
221 | ||
5fc6ced9 PNA |
222 | static void nft_rhash_walk(const struct nft_ctx *ctx, struct nft_set *set, |
223 | struct nft_set_iter *iter) | |
96518518 | 224 | { |
5fc6ced9 PNA |
225 | struct nft_rhash *priv = nft_set_priv(set); |
226 | struct nft_rhash_elem *he; | |
9a776628 | 227 | struct rhashtable_iter hti; |
20a69341 | 228 | struct nft_set_elem elem; |
9a776628 | 229 | int err; |
96518518 | 230 | |
fa803605 | 231 | err = rhashtable_walk_init(&priv->ht, &hti, GFP_ATOMIC); |
9a776628 HX |
232 | iter->err = err; |
233 | if (err) | |
234 | return; | |
88d6ed15 | 235 | |
9a776628 HX |
236 | err = rhashtable_walk_start(&hti); |
237 | if (err && err != -EAGAIN) { | |
238 | iter->err = err; | |
239 | goto out; | |
240 | } | |
241 | ||
242 | while ((he = rhashtable_walk_next(&hti))) { | |
243 | if (IS_ERR(he)) { | |
244 | err = PTR_ERR(he); | |
245 | if (err != -EAGAIN) { | |
246 | iter->err = err; | |
247 | goto out; | |
248 | } | |
d8bdff59 HX |
249 | |
250 | continue; | |
9a776628 HX |
251 | } |
252 | ||
253 | if (iter->count < iter->skip) | |
254 | goto cont; | |
9d098292 PM |
255 | if (nft_set_elem_expired(&he->ext)) |
256 | goto cont; | |
8588ac09 | 257 | if (!nft_set_elem_active(&he->ext, iter->genmask)) |
cc02e457 | 258 | goto cont; |
20a69341 | 259 | |
fe2811eb | 260 | elem.priv = he; |
9a776628 HX |
261 | |
262 | iter->err = iter->fn(ctx, set, iter, &elem); | |
263 | if (iter->err < 0) | |
264 | goto out; | |
20a69341 | 265 | |
20a69341 | 266 | cont: |
9a776628 | 267 | iter->count++; |
96518518 | 268 | } |
9a776628 HX |
269 | |
270 | out: | |
271 | rhashtable_walk_stop(&hti); | |
272 | rhashtable_walk_exit(&hti); | |
96518518 PM |
273 | } |
274 | ||
5fc6ced9 | 275 | static void nft_rhash_gc(struct work_struct *work) |
9d098292 | 276 | { |
3dd0673a | 277 | struct nft_set *set; |
5fc6ced9 PNA |
278 | struct nft_rhash_elem *he; |
279 | struct nft_rhash *priv; | |
9d098292 PM |
280 | struct nft_set_gc_batch *gcb = NULL; |
281 | struct rhashtable_iter hti; | |
282 | int err; | |
283 | ||
5fc6ced9 | 284 | priv = container_of(work, struct nft_rhash, gc_work.work); |
9d098292 PM |
285 | set = nft_set_container_of(priv); |
286 | ||
8f6fd83c | 287 | err = rhashtable_walk_init(&priv->ht, &hti, GFP_KERNEL); |
9d098292 PM |
288 | if (err) |
289 | goto schedule; | |
290 | ||
291 | err = rhashtable_walk_start(&hti); | |
292 | if (err && err != -EAGAIN) | |
293 | goto out; | |
294 | ||
295 | while ((he = rhashtable_walk_next(&hti))) { | |
296 | if (IS_ERR(he)) { | |
297 | if (PTR_ERR(he) != -EAGAIN) | |
298 | goto out; | |
299 | continue; | |
300 | } | |
301 | ||
302 | if (!nft_set_elem_expired(&he->ext)) | |
303 | continue; | |
304 | if (nft_set_elem_mark_busy(&he->ext)) | |
305 | continue; | |
306 | ||
307 | gcb = nft_set_gc_batch_check(set, gcb, GFP_ATOMIC); | |
308 | if (gcb == NULL) | |
309 | goto out; | |
5fc6ced9 | 310 | rhashtable_remove_fast(&priv->ht, &he->node, nft_rhash_params); |
3dd0673a | 311 | atomic_dec(&set->nelems); |
9d098292 PM |
312 | nft_set_gc_batch_add(gcb, he); |
313 | } | |
314 | out: | |
315 | rhashtable_walk_stop(&hti); | |
316 | rhashtable_walk_exit(&hti); | |
317 | ||
318 | nft_set_gc_batch_complete(gcb); | |
319 | schedule: | |
320 | queue_delayed_work(system_power_efficient_wq, &priv->gc_work, | |
321 | nft_set_gc_interval(set)); | |
322 | } | |
323 | ||
347b408d PNA |
324 | static unsigned int nft_rhash_privsize(const struct nlattr * const nla[], |
325 | const struct nft_set_desc *desc) | |
20a69341 | 326 | { |
5fc6ced9 | 327 | return sizeof(struct nft_rhash); |
cfe4a9dd TG |
328 | } |
329 | ||
5fc6ced9 PNA |
330 | static int nft_rhash_init(const struct nft_set *set, |
331 | const struct nft_set_desc *desc, | |
332 | const struct nlattr * const tb[]) | |
96518518 | 333 | { |
5fc6ced9 PNA |
334 | struct nft_rhash *priv = nft_set_priv(set); |
335 | struct rhashtable_params params = nft_rhash_params; | |
9d098292 | 336 | int err; |
fa377321 | 337 | |
5fc6ced9 | 338 | params.nelem_hint = desc->size ?: NFT_RHASH_ELEMENT_HINT; |
45d84751 | 339 | params.key_len = set->klen; |
96518518 | 340 | |
9d098292 PM |
341 | err = rhashtable_init(&priv->ht, ¶ms); |
342 | if (err < 0) | |
343 | return err; | |
344 | ||
5fc6ced9 | 345 | INIT_DEFERRABLE_WORK(&priv->gc_work, nft_rhash_gc); |
9d098292 PM |
346 | if (set->flags & NFT_SET_TIMEOUT) |
347 | queue_delayed_work(system_power_efficient_wq, &priv->gc_work, | |
348 | nft_set_gc_interval(set)); | |
349 | return 0; | |
96518518 PM |
350 | } |
351 | ||
5fc6ced9 | 352 | static void nft_rhash_elem_destroy(void *ptr, void *arg) |
96518518 | 353 | { |
68ad546a | 354 | nft_set_elem_destroy(arg, ptr, true); |
6b6f302c | 355 | } |
97defe1e | 356 | |
5fc6ced9 | 357 | static void nft_rhash_destroy(const struct nft_set *set) |
6b6f302c | 358 | { |
5fc6ced9 | 359 | struct nft_rhash *priv = nft_set_priv(set); |
745f5450 | 360 | |
9d098292 | 361 | cancel_delayed_work_sync(&priv->gc_work); |
5fc6ced9 | 362 | rhashtable_free_and_destroy(&priv->ht, nft_rhash_elem_destroy, |
61edafbb | 363 | (void *)set); |
96518518 PM |
364 | } |
365 | ||
5fc6ced9 PNA |
366 | static bool nft_rhash_estimate(const struct nft_set_desc *desc, u32 features, |
367 | struct nft_set_estimate *est) | |
c50b960c | 368 | { |
080ed636 | 369 | if (desc->size) |
5fc6ced9 | 370 | est->size = sizeof(struct nft_rhash) + |
cfe4a9dd | 371 | roundup_pow_of_two(desc->size * 4 / 3) * |
5fc6ced9 PNA |
372 | sizeof(struct nft_rhash_elem *) + |
373 | desc->size * sizeof(struct nft_rhash_elem); | |
080ed636 PNA |
374 | else |
375 | est->size = ~0; | |
c50b960c | 376 | |
55af753c | 377 | est->lookup = NFT_SET_CLASS_O_1; |
0b5a7874 | 378 | est->space = NFT_SET_CLASS_O_N; |
c50b960c PM |
379 | |
380 | return true; | |
381 | } | |
382 | ||
2b664957 | 383 | static struct nft_set_type nft_hash_type; |
5fc6ced9 | 384 | static struct nft_set_ops nft_rhash_ops __read_mostly = { |
2b664957 | 385 | .type = &nft_hash_type, |
5fc6ced9 PNA |
386 | .privsize = nft_rhash_privsize, |
387 | .elemsize = offsetof(struct nft_rhash_elem, ext), | |
388 | .estimate = nft_rhash_estimate, | |
389 | .init = nft_rhash_init, | |
390 | .destroy = nft_rhash_destroy, | |
391 | .insert = nft_rhash_insert, | |
392 | .activate = nft_rhash_activate, | |
393 | .deactivate = nft_rhash_deactivate, | |
394 | .flush = nft_rhash_flush, | |
395 | .remove = nft_rhash_remove, | |
396 | .lookup = nft_rhash_lookup, | |
397 | .update = nft_rhash_update, | |
398 | .walk = nft_rhash_walk, | |
7286ff7f | 399 | .features = NFT_SET_MAP | NFT_SET_OBJECT | NFT_SET_TIMEOUT, |
2b664957 PNA |
400 | }; |
401 | ||
402 | static struct nft_set_type nft_hash_type __read_mostly = { | |
403 | .ops = &nft_rhash_ops, | |
20a69341 | 404 | .owner = THIS_MODULE, |
96518518 PM |
405 | }; |
406 | ||
407 | static int __init nft_hash_module_init(void) | |
408 | { | |
2b664957 | 409 | return nft_register_set(&nft_hash_type); |
96518518 PM |
410 | } |
411 | ||
412 | static void __exit nft_hash_module_exit(void) | |
413 | { | |
2b664957 | 414 | nft_unregister_set(&nft_hash_type); |
96518518 PM |
415 | } |
416 | ||
417 | module_init(nft_hash_module_init); | |
418 | module_exit(nft_hash_module_exit); | |
419 | ||
420 | MODULE_LICENSE("GPL"); | |
421 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); | |
20a69341 | 422 | MODULE_ALIAS_NFT_SET(); |