2 * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net>
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.
8 * Development of this code funded by Astaro AG (http://www.astaro.com/)
11 #include <linux/kernel.h>
12 #include <linux/init.h>
13 #include <linux/module.h>
14 #include <linux/list.h>
15 #include <linux/log2.h>
16 #include <linux/jhash.h>
17 #include <linux/netlink.h>
18 #include <linux/rhashtable.h>
19 #include <linux/netfilter.h>
20 #include <linux/netfilter/nf_tables.h>
21 #include <net/netfilter/nf_tables.h>
23 /* We target a hash table size of 4, element hint is 75% of final size */
24 #define NFT_HASH_ELEMENT_HINT 3
26 struct nft_hash_elem
{
27 struct rhash_head node
;
29 struct nft_data data
[];
32 static bool nft_hash_lookup(const struct nft_set
*set
,
33 const struct nft_data
*key
,
34 struct nft_data
*data
)
36 const struct rhashtable
*priv
= nft_set_priv(set
);
37 const struct nft_hash_elem
*he
;
39 he
= rhashtable_lookup(priv
, key
);
40 if (he
&& set
->flags
& NFT_SET_MAP
)
41 nft_data_copy(data
, he
->data
);
46 static int nft_hash_insert(const struct nft_set
*set
,
47 const struct nft_set_elem
*elem
)
49 struct rhashtable
*priv
= nft_set_priv(set
);
50 struct nft_hash_elem
*he
;
57 if (set
->flags
& NFT_SET_MAP
)
58 size
+= sizeof(he
->data
[0]);
60 he
= kzalloc(size
, GFP_KERNEL
);
64 nft_data_copy(&he
->key
, &elem
->key
);
65 if (set
->flags
& NFT_SET_MAP
)
66 nft_data_copy(he
->data
, &elem
->data
);
68 rhashtable_insert(priv
, &he
->node
, GFP_KERNEL
);
73 static void nft_hash_elem_destroy(const struct nft_set
*set
,
74 struct nft_hash_elem
*he
)
76 nft_data_uninit(&he
->key
, NFT_DATA_VALUE
);
77 if (set
->flags
& NFT_SET_MAP
)
78 nft_data_uninit(he
->data
, set
->dtype
);
82 static void nft_hash_remove(const struct nft_set
*set
,
83 const struct nft_set_elem
*elem
)
85 struct rhashtable
*priv
= nft_set_priv(set
);
86 struct rhash_head
*he
, __rcu
**pprev
;
89 he
= rht_dereference((*pprev
), priv
);
91 rhashtable_remove_pprev(priv
, he
, pprev
, GFP_KERNEL
);
97 static int nft_hash_get(const struct nft_set
*set
, struct nft_set_elem
*elem
)
99 const struct rhashtable
*priv
= nft_set_priv(set
);
100 const struct bucket_table
*tbl
= rht_dereference_rcu(priv
->tbl
, priv
);
101 struct rhash_head __rcu
* const *pprev
;
102 struct nft_hash_elem
*he
;
105 h
= rhashtable_hashfn(priv
, &elem
->key
, set
->klen
);
106 pprev
= &tbl
->buckets
[h
];
107 rht_for_each_entry_rcu(he
, tbl
->buckets
[h
], node
) {
108 if (nft_data_cmp(&he
->key
, &elem
->key
, set
->klen
)) {
109 pprev
= &he
->node
.next
;
113 elem
->cookie
= (void *)pprev
;
115 if (set
->flags
& NFT_SET_MAP
)
116 nft_data_copy(&elem
->data
, he
->data
);
122 static void nft_hash_walk(const struct nft_ctx
*ctx
, const struct nft_set
*set
,
123 struct nft_set_iter
*iter
)
125 const struct rhashtable
*priv
= nft_set_priv(set
);
126 const struct bucket_table
*tbl
;
127 const struct nft_hash_elem
*he
;
128 struct nft_set_elem elem
;
131 tbl
= rht_dereference_rcu(priv
->tbl
, priv
);
132 for (i
= 0; i
< tbl
->size
; i
++) {
133 rht_for_each_entry_rcu(he
, tbl
->buckets
[i
], node
) {
134 if (iter
->count
< iter
->skip
)
137 memcpy(&elem
.key
, &he
->key
, sizeof(elem
.key
));
138 if (set
->flags
& NFT_SET_MAP
)
139 memcpy(&elem
.data
, he
->data
, sizeof(elem
.data
));
142 iter
->err
= iter
->fn(ctx
, set
, iter
, &elem
);
151 static unsigned int nft_hash_privsize(const struct nlattr
* const nla
[])
153 return sizeof(struct rhashtable
);
156 static int lockdep_nfnl_lock_is_held(void)
158 return lockdep_nfnl_is_held(NFNL_SUBSYS_NFTABLES
);
161 static int nft_hash_init(const struct nft_set
*set
,
162 const struct nft_set_desc
*desc
,
163 const struct nlattr
* const tb
[])
165 struct rhashtable
*priv
= nft_set_priv(set
);
166 struct rhashtable_params params
= {
167 .nelem_hint
= desc
->size
? : NFT_HASH_ELEMENT_HINT
,
168 .head_offset
= offsetof(struct nft_hash_elem
, node
),
169 .key_offset
= offsetof(struct nft_hash_elem
, key
),
170 .key_len
= set
->klen
,
172 .grow_decision
= rht_grow_above_75
,
173 .shrink_decision
= rht_shrink_below_30
,
174 .mutex_is_held
= lockdep_nfnl_lock_is_held
,
177 return rhashtable_init(priv
, ¶ms
);
180 static void nft_hash_destroy(const struct nft_set
*set
)
182 const struct rhashtable
*priv
= nft_set_priv(set
);
183 const struct bucket_table
*tbl
;
184 struct nft_hash_elem
*he
, *next
;
187 tbl
= rht_dereference(priv
->tbl
, priv
);
188 for (i
= 0; i
< tbl
->size
; i
++)
189 rht_for_each_entry_safe(he
, next
, tbl
->buckets
[i
], priv
, node
)
190 nft_hash_elem_destroy(set
, he
);
192 rhashtable_destroy(priv
);
195 static bool nft_hash_estimate(const struct nft_set_desc
*desc
, u32 features
,
196 struct nft_set_estimate
*est
)
200 esize
= sizeof(struct nft_hash_elem
);
201 if (features
& NFT_SET_MAP
)
202 esize
+= FIELD_SIZEOF(struct nft_hash_elem
, data
[0]);
205 est
->size
= sizeof(struct rhashtable
) +
206 roundup_pow_of_two(desc
->size
* 4 / 3) *
207 sizeof(struct nft_hash_elem
*) +
210 /* Resizing happens when the load drops below 30% or goes
211 * above 75%. The average of 52.5% load (approximated by 50%)
212 * is used for the size estimation of the hash buckets,
213 * meaning we calculate two buckets per element.
215 est
->size
= esize
+ 2 * sizeof(struct nft_hash_elem
*);
218 est
->class = NFT_SET_CLASS_O_1
;
223 static struct nft_set_ops nft_hash_ops __read_mostly
= {
224 .privsize
= nft_hash_privsize
,
225 .estimate
= nft_hash_estimate
,
226 .init
= nft_hash_init
,
227 .destroy
= nft_hash_destroy
,
229 .insert
= nft_hash_insert
,
230 .remove
= nft_hash_remove
,
231 .lookup
= nft_hash_lookup
,
232 .walk
= nft_hash_walk
,
233 .features
= NFT_SET_MAP
,
234 .owner
= THIS_MODULE
,
237 static int __init
nft_hash_module_init(void)
239 return nft_register_set(&nft_hash_ops
);
242 static void __exit
nft_hash_module_exit(void)
244 nft_unregister_set(&nft_hash_ops
);
247 module_init(nft_hash_module_init
);
248 module_exit(nft_hash_module_exit
);
250 MODULE_LICENSE("GPL");
251 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
252 MODULE_ALIAS_NFT_SET();