aboutsummaryrefslogtreecommitdiffstats
path: root/net/netfilter/nft_hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/netfilter/nft_hash.c')
-rw-r--r--net/netfilter/nft_hash.c128
1 files changed, 118 insertions, 10 deletions
diff --git a/net/netfilter/nft_hash.c b/net/netfilter/nft_hash.c
index c7e1a9d7d46f..3f9d45d3d9b7 100644
--- a/net/netfilter/nft_hash.c
+++ b/net/netfilter/nft_hash.c
@@ -15,6 +15,7 @@
#include <linux/log2.h>
#include <linux/jhash.h>
#include <linux/netlink.h>
+#include <linux/workqueue.h>
#include <linux/rhashtable.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
@@ -25,6 +26,7 @@
struct nft_hash {
struct rhashtable ht;
+ struct delayed_work gc_work;
};
struct nft_hash_elem {
@@ -34,7 +36,7 @@ struct nft_hash_elem {
struct nft_hash_cmp_arg {
const struct nft_set *set;
- const struct nft_data *key;
+ const u32 *key;
u8 genmask;
};
@@ -60,15 +62,16 @@ static inline int nft_hash_cmp(struct rhashtable_compare_arg *arg,
const struct nft_hash_cmp_arg *x = arg->key;
const struct nft_hash_elem *he = ptr;
- if (nft_data_cmp(nft_set_ext_key(&he->ext), x->key, x->set->klen))
+ if (memcmp(nft_set_ext_key(&he->ext), x->key, x->set->klen))
+ return 1;
+ if (nft_set_elem_expired(&he->ext))
return 1;
if (!nft_set_elem_active(&he->ext, x->genmask))
return 1;
return 0;
}
-static bool nft_hash_lookup(const struct nft_set *set,
- const struct nft_data *key,
+static bool nft_hash_lookup(const struct nft_set *set, const u32 *key,
const struct nft_set_ext **ext)
{
struct nft_hash *priv = nft_set_priv(set);
@@ -86,6 +89,42 @@ static bool nft_hash_lookup(const struct nft_set *set,
return !!he;
}
+static bool nft_hash_update(struct nft_set *set, const u32 *key,
+ void *(*new)(struct nft_set *,
+ const struct nft_expr *,
+ struct nft_regs *regs),
+ const struct nft_expr *expr,
+ struct nft_regs *regs,
+ const struct nft_set_ext **ext)
+{
+ struct nft_hash *priv = nft_set_priv(set);
+ struct nft_hash_elem *he;
+ struct nft_hash_cmp_arg arg = {
+ .genmask = NFT_GENMASK_ANY,
+ .set = set,
+ .key = key,
+ };
+
+ he = rhashtable_lookup_fast(&priv->ht, &arg, nft_hash_params);
+ if (he != NULL)
+ goto out;
+
+ he = new(set, expr, regs);
+ if (he == NULL)
+ goto err1;
+ if (rhashtable_lookup_insert_key(&priv->ht, &arg, &he->node,
+ nft_hash_params))
+ goto err2;
+out:
+ *ext = &he->ext;
+ return true;
+
+err2:
+ nft_set_elem_destroy(set, he);
+err1:
+ return false;
+}
+
static int nft_hash_insert(const struct nft_set *set,
const struct nft_set_elem *elem)
{
@@ -94,7 +133,7 @@ static int nft_hash_insert(const struct nft_set *set,
struct nft_hash_cmp_arg arg = {
.genmask = nft_genmask_next(read_pnet(&set->pnet)),
.set = set,
- .key = &elem->key,
+ .key = elem->key.val.data,
};
return rhashtable_lookup_insert_key(&priv->ht, &arg, &he->node,
@@ -107,6 +146,7 @@ static void nft_hash_activate(const struct nft_set *set,
struct nft_hash_elem *he = elem->priv;
nft_set_elem_change_active(set, &he->ext);
+ nft_set_elem_clear_busy(&he->ext);
}
static void *nft_hash_deactivate(const struct nft_set *set,
@@ -117,12 +157,18 @@ static void *nft_hash_deactivate(const struct nft_set *set,
struct nft_hash_cmp_arg arg = {
.genmask = nft_genmask_next(read_pnet(&set->pnet)),
.set = set,
- .key = &elem->key,
+ .key = elem->key.val.data,
};
+ rcu_read_lock();
he = rhashtable_lookup_fast(&priv->ht, &arg, nft_hash_params);
- if (he != NULL)
- nft_set_elem_change_active(set, &he->ext);
+ if (he != NULL) {
+ if (!nft_set_elem_mark_busy(&he->ext))
+ nft_set_elem_change_active(set, &he->ext);
+ else
+ he = NULL;
+ }
+ rcu_read_unlock();
return he;
}
@@ -170,6 +216,8 @@ static void nft_hash_walk(const struct nft_ctx *ctx, const struct nft_set *set,
if (iter->count < iter->skip)
goto cont;
+ if (nft_set_elem_expired(&he->ext))
+ goto cont;
if (!nft_set_elem_active(&he->ext, genmask))
goto cont;
@@ -188,6 +236,55 @@ out:
rhashtable_walk_exit(&hti);
}
+static void nft_hash_gc(struct work_struct *work)
+{
+ struct nft_set *set;
+ struct nft_hash_elem *he;
+ struct nft_hash *priv;
+ struct nft_set_gc_batch *gcb = NULL;
+ struct rhashtable_iter hti;
+ int err;
+
+ priv = container_of(work, struct nft_hash, gc_work.work);
+ set = nft_set_container_of(priv);
+
+ err = rhashtable_walk_init(&priv->ht, &hti);
+ if (err)
+ goto schedule;
+
+ err = rhashtable_walk_start(&hti);
+ if (err && err != -EAGAIN)
+ goto out;
+
+ while ((he = rhashtable_walk_next(&hti))) {
+ if (IS_ERR(he)) {
+ if (PTR_ERR(he) != -EAGAIN)
+ goto out;
+ continue;
+ }
+
+ if (!nft_set_elem_expired(&he->ext))
+ continue;
+ if (nft_set_elem_mark_busy(&he->ext))
+ continue;
+
+ gcb = nft_set_gc_batch_check(set, gcb, GFP_ATOMIC);
+ if (gcb == NULL)
+ goto out;
+ rhashtable_remove_fast(&priv->ht, &he->node, nft_hash_params);
+ atomic_dec(&set->nelems);
+ nft_set_gc_batch_add(gcb, he);
+ }
+out:
+ rhashtable_walk_stop(&hti);
+ rhashtable_walk_exit(&hti);
+
+ nft_set_gc_batch_complete(gcb);
+schedule:
+ queue_delayed_work(system_power_efficient_wq, &priv->gc_work,
+ nft_set_gc_interval(set));
+}
+
static unsigned int nft_hash_privsize(const struct nlattr * const nla[])
{
return sizeof(struct nft_hash);
@@ -207,11 +304,20 @@ static int nft_hash_init(const struct nft_set *set,
{
struct nft_hash *priv = nft_set_priv(set);
struct rhashtable_params params = nft_hash_params;
+ int err;
params.nelem_hint = desc->size ?: NFT_HASH_ELEMENT_HINT;
params.key_len = set->klen;
- return rhashtable_init(&priv->ht, &params);
+ err = rhashtable_init(&priv->ht, &params);
+ if (err < 0)
+ return err;
+
+ INIT_DEFERRABLE_WORK(&priv->gc_work, nft_hash_gc);
+ if (set->flags & NFT_SET_TIMEOUT)
+ queue_delayed_work(system_power_efficient_wq, &priv->gc_work,
+ nft_set_gc_interval(set));
+ return 0;
}
static void nft_hash_elem_destroy(void *ptr, void *arg)
@@ -223,6 +329,7 @@ static void nft_hash_destroy(const struct nft_set *set)
{
struct nft_hash *priv = nft_set_priv(set);
+ cancel_delayed_work_sync(&priv->gc_work);
rhashtable_free_and_destroy(&priv->ht, nft_hash_elem_destroy,
(void *)set);
}
@@ -263,8 +370,9 @@ static struct nft_set_ops nft_hash_ops __read_mostly = {
.deactivate = nft_hash_deactivate,
.remove = nft_hash_remove,
.lookup = nft_hash_lookup,
+ .update = nft_hash_update,
.walk = nft_hash_walk,
- .features = NFT_SET_MAP,
+ .features = NFT_SET_MAP | NFT_SET_TIMEOUT,
.owner = THIS_MODULE,
};