* [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable
@ 2026-09-04 18:53 Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
` (12 more replies)
0 siblings, 13 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
v4: add rhashtable_flush_and_free() helper to rhashtable, then
use it in patch 4.
Sending the entire thing at once as this is now deferred to -next anyway
due to need for new rhashtable function.
It would be possible to stop after 're-add forceadd' and push
the rest in a different merge request in case this is too much for
one nf-next -> net-next batch.
1) Add rhashtable_flush_and_free helper to rhashtable.
2) Add rhashtable boilerplate stubs to ipset. Implement initialization and
destruction of the rhashtable.
3) Add rhltable boilerplate stubs to netfilter ipset.
4) Replace ipset internal hash tables with rhashtable. Enforce an explicit
chain length limit via RHL_MAX_CHAINLEN. Maintain backward compatibility
for old configuration options.
5) Restore forceadd support in ipset by re-implementing removed eviction
logic. Add a helper to locate random key slots for potential element
replacement.
6) Include dynamic memory allocation for CIDR storage in userspace reports.
7) Remove obsolete data_next stubs in netfilter ipset. Retain only the
necessary stubs for specific backend netmask expansion.
8) Remove last region lock usage in ipset. Move locking responsibility to
kadt, uadt, and flush callbacks. Keep IPSET_TEST bitmap types lockless.
9) Remove multi-flag from netfilter ipset. Eliminate legacy logic used for
skipping identical entries and sizing hash buckets.
From Jozsef Kadlecsik.
10) Remove ipset resize functionality. Also from Jozsef.
11) Remove trivial kvfree wrapper in netfilter ipset.
12) Replace rcu_read_lock_bh() with plain rcu_read_lock.
13) Improve ipset lockdep coverage by removing always-true arguments to
rcu_dereference_protected(). Add assertions to verify mutex holding in
specific callpaths.
Florian Westphal (11):
rhashtable: add rhashtable_flush_and_free helper
netfilter: ipset: add rhashtable boilerplate stubs
netfilter: ipset: add rhltable boilerplate stubs
netfilter: ipset: replace internal hash table with rhashtable
netfilter: ipset: re-add forceadd support
netfilter: ipset: also report mem size for cidr storage to userspace
netfilter: ipset: remove obsolete data_next stubs
netfilter: ipset: remove last region lock usage
netfilter: ipset: remove trivial kvfree wrapper
netfilter: ipset: use plain rcu_read_lock
netfilter: ipset: improve lockdep coverage
Jozsef Kadlecsik (2):
netfilter: ipset: remove multi-flag
netfilter: ipset: remove resize completely
include/linux/netfilter/ipset/ip_set.h | 22 +-
include/linux/rhashtable.h | 19 +
lib/rhashtable.c | 127 ++
net/netfilter/ipset/ip_set_bitmap_gen.h | 10 +-
net/netfilter/ipset/ip_set_bitmap_ip.c | 16 +-
net/netfilter/ipset/ip_set_bitmap_ipmac.c | 15 +-
net/netfilter/ipset/ip_set_bitmap_port.c | 16 +-
net/netfilter/ipset/ip_set_core.c | 77 +-
net/netfilter/ipset/ip_set_hash_gen.h | 1618 +++++++-----------
net/netfilter/ipset/ip_set_hash_ip.c | 11 +-
net/netfilter/ipset/ip_set_hash_ipmac.c | 19 +-
net/netfilter/ipset/ip_set_hash_ipmark.c | 12 +-
net/netfilter/ipset/ip_set_hash_ipport.c | 13 +-
net/netfilter/ipset/ip_set_hash_ipportip.c | 13 +-
net/netfilter/ipset/ip_set_hash_ipportnet.c | 13 +-
net/netfilter/ipset/ip_set_hash_mac.c | 9 +-
net/netfilter/ipset/ip_set_hash_net.c | 12 +-
net/netfilter/ipset/ip_set_hash_netiface.c | 38 +-
net/netfilter/ipset/ip_set_hash_netnet.c | 20 +-
net/netfilter/ipset/ip_set_hash_netport.c | 13 +-
net/netfilter/ipset/ip_set_hash_netportnet.c | 21 +-
net/netfilter/ipset/ip_set_list_set.c | 27 +-
22 files changed, 932 insertions(+), 1209 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 19:29 ` Florian Westphal
` (2 more replies)
2026-09-04 18:53 ` [PATCH nf-next v4 02/13] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
` (11 subsequent siblings)
12 siblings, 3 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal, herbert, linux-crypto
Will be used by upcoming ipset rhashtable conversion.
"walk rht with unlink+free" triggers LLM reject pattern:
"possible softirq CPU stall".
"walk rht with unlink+free + cond_resched" triggers
"possibly skipped elements".
Add a helper to detach current hash backend storage from the
rhashtable, then iterate and flush all contained elements.
Cc: herbert@gondor.apana.org.au
Cc: linux-crypto@vger.kernel.org
Link: https://sashiko.dev/#/patchset/20260828152256.8759-1-fw%40strlen.de
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Florian Westphal <fw@strlen.de>
---
Herbert: If you prefer to take this via the crypto tree, please
let me know.
Otherwise, an explicit Ack would be appreciated, so this can
be handled via nf-next. Thanks.
net/ipv6/ila/ could be converted to use this helper too.
include/linux/rhashtable.h | 19 ++++++
lib/rhashtable.c | 127 +++++++++++++++++++++++++++++++++++++
2 files changed, 146 insertions(+)
diff --git a/include/linux/rhashtable.h b/include/linux/rhashtable.h
index 57a2a29bef0e..213e1cb77d45 100644
--- a/include/linux/rhashtable.h
+++ b/include/linux/rhashtable.h
@@ -255,6 +255,10 @@ void rhashtable_free_and_destroy(struct rhashtable *ht,
void *arg);
void rhashtable_destroy(struct rhashtable *ht);
+void rhashtable_flush_and_free(struct rhashtable *ht,
+ void (*free_fn)(void *ptr, void *arg),
+ void *arg);
+
struct rhash_lock_head __rcu **rht_bucket_nested(
const struct bucket_table *tbl, unsigned int hash);
struct rhash_lock_head __rcu **__rht_bucket_nested(
@@ -1335,4 +1339,19 @@ static inline void rhltable_destroy(struct rhltable *hlt)
rhltable_free_and_destroy(hlt, NULL, NULL);
}
+/**
+ * rhltable_flush_and_free - unlink and free all elements in the hash list table
+ * @hlt: the hash list table to destroy
+ * @free_fn: callback to release resources of element
+ * @arg: pointer passed to free_fn
+ *
+ * See documentation for rhashtable_flush_and_free.
+ */
+static inline void rhltable_flush_and_free(struct rhltable *hlt,
+ void (*free_fn)(void *ptr,
+ void *arg),
+ void *arg)
+{
+ rhashtable_flush_and_free(&hlt->ht, free_fn, arg);
+}
#endif /* _LINUX_RHASHTABLE_H */
diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index 6362896e4f09..656c5021d8b2 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -1339,6 +1339,133 @@ void rhashtable_destroy(struct rhashtable *ht)
}
EXPORT_SYMBOL_GPL(rhashtable_destroy);
+struct rht_flush_arg {
+ struct rhashtable *ht;
+ void (*free_fn)(void *ptr, void *arg);
+ void *arg;
+};
+
+static void flush_cb(void *ptr, void *arg)
+{
+ struct rht_flush_arg *fa = arg;
+
+ atomic_dec(&fa->ht->nelems);
+ if (fa->free_fn)
+ fa->free_fn(ptr, fa->arg);
+}
+
+static void rhashtable_flush_one(struct rhashtable *ht, struct rhash_head *obj,
+ void (*free_fn)(void *ptr, void *arg),
+ void *arg)
+{
+ struct rht_flush_arg fa = {
+ .ht = ht,
+ .free_fn = free_fn,
+ .arg = arg,
+ };
+
+ rhashtable_free_one(ht, obj, flush_cb, &fa);
+}
+
+static void rhashtable_flush_chain(struct rhashtable *ht,
+ struct bucket_table *tbl,
+ unsigned int hash,
+ void (*free_fn)(void *ptr, void *arg),
+ void *arg)
+{
+ struct rhash_lock_head __rcu **bkt = rht_bucket_var(tbl, hash);
+ struct rhash_head *pos, *next;
+ unsigned long flags;
+
+ if (!bkt)
+ return;
+
+ flags = rht_lock(tbl, bkt);
+ pos = rht_ptr(bkt, tbl, hash);
+ rht_assign_unlock(tbl, bkt, NULL, flags);
+
+ /* Nothing can reach @pos through @tbl any more: the bucket has
+ * been emptied above, and @tbl itself is unreachable from ht->tbl
+ * (see rhashtable_flush_and_free()). Walk it the same way
+ * rhashtable_free_and_destroy() walks a table it exclusively
+ * owns.
+ */
+ while (!rht_is_a_nulls(pos)) {
+ next = rcu_dereference_raw(pos->next);
+ rhashtable_flush_one(ht, pos, free_fn, arg);
+ pos = next;
+ }
+}
+
+/**
+ * rhashtable_flush_and_free - detach and discard all current elements
+ * @ht: the hash table to flush
+ * @free_fn: callback to release resources of an element, may be %NULL
+ * @arg: pointer passed to free_fn
+ *
+ * Swaps the bucket table backing @ht for a new, empty table.
+ *
+ * The detached table is then walked and every element found is
+ * unlinked, and, if @free_fn is given, handed to it for release.
+ * Note that RCU protected readers may still be accessing the elements.
+ * Releasing of resources must occur in a compatible manner.
+ *
+ * Unlike rhashtable_destroy(), @ht is left fully initialized and may
+ * continue to be used for lookups, insertions, and removals.
+ *
+ * This function may sleep, it cannot be called from atomic context or
+ * RCU read-side critical sections.
+ */
+void rhashtable_flush_and_free(struct rhashtable *ht,
+ void (*free_fn)(void *ptr, void *arg),
+ void *arg)
+{
+ struct bucket_table *tbl, *old_tbl, *last_tbl, *new_tbl;
+ struct rhashtable_walker *walker;
+ unsigned int i;
+
+ new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
+ GFP_KERNEL);
+ if (!new_tbl)
+ new_tbl = bucket_table_alloc(ht, ht->p.min_size,
+ GFP_KERNEL | __GFP_NOFAIL);
+
+ mutex_lock(&ht->mutex);
+
+ /* Splice the new, empty table onto the tail of the live table ... */
+ old_tbl = rht_dereference(ht->tbl, ht);
+ do {
+ last_tbl = rhashtable_last_table(ht, old_tbl);
+ } while (rhashtable_rehash_attach(ht, last_tbl, new_tbl));
+
+ /* ...then publish it as ht->tbl. */
+ rcu_assign_pointer(ht->tbl, new_tbl);
+ mutex_unlock(&ht->mutex);
+
+ tbl = old_tbl;
+ do {
+ struct bucket_table *next_tbl = rcu_dereference_raw(tbl->future_tbl);
+
+ for (i = 0; i < tbl->size; i++) {
+ cond_resched();
+ rhashtable_flush_chain(ht, tbl, i, free_fn, arg);
+ }
+
+ spin_lock(&ht->lock);
+ list_for_each_entry(walker, &tbl->walkers, list)
+ walker->tbl = NULL;
+ /* See rhashtable_rehash_table(): done under ->lock so
+ * rhashtable_walk_stop() can use rcu_head_after_call_rcu()
+ * to decide whether to re-link the walker onto this table.
+ */
+ call_rcu(&tbl->rcu, bucket_table_free_rcu);
+ spin_unlock(&ht->lock);
+
+ tbl = next_tbl;
+ } while (tbl && tbl != new_tbl);
+}
+EXPORT_SYMBOL_GPL(rhashtable_flush_and_free);
+
struct rhash_lock_head __rcu **__rht_bucket_nested(
const struct bucket_table *tbl, unsigned int hash)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 02/13] netfilter: ipset: add rhashtable boilerplate stubs
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 03/13] netfilter: ipset: add rhltable " Florian Westphal
` (10 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
Preparation patch. Adds an rhashtable to the set and initialises
and destroys it. No elements are ever added to this hashtable.
This change is supposed to be devoid of side effects and is
separate to reduce size of the conversion patch.
Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 113 ++++++++++++++++++++++++--
1 file changed, 108 insertions(+), 5 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 80ca523f304b..ec31c9ea83e2 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -8,6 +8,7 @@
#include <linux/rcupdate_wait.h>
#include <linux/jhash.h>
#include <linux/types.h>
+#include <linux/rhashtable.h>
#include <linux/seqlock.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/ipset/ip_set.h>
@@ -217,9 +218,16 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_ahash_destroy
#undef mtype_ext_cleanup
+#undef mtype_rht_elem
+#undef mtype_rht_hashfn
+#undef mtype_rht_obj_hashfn
+#undef mtype_rht_cmpfn
+#undef mtype_rht_params
+
#undef mtype_add_cidr
#undef mtype_del_cidr
#undef mtype_del_cidr_all
+#undef mtype_flush_elem
#undef mtype_ahash_memsize
#undef mtype_flush
#undef mtype_destroy
@@ -265,9 +273,17 @@ static const union nf_inet_addr zeromask = {};
#define mtype_ahash_destroy IPSET_TOKEN(MTYPE, _ahash_destroy)
#define mtype_ext_cleanup IPSET_TOKEN(MTYPE, _ext_cleanup)
+
+#define mtype_rht_elem IPSET_TOKEN(MTYPE, _rht_elem)
+#define mtype_rht_hashfn IPSET_TOKEN(MTYPE, _rht_hashfn)
+#define mtype_rht_obj_hashfn IPSET_TOKEN(MTYPE, _rht_obj_hashfn)
+#define mtype_rht_cmpfn IPSET_TOKEN(MTYPE, _rht_cmpfn)
+#define mtype_rht_params IPSET_TOKEN(MTYPE, _rht_params)
+
#define mtype_add_cidr IPSET_TOKEN(MTYPE, _add_cidr)
#define mtype_del_cidr IPSET_TOKEN(MTYPE, _del_cidr)
#define mtype_del_cidr_all IPSET_TOKEN(MTYPE, _del_cidr_all)
+#define mtype_flush_elem IPSET_TOKEN(MTYPE, _flush_elem)
#define mtype_ahash_memsize IPSET_TOKEN(MTYPE, _ahash_memsize)
#define mtype_flush IPSET_TOKEN(MTYPE, _flush)
#define mtype_destroy IPSET_TOKEN(MTYPE, _destroy)
@@ -300,6 +316,62 @@ static const union nf_inet_addr zeromask = {};
#define htype MTYPE
+/* Per-element rhashtable object. Extensions follow the elem field inline;
+ * allocate as offsetof(struct mtype_rht_elem, elem) + set->dsize bytes.
+ */
+struct mtype_rht_elem {
+ struct rhash_head node;
+ struct rcu_head rcu; /* deferred free after removal */
+
+ /* element data; must be last. extensions follow. */
+ struct mtype_elem elem __aligned(__alignof__(u64));
+};
+
+/* jhash of the lookup key */
+static u32 mtype_rht_hashfn(const void *data, u32 len, u32 seed)
+{
+ BUILD_BUG_ON(HKEY_DATALEN % sizeof(u32) != 0);
+ return jhash2((const u32 *)data, HKEY_DATALEN / sizeof(u32), seed);
+}
+
+/* jhash of an existing element object */
+static u32 mtype_rht_obj_hashfn(const void *obj, u32 len, u32 seed)
+{
+ const struct mtype_rht_elem *e = obj;
+#ifdef IP_SET_HASH_WITH_NETS
+ /* Reset transient flags (e.g. nomatch) before hashing so that the
+ * object hash always equals the lookup-key hash computed by hashfn.
+ */
+ struct mtype_elem tmp;
+ u8 flags = 0;
+
+ memcpy(&tmp, &e->elem, HKEY_DATALEN);
+ mtype_data_reset_flags(&tmp, &flags);
+ return jhash2((const u32 *)&tmp, HKEY_DATALEN / sizeof(u32), seed);
+#else
+ return jhash2((const u32 *)&e->elem, HKEY_DATALEN / sizeof(u32), seed);
+#endif
+}
+
+/* 0 = key matches object (equal), non-zero = not equal */
+static int mtype_rht_cmpfn(struct rhashtable_compare_arg *arg, const void *obj)
+{
+ const struct mtype_rht_elem *e = obj;
+ u32 multi = 0;
+
+ return !mtype_data_equal(&e->elem,
+ (const struct mtype_elem *)arg->key, &multi);
+}
+
+static const struct rhashtable_params mtype_rht_params = {
+ .head_offset = offsetof(struct mtype_rht_elem, node),
+ .key_offset = offsetof(struct mtype_rht_elem, elem),
+ .hashfn = mtype_rht_hashfn,
+ .obj_hashfn = mtype_rht_obj_hashfn,
+ .obj_cmpfn = mtype_rht_cmpfn,
+ .key_len = HKEY_DATALEN,
+};
+
#define HKEY(data, initval, htable_bits) \
({ \
const u32 *__k = (const u32 *)data; \
@@ -313,6 +385,7 @@ static const union nf_inet_addr zeromask = {};
/* The generic hash structure */
struct htype {
struct htable __rcu *table; /* the hash table */
+ struct rhashtable ht; /* the hash table */
struct net_prefixes __rcu *rnets[IPSET_NET_COUNT]; /* cidr prefixes */
struct htable_gc gc; /* gc workqueue */
u32 maxelem; /* max elements in the hash */
@@ -482,6 +555,18 @@ mtype_del_cidr_all(struct ip_set *set, struct htype *h, const struct mtype_elem
#endif
}
+/* Free one element: called by rhashtable_free_and_destroy */
+static void
+mtype_flush_elem(void *ptr, void *arg)
+{
+ struct mtype_rht_elem *e = ptr;
+ struct ip_set *set = arg;
+
+ rcu_read_lock();
+ ip_set_ext_destroy(set, &e->elem);
+ kfree_rcu(e, rcu);
+ rcu_read_unlock();
+}
/* Calculate the actual memory size of the set data */
static size_t
mtype_ahash_memsize(const struct htype *h, const struct htable *t)
@@ -586,6 +671,8 @@ mtype_destroy(struct ip_set *set)
struct htable *t = (__force struct htable *)h->table;
struct list_head *l, *lt;
+ rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
+
list_for_each_safe(l, lt, &t->ad) {
list_del(l);
kfree(l);
@@ -1652,6 +1739,7 @@ static int
IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
struct nlattr *tb[], u32 flags)
{
+ struct rhashtable_params params;
u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM;
#ifdef IP_SET_HASH_WITH_MARKMASK
u32 markmask;
@@ -1668,6 +1756,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
size_t hsize;
struct htype *h;
struct htable *t;
+ int err;
u32 i;
pr_debug("Create set %s with family %s\n",
@@ -1749,15 +1838,27 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
#ifdef IP_SET_PROTO_UNDEF
hsize = sizeof(struct htype);
+ params = mtype_rht_params;
#else
- hsize = set->family == NFPROTO_IPV6 ?
- sizeof(struct IPSET_TOKEN(HTYPE, 6)) :
- sizeof(struct IPSET_TOKEN(HTYPE, 4));
+ if (set->family == NFPROTO_IPV6) {
+ hsize = sizeof(struct IPSET_TOKEN(HTYPE, 6));
+ params = IPSET_TOKEN(HTYPE, 6_rht_params);
+ } else {
+ hsize = sizeof(struct IPSET_TOKEN(HTYPE, 4));
+ params = IPSET_TOKEN(HTYPE, 4_rht_params);
+ }
#endif
h = kzalloc(hsize, GFP_KERNEL);
if (!h)
return -ENOMEM;
+ /* maxsize: maximum bucket table size to expand to */
+ params.max_size = maxelem;
+
+ err = rhashtable_init(&h->ht, ¶ms);
+ if (err)
+ goto free_h;
+
/* Compute htable_bits from the user input parameter hashsize.
* Assume that hashsize == 2^htable_bits,
* otherwise round up to the first 2^n value.
@@ -1765,10 +1866,10 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
hbits = fls(hashsize - 1);
hsize = htable_size(hbits);
if (hsize == 0)
- goto free_h;
+ goto free_rht;
t = ip_set_alloc(hsize);
if (!t)
- goto free_h;
+ goto free_rht;
t->hregion = ip_set_alloc(ahash_sizeof_regions(hbits));
if (!t->hregion)
goto free_t;
@@ -1855,6 +1956,8 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
#endif
free_t:
ip_set_free(t);
+free_rht:
+ rhashtable_destroy(&h->ht);
free_h:
kfree(h);
return -ENOMEM;
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 03/13] netfilter: ipset: add rhltable boilerplate stubs
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 02/13] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 04/13] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
` (9 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
Preparation patch. ip_set_hash_netiface.c (IP_SET_HASH_WITH_MULTI)
may store distinct elements with the same hash key. rhashtable doesn't
support this. For these sets, switch to rhltable which stores identical
hlist heads. We can then walk the list after lookup to find best match.
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 41 +++++++++++++++++++++-
net/netfilter/ipset/ip_set_hash_netiface.c | 24 +++++++++----
2 files changed, 58 insertions(+), 7 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index ec31c9ea83e2..802b8b60f1f2 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -206,6 +206,7 @@ static const union nf_inet_addr zeromask = {};
/* Family dependent templates */
#undef ahash_data
+#undef mtype_key_equal
#undef mtype_data_equal
#undef mtype_do_data_match
#undef mtype_data_set_flags
@@ -257,6 +258,9 @@ static const union nf_inet_addr zeromask = {};
#undef htype
#undef HKEY
+#ifdef IP_SET_HASH_WITH_MULTI
+#define mtype_key_equal IPSET_TOKEN(MTYPE, _key_equal)
+#endif
#define mtype_data_equal IPSET_TOKEN(MTYPE, _data_equal)
#ifdef IP_SET_HASH_WITH_NETS
#define mtype_do_data_match IPSET_TOKEN(MTYPE, _do_data_match)
@@ -320,7 +324,11 @@ static const union nf_inet_addr zeromask = {};
* allocate as offsetof(struct mtype_rht_elem, elem) + set->dsize bytes.
*/
struct mtype_rht_elem {
+#ifdef IP_SET_HASH_WITH_MULTI
+ struct rhlist_head node;
+#else
struct rhash_head node;
+#endif
struct rcu_head rcu; /* deferred free after removal */
/* element data; must be last. extensions follow. */
@@ -357,10 +365,15 @@ static u32 mtype_rht_obj_hashfn(const void *obj, u32 len, u32 seed)
static int mtype_rht_cmpfn(struct rhashtable_compare_arg *arg, const void *obj)
{
const struct mtype_rht_elem *e = obj;
+#ifdef IP_SET_HASH_WITH_MULTI
+ return !mtype_key_equal(&e->elem,
+ (const struct mtype_elem *)arg->key);
+#else
u32 multi = 0;
return !mtype_data_equal(&e->elem,
- (const struct mtype_elem *)arg->key, &multi);
+ (const struct mtype_elem *)arg->key, &multi);
+#endif
}
static const struct rhashtable_params mtype_rht_params = {
@@ -385,7 +398,11 @@ static const struct rhashtable_params mtype_rht_params = {
/* The generic hash structure */
struct htype {
struct htable __rcu *table; /* the hash table */
+#ifdef IP_SET_HASH_WITH_MULTI
+ struct rhltable rhlt; /* the hashlist table */
+#else
struct rhashtable ht; /* the hash table */
+#endif
struct net_prefixes __rcu *rnets[IPSET_NET_COUNT]; /* cidr prefixes */
struct htable_gc gc; /* gc workqueue */
u32 maxelem; /* max elements in the hash */
@@ -404,6 +421,16 @@ struct htype {
struct mtype_elem next; /* temporary storage for uadd */
};
+#ifdef IP_SET_HASH_WITH_MULTI
+#define ipset_hash_nelems(h) atomic_read(&(h)->rhlt.ht.nelems)
+#define ipset_hash_walk_enter(h, iter) rhltable_walk_enter(&(h)->rhlt, (iter))
+#define ipset_hash_remove(h, e) rhltable_remove(&(h)->rhlt, &(e)->node, mtype_rht_params)
+#else
+#define ipset_hash_nelems(h) atomic_read(&(h)->ht.nelems)
+#define ipset_hash_walk_enter(h, iter) rhashtable_walk_enter(&(h)->ht, (iter))
+#define ipset_hash_remove(h, e) rhashtable_remove_fast(&(h)->ht, &(e)->node, mtype_rht_params)
+#endif
+
/* ADD|DEL entries saved during resize */
struct mtype_resize_ad {
struct list_head list;
@@ -671,7 +698,11 @@ mtype_destroy(struct ip_set *set)
struct htable *t = (__force struct htable *)h->table;
struct list_head *l, *lt;
+#ifdef IP_SET_HASH_WITH_MULTI
+ rhltable_free_and_destroy(&h->rhlt, mtype_flush_elem, set);
+#else
rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
+#endif
list_for_each_safe(l, lt, &t->ad) {
list_del(l);
@@ -1855,7 +1886,11 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
/* maxsize: maximum bucket table size to expand to */
params.max_size = maxelem;
+#ifdef IP_SET_HASH_WITH_MULTI
+ err = rhltable_init(&h->rhlt, ¶ms);
+#else
err = rhashtable_init(&h->ht, ¶ms);
+#endif
if (err)
goto free_h;
@@ -1957,7 +1992,11 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
free_t:
ip_set_free(t);
free_rht:
+#ifdef IP_SET_HASH_WITH_MULTI
+ rhltable_destroy(&h->rhlt);
+#else
rhashtable_destroy(&h->ht);
+#endif
free_h:
kfree(h);
return -ENOMEM;
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index b602cc43565d..edadd6307675 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -63,16 +63,22 @@ struct hash_netiface4_elem {
};
/* Common functions */
+static bool
+hash_netiface4_key_equal(const struct hash_netiface4_elem *ip1,
+ const struct hash_netiface4_elem *ip2)
+{
+ return ip1->ip == ip2->ip &&
+ ip1->cidr == ip2->cidr &&
+ ip1->physdev == ip2->physdev;
+}
static bool
hash_netiface4_data_equal(const struct hash_netiface4_elem *ip1,
const struct hash_netiface4_elem *ip2,
u32 *multi)
{
- return ip1->ip == ip2->ip &&
- ip1->cidr == ip2->cidr &&
+ return hash_netiface4_key_equal(ip1, ip2) &&
(++*multi) &&
- ip1->physdev == ip2->physdev &&
(ip1->wildcard ?
strncmp(ip1->iface, ip2->iface, strlen(ip1->iface)) == 0 :
strcmp(ip1->iface, ip2->iface) == 0);
@@ -297,16 +303,22 @@ struct hash_netiface6_elem {
};
/* Common functions */
+static bool
+hash_netiface6_key_equal(const struct hash_netiface6_elem *ip1,
+ const struct hash_netiface6_elem *ip2)
+{
+ return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
+ ip1->cidr == ip2->cidr &&
+ ip1->physdev == ip2->physdev;
+}
static bool
hash_netiface6_data_equal(const struct hash_netiface6_elem *ip1,
const struct hash_netiface6_elem *ip2,
u32 *multi)
{
- return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
- ip1->cidr == ip2->cidr &&
+ return hash_netiface6_key_equal(ip1, ip2) &&
(++*multi) &&
- ip1->physdev == ip2->physdev &&
(ip1->wildcard ?
strncmp(ip1->iface, ip2->iface, strlen(ip1->iface)) == 0 :
strcmp(ip1->iface, ip2->iface) == 0);
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 04/13] netfilter: ipset: replace internal hash table with rhashtable
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (2 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 03/13] netfilter: ipset: add rhltable " Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 05/13] netfilter: ipset: re-add forceadd support Florian Westphal
` (8 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
Replace ipset hash tables with rhashtable.
The IP_SET_HASH_WITH_MULTI hash type is converted to an rhltable:
It stores *lists* of identical hash keys that contain different data.
For this set type (netiface) the hash is the address/netmask and the data
part also stores an interface name.
Matching in such a set needs to first obtain a the best net/mask match,
and then walk the rhlist to search for a matching interface name.
This optionally allows wildcard matching / partial matching of the name
prefix.
The hash table replaced here supported this via the "multi" flag, which
is now obsolete and will be removed later.
The replaced hash table also had a AHASH_MAX_TUNED value of 64: this
provided an implicit upper bound of identical hash keys that could be
inserted. An existing test in ipset.git fails if more than 64 entries can
be added.
As the chain list walk becomes expensive with increasing length, keep
this limit but make it explicit via RHL_MAX_CHAINLEN.
FORCEADD is removed to reduce diff size, it is added back later.
Initval, bucket size and hashtable bits configuration options are
kept for backwards compatibility; they are not used anymore.
Rename mtype_ext_size() to mtype_rht_size() because the function
returns the size of the rhashtable.
automatic rhashtable shrinking remains disabled for now. Quoting
Jozsef: "the hash types of ipset presently can only grow to the max size
but can never shrink. [..] if automatic_shrinking is set to false, then
both the current behaviour is kept and no resizing happens due to running
a gc before listing head/dumping the whole set."
Setting rhashtable .automatic_shrinking to true will not break the
kernel, but it does cause test flakiness in a few ipset test cases:
those check that timed out elements disappear after a few seconds.
With automatic_shrinking enabled, that can cause the dump walker to
hit -EAGAIN during resize which may results in duplicate or omitted
elements.
Followup patch should also mark such dumps as interrupted so userspace
can retry, but this problem is not limited to ipset.
Several left-overs from the old implementation remain:
- resize callback and resize checks
- data_next stubs
- region locking
- multi-flag
These will be removed in a followup series.
Joint work with Jozsef Kadlecisk.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 1426 ++++++++-----------------
1 file changed, 437 insertions(+), 989 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 802b8b60f1f2..7cc2b515e71c 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -5,7 +5,6 @@
#define _IP_SET_HASH_GEN_H
#include <linux/rcupdate.h>
-#include <linux/rcupdate_wait.h>
#include <linux/jhash.h>
#include <linux/types.h>
#include <linux/rhashtable.h>
@@ -18,84 +17,23 @@
#define ipset_dereference_nfnl(p) \
rcu_dereference_protected(p, \
lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
-#define ipset_dereference_set(p, set) \
- rcu_dereference_protected(p, \
- lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET) || \
- lockdep_is_held(&(set)->lock))
#define ipset_dereference_bh_nfnl(p) \
rcu_dereference_bh_check(p, \
lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
-/* Hashing which uses arrays to resolve clashing. The hash table is resized
- * (doubled) when searching becomes too long.
- * Internally jhash is used with the assumption that the size of the
- * stored data is a multiple of sizeof(u32).
- *
- * Readers and resizing
- *
- * Resizing can be triggered by userspace command only, and those
- * are serialized by the nfnl mutex. During resizing the set is
- * read-locked, so the only possible concurrent operations are
- * the kernel side readers. Those must be protected by proper RCU locking.
- */
-
-/* Number of elements to store in an initial array block */
+/* Kept for backward compatibility */
#define AHASH_INIT_SIZE 2
-/* Max number of elements to store in an array block */
#define AHASH_MAX_SIZE (6 * AHASH_INIT_SIZE)
-/* Max muber of elements in the array block when tuned */
-#define AHASH_MAX_TUNED 64
-#define AHASH_MAX(h) ((h)->bucketsize)
-
-/* A hash bucket */
-struct hbucket {
- struct rcu_head rcu; /* for call_rcu */
- /* Which positions are used in the array */
- DECLARE_BITMAP(used, AHASH_MAX_TUNED);
- u8 size; /* size of the array */
- u8 pos; /* position of the first free entry */
- unsigned char value[] /* the array of the values */
- __aligned(__alignof__(u64));
-};
-/* Region size for locking == 2^HTABLE_REGION_BITS */
-#define HTABLE_REGION_BITS 10
-#define ahash_numof_locks(htable_bits) \
- ((htable_bits) < HTABLE_REGION_BITS ? 1 \
- : jhash_size((htable_bits) - HTABLE_REGION_BITS))
-#define ahash_sizeof_regions(htable_bits) \
- (ahash_numof_locks(htable_bits) * sizeof(struct ip_set_region))
-#define ahash_region(n) \
- ((n) / jhash_size(HTABLE_REGION_BITS))
-#define ahash_bucket_start(h, htable_bits) \
- ((htable_bits) < HTABLE_REGION_BITS ? 0 \
- : (h) * jhash_size(HTABLE_REGION_BITS))
-#define ahash_bucket_end(h, htable_bits) \
- ((htable_bits) < HTABLE_REGION_BITS ? jhash_size(htable_bits) \
- : ((h) + 1) * jhash_size(HTABLE_REGION_BITS))
+#ifdef IP_SET_HASH_WITH_MULTI
+#define RHL_MAX_CHAINLEN 64
+#endif
struct htable_gc {
struct delayed_work dwork;
struct ip_set *set; /* Set the gc belongs to */
- spinlock_t lock; /* Lock to exclude gc and resize */
- u32 region; /* Last gc run position */
};
-/* The hash table: the table size stored here in order to make resizing easy */
-struct htable {
- bool resizing; /* Mark ongoing resize */
- atomic_t uref; /* References for dumping and gc */
- u8 htable_bits; /* size of hash table == 2^htable_bits */
- u32 maxelem; /* Maxelem per region */
- struct list_head ad; /* Resize add|del backlist */
- struct ip_set_region *hregion; /* Region locks and ext sizes */
- struct hbucket __rcu *bucket[]; /* hashtable buckets */
-};
-
-#define hbucket(h, i) ((h)->bucket[i])
-#define ext_size(n, dsize) \
- (sizeof(struct hbucket) + (n) * (dsize))
-
#ifndef IPSET_NET_COUNT
#define IPSET_NET_COUNT 1
#endif
@@ -132,23 +70,6 @@ struct net_prefixes {
struct net_prefix nets[] __counted_by(len);
};
-/* Compute the hash table size */
-static size_t
-htable_size(u8 hbits)
-{
- size_t hsize;
-
- /* We must fit both into u32 in jhash and INT_MAX in kvmalloc_node() */
- if (hbits > 31)
- return 0;
- hsize = jhash_size(hbits);
- if ((INT_MAX - sizeof(struct htable)) / sizeof(struct hbucket *)
- < hsize)
- return 0;
-
- return hsize * sizeof(struct hbucket *) + sizeof(struct htable);
-}
-
#ifdef IP_SET_HASH_WITH_NETS
#if IPSET_NET_COUNT > 1
#define __CIDR(cidr, i) (cidr[i])
@@ -217,8 +138,6 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_data_next
#undef mtype_elem
-#undef mtype_ahash_destroy
-#undef mtype_ext_cleanup
#undef mtype_rht_elem
#undef mtype_rht_hashfn
#undef mtype_rht_obj_hashfn
@@ -229,23 +148,19 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_del_cidr
#undef mtype_del_cidr_all
#undef mtype_flush_elem
-#undef mtype_ahash_memsize
#undef mtype_flush
#undef mtype_destroy
#undef mtype_same_set
#undef mtype_kadt
#undef mtype_uadt
-#undef mtype_bucket_size
-#undef mtype_hash_size
#undef mtype_add
+#undef mtype_do_set_exts
#undef mtype_del
#undef mtype_test_cidrs
#undef mtype_test
#undef mtype_uref
-#undef mtype_resize
-#undef mtype_ext_size
-#undef mtype_resize_ad
+#undef mtype_rht_size
#undef mtype_head
#undef mtype_list
#undef mtype_gc_do
@@ -256,7 +171,6 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_data_match
#undef htype
-#undef HKEY
#ifdef IP_SET_HASH_WITH_MULTI
#define mtype_key_equal IPSET_TOKEN(MTYPE, _key_equal)
@@ -275,9 +189,6 @@ static const union nf_inet_addr zeromask = {};
#define mtype_data_next IPSET_TOKEN(MTYPE, _data_next)
#define mtype_elem IPSET_TOKEN(MTYPE, _elem)
-#define mtype_ahash_destroy IPSET_TOKEN(MTYPE, _ahash_destroy)
-#define mtype_ext_cleanup IPSET_TOKEN(MTYPE, _ext_cleanup)
-
#define mtype_rht_elem IPSET_TOKEN(MTYPE, _rht_elem)
#define mtype_rht_hashfn IPSET_TOKEN(MTYPE, _rht_hashfn)
#define mtype_rht_obj_hashfn IPSET_TOKEN(MTYPE, _rht_obj_hashfn)
@@ -288,23 +199,18 @@ static const union nf_inet_addr zeromask = {};
#define mtype_del_cidr IPSET_TOKEN(MTYPE, _del_cidr)
#define mtype_del_cidr_all IPSET_TOKEN(MTYPE, _del_cidr_all)
#define mtype_flush_elem IPSET_TOKEN(MTYPE, _flush_elem)
-#define mtype_ahash_memsize IPSET_TOKEN(MTYPE, _ahash_memsize)
#define mtype_flush IPSET_TOKEN(MTYPE, _flush)
#define mtype_destroy IPSET_TOKEN(MTYPE, _destroy)
#define mtype_same_set IPSET_TOKEN(MTYPE, _same_set)
#define mtype_kadt IPSET_TOKEN(MTYPE, _kadt)
#define mtype_uadt IPSET_TOKEN(MTYPE, _uadt)
-#define mtype_bucket_size IPSET_TOKEN(MTYPE, _bucket_size)
-#define mtype_hash_size IPSET_TOKEN(MTYPE, _hash_size)
#define mtype_add IPSET_TOKEN(MTYPE, _add)
#define mtype_del IPSET_TOKEN(MTYPE, _del)
#define mtype_test_cidrs IPSET_TOKEN(MTYPE, _test_cidrs)
#define mtype_test IPSET_TOKEN(MTYPE, _test)
#define mtype_uref IPSET_TOKEN(MTYPE, _uref)
-#define mtype_resize IPSET_TOKEN(MTYPE, _resize)
-#define mtype_ext_size IPSET_TOKEN(MTYPE, _ext_size)
-#define mtype_resize_ad IPSET_TOKEN(MTYPE, _resize_ad)
+#define mtype_rht_size IPSET_TOKEN(MTYPE, _rht_size)
#define mtype_head IPSET_TOKEN(MTYPE, _head)
#define mtype_list IPSET_TOKEN(MTYPE, _list)
#define mtype_gc_do IPSET_TOKEN(MTYPE, _gc_do)
@@ -366,13 +272,12 @@ static int mtype_rht_cmpfn(struct rhashtable_compare_arg *arg, const void *obj)
{
const struct mtype_rht_elem *e = obj;
#ifdef IP_SET_HASH_WITH_MULTI
- return !mtype_key_equal(&e->elem,
- (const struct mtype_elem *)arg->key);
+ return !mtype_key_equal(&e->elem, (const struct mtype_elem *)arg->key);
#else
u32 multi = 0;
return !mtype_data_equal(&e->elem,
- (const struct mtype_elem *)arg->key, &multi);
+ (const struct mtype_elem *)arg->key, &multi);
#endif
}
@@ -385,19 +290,8 @@ static const struct rhashtable_params mtype_rht_params = {
.key_len = HKEY_DATALEN,
};
-#define HKEY(data, initval, htable_bits) \
-({ \
- const u32 *__k = (const u32 *)data; \
- u32 __l = HKEY_DATALEN / sizeof(u32); \
- \
- BUILD_BUG_ON(HKEY_DATALEN % sizeof(u32) != 0); \
- \
- jhash2(__k, __l, initval) & jhash_mask(htable_bits); \
-})
-
-/* The generic hash structure */
+/* The hash set type */
struct htype {
- struct htable __rcu *table; /* the hash table */
#ifdef IP_SET_HASH_WITH_MULTI
struct rhltable rhlt; /* the hashlist table */
#else
@@ -406,11 +300,12 @@ struct htype {
struct net_prefixes __rcu *rnets[IPSET_NET_COUNT]; /* cidr prefixes */
struct htable_gc gc; /* gc workqueue */
u32 maxelem; /* max elements in the hash */
- u32 initval; /* random jhash init value */
+ u32 initval; /* kept for backward compatibility */
#ifdef IP_SET_HASH_WITH_MARKMASK
u32 markmask; /* markmask value for mark mask to store */
#endif
- u8 bucketsize; /* max elements in an array block */
+ u8 htable_bits; /* kept for backward compatibility */
+ u8 bucketsize; /* kept for backward compatibility */
#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
u8 netmask; /* netmask value for subnets to store */
union nf_inet_addr bitmask; /* stores bitmask */
@@ -421,6 +316,14 @@ struct htype {
struct mtype_elem next; /* temporary storage for uadd */
};
+#define ipset_hash_elem_destroy_free(set, e) do { \
+ typeof(set) __set = (set); \
+ typeof(e) __e = (e); \
+ mtype_del_cidr_all(__set, __set->data, &__e->elem); \
+ ip_set_ext_destroy(__set, &__e->elem); \
+ kfree_rcu(__e, rcu); \
+} while (0)
+
#ifdef IP_SET_HASH_WITH_MULTI
#define ipset_hash_nelems(h) atomic_read(&(h)->rhlt.ht.nelems)
#define ipset_hash_walk_enter(h, iter) rhltable_walk_enter(&(h)->rhlt, (iter))
@@ -431,16 +334,6 @@ struct htype {
#define ipset_hash_remove(h, e) rhashtable_remove_fast(&(h)->ht, &(e)->node, mtype_rht_params)
#endif
-/* ADD|DEL entries saved during resize */
-struct mtype_resize_ad {
- struct list_head list;
- enum ipset_adt ad; /* ADD|DEL element */
- struct mtype_elem d; /* Element value */
- struct ip_set_ext ext; /* Extensions for ADD */
- struct ip_set_ext mext; /* Target extensions for ADD */
- u32 flags; /* Flags for ADD */
-};
-
#ifdef IP_SET_HASH_WITH_NETS
/**
* mtype_add_cidr - Add a CIDR entry to hash table bookkeeping
@@ -590,104 +483,28 @@ mtype_flush_elem(void *ptr, void *arg)
struct ip_set *set = arg;
rcu_read_lock();
- ip_set_ext_destroy(set, &e->elem);
- kfree_rcu(e, rcu);
+ ipset_hash_elem_destroy_free(set, e);
rcu_read_unlock();
}
-/* Calculate the actual memory size of the set data */
-static size_t
-mtype_ahash_memsize(const struct htype *h, const struct htable *t)
-{
- return sizeof(*h) + sizeof(*t) + ahash_sizeof_regions(t->htable_bits);
-}
-
-/* Get the ith element from the array block n */
-#define ahash_data(n, i, dsize) \
- ((struct mtype_elem *)((n)->value + ((i) * (dsize))))
-static void
-mtype_ext_cleanup(struct ip_set *set, struct hbucket *n)
-{
- int i;
- u8 pos = smp_load_acquire(&n->pos);
-
- for (i = 0; i < pos; i++)
- if (test_bit(i, n->used))
- ip_set_ext_destroy(set, ahash_data(n, i, set->dsize));
-}
-
-/* Flush a hash type of set: destroy all elements */
+/**
+ * mtype_flush() - Flush a hash set type by destroying all elements.
+ * @set: Pointer to the ip_set.
+ *
+ * Because other CPUs may concurrently insert new entries into the table
+ * while flush is in progress, there is no guarantee that the table will
+ * be empty upon return.
+ */
static void
mtype_flush(struct ip_set *set)
{
struct htype *h = set->data;
-#ifdef IP_SET_HASH_WITH_NETS
- struct net_prefixes *nets;
-#endif
- struct htable *t;
- struct hbucket *n;
- u32 r, i;
-
- t = ipset_dereference_nfnl(h->table);
- for (r = 0; r < ahash_numof_locks(t->htable_bits); r++) {
- spin_lock_bh(&t->hregion[r].lock);
- for (i = ahash_bucket_start(r, t->htable_bits);
- i < ahash_bucket_end(r, t->htable_bits); i++) {
- n = __ipset_dereference(hbucket(t, i));
- if (!n)
- continue;
- if (set->extensions & IPSET_EXT_DESTROY)
- mtype_ext_cleanup(set, n);
- /* FIXME: use slab cache */
- rcu_assign_pointer(hbucket(t, i), NULL);
- kfree_rcu(n, rcu);
- }
- t->hregion[r].ext_size = 0;
- t->hregion[r].elements = 0;
- spin_unlock_bh(&t->hregion[r].lock);
- }
-#ifdef IP_SET_HASH_WITH_NETS
- for (i = 0; i < IPSET_NET_COUNT; i++) {
- u8 j;
-
- spin_lock_bh(&set->lock);
- nets = ipset_dereference_nfnl(h->rnets[i]);
- write_seqcount_begin(&nets->seq);
- for (j = 0; j < nets->len; j++)
- WRITE_ONCE(nets->nets[j], (struct net_prefix){});
- write_seqcount_end(&nets->seq);
- spin_unlock_bh(&set->lock);
- }
-#endif
-}
-
-/* Destroy the hashtable part of the set */
-static void
-mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy)
-{
-#ifdef IP_SET_HASH_WITH_NETS
- struct htype *h = set->data;
-#endif
- struct hbucket *n;
- u32 i;
- for (i = 0; i < jhash_size(t->htable_bits); i++) {
- n = (__force struct hbucket *)hbucket(t, i);
- if (!n)
- continue;
- if (set->extensions & IPSET_EXT_DESTROY && ext_destroy)
- mtype_ext_cleanup(set, n);
- /* FIXME: use slab cache */
- kfree(n);
- }
-
-#ifdef IP_SET_HASH_WITH_NETS
- if (ext_destroy)
- for (i = 0; i < IPSET_NET_COUNT; i++)
- kfree(rcu_dereference_raw(h->rnets[i]));
+#ifdef IP_SET_HASH_WITH_MULTI
+ rhltable_flush_and_free(&h->rhlt, mtype_flush_elem, set);
+#else
+ rhashtable_flush_and_free(&h->ht, mtype_flush_elem, set);
#endif
- ip_set_free(t->hregion);
- ip_set_free(t);
}
/* Destroy a hash type of set */
@@ -695,8 +512,9 @@ static void
mtype_destroy(struct ip_set *set)
{
struct htype *h = set->data;
- struct htable *t = (__force struct htable *)h->table;
- struct list_head *l, *lt;
+#ifdef IP_SET_HASH_WITH_NETS
+ u32 i;
+#endif
#ifdef IP_SET_HASH_WITH_MULTI
rhltable_free_and_destroy(&h->rhlt, mtype_flush_elem, set);
@@ -704,11 +522,10 @@ mtype_destroy(struct ip_set *set)
rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
#endif
- list_for_each_safe(l, lt, &t->ad) {
- list_del(l);
- kfree(l);
- }
- mtype_ahash_destroy(set, t, true);
+#ifdef IP_SET_HASH_WITH_NETS
+ for (i = 0; i < IPSET_NET_COUNT; i++)
+ kfree(rcu_dereference_raw(h->rnets[i]));
+#endif
kfree(h);
set->data = NULL;
@@ -720,7 +537,6 @@ mtype_same_set(const struct ip_set *a, const struct ip_set *b)
const struct htype *x = a->data;
const struct htype *y = b->data;
- /* Resizing changes htable_bits, so we ignore it */
return x->maxelem == y->maxelem &&
a->timeout == b->timeout &&
#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
@@ -733,69 +549,40 @@ mtype_same_set(const struct ip_set *a, const struct ip_set *b)
}
static void
-mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
+mtype_gc_do(struct ip_set *set)
{
- struct hbucket *n, *tmp;
- struct mtype_elem *data;
- u32 i, j, d;
- size_t dsize = set->dsize;
- u8 pos, htable_bits = t->htable_bits;
-
- spin_lock_bh(&t->hregion[r].lock);
- for (i = ahash_bucket_start(r, htable_bits);
- i < ahash_bucket_end(r, htable_bits); i++) {
- n = __ipset_dereference(hbucket(t, i));
- if (!n)
- continue;
- pos = smp_load_acquire(&n->pos);
- for (j = 0, d = 0; j < pos; j++) {
- if (!test_bit(j, n->used)) {
- d++;
- continue;
- }
- data = ahash_data(n, j, dsize);
- if (!ip_set_timeout_expired(ext_timeout(data, set)))
+ struct htype *h = set->data;
+ struct rhashtable_iter hti;
+ struct mtype_rht_elem *e;
+ unsigned int seen;
+
+ ipset_hash_walk_enter(h, &hti);
+restart:
+ seen = 0;
+ rhashtable_walk_start(&hti);
+
+ while ((e = rhashtable_walk_next(&hti))) {
+ if (IS_ERR(e)) {
+ if (PTR_ERR(e) == -EAGAIN)
continue;
- pr_debug("expired %u/%u\n", i, j);
- clear_bit(j, n->used);
- smp_mb__after_atomic();
- mtype_del_cidr_all(set, h, data);
- t->hregion[r].elements--;
- ip_set_ext_destroy(set, data);
- d++;
+ break;
}
- if (d >= AHASH_INIT_SIZE) {
- if (d >= n->size) {
- t->hregion[r].ext_size -=
- ext_size(n->size, dsize);
- rcu_assign_pointer(hbucket(t, i), NULL);
- kfree_rcu(n, rcu);
- continue;
- }
- tmp = kzalloc(sizeof(*tmp) +
- (n->size - AHASH_INIT_SIZE) * dsize,
- GFP_ATOMIC);
- if (!tmp)
- /* Still try to delete expired elements. */
- continue;
- tmp->size = n->size - AHASH_INIT_SIZE;
- for (j = 0, d = 0; j < pos; j++) {
- if (!test_bit(j, n->used))
- continue;
- data = ahash_data(n, j, dsize);
- memcpy(tmp->value + d * dsize,
- data, dsize);
- set_bit(d, tmp->used);
- d++;
- }
- tmp->pos = d;
- t->hregion[r].ext_size -=
- ext_size(AHASH_INIT_SIZE, dsize);
- rcu_assign_pointer(hbucket(t, i), tmp);
- kfree_rcu(n, rcu);
+
+ if (ip_set_timeout_expired(ext_timeout(&e->elem, set)) &&
+ ipset_hash_remove(h, e) == 0)
+ ipset_hash_elem_destroy_free(set, e);
+
+ if (seen++ > 128 && need_resched()) {
+ /* stop+start can miss entries (we might have unlinked
+ * the element). This is harmless.
+ */
+ rhashtable_walk_stop(&hti);
+ cond_resched();
+ goto restart;
}
}
- spin_unlock_bh(&t->hregion[r].lock);
+ rhashtable_walk_stop(&hti);
+ rhashtable_walk_exit(&hti);
}
static void
@@ -803,40 +590,18 @@ mtype_gc(struct work_struct *work)
{
struct htable_gc *gc;
struct ip_set *set;
- struct htype *h;
- struct htable *t;
- u32 r, numof_locks;
unsigned int next_run;
gc = container_of(work, struct htable_gc, dwork.work);
set = gc->set;
- h = set->data;
- rcu_read_lock_bh();
- t = rcu_dereference_bh(h->table);
- atomic_inc(&t->uref);
- rcu_read_unlock_bh();
- numof_locks = ahash_numof_locks(t->htable_bits);
- r = gc->region++;
- if (r >= numof_locks) {
- r = gc->region = 0;
- }
- next_run = (IPSET_GC_PERIOD(set->timeout) * HZ) / numof_locks;
- if (next_run < HZ/10)
- next_run = HZ/10;
-
- spin_lock_bh(&gc->lock);
- if (!t->resizing)
- mtype_gc_do(set, h, t, r);
- spin_unlock_bh(&gc->lock);
-
- if (atomic_dec_and_test(&t->uref) && t->resizing) {
- pr_debug("Table destroy after resize by expire: %p\n", t);
- mtype_ahash_destroy(set, t, false);
- }
+ next_run = IPSET_GC_PERIOD(set->timeout) * HZ;
+ if (next_run < HZ)
+ next_run = HZ;
- queue_delayed_work(system_power_efficient_wq, &gc->dwork, next_run);
+ mtype_gc_do(set);
+ queue_delayed_work(system_power_efficient_wq, &gc->dwork, next_run);
}
static void
@@ -855,550 +620,270 @@ mtype_cancel_gc(struct ip_set *set)
disable_delayed_work_sync(&h->gc.dwork);
}
-static int
-mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
- struct ip_set_ext *mext, u32 flags);
-static int
-mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
- struct ip_set_ext *mext, u32 flags);
+/* Get the current number of elements and per-element memory in the set */
+static void
+mtype_rht_size(struct ip_set *set, u32 *elements, size_t *ext_size)
+{
+ const struct htype *h = set->data;
+
+ /* Do GC to collect expired elements now so that the reported
+ * element count doesn't include expired elements.
+ */
+ if (SET_WITH_TIMEOUT(set))
+ mtype_gc_do(set);
-/* Resize a hash: create a new hash table with doubling the hashsize
- * and inserting the elements to it. Repeat until we succeed or
- * fail due to memory pressures.
+ *elements = ipset_hash_nelems(h);
+ *ext_size = *elements *
+ (offsetof(struct mtype_rht_elem, elem) + set->dsize);
+}
+
+/* Add an element to a hash and update the internal counters when succeeded,
+ * otherwise report the proper error code.
*/
static int
-mtype_resize(struct ip_set *set, bool retried)
+mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
+ struct ip_set_ext *mext, u32 flags)
{
+ bool flag_exist = flags & IPSET_FLAG_EXIST;
+ struct mtype_rht_elem *e, *old = NULL;
+ const struct mtype_elem *d = value;
struct htype *h = set->data;
- struct htable *t, *orig;
- u8 pos, htable_bits;
- size_t hsize, dsize = set->dsize;
-#ifdef IP_SET_HASH_WITH_NETS
- u8 flags;
- struct mtype_elem *tmp;
-#endif
- struct mtype_elem *data;
- struct mtype_elem *d;
- struct hbucket *n, *m;
- struct list_head *l, *lt;
- struct mtype_resize_ad *x;
- u32 i, j, r, nr, key;
- int ret;
-
+ int ret = 0;
#ifdef IP_SET_HASH_WITH_NETS
- tmp = kmalloc(dsize, GFP_KERNEL);
- if (!tmp)
- return -ENOMEM;
+ int i;
#endif
- orig = ipset_dereference_bh_nfnl(h->table);
- htable_bits = orig->htable_bits;
-retry:
- ret = 0;
- htable_bits++;
- if (!htable_bits)
- goto hbwarn;
- hsize = htable_size(htable_bits);
- if (!hsize)
- goto hbwarn;
- t = ip_set_alloc(hsize);
- if (!t) {
- ret = -ENOMEM;
- goto out;
- }
- t->hregion = ip_set_alloc(ahash_sizeof_regions(htable_bits));
- if (!t->hregion) {
- ip_set_free(t);
- ret = -ENOMEM;
- goto out;
- }
- t->htable_bits = htable_bits;
- t->maxelem = h->maxelem / ahash_numof_locks(htable_bits);
- INIT_LIST_HEAD(&t->ad);
- for (i = 0; i < ahash_numof_locks(htable_bits); i++)
- spin_lock_init(&t->hregion[i].lock);
-
- /* There can't be another parallel resizing,
- * but dumping and kernel side add/del are possible
- */
- orig = ipset_dereference_bh_nfnl(h->table);
- atomic_inc(&orig->uref);
- spin_lock_bh(&h->gc.lock);
- orig->resizing = true;
- spin_unlock_bh(&h->gc.lock);
- pr_debug("attempt to resize set %s from %u to %u, t %p\n",
- set->name, orig->htable_bits, htable_bits, orig);
- for (r = 0; r < ahash_numof_locks(orig->htable_bits); r++) {
- /* Expire may replace a hbucket with another one */
- rcu_read_lock_bh();
- for (i = ahash_bucket_start(r, orig->htable_bits);
- i < ahash_bucket_end(r, orig->htable_bits); i++) {
- n = __ipset_dereference(hbucket(orig, i));
- if (!n)
+ rcu_read_lock_bh();
+#ifdef IP_SET_HASH_WITH_MULTI
+ {
+ struct rhlist_head *tmp, *list;
+ unsigned int seen = 0;
+ u32 multi = 0;
+
+ list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
+ if (!list)
+ goto insert;
+
+ rhl_for_each_entry_rcu(old, tmp, list, node) {
+ if (SET_ELEM_EXPIRED(set, &old->elem)) {
+ if (rhltable_remove(&h->rhlt, &old->node, mtype_rht_params) == 0)
+ ipset_hash_elem_destroy_free(set, old);
continue;
- pos = smp_load_acquire(&n->pos);
- for (j = 0; j < pos; j++) {
- if (!test_bit_acquire(j, n->used))
- continue;
- data = ahash_data(n, j, dsize);
- if (SET_ELEM_EXPIRED(set, data))
- continue;
-#ifdef IP_SET_HASH_WITH_NETS
- /* We have readers running parallel with us,
- * so the live data cannot be modified.
- */
- flags = 0;
- memcpy(tmp, data, dsize);
- data = tmp;
- mtype_data_reset_flags(data, &flags);
-#endif
- key = HKEY(data, h->initval, htable_bits);
- m = __ipset_dereference(hbucket(t, key));
- nr = ahash_region(key);
- if (!m) {
- m = kzalloc(sizeof(*m) +
- AHASH_INIT_SIZE * dsize,
- GFP_ATOMIC);
- if (!m) {
- ret = -ENOMEM;
- goto cleanup;
- }
- m->size = AHASH_INIT_SIZE;
- t->hregion[nr].ext_size +=
- ext_size(AHASH_INIT_SIZE,
- dsize);
- RCU_INIT_POINTER(hbucket(t, key), m);
- } else if (m->pos >= m->size) {
- struct hbucket *ht;
-
- if (m->size >= AHASH_MAX(h)) {
- ret = -EAGAIN;
- } else {
- ht = kzalloc(sizeof(*ht) +
- (m->size + AHASH_INIT_SIZE)
- * dsize,
- GFP_ATOMIC);
- if (!ht)
- ret = -ENOMEM;
- }
- if (ret < 0)
- goto cleanup;
- memcpy(ht, m, sizeof(struct hbucket) +
- m->size * dsize);
- ht->size = m->size + AHASH_INIT_SIZE;
- t->hregion[nr].ext_size +=
- ext_size(AHASH_INIT_SIZE,
- dsize);
- kfree(m);
- m = ht;
- RCU_INIT_POINTER(hbucket(t, key), ht);
- }
- d = ahash_data(m, m->pos, dsize);
- memcpy(d, data, dsize);
- set_bit(m->pos++, m->used);
- t->hregion[nr].elements++;
-#ifdef IP_SET_HASH_WITH_NETS
- mtype_data_reset_flags(d, &flags);
-#endif
}
- }
- rcu_read_unlock_bh();
- }
-
- /* There can't be any other writer. */
- rcu_assign_pointer(h->table, t);
- /* Give time to other readers of the set */
- synchronize_rcu();
+ if (mtype_data_equal(&old->elem, d, &multi))
+ goto insert;
+ ++seen;
+ }
- pr_debug("set %s resized from %u (%p) to %u (%p)\n", set->name,
- orig->htable_bits, orig, t->htable_bits, t);
- /* Add/delete elements processed by the SET target during resize.
- * Kernel-side add cannot trigger a resize and userspace actions
- * are serialized by the mutex.
- */
- list_for_each_safe(l, lt, &orig->ad) {
- x = list_entry(l, struct mtype_resize_ad, list);
- if (x->ad == IPSET_ADD) {
- mtype_add(set, &x->d, &x->ext, &x->mext, x->flags);
- } else {
- mtype_del(set, &x->d, NULL, NULL, 0);
+ if (seen >= RHL_MAX_CHAINLEN) {
+ ret = -IPSET_ERR_HASH_FULL;
+ goto out_rcu_unlock;
}
- list_del(l);
- kfree(l);
+ old = NULL;
}
- /* If there's nobody else using the table, destroy it */
- if (atomic_dec_and_test(&orig->uref)) {
- pr_debug("Table destroy by resize %p\n", orig);
- mtype_ahash_destroy(set, orig, false);
+#else
+ old = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+ if (!old)
+ goto insert;
+ /* simple case 1: element is expired, same as old == NULL. */
+ if (SET_ELEM_EXPIRED(set, &old->elem)) {
+ if (rhashtable_remove_fast(&h->ht, &old->node,
+ mtype_rht_params) == 0)
+ ipset_hash_elem_destroy_free(set, old);
+ old = NULL;
}
-
-out:
-#ifdef IP_SET_HASH_WITH_NETS
- kfree(tmp);
#endif
- return ret;
+insert:
+ /* simple case 2: exists and we are not replacing.
+ * Can't do in-place extension updates, this can race with GC
+ * and mtype_del(): could end up adding new comment to an
+ * element that had kfree_rcu() called on it.
+ */
+ if (old && !flag_exist) {
+ ret = -IPSET_ERR_EXIST;
+ goto out_rcu_unlock;
+ }
-cleanup:
- rcu_read_unlock_bh();
- spin_lock_bh(&h->gc.lock);
- orig->resizing = false;
- spin_unlock_bh(&h->gc.lock);
- /* Make sure parallel readers see that orig->resizing is false
- * before we decrement uref */
- synchronize_rcu();
- atomic_dec(&orig->uref);
- mtype_ahash_destroy(set, t, false);
- if (ret == -EAGAIN)
- goto retry;
+ if (!old && ipset_hash_nelems(h) >= h->maxelem) {
+ if (net_ratelimit())
+ pr_warn("Set %s is full, maxelem %u reached\n",
+ set->name, h->maxelem);
+ ret = -IPSET_ERR_HASH_FULL;
+ goto out_rcu_unlock;
+ }
- /* Cleanup the backlog of ADD/DEL elements */
- spin_lock_bh(&set->lock);
- list_for_each_safe(l, lt, &orig->ad) {
- list_del(l);
- kfree(l);
+ e = kzalloc(offsetof(struct mtype_rht_elem, elem) + set->dsize,
+ GFP_ATOMIC);
+ if (!e) {
+ ret = -ENOMEM;
+ goto out_rcu_unlock;
}
- spin_unlock_bh(&set->lock);
- goto out;
-hbwarn:
- /* In case we have plenty of memory :-) */
- pr_warn("Cannot increase the hashsize of set %s further\n", set->name);
- ret = -IPSET_ERR_HASH_FULL;
- goto out;
-}
+ memcpy(&e->elem, d, sizeof(struct mtype_elem));
-/* Get the current number of elements and ext_size in the set */
-static void
-mtype_ext_size(struct ip_set *set, u32 *elements, size_t *ext_size)
-{
- struct htype *h = set->data;
- const struct htable *t;
- struct hbucket *n;
- struct mtype_elem *data;
- u32 i, j, r;
- u8 pos;
-
- t = rcu_dereference_bh(h->table);
- for (r = 0; r < ahash_numof_locks(t->htable_bits); r++) {
- for (i = ahash_bucket_start(r, t->htable_bits);
- i < ahash_bucket_end(r, t->htable_bits); i++) {
- n = rcu_dereference_bh(hbucket(t, i));
- if (!n)
- continue;
- pos = smp_load_acquire(&n->pos);
- for (j = 0; j < pos; j++) {
- if (!test_bit_acquire(j, n->used))
- continue;
- data = ahash_data(n, j, set->dsize);
- if (!SET_ELEM_EXPIRED(set, data))
- (*elements)++;
+#ifdef IP_SET_HASH_WITH_NETS
+ for (i = 0; i < IPSET_NET_COUNT; i++) {
+ ret = mtype_add_cidr(set, h, DCIDR_GET(d->cidr, i), i);
+ if (ret) {
+ while (i > 0) {
+ --i;
+ mtype_del_cidr(set, h, DCIDR_GET(d->cidr, i), i);
}
+ kfree(e);
+ goto out_rcu_unlock;
}
- *ext_size += t->hregion[r].ext_size;
}
-}
-/* Add an element to a hash and update the internal counters when succeeded,
- * otherwise report the proper error code.
- */
-static int
-mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
- struct ip_set_ext *mext, u32 flags)
-{
- struct htype *h = set->data;
- struct htable *t;
- const struct mtype_elem *d = value;
- struct mtype_elem *data;
- struct hbucket *n, *old = ERR_PTR(-ENOENT);
- int i, j = -1, ret;
- bool flag_exist = flags & IPSET_FLAG_EXIST;
- bool deleted = false, forceadd = false, reuse = false;
- u32 r, key, multi = 0, elements, maxelem;
- u8 npos = 0;
+ mtype_data_set_flags(&e->elem, flags);
+#endif
+ if (SET_WITH_COUNTER(set))
+ ip_set_init_counter(ext_counter(&e->elem, set), ext);
+ if (SET_WITH_COMMENT(set) && !ext->target)
+ ip_set_init_comment(set, ext_comment(&e->elem, set), ext);
+ if (SET_WITH_SKBINFO(set))
+ ip_set_init_skbinfo(ext_skbinfo(&e->elem, set), ext);
+ if (SET_WITH_TIMEOUT(set))
+ ip_set_timeout_set(ext_timeout(&e->elem, set), ext->timeout);
- rcu_read_lock_bh();
- t = rcu_dereference_bh(h->table);
- key = HKEY(value, h->initval, t->htable_bits);
- r = ahash_region(key);
- atomic_inc(&t->uref);
- rcu_read_unlock_bh();
- elements = t->hregion[r].elements;
- maxelem = t->maxelem;
- if (elements >= maxelem) {
- u32 e;
- if (SET_WITH_TIMEOUT(set))
- mtype_gc_do(set, h, t, r);
- maxelem = h->maxelem;
- elements = 0;
- for (e = 0; e < ahash_numof_locks(t->htable_bits); e++)
- elements += t->hregion[e].elements;
- if (elements >= maxelem && SET_WITH_FORCEADD(set))
- forceadd = true;
- }
+#ifdef IP_SET_HASH_WITH_MULTI
+ spin_lock_bh(&set->lock);
+ ret = rhltable_insert_key(&h->rhlt, &e->elem, &e->node, mtype_rht_params);
+ if (ret == 0) {
+ struct rhlist_head *list;
+
+ /* rhltable_remove can fail here in case of parallel GC: harmless. */
+ if (old && rhltable_remove(&h->rhlt, &old->node, mtype_rht_params) != 0)
+ old = NULL;
+
+ list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
+ if (list) {
+ const struct mtype_rht_elem *dup;
+ struct rhlist_head *tmp;
+ u32 multi = 0;
+
+ rhl_for_each_entry_rcu(dup, tmp, list, node) {
+ if (dup == e || !mtype_data_equal(&dup->elem, d, &multi))
+ continue;
- spin_lock_bh(&t->hregion[r].lock);
- n = rcu_dereference_bh(hbucket(t, key));
- if (!n) {
- if (forceadd || elements >= maxelem)
- goto set_full;
- old = NULL;
- n = kzalloc(sizeof(*n) + AHASH_INIT_SIZE * set->dsize,
- GFP_ATOMIC);
- if (!n) {
- ret = -ENOMEM;
- goto unlock;
- }
- n->size = AHASH_INIT_SIZE;
- t->hregion[r].ext_size +=
- ext_size(AHASH_INIT_SIZE, set->dsize);
- goto copy_elem;
- }
- npos = smp_load_acquire(&n->pos);
- for (i = 0; i < npos; i++) {
- if (!test_bit(i, n->used)) {
- /* Reuse first deleted entry */
- if (j == -1) {
- deleted = reuse = true;
- j = i;
+ /* check for duplicate key insertion: unlikely,
+ * but several CPUs can race to insert same key
+ * with same interface. We inserted e, but dup
+ * has same content: remove e again.
+ */
+ if (rhltable_remove(&h->rhlt, &e->node, mtype_rht_params) == 0) {
+ spin_unlock_bh(&set->lock);
+ if (old) {
+ ipset_hash_elem_destroy_free(set, old);
+ old = NULL;
+ }
+ ret = -EEXIST;
+ goto out_duplicate;
+ }
}
- continue;
- }
- data = ahash_data(n, i, set->dsize);
- if (mtype_data_equal(data, d, &multi)) {
- if (flag_exist || SET_ELEM_EXPIRED(set, data)) {
- /* Just the extensions could be overwritten */
- j = i;
- goto overwrite_extensions;
- }
- ret = -IPSET_ERR_EXIST;
- goto unlock;
- }
- /* Reuse first timed out entry */
- if (SET_ELEM_EXPIRED(set, data) && j == -1) {
- j = i;
- reuse = true;
}
}
- if (reuse || forceadd) {
- if (j == -1)
- j = 0;
- data = ahash_data(n, j, set->dsize);
- if (!deleted) {
- mtype_del_cidr_all(set, h, data);
- ip_set_ext_destroy(set, data);
- t->hregion[r].elements--;
- }
- goto copy_data;
+ spin_unlock_bh(&set->lock);
+out_duplicate:
+#else
+ if (old)
+ ret = rhashtable_replace_fast(&h->ht, &old->node, &e->node, mtype_rht_params);
+ else
+ ret = rhashtable_lookup_insert_key(&h->ht, d, &e->node, mtype_rht_params);
+
+ if (ret == -ENOENT && old) {
+ /* replace failed: maybe GC or concurrent mtype_del()
+ * reaped old element right now.
+ *
+ * Try once more with plain insert.
+ */
+ old = NULL;
+ ret = rhashtable_lookup_insert_key(&h->ht, d, &e->node, mtype_rht_params);
}
- if (elements >= maxelem)
- goto set_full;
- /* Create a new slot */
- if (npos >= n->size) {
-#ifdef IP_SET_HASH_WITH_MULTI
- if (h->bucketsize >= AHASH_MAX_TUNED)
- goto set_full;
- else if (h->bucketsize <= multi)
- h->bucketsize += AHASH_INIT_SIZE;
#endif
- if (n->size >= AHASH_MAX(h)) {
- /* Trigger rehashing */
- mtype_data_next(&h->next, d);
- ret = -EAGAIN;
- goto resize;
- }
- old = n;
- n = kzalloc(sizeof(*n) +
- (old->size + AHASH_INIT_SIZE) * set->dsize,
- GFP_ATOMIC);
- if (!n) {
- ret = -ENOMEM;
- goto unlock;
+ if (ret) {
+ ip_set_ext_destroy(set, &e->elem);
+ kfree_rcu(e, rcu);
+ /* error: Undo mtype_add_cidr(). */
+ mtype_del_cidr_all(set, h, d);
+ } else if (old) {
+ /* Update the new elements counters with the replaced one,
+ * unless userspace gave specific start values. old has
+ * been unlinked from table, but is still visible to other
+ * CPUs, so this is best-effort only.
+ */
+ if (ext->packets == ULLONG_MAX &&
+ SET_WITH_COUNTER(set) &&
+ !SET_ELEM_EXPIRED(set, &old->elem)) {
+ const struct ip_set_counter *old_c = ext_counter(&old->elem, set);
+ struct ip_set_counter *new_c = ext_counter(&e->elem, set);
+
+ atomic64_add(atomic64_read(&old_c->bytes), &new_c->bytes);
+ atomic64_add(atomic64_read(&old_c->packets), &new_c->packets);
}
- memcpy(n, old, sizeof(struct hbucket) +
- old->size * set->dsize);
- n->size = old->size + AHASH_INIT_SIZE;
- t->hregion[r].ext_size +=
- ext_size(AHASH_INIT_SIZE, set->dsize);
+ ip_set_ext_destroy(set, &old->elem);
+ kfree_rcu(old, rcu);
+ /* successful replace: Undo mtype_add_cidr(). */
+ mtype_del_cidr_all(set, h, d);
}
-copy_elem:
- j = npos++;
- data = ahash_data(n, j, set->dsize);
-copy_data:
- t->hregion[r].elements++;
-#ifdef IP_SET_HASH_WITH_NETS
- for (i = 0; i < IPSET_NET_COUNT; i++)
- mtype_add_cidr(set, h, DCIDR_GET(d->cidr, i), i);
-#endif
- memcpy(data, d, sizeof(struct mtype_elem));
-overwrite_extensions:
-#ifdef IP_SET_HASH_WITH_NETS
- mtype_data_set_flags(data, flags);
-#endif
- if (SET_WITH_COUNTER(set))
- ip_set_init_counter(ext_counter(data, set), ext);
- if (SET_WITH_COMMENT(set) && !ext->target)
- ip_set_init_comment(set, ext_comment(data, set), ext);
- if (SET_WITH_SKBINFO(set))
- ip_set_init_skbinfo(ext_skbinfo(data, set), ext);
- /* Must come last for the case when timed out entry is reused */
- if (SET_WITH_TIMEOUT(set))
- ip_set_timeout_set(ext_timeout(data, set), ext->timeout);
- smp_mb__before_atomic();
- /* Ensure all data writes are visible before updating position */
- smp_store_release(&n->pos, npos);
- set_bit(j, n->used);
- if (old != ERR_PTR(-ENOENT)) {
- rcu_assign_pointer(hbucket(t, key), n);
- if (old)
- kfree_rcu(old, rcu);
- }
- ret = 0;
-resize:
- spin_unlock_bh(&t->hregion[r].lock);
- if (t->resizing && ext && ext->target) {
- /* Resize is in process and kernel side add, save values */
- struct mtype_resize_ad *x;
-
- x = kzalloc_obj(struct mtype_resize_ad, GFP_ATOMIC);
- if (!x)
- /* Don't bother */
- goto out;
- x->ad = IPSET_ADD;
- memcpy(&x->d, value, sizeof(struct mtype_elem));
- memcpy(&x->ext, ext, sizeof(struct ip_set_ext));
- memcpy(&x->mext, mext, sizeof(struct ip_set_ext));
- x->flags = flags;
- spin_lock_bh(&set->lock);
- list_add_tail(&x->list, &t->ad);
- spin_unlock_bh(&set->lock);
- }
- goto out;
+ if (ret == -EEXIST)
+ ret = flag_exist ? 0 : -IPSET_ERR_EXIST;
-set_full:
- if (net_ratelimit())
- pr_warn("Set %s is full, maxelem %u reached\n",
- set->name, maxelem);
- ret = -IPSET_ERR_HASH_FULL;
-unlock:
- spin_unlock_bh(&t->hregion[r].lock);
-out:
- if (atomic_dec_and_test(&t->uref) && t->resizing) {
- pr_debug("Table destroy after resize by add: %p\n", t);
- mtype_ahash_destroy(set, t, false);
- }
+ if (0) /* to be removed */
+ mtype_data_next(&h->next, d);
+out_rcu_unlock:
+ rcu_read_unlock_bh();
return ret;
}
-/* Delete an element from the hash and free up space if possible.
- */
+/* Delete an element from the hash */
static int
mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
struct ip_set_ext *mext, u32 flags)
{
struct htype *h = set->data;
- struct htable *t;
const struct mtype_elem *d = value;
- struct mtype_elem *data;
- struct hbucket *n;
- struct mtype_resize_ad *x = NULL;
- int i, j, k, r, ret = -IPSET_ERR_EXIST;
- u32 key, multi = 0;
- size_t dsize = set->dsize;
- u8 pos;
-
- /* Userspace add and resize is excluded by the mutex.
- * Kernespace add does not trigger resize.
- */
- rcu_read_lock_bh();
- t = rcu_dereference_bh(h->table);
- key = HKEY(value, h->initval, t->htable_bits);
- r = ahash_region(key);
- atomic_inc(&t->uref);
- rcu_read_unlock_bh();
-
- spin_lock_bh(&t->hregion[r].lock);
- n = rcu_dereference_bh(hbucket(t, key));
- if (!n)
- goto out;
- pos = smp_load_acquire(&n->pos);
- for (i = 0, k = 0; i < pos; i++) {
- if (!test_bit(i, n->used)) {
- k++;
- continue;
- }
- data = ahash_data(n, i, dsize);
- if (!mtype_data_equal(data, d, &multi))
- continue;
- if (SET_ELEM_EXPIRED(set, data))
- goto out;
+ struct mtype_rht_elem *e;
+ int ret = -IPSET_ERR_EXIST;
- ret = 0;
- clear_bit(i, n->used);
- smp_mb__after_atomic();
- if (i + 1 == pos)
- smp_store_release(&n->pos, --pos);
- t->hregion[r].elements--;
- mtype_del_cidr_all(set, h, d);
- ip_set_ext_destroy(set, data);
-
- if (t->resizing && ext && ext->target) {
- /* Resize is in process and kernel side del,
- * save values
- */
- x = kzalloc_obj(struct mtype_resize_ad, GFP_ATOMIC);
- if (x) {
- x->ad = IPSET_DEL;
- memcpy(&x->d, value,
- sizeof(struct mtype_elem));
- x->flags = flags;
- }
- }
- for (; i < pos; i++) {
- if (!test_bit(i, n->used))
- k++;
- }
- if (k == pos) {
- t->hregion[r].ext_size -= ext_size(n->size, dsize);
- rcu_assign_pointer(hbucket(t, key), NULL);
- kfree_rcu(n, rcu);
- } else if (k >= AHASH_INIT_SIZE) {
- struct hbucket *tmp = kzalloc(sizeof(*tmp) +
- (n->size - AHASH_INIT_SIZE) * dsize,
- GFP_ATOMIC);
- if (!tmp)
- goto out;
- tmp->size = n->size - AHASH_INIT_SIZE;
- for (j = 0, k = 0; j < pos; j++) {
- if (!test_bit(j, n->used))
- continue;
- data = ahash_data(n, j, dsize);
- memcpy(tmp->value + k * dsize, data, dsize);
- set_bit(k, tmp->used);
- k++;
- }
- tmp->pos = k;
- t->hregion[r].ext_size -=
- ext_size(AHASH_INIT_SIZE, dsize);
- rcu_assign_pointer(hbucket(t, key), tmp);
- kfree_rcu(n, rcu);
+ rcu_read_lock_bh();
+#ifdef IP_SET_HASH_WITH_MULTI
+ {
+ struct rhlist_head *tmp, *list;
+ u32 multi = 0;
+
+ list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
+ if (!list)
+ goto out_unlock;
+ rhl_for_each_entry_rcu(e, tmp, list, node) {
+ if (!mtype_data_equal(&e->elem, d, &multi))
+ continue;
+ if (SET_ELEM_EXPIRED(set, &e->elem))
+ goto out_unlock;
+ /* fails if GC evicts this entry right now. */
+ ret = rhltable_remove(&h->rhlt, &e->node, mtype_rht_params);
+ break;
}
- goto out;
- }
-out:
- spin_unlock_bh(&t->hregion[r].lock);
- if (x) {
- spin_lock_bh(&set->lock);
- list_add(&x->list, &t->ad);
- spin_unlock_bh(&set->lock);
+ /* either no entry found or race with GC */
+ if (ret)
+ goto out_unlock;
}
- if (atomic_dec_and_test(&t->uref) && t->resizing) {
- pr_debug("Table destroy after resize by del: %p\n", t);
- mtype_ahash_destroy(set, t, false);
- }
- return ret;
+#else
+ e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+ if (!e || SET_ELEM_EXPIRED(set, &e->elem))
+ goto out_unlock;
+ ret = rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params);
+ if (ret)
+ goto out_unlock;
+#endif
+ mtype_del_cidr_all(set, h, d);
+ ip_set_ext_destroy(set, &e->elem);
+ kfree_rcu(e, rcu);
+out_unlock:
+ rcu_read_unlock_bh();
+ return ret ? -IPSET_ERR_EXIST : 0;
}
static int
@@ -1421,30 +906,26 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
struct ip_set_ext *mext, u32 flags)
{
struct htype *h = set->data;
- struct htable *t = rcu_dereference_bh(h->table);
struct net_prefixes *nets0;
- struct hbucket *n;
- struct mtype_elem *data;
+ struct mtype_rht_elem *e;
#if IPSET_NET_COUNT == 2
struct net_prefixes *nets1;
struct mtype_elem orig = *d;
unsigned int seq1;
- int ret, i, j, k;
+ int ret, j, k;
#else
- int ret, i, j;
+ int ret, j;
#endif
unsigned int seq0;
- u32 key, multi;
- u8 pos;
+ u32 multi;
pr_debug("test by nets\n");
- rcu_read_lock_bh();
retry:
multi = 0;
- nets0 = rcu_dereference_bh(h->rnets[0]);
+ nets0 = ipset_dereference_bh_nfnl(h->rnets[0]);
seq0 = read_seqcount_begin(&nets0->seq);
#if IPSET_NET_COUNT == 2
- nets1 = rcu_dereference_bh(h->rnets[1]);
+ nets1 = ipset_dereference_bh_nfnl(h->rnets[1]);
seq1 = read_seqcount_begin(&nets1->seq);
#endif
for (j = 0; j < nets0->len && !multi; j++) {
@@ -1464,38 +945,48 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
#else
mtype_data_netmask(d, p0.cidr);
#endif
- key = HKEY(d, h->initval, t->htable_bits);
- n = rcu_dereference_bh(hbucket(t, key));
- if (!n)
+#ifdef IP_SET_HASH_WITH_MULTI
+ {
+ struct rhlist_head *tmp, *list;
+
+ list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
+ if (!list)
continue;
- pos = smp_load_acquire(&n->pos);
- for (i = 0; i < pos; i++) {
- if (!test_bit_acquire(i, n->used))
- continue;
- data = ahash_data(n, i, set->dsize);
- if (!mtype_data_equal(data, d, &multi))
+
+ rhl_for_each_entry_rcu(e, tmp, list, node) {
+ if (!SET_ELEM_EXPIRED(set, &e->elem))
+ multi = true;
+ if (!mtype_data_equal(&e->elem, d, &multi))
continue;
- ret = mtype_data_match(data, ext, mext, set, flags);
+ ret = mtype_data_match(&e->elem, ext, mext, set, flags);
+ if (ret)
+ goto check_retry;
+ multi = false;
+ }
+ }
+#else
+ e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+ if (e) {
+ if (!SET_ELEM_EXPIRED(set, &e->elem))
+ multi = true;
+ ret = mtype_data_match(&e->elem, ext, mext, set, flags);
if (ret != 0)
- goto unlock;
-#ifdef IP_SET_HASH_WITH_MULTI
- /* No match, reset multiple match flag */
- multi = 0;
-#endif
+ goto check_retry;
+ multi = false;
}
+#endif
#if IPSET_NET_COUNT == 2
}
#endif
}
ret = 0;
-unlock:
+check_retry:
if (read_seqcount_retry(&nets0->seq, seq0))
goto retry;
#if IPSET_NET_COUNT == 2
if (read_seqcount_retry(&nets1->seq, seq1))
goto retry;
#endif
- rcu_read_unlock_bh();
return ret;
}
#endif
@@ -1506,16 +997,14 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
struct ip_set_ext *mext, u32 flags)
{
struct htype *h = set->data;
- struct htable *t;
struct mtype_elem *d = value;
- struct hbucket *n;
- struct mtype_elem *data;
- int i, ret = 0;
- u32 key, multi = 0;
- u8 pos;
+ struct mtype_rht_elem *e;
+ int ret = 0;
+#ifdef IP_SET_HASH_WITH_NETS
+ int i;
+#endif
rcu_read_lock_bh();
- t = rcu_dereference_bh(h->table);
#ifdef IP_SET_HASH_WITH_NETS
/* If we test an IP address and not a network address,
* try all possible network sizes
@@ -1529,68 +1018,53 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
}
#endif
- key = HKEY(d, h->initval, t->htable_bits);
- n = rcu_dereference_bh(hbucket(t, key));
- if (!n) {
- ret = 0;
- goto out;
- }
- pos = smp_load_acquire(&n->pos);
- for (i = 0; i < pos; i++) {
- if (!test_bit_acquire(i, n->used))
- continue;
- data = ahash_data(n, i, set->dsize);
- if (!mtype_data_equal(data, d, &multi))
- continue;
- ret = mtype_data_match(data, ext, mext, set, flags);
- if (ret != 0)
+#ifdef IP_SET_HASH_WITH_MULTI
+ {
+ struct rhlist_head *tmp, *list;
+ u32 multi = 0;
+
+ list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
+ if (!list)
goto out;
+
+ rhl_for_each_entry_rcu(e, tmp, list, node) {
+ if (!mtype_data_equal(&e->elem, d, &multi))
+ continue;
+ ret = mtype_data_match(&e->elem, ext, mext, set, flags);
+ if (ret)
+ goto out;
+ }
}
+#else
+ e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+ if (!e)
+ goto out;
+
+ ret = mtype_data_match(&e->elem, ext, mext, set, flags);
+#endif
out:
rcu_read_unlock_bh();
return ret;
}
-static u32 mtype_hash_size(const struct htype *h)
-{
- const struct htable *t;
- u8 htable_bits;
-
- rcu_read_lock();
- t = rcu_dereference(h->table);
- htable_bits = t->htable_bits;
- rcu_read_unlock();
-
- return jhash_size(htable_bits);
-}
-
-static u32 mtype_bucket_size(const struct htype *h)
-{
- return h->bucketsize;
-}
-
/* Reply a HEADER request: fill out the header part of the set */
static int
mtype_head(struct ip_set *set, struct sk_buff *skb)
{
struct htype *h = set->data;
- const struct htable *t;
struct nlattr *nested;
size_t memsize;
u32 elements = 0;
size_t ext_size = 0;
- rcu_read_lock_bh();
- t = rcu_dereference_bh(h->table);
- mtype_ext_size(set, &elements, &ext_size);
- memsize = mtype_ahash_memsize(h, t) + ext_size + atomic64_read(&set->ext_size);
- rcu_read_unlock_bh();
+ mtype_rht_size(set, &elements, &ext_size);
+ memsize = sizeof(*h) + ext_size + atomic64_read(&set->ext_size);
nested = nla_nest_start(skb, IPSET_ATTR_DATA);
if (!nested)
goto nla_put_failure;
- if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE, htonl(mtype_hash_size(h))))
+ if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE, htonl(jhash_size(h->htable_bits))))
goto nla_put_failure;
if (nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)))
goto nla_put_failure;
@@ -1616,7 +1090,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
goto nla_put_failure;
#endif
if (set->flags & IPSET_CREATE_FLAG_BUCKETSIZE) {
- if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, mtype_bucket_size(h)))
+ if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, h->bucketsize))
goto nla_put_failure;
if (nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
goto nla_put_failure;
@@ -1634,25 +1108,23 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
return -EMSGSIZE;
}
-/* Make possible to run dumping parallel with resizing */
+/* Manage the rhashtable_iter lifetime for dump operations */
static void
mtype_uref(struct ip_set *set, struct netlink_callback *cb, bool start)
{
struct htype *h = set->data;
- struct htable *t;
+ struct rhashtable_iter *hti;
if (start) {
- rcu_read_lock_bh();
- t = ipset_dereference_bh_nfnl(h->table);
- atomic_inc(&t->uref);
- cb->args[IPSET_CB_PRIVATE] = (unsigned long)t;
- rcu_read_unlock_bh();
- } else if (cb->args[IPSET_CB_PRIVATE]) {
- t = (struct htable *)cb->args[IPSET_CB_PRIVATE];
- if (atomic_dec_and_test(&t->uref) && t->resizing) {
- pr_debug("Table destroy after resize "
- " by dump: %p\n", t);
- mtype_ahash_destroy(set, t, false);
+ hti = kmalloc_obj(*hti, GFP_ATOMIC);
+ if (hti)
+ ipset_hash_walk_enter(h, hti);
+ cb->args[IPSET_CB_PRIVATE] = (unsigned long)hti;
+ } else {
+ hti = (struct rhashtable_iter *)cb->args[IPSET_CB_PRIVATE];
+ if (hti) {
+ rhashtable_walk_exit(hti);
+ kfree(hti);
}
cb->args[IPSET_CB_PRIVATE] = 0;
}
@@ -1663,77 +1135,77 @@ static int
mtype_list(const struct ip_set *set,
struct sk_buff *skb, struct netlink_callback *cb)
{
- const struct htable *t;
+ struct rhashtable_iter *hti =
+ (struct rhashtable_iter *)cb->args[IPSET_CB_PRIVATE];
+ struct mtype_rht_elem *e, *peeked;
struct nlattr *atd, *nested;
- const struct hbucket *n;
- const struct mtype_elem *e;
- u32 first = cb->args[IPSET_CB_ARG0];
- /* We assume that one hash bucket fills into one page */
void *incomplete;
- int i, ret = 0;
- u8 pos;
+ u32 emitted = 0;
+ int ret = 0;
+
+ if (!hti)
+ return -EMSGSIZE;
atd = nla_nest_start(skb, IPSET_ATTR_ADT);
if (!atd)
return -EMSGSIZE;
- pr_debug("list hash set %s\n", set->name);
- t = (const struct htable *)cb->args[IPSET_CB_PRIVATE];
- /* Expire may replace a hbucket with another one */
- rcu_read_lock();
- for (; cb->args[IPSET_CB_ARG0] < jhash_size(t->htable_bits);
- cb->args[IPSET_CB_ARG0]++) {
- cond_resched_rcu();
- incomplete = skb_tail_pointer(skb);
- n = rcu_dereference(hbucket(t, cb->args[IPSET_CB_ARG0]));
- pr_debug("cb->arg bucket: %lu, t %p n %p\n",
- cb->args[IPSET_CB_ARG0], t, n);
- if (!n)
- continue;
- pos = smp_load_acquire(&n->pos);
- for (i = 0; i < pos; i++) {
- if (!test_bit_acquire(i, n->used))
+ rhashtable_walk_start(hti);
+ while ((e = rhashtable_walk_peek(hti))) {
+ if (IS_ERR(e)) {
+ if (PTR_ERR(e) == -EAGAIN)
continue;
- e = ahash_data(n, i, set->dsize);
- if (SET_ELEM_EXPIRED(set, e))
- continue;
- pr_debug("list hash %lu hbucket %p i %u, data %p\n",
- cb->args[IPSET_CB_ARG0], n, i, e);
- nested = nla_nest_start(skb, IPSET_ATTR_DATA);
- if (!nested) {
- if (cb->args[IPSET_CB_ARG0] == first) {
- nla_nest_cancel(skb, atd);
- ret = -EMSGSIZE;
- goto out;
- }
- goto nla_put_failure;
- }
- if (mtype_data_list(skb, e))
- goto nla_put_failure;
- if (ip_set_put_extensions(skb, set, e, true))
- goto nla_put_failure;
- nla_nest_end(skb, nested);
+ ret = PTR_ERR(e);
+ break;
+ }
+ peeked = e;
+next_dump:
+ if (SET_ELEM_EXPIRED(set, &e->elem))
+ goto next_entry;
+
+ incomplete = skb_tail_pointer(skb);
+ nested = nla_nest_start(skb, IPSET_ATTR_DATA);
+ if (!nested) {
+ nlmsg_trim(skb, incomplete);
+ goto paused;
+ }
+ if (mtype_data_list(skb, &e->elem) ||
+ ip_set_put_extensions(skb, set, &e->elem, true)) {
+ nla_nest_cancel(skb, nested);
+ nlmsg_trim(skb, incomplete);
+ goto paused;
+ }
+ nla_nest_end(skb, nested);
+ emitted++;
+next_entry:
+ e = rhashtable_walk_next(hti);
+ if (IS_ERR(e)) {
+ ret = PTR_ERR(e);
+ if (ret != -EAGAIN)
+ break;
+ ret = 0;
+ } else if (peeked && e != peeked) {
+ peeked = NULL;
+ if (e)
+ goto next_dump;
}
}
+ /* Walk exhausted: listing done */
nla_nest_end(skb, atd);
- /* Set listing finished */
+ rhashtable_walk_stop(hti);
cb->args[IPSET_CB_ARG0] = 0;
+ return ret;
- goto out;
-
-nla_put_failure:
- nlmsg_trim(skb, incomplete);
- if (unlikely(first == cb->args[IPSET_CB_ARG0])) {
- pr_warn("Can't list set %s: one bucket does not fit into a message. Please report it!\n",
- set->name);
- cb->args[IPSET_CB_ARG0] = 0;
- ret = -EMSGSIZE;
- } else {
- nla_nest_end(skb, atd);
+paused:
+ if (emitted == 0) {
+ nla_nest_cancel(skb, atd);
+ rhashtable_walk_stop(hti);
+ return -EMSGSIZE;
}
-out:
- rcu_read_unlock();
- return ret;
+ cb->args[IPSET_CB_ARG0] = 1;
+ nla_nest_end(skb, atd);
+ rhashtable_walk_stop(hti);
+ return 0;
}
static int
@@ -1759,7 +1231,7 @@ static const struct ip_set_type_variant mtype_variant = {
.head = mtype_head,
.list = mtype_list,
.uref = mtype_uref,
- .resize = mtype_resize,
+ .resize = NULL,
.same_set = mtype_same_set,
.cancel_gc = mtype_cancel_gc,
.region_lock = true,
@@ -1770,12 +1242,11 @@ static int
IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
struct nlattr *tb[], u32 flags)
{
- struct rhashtable_params params;
u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM;
+ struct rhashtable_params params;
#ifdef IP_SET_HASH_WITH_MARKMASK
u32 markmask;
#endif
- u8 hbits;
#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
int ret __attribute__((unused)) = 0;
u8 netmask = set->family == NFPROTO_IPV4 ? 32 : 128;
@@ -1783,12 +1254,11 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
#endif
#ifdef IP_SET_HASH_WITH_NETS
struct net_prefixes *nets;
+ int i;
#endif
size_t hsize;
struct htype *h;
- struct htable *t;
int err;
- u32 i;
pr_debug("Create set %s with family %s\n",
set->name, set->family == NFPROTO_IPV4 ? "inet" : "inet6");
@@ -1859,7 +1329,12 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
#endif
if (tb[IPSET_ATTR_HASHSIZE]) {
+ /* Not used anymore, just for backwards compat, old hash
+ * implementation used to -ENOMEM on too large hashsize.
+ */
hashsize = ip_set_get_h32(tb[IPSET_ATTR_HASHSIZE]);
+ if (hashsize > INT_MAX)
+ return -ENOMEM;
if (hashsize < IPSET_MIMINAL_HASHSIZE)
hashsize = IPSET_MIMINAL_HASHSIZE;
}
@@ -1894,36 +1369,19 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
if (err)
goto free_h;
- /* Compute htable_bits from the user input parameter hashsize.
- * Assume that hashsize == 2^htable_bits,
- * otherwise round up to the first 2^n value.
- */
- hbits = fls(hashsize - 1);
- hsize = htable_size(hbits);
- if (hsize == 0)
- goto free_rht;
- t = ip_set_alloc(hsize);
- if (!t)
- goto free_rht;
- t->hregion = ip_set_alloc(ahash_sizeof_regions(hbits));
- if (!t->hregion)
- goto free_t;
#ifdef IP_SET_HASH_WITH_NETS
for (i = 0; i < IPSET_NET_COUNT; i++) {
nets = kzalloc_obj(*nets);
if (!nets) {
while (i > 0)
kfree(rcu_dereference_raw(h->rnets[--i]));
- goto free_hregion;
+ goto free_rht;
}
seqcount_spinlock_init(&nets->seq, &set->lock);
RCU_INIT_POINTER(h->rnets[i], nets);
}
#endif
h->gc.set = set;
- spin_lock_init(&h->gc.lock);
- for (i = 0; i < ahash_numof_locks(hbits); i++)
- spin_lock_init(&t->hregion[i].lock);
h->maxelem = maxelem;
#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
h->bitmask = bitmask;
@@ -1932,10 +1390,8 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
#ifdef IP_SET_HASH_WITH_MARKMASK
h->markmask = markmask;
#endif
- if (tb[IPSET_ATTR_INITVAL])
+ if (tb[IPSET_ATTR_INITVAL]) /* only stored for userspace dump compatibility */
h->initval = ntohl(nla_get_be32(tb[IPSET_ATTR_INITVAL]));
- else
- get_random_bytes(&h->initval, sizeof(h->initval));
h->bucketsize = AHASH_MAX_SIZE;
if (tb[IPSET_ATTR_BUCKETSIZE]) {
h->bucketsize = nla_get_u8(tb[IPSET_ATTR_BUCKETSIZE]);
@@ -1946,10 +1402,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
else if (h->bucketsize % 2)
h->bucketsize += 1;
}
- t->htable_bits = hbits;
- t->maxelem = h->maxelem / ahash_numof_locks(hbits);
- INIT_LIST_HEAD(&t->ad);
- RCU_INIT_POINTER(h->table, t);
+ h->htable_bits = fls(hashsize - 1);
set->data = h;
#ifndef IP_SET_PROTO_UNDEF
@@ -1979,27 +1432,22 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
IPSET_TOKEN(HTYPE, 6_gc_init)(&h->gc);
#endif
}
- pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n",
- set->name, mtype_hash_size(h),
- t->htable_bits, h->maxelem, set->data, t);
+ pr_debug("create %s hashsize %u maxelem %u\n",
+ set->name, jhash_size(h->htable_bits), h->maxelem);
return 0;
#ifdef IP_SET_HASH_WITH_NETS
-free_hregion:
- ip_set_free(t->hregion);
-#endif
-free_t:
- ip_set_free(t);
free_rht:
#ifdef IP_SET_HASH_WITH_MULTI
rhltable_destroy(&h->rhlt);
#else
rhashtable_destroy(&h->ht);
#endif
+#endif
free_h:
kfree(h);
- return -ENOMEM;
+ return err ? err : -ENOMEM;
}
#endif /* IP_SET_EMIT_CREATE */
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 05/13] netfilter: ipset: re-add forceadd support
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (3 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 04/13] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 06/13] netfilter: ipset: also report mem size for cidr storage to userspace Florian Westphal
` (7 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
The rhashtable conversion removed the SET_WITH_FORCEADD eviction logic.
Add mtype_remove_random() helper to lookup a random key slot.
If there is an element, try to evict it and allow add of the new element
just like before the rhashtable conversion.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 76 +++++++++++++++++++++++++--
1 file changed, 71 insertions(+), 5 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 7cc2b515e71c..ad190b2f8632 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -154,6 +154,9 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_kadt
#undef mtype_uadt
+#undef mtype_remove_random
+#undef mtype_remove_key
+#undef mtype_remove_cmpfn
#undef mtype_add
#undef mtype_do_set_exts
#undef mtype_del
@@ -205,6 +208,9 @@ static const union nf_inet_addr zeromask = {};
#define mtype_kadt IPSET_TOKEN(MTYPE, _kadt)
#define mtype_uadt IPSET_TOKEN(MTYPE, _uadt)
+#define mtype_remove_random IPSET_TOKEN(MTYPE, _remove_random)
+#define mtype_remove_key IPSET_TOKEN(MTYPE, _remove_key)
+#define mtype_remove_cmpfn IPSET_TOKEN(MTYPE, _remove_cmpfn)
#define mtype_add IPSET_TOKEN(MTYPE, _add)
#define mtype_del IPSET_TOKEN(MTYPE, _del)
#define mtype_test_cidrs IPSET_TOKEN(MTYPE, _test_cidrs)
@@ -637,6 +643,64 @@ mtype_rht_size(struct ip_set *set, u32 *elements, size_t *ext_size)
(offsetof(struct mtype_rht_elem, elem) + set->dsize);
}
+static u32 mtype_remove_key(const void *data, u32 len, u32 seed)
+{
+ return get_random_u32();
+}
+
+static int mtype_remove_cmpfn(struct rhashtable_compare_arg *arg, const void *obj)
+{
+ return 0; /* always match */
+}
+
+/**
+ * mtype_remove_random() - Remove a random element from the set (forceadd)
+ * @set: Pointer to the ip_set
+ * @h: Pointer to the htype
+ *
+ * When sets created with forceadd option become full the next addition
+ * to the set may succeed and evict a random entry from the set.
+ * Best-effort: no linear scan; 'return false' is fine.
+ *
+ * Return: true if an element was evicted, false otherwise.
+ */
+static bool
+mtype_remove_random(struct ip_set *set)
+{
+ static const struct rhashtable_params ip_set_hash_rnd_params = {
+ .head_offset = offsetof(struct mtype_rht_elem, node),
+ .key_offset = offsetof(struct mtype_rht_elem, elem),
+ .hashfn = mtype_remove_key,
+ .obj_hashfn = mtype_rht_obj_hashfn,
+ .obj_cmpfn = mtype_remove_cmpfn,
+ .key_len = sizeof(u32),
+ };
+ struct mtype_rht_elem *e = NULL;
+ struct htype *h = set->data;
+ bool removed = false;
+ static const u32 k;
+
+#ifdef IP_SET_HASH_WITH_MULTI
+ {
+ struct rhlist_head *list = rhltable_lookup(&h->rhlt, &k,
+ ip_set_hash_rnd_params);
+ if (!list)
+ return false;
+
+ e = container_of(list, typeof(*e), node);
+ }
+#else
+ e = rhashtable_lookup(&h->ht, &k, ip_set_hash_rnd_params);
+#endif
+ if (e && !ipset_hash_remove(h, e))
+ removed = true;
+
+ if (removed)
+ ipset_hash_elem_destroy_free(set, e);
+
+ return removed;
+}
+
/* Add an element to a hash and update the internal counters when succeeded,
* otherwise report the proper error code.
*/
@@ -706,11 +770,13 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
}
if (!old && ipset_hash_nelems(h) >= h->maxelem) {
- if (net_ratelimit())
- pr_warn("Set %s is full, maxelem %u reached\n",
- set->name, h->maxelem);
- ret = -IPSET_ERR_HASH_FULL;
- goto out_rcu_unlock;
+ if (!SET_WITH_FORCEADD(set) || !mtype_remove_random(set)) {
+ if (net_ratelimit())
+ pr_warn("Set %s is full, maxelem %u reached\n",
+ set->name, h->maxelem);
+ ret = -IPSET_ERR_HASH_FULL;
+ goto out_rcu_unlock;
+ }
}
e = kzalloc(offsetof(struct mtype_rht_elem, elem) + set->dsize,
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 06/13] netfilter: ipset: also report mem size for cidr storage to userspace
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (4 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 05/13] netfilter: ipset: re-add forceadd support Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 07/13] netfilter: ipset: remove obsolete data_next stubs Florian Westphal
` (6 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
After the 'Fixes' commit, the storage is allocated dynamically.
Include this when reporting the mem usage.
Fixes: 8e5fd2a55e24 ("netfilter: ipset: rework cidr bookkeeping")
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index ad190b2f8632..4d2e1e867606 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -641,6 +641,23 @@ mtype_rht_size(struct ip_set *set, u32 *elements, size_t *ext_size)
*elements = ipset_hash_nelems(h);
*ext_size = *elements *
(offsetof(struct mtype_rht_elem, elem) + set->dsize);
+
+#ifdef IP_SET_HASH_WITH_NETS
+ {
+ const struct net_prefixes *nets;
+ int i;
+
+ rcu_read_lock();
+
+ for (i = 0; i < IPSET_NET_COUNT; i++) {
+ nets = rcu_dereference(h->rnets[i]);
+ if (nets)
+ *ext_size += ksize(nets);
+ }
+
+ rcu_read_unlock();
+ }
+#endif
}
static u32 mtype_remove_key(const void *data, u32 len, u32 seed)
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 07/13] netfilter: ipset: remove obsolete data_next stubs
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (5 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 06/13] netfilter: ipset: also report mem size for cidr storage to userspace Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 08/13] netfilter: ipset: remove last region lock usage Florian Westphal
` (5 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
Was used in the generic resize code. This code is now gone, we only
need this is a few selected set backends for netmask expansion on
insert.
Retain it in those specific helpers and remove all the obsolete/unused
stubs.
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 4 ----
net/netfilter/ipset/ip_set_hash_ip.c | 5 -----
net/netfilter/ipset/ip_set_hash_ipmac.c | 13 -------------
net/netfilter/ipset/ip_set_hash_ipmark.c | 6 ------
net/netfilter/ipset/ip_set_hash_ipport.c | 7 -------
net/netfilter/ipset/ip_set_hash_ipportip.c | 7 -------
net/netfilter/ipset/ip_set_hash_ipportnet.c | 7 -------
net/netfilter/ipset/ip_set_hash_mac.c | 6 ------
net/netfilter/ipset/ip_set_hash_net.c | 6 ------
net/netfilter/ipset/ip_set_hash_netiface.c | 6 ------
net/netfilter/ipset/ip_set_hash_netnet.c | 6 ------
net/netfilter/ipset/ip_set_hash_netport.c | 7 -------
net/netfilter/ipset/ip_set_hash_netportnet.c | 7 -------
13 files changed, 87 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 4d2e1e867606..4efc93333aef 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -135,7 +135,6 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_data_reset_flags
#undef mtype_data_netmask
#undef mtype_data_list
-#undef mtype_data_next
#undef mtype_elem
#undef mtype_rht_elem
@@ -189,7 +188,6 @@ static const union nf_inet_addr zeromask = {};
#define mtype_data_reset_flags IPSET_TOKEN(MTYPE, _data_reset_flags)
#define mtype_data_netmask IPSET_TOKEN(MTYPE, _data_netmask)
#define mtype_data_list IPSET_TOKEN(MTYPE, _data_list)
-#define mtype_data_next IPSET_TOKEN(MTYPE, _data_next)
#define mtype_elem IPSET_TOKEN(MTYPE, _elem)
#define mtype_rht_elem IPSET_TOKEN(MTYPE, _rht_elem)
@@ -913,8 +911,6 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
if (ret == -EEXIST)
ret = flag_exist ? 0 : -IPSET_ERR_EXIST;
- if (0) /* to be removed */
- mtype_data_next(&h->next, d);
out_rcu_unlock:
rcu_read_unlock_bh();
return ret;
diff --git a/net/netfilter/ipset/ip_set_hash_ip.c b/net/netfilter/ipset/ip_set_hash_ip.c
index c9f4e3859663..9b82ec5d4037 100644
--- a/net/netfilter/ipset/ip_set_hash_ip.c
+++ b/net/netfilter/ipset/ip_set_hash_ip.c
@@ -198,11 +198,6 @@ hash_ip6_data_list(struct sk_buff *skb, const struct hash_ip6_elem *e)
return true;
}
-static void
-hash_ip6_data_next(struct hash_ip6_elem *next, const struct hash_ip6_elem *e)
-{
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_ipmac.c b/net/netfilter/ipset/ip_set_hash_ipmac.c
index b9a2681e2488..dced4c5f3ca3 100644
--- a/net/netfilter/ipset/ip_set_hash_ipmac.c
+++ b/net/netfilter/ipset/ip_set_hash_ipmac.c
@@ -68,13 +68,6 @@ hash_ipmac4_data_list(struct sk_buff *skb, const struct hash_ipmac4_elem *e)
return true;
}
-static void
-hash_ipmac4_data_next(struct hash_ipmac4_elem *next,
- const struct hash_ipmac4_elem *e)
-{
- next->ip = e->ip;
-}
-
#define MTYPE hash_ipmac4
#define PF 4
#define HOST_MASK 32
@@ -176,12 +169,6 @@ hash_ipmac6_data_list(struct sk_buff *skb, const struct hash_ipmac6_elem *e)
return true;
}
-static void
-hash_ipmac6_data_next(struct hash_ipmac6_elem *next,
- const struct hash_ipmac6_elem *e)
-{
-}
-
#undef MTYPE
#undef PF
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_ipmark.c b/net/netfilter/ipset/ip_set_hash_ipmark.c
index e26ca2a370e3..6196b0112cf9 100644
--- a/net/netfilter/ipset/ip_set_hash_ipmark.c
+++ b/net/netfilter/ipset/ip_set_hash_ipmark.c
@@ -201,12 +201,6 @@ hash_ipmark6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_ipmark6_data_next(struct hash_ipmark6_elem *next,
- const struct hash_ipmark6_elem *d)
-{
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_ipport.c b/net/netfilter/ipset/ip_set_hash_ipport.c
index 41ca24a22a02..7842ff4ad74f 100644
--- a/net/netfilter/ipset/ip_set_hash_ipport.c
+++ b/net/netfilter/ipset/ip_set_hash_ipport.c
@@ -245,13 +245,6 @@ hash_ipport6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_ipport6_data_next(struct hash_ipport6_elem *next,
- const struct hash_ipport6_elem *d)
-{
- next->port = d->port;
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_ipportip.c b/net/netfilter/ipset/ip_set_hash_ipportip.c
index b9ac2efaa15c..b1b24358c810 100644
--- a/net/netfilter/ipset/ip_set_hash_ipportip.c
+++ b/net/netfilter/ipset/ip_set_hash_ipportip.c
@@ -244,13 +244,6 @@ hash_ipportip6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_ipportip6_data_next(struct hash_ipportip6_elem *next,
- const struct hash_ipportip6_elem *d)
-{
- next->port = d->port;
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_ipportnet.c b/net/netfilter/ipset/ip_set_hash_ipportnet.c
index 195853a25b06..58a47a0f89f0 100644
--- a/net/netfilter/ipset/ip_set_hash_ipportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_ipportnet.c
@@ -375,13 +375,6 @@ hash_ipportnet6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_ipportnet6_data_next(struct hash_ipportnet6_elem *next,
- const struct hash_ipportnet6_elem *d)
-{
- next->port = d->port;
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_mac.c b/net/netfilter/ipset/ip_set_hash_mac.c
index 41a122591fe2..4c195db349f7 100644
--- a/net/netfilter/ipset/ip_set_hash_mac.c
+++ b/net/netfilter/ipset/ip_set_hash_mac.c
@@ -57,12 +57,6 @@ hash_mac4_data_list(struct sk_buff *skb, const struct hash_mac4_elem *e)
return true;
}
-static void
-hash_mac4_data_next(struct hash_mac4_elem *next,
- const struct hash_mac4_elem *e)
-{
-}
-
#define MTYPE hash_mac4
#define HOST_MASK 32
#define IP_SET_EMIT_CREATE
diff --git a/net/netfilter/ipset/ip_set_hash_net.c b/net/netfilter/ipset/ip_set_hash_net.c
index 092f3c9281b8..157660674f1d 100644
--- a/net/netfilter/ipset/ip_set_hash_net.c
+++ b/net/netfilter/ipset/ip_set_hash_net.c
@@ -269,12 +269,6 @@ hash_net6_data_list(struct sk_buff *skb, const struct hash_net6_elem *data)
return true;
}
-static void
-hash_net6_data_next(struct hash_net6_elem *next,
- const struct hash_net6_elem *d)
-{
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index edadd6307675..82b00337cfef 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -370,12 +370,6 @@ hash_netiface6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_netiface6_data_next(struct hash_netiface6_elem *next,
- const struct hash_netiface6_elem *d)
-{
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c
index f7c8a1cc30fc..4e77e40ebc2c 100644
--- a/net/netfilter/ipset/ip_set_hash_netnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netnet.c
@@ -359,12 +359,6 @@ hash_netnet6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_netnet6_data_next(struct hash_netnet6_elem *next,
- const struct hash_netnet6_elem *d)
-{
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_netport.c b/net/netfilter/ipset/ip_set_hash_netport.c
index 5de4b511de76..dfe8f995abbe 100644
--- a/net/netfilter/ipset/ip_set_hash_netport.c
+++ b/net/netfilter/ipset/ip_set_hash_netport.c
@@ -330,13 +330,6 @@ hash_netport6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_netport6_data_next(struct hash_netport6_elem *next,
- const struct hash_netport6_elem *d)
-{
- next->port = d->port;
-}
-
#undef MTYPE
#undef HOST_MASK
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index 61af1ce27127..0dc79d37beac 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -421,13 +421,6 @@ hash_netportnet6_data_list(struct sk_buff *skb,
return true;
}
-static void
-hash_netportnet6_data_next(struct hash_netportnet6_elem *next,
- const struct hash_netportnet6_elem *d)
-{
- next->port = d->port;
-}
-
#undef MTYPE
#undef HOST_MASK
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 08/13] netfilter: ipset: remove last region lock usage
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (6 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 07/13] netfilter: ipset: remove obsolete data_next stubs Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 09/13] netfilter: ipset: remove multi-flag Florian Westphal
` (4 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
Move lock responsibility into kadt/uadt/flush callbacks and remove the
last .region_lock users. Keep bitmap types IPSET_TEST lockless, this
is called from xt_set / datapath.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/linux/netfilter/ipset/ip_set.h | 8 -------
net/netfilter/ipset/ip_set_bitmap_gen.h | 2 ++
net/netfilter/ipset/ip_set_bitmap_ip.c | 14 +++++++++--
net/netfilter/ipset/ip_set_bitmap_ipmac.c | 13 +++++++++-
net/netfilter/ipset/ip_set_bitmap_port.c | 14 +++++++++--
net/netfilter/ipset/ip_set_core.c | 29 +----------------------
net/netfilter/ipset/ip_set_hash_gen.h | 1 -
net/netfilter/ipset/ip_set_list_set.c | 7 ++++++
8 files changed, 46 insertions(+), 42 deletions(-)
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index c46864cc6623..37cfda517c1b 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -188,14 +188,6 @@ struct ip_set_type_variant {
bool (*same_set)(const struct ip_set *a, const struct ip_set *b);
/* Cancel ongoing garbage collectors before destroying the set*/
void (*cancel_gc)(struct ip_set *set);
- /* Region-locking is used */
- bool region_lock;
-};
-
-struct ip_set_region {
- spinlock_t lock; /* Region lock */
- size_t ext_size; /* Size of the dynamic extensions */
- u32 elements; /* Number of elements vs timeout */
};
/* Max range where every element is added/deleted in one step */
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index d6a7e6604542..0b6dd2e13433 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -73,11 +73,13 @@ mtype_flush(struct ip_set *set)
{
struct mtype *map = set->data;
+ spin_lock_bh(&set->lock);
if (set->extensions & IPSET_EXT_DESTROY)
mtype_ext_cleanup(set);
bitmap_zero(map->members, map->elements);
set->elements = 0;
DEBUG_NET_WARN_ON_ONCE(atomic64_read(&set->ext_size) > 0);
+ spin_unlock_bh(&set->lock);
}
/* Calculate the actual memory size of the set data */
diff --git a/net/netfilter/ipset/ip_set_bitmap_ip.c b/net/netfilter/ipset/ip_set_bitmap_ip.c
index ac7febce074f..9307c89d2837 100644
--- a/net/netfilter/ipset/ip_set_bitmap_ip.c
+++ b/net/netfilter/ipset/ip_set_bitmap_ip.c
@@ -115,6 +115,7 @@ bitmap_ip_kadt(struct ip_set *set, const struct sk_buff *skb,
ipset_adtfn adtfn = set->variant->adt[adt];
struct bitmap_ip_adt_elem e = { .id = 0 };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
+ int ret;
u32 ip;
ip = ntohl(ip4addr(skb, opt->flags & IPSET_DIM_ONE_SRC));
@@ -123,7 +124,14 @@ bitmap_ip_kadt(struct ip_set *set, const struct sk_buff *skb,
e.id = ip_to_id(map, ip);
- return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+ if (adt == IPSET_TEST)
+ return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+
+ spin_lock_bh(&set->lock);
+ ret = adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+ spin_unlock_bh(&set->lock);
+
+ return ret;
}
static int
@@ -178,15 +186,17 @@ bitmap_ip_uadt(struct ip_set *set, struct nlattr *tb[],
if (ip < map->first_ip || ip_to > map->last_ip)
return -IPSET_ERR_BITMAP_RANGE;
+ spin_lock_bh(&set->lock);
for (; !before(ip_to, ip); ip += map->hosts) {
e.id = ip_to_id(map, ip);
ret = adtfn(set, &e, &ext, &ext, flags);
if (ret && !ip_set_eexist(ret, flags))
- return ret;
+ break;
ret = 0;
}
+ spin_unlock_bh(&set->lock);
return ret;
}
diff --git a/net/netfilter/ipset/ip_set_bitmap_ipmac.c b/net/netfilter/ipset/ip_set_bitmap_ipmac.c
index 5921fd9d2dca..720650d0c3f1 100644
--- a/net/netfilter/ipset/ip_set_bitmap_ipmac.c
+++ b/net/netfilter/ipset/ip_set_bitmap_ipmac.c
@@ -214,6 +214,7 @@ bitmap_ipmac_kadt(struct ip_set *set, const struct sk_buff *skb,
ipset_adtfn adtfn = set->variant->adt[adt];
struct bitmap_ipmac_adt_elem e = { .id = 0, .add_mac = 1 };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
+ int ret;
u32 ip;
ip = ntohl(ip4addr(skb, opt->flags & IPSET_DIM_ONE_SRC));
@@ -235,7 +236,14 @@ bitmap_ipmac_kadt(struct ip_set *set, const struct sk_buff *skb,
if (is_zero_ether_addr(e.ether))
return -EINVAL;
- return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+ if (adt == IPSET_TEST)
+ return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+
+ spin_lock_bh(&set->lock);
+ ret = adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+ spin_unlock_bh(&set->lock);
+
+ return ret;
}
static int
@@ -273,7 +281,10 @@ bitmap_ipmac_uadt(struct ip_set *set, struct nlattr *tb[],
memcpy(e.ether, nla_data(tb[IPSET_ATTR_ETHER]), ETH_ALEN);
e.add_mac = 1;
}
+
+ spin_lock_bh(&set->lock);
ret = adtfn(set, &e, &ext, &ext, flags);
+ spin_unlock_bh(&set->lock);
return ip_set_eexist(ret, flags) ? 0 : ret;
}
diff --git a/net/netfilter/ipset/ip_set_bitmap_port.c b/net/netfilter/ipset/ip_set_bitmap_port.c
index ca875c982424..ea644bd420c5 100644
--- a/net/netfilter/ipset/ip_set_bitmap_port.c
+++ b/net/netfilter/ipset/ip_set_bitmap_port.c
@@ -134,6 +134,7 @@ bitmap_port_kadt(struct ip_set *set, const struct sk_buff *skb,
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
__be16 __port;
u16 port = 0;
+ int ret;
if (!ip_set_get_ip_port(skb, opt->family,
opt->flags & IPSET_DIM_ONE_SRC, &__port))
@@ -146,7 +147,14 @@ bitmap_port_kadt(struct ip_set *set, const struct sk_buff *skb,
e.id = port_to_id(map, port);
- return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+ if (adt == IPSET_TEST)
+ return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+
+ spin_lock_bh(&set->lock);
+ ret = adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
+ spin_unlock_bh(&set->lock);
+
+ return ret;
}
static int
@@ -194,15 +202,17 @@ bitmap_port_uadt(struct ip_set *set, struct nlattr *tb[],
if (port_to > map->last_port)
return -IPSET_ERR_BITMAP_RANGE;
+ spin_lock_bh(&set->lock);
for (; port <= port_to; port++) {
e.id = port_to_id(map, port);
ret = adtfn(set, &e, &ext, &ext, flags);
if (ret && !ip_set_eexist(ret, flags))
- return ret;
+ break;
ret = 0;
}
+ spin_unlock_bh(&set->lock);
return ret;
}
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 0a86a170ba90..339726b33342 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -730,20 +730,6 @@ ip_set_rcu_get(struct net *net, ip_set_id_t index)
return ip_set_dereference_nfnl(inst->ip_set_list)[index];
}
-static inline void
-ip_set_lock(struct ip_set *set)
-{
- if (!set->variant->region_lock)
- spin_lock_bh(&set->lock);
-}
-
-static inline void
-ip_set_unlock(struct ip_set *set)
-{
- if (!set->variant->region_lock)
- spin_unlock_bh(&set->lock);
-}
-
int
ip_set_test(ip_set_id_t index, const struct sk_buff *skb,
const struct xt_action_param *par, struct ip_set_adt_opt *opt)
@@ -763,9 +749,7 @@ ip_set_test(ip_set_id_t index, const struct sk_buff *skb,
if (ret == -EAGAIN) {
/* Type requests element to be completed */
pr_debug("element must be completed, ADD is triggered\n");
- ip_set_lock(set);
set->variant->kadt(set, skb, par, IPSET_ADD, opt);
- ip_set_unlock(set);
ret = 1;
} else {
/* --return-nomatch: invert matched element */
@@ -794,9 +778,7 @@ ip_set_add(ip_set_id_t index, const struct sk_buff *skb,
!(opt->family == set->family || set->family == NFPROTO_UNSPEC))
return -IPSET_ERR_TYPE_MISMATCH;
- ip_set_lock(set);
ret = set->variant->kadt(set, skb, par, IPSET_ADD, opt);
- ip_set_unlock(set);
return ret;
}
@@ -807,7 +789,6 @@ ip_set_del(ip_set_id_t index, const struct sk_buff *skb,
const struct xt_action_param *par, struct ip_set_adt_opt *opt)
{
struct ip_set *set = ip_set_rcu_get(xt_net(par), index);
- int ret = 0;
BUG_ON(!set);
pr_debug("set %s, index %u\n", set->name, index);
@@ -816,11 +797,7 @@ ip_set_del(ip_set_id_t index, const struct sk_buff *skb,
!(opt->family == set->family || set->family == NFPROTO_UNSPEC))
return -IPSET_ERR_TYPE_MISMATCH;
- ip_set_lock(set);
- ret = set->variant->kadt(set, skb, par, IPSET_DEL, opt);
- ip_set_unlock(set);
-
- return ret;
+ return set->variant->kadt(set, skb, par, IPSET_DEL, opt);
}
EXPORT_SYMBOL_GPL(ip_set_del);
@@ -1311,9 +1288,7 @@ ip_set_flush_set(struct ip_set *set)
{
pr_debug("set: %s\n", set->name);
- ip_set_lock(set);
set->variant->flush(set);
- ip_set_unlock(set);
}
static int ip_set_flush(struct sk_buff *skb, const struct nfnl_info *info,
@@ -1767,9 +1742,7 @@ call_ad(struct net *net, struct sock *ctnl, struct sk_buff *skb,
__ip_set_put_netlink(set);
}
- ip_set_lock(set);
ret = set->variant->uadt(set, tb, adt, &lineno, flags, retried);
- ip_set_unlock(set);
retried = true;
} while (ret == -ERANGE ||
(ret == -EAGAIN &&
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 4efc93333aef..b6614ddb9e78 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -1313,7 +1313,6 @@ static const struct ip_set_type_variant mtype_variant = {
.resize = NULL,
.same_set = mtype_same_set,
.cancel_gc = mtype_cancel_gc,
- .region_lock = true,
};
#ifdef IP_SET_EMIT_CREATE
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index f070088742d6..738f14a73684 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -119,6 +119,7 @@ list_set_kadt(struct ip_set *set, const struct sk_buff *skb,
int ret = -EINVAL;
rcu_read_lock();
+ spin_lock_bh(&set->lock);
switch (adt) {
case IPSET_TEST:
ret = list_set_ktest(set, skb, par, opt, &ext);
@@ -132,6 +133,7 @@ list_set_kadt(struct ip_set *set, const struct sk_buff *skb,
default:
break;
}
+ spin_unlock_bh(&set->lock);
rcu_read_unlock();
return ret;
@@ -401,10 +403,13 @@ list_set_uadt(struct ip_set *set, struct nlattr *tb[],
if (!e.before)
e.before = -1;
}
+
+ spin_lock_bh(&set->lock);
if (adt != IPSET_TEST && SET_WITH_TIMEOUT(set))
set_cleanup_entries(set);
ret = adtfn(set, &e, &ext, &ext, flags);
+ spin_unlock_bh(&set->lock);
finish:
if (e.refid != IPSET_INVALID_ID)
@@ -421,9 +426,11 @@ list_set_flush(struct ip_set *set)
struct list_set *map = set->data;
struct set_elem *e, *n;
+ spin_lock_bh(&set->lock);
list_for_each_entry_safe(e, n, &map->members, list)
list_set_del(set, e);
DEBUG_NET_WARN_ON_ONCE(set->elements > 0);
+ spin_unlock_bh(&set->lock);
}
static void
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 09/13] netfilter: ipset: remove multi-flag
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (7 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 08/13] netfilter: ipset: remove last region lock usage Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 10/13] netfilter: ipset: remove resize completely Florian Westphal
` (3 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
From: Jozsef Kadlecsik <kadlec@netfilter.org>
This is a leftover from the old hash table that needed this to know both
when to skip already-matched identical ip/mask pairs and to know the
number of same-key elements to size the hash buckets.
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 22 +++++++-------------
net/netfilter/ipset/ip_set_hash_ip.c | 6 ++----
net/netfilter/ipset/ip_set_hash_ipmac.c | 6 ++----
net/netfilter/ipset/ip_set_hash_ipmark.c | 6 ++----
net/netfilter/ipset/ip_set_hash_ipport.c | 6 ++----
net/netfilter/ipset/ip_set_hash_ipportip.c | 6 ++----
net/netfilter/ipset/ip_set_hash_ipportnet.c | 6 ++----
net/netfilter/ipset/ip_set_hash_mac.c | 3 +--
net/netfilter/ipset/ip_set_hash_net.c | 6 ++----
net/netfilter/ipset/ip_set_hash_netiface.c | 8 ++-----
net/netfilter/ipset/ip_set_hash_netnet.c | 6 ++----
net/netfilter/ipset/ip_set_hash_netport.c | 6 ++----
net/netfilter/ipset/ip_set_hash_netportnet.c | 6 ++----
13 files changed, 30 insertions(+), 63 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index b6614ddb9e78..973adb854082 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -278,10 +278,7 @@ static int mtype_rht_cmpfn(struct rhashtable_compare_arg *arg, const void *obj)
#ifdef IP_SET_HASH_WITH_MULTI
return !mtype_key_equal(&e->elem, (const struct mtype_elem *)arg->key);
#else
- u32 multi = 0;
-
- return !mtype_data_equal(&e->elem,
- (const struct mtype_elem *)arg->key, &multi);
+ return !mtype_data_equal(&e->elem, (const struct mtype_elem *)arg->key);
#endif
}
@@ -737,7 +734,6 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
{
struct rhlist_head *tmp, *list;
unsigned int seen = 0;
- u32 multi = 0;
list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
if (!list)
@@ -749,8 +745,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
ipset_hash_elem_destroy_free(set, old);
continue;
}
-
- if (mtype_data_equal(&old->elem, d, &multi))
+ if (mtype_data_equal(&old->elem, d))
goto insert;
++seen;
}
@@ -841,10 +836,9 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
if (list) {
const struct mtype_rht_elem *dup;
struct rhlist_head *tmp;
- u32 multi = 0;
rhl_for_each_entry_rcu(dup, tmp, list, node) {
- if (dup == e || !mtype_data_equal(&dup->elem, d, &multi))
+ if (dup == e || !mtype_data_equal(&dup->elem, d))
continue;
/* check for duplicate key insertion: unlikely,
@@ -930,13 +924,12 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
#ifdef IP_SET_HASH_WITH_MULTI
{
struct rhlist_head *tmp, *list;
- u32 multi = 0;
list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
if (!list)
goto out_unlock;
rhl_for_each_entry_rcu(e, tmp, list, node) {
- if (!mtype_data_equal(&e->elem, d, &multi))
+ if (!mtype_data_equal(&e->elem, d))
continue;
if (SET_ELEM_EXPIRED(set, &e->elem))
goto out_unlock;
@@ -995,8 +988,8 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
#else
int ret, j;
#endif
+ bool multi = false;
unsigned int seq0;
- u32 multi;
pr_debug("test by nets\n");
retry:
@@ -1035,7 +1028,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
rhl_for_each_entry_rcu(e, tmp, list, node) {
if (!SET_ELEM_EXPIRED(set, &e->elem))
multi = true;
- if (!mtype_data_equal(&e->elem, d, &multi))
+ if (!mtype_data_equal(&e->elem, d))
continue;
ret = mtype_data_match(&e->elem, ext, mext, set, flags);
if (ret)
@@ -1100,14 +1093,13 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
#ifdef IP_SET_HASH_WITH_MULTI
{
struct rhlist_head *tmp, *list;
- u32 multi = 0;
list = rhltable_lookup(&h->rhlt, d, mtype_rht_params);
if (!list)
goto out;
rhl_for_each_entry_rcu(e, tmp, list, node) {
- if (!mtype_data_equal(&e->elem, d, &multi))
+ if (!mtype_data_equal(&e->elem, d))
continue;
ret = mtype_data_match(&e->elem, ext, mext, set, flags);
if (ret)
diff --git a/net/netfilter/ipset/ip_set_hash_ip.c b/net/netfilter/ipset/ip_set_hash_ip.c
index 9b82ec5d4037..44aac980b0b2 100644
--- a/net/netfilter/ipset/ip_set_hash_ip.c
+++ b/net/netfilter/ipset/ip_set_hash_ip.c
@@ -49,8 +49,7 @@ struct hash_ip4_elem {
static bool
hash_ip4_data_equal(const struct hash_ip4_elem *e1,
- const struct hash_ip4_elem *e2,
- u32 *multi)
+ const struct hash_ip4_elem *e2)
{
return e1->ip == e2->ip;
}
@@ -181,8 +180,7 @@ struct hash_ip6_elem {
static bool
hash_ip6_data_equal(const struct hash_ip6_elem *ip1,
- const struct hash_ip6_elem *ip2,
- u32 *multi)
+ const struct hash_ip6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6);
}
diff --git a/net/netfilter/ipset/ip_set_hash_ipmac.c b/net/netfilter/ipset/ip_set_hash_ipmac.c
index dced4c5f3ca3..88f4931aadeb 100644
--- a/net/netfilter/ipset/ip_set_hash_ipmac.c
+++ b/net/netfilter/ipset/ip_set_hash_ipmac.c
@@ -50,8 +50,7 @@ struct hash_ipmac4_elem {
static bool
hash_ipmac4_data_equal(const struct hash_ipmac4_elem *e1,
- const struct hash_ipmac4_elem *e2,
- u32 *multi)
+ const struct hash_ipmac4_elem *e2)
{
return e1->ip == e2->ip && ether_addr_equal(e1->ether, e2->ether);
}
@@ -150,8 +149,7 @@ struct hash_ipmac6_elem {
static bool
hash_ipmac6_data_equal(const struct hash_ipmac6_elem *e1,
- const struct hash_ipmac6_elem *e2,
- u32 *multi)
+ const struct hash_ipmac6_elem *e2)
{
return ipv6_addr_equal(&e1->ip.in6, &e2->ip.in6) &&
ether_addr_equal(e1->ether, e2->ether);
diff --git a/net/netfilter/ipset/ip_set_hash_ipmark.c b/net/netfilter/ipset/ip_set_hash_ipmark.c
index 6196b0112cf9..88c8239ea3b8 100644
--- a/net/netfilter/ipset/ip_set_hash_ipmark.c
+++ b/net/netfilter/ipset/ip_set_hash_ipmark.c
@@ -45,8 +45,7 @@ struct hash_ipmark4_elem {
static bool
hash_ipmark4_data_equal(const struct hash_ipmark4_elem *ip1,
- const struct hash_ipmark4_elem *ip2,
- u32 *multi)
+ const struct hash_ipmark4_elem *ip2)
{
return ip1->ip == ip2->ip &&
ip1->mark == ip2->mark;
@@ -181,8 +180,7 @@ struct hash_ipmark6_elem {
static bool
hash_ipmark6_data_equal(const struct hash_ipmark6_elem *ip1,
- const struct hash_ipmark6_elem *ip2,
- u32 *multi)
+ const struct hash_ipmark6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
ip1->mark == ip2->mark;
diff --git a/net/netfilter/ipset/ip_set_hash_ipport.c b/net/netfilter/ipset/ip_set_hash_ipport.c
index 7842ff4ad74f..f2f41700d59e 100644
--- a/net/netfilter/ipset/ip_set_hash_ipport.c
+++ b/net/netfilter/ipset/ip_set_hash_ipport.c
@@ -53,8 +53,7 @@ struct hash_ipport4_elem {
static bool
hash_ipport4_data_equal(const struct hash_ipport4_elem *ip1,
- const struct hash_ipport4_elem *ip2,
- u32 *multi)
+ const struct hash_ipport4_elem *ip2)
{
return ip1->ip == ip2->ip &&
ip1->port == ip2->port &&
@@ -223,8 +222,7 @@ struct hash_ipport6_elem {
static bool
hash_ipport6_data_equal(const struct hash_ipport6_elem *ip1,
- const struct hash_ipport6_elem *ip2,
- u32 *multi)
+ const struct hash_ipport6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
ip1->port == ip2->port &&
diff --git a/net/netfilter/ipset/ip_set_hash_ipportip.c b/net/netfilter/ipset/ip_set_hash_ipportip.c
index b1b24358c810..7e82bcd9aa42 100644
--- a/net/netfilter/ipset/ip_set_hash_ipportip.c
+++ b/net/netfilter/ipset/ip_set_hash_ipportip.c
@@ -49,8 +49,7 @@ struct hash_ipportip4_elem {
static bool
hash_ipportip4_data_equal(const struct hash_ipportip4_elem *ip1,
- const struct hash_ipportip4_elem *ip2,
- u32 *multi)
+ const struct hash_ipportip4_elem *ip2)
{
return ip1->ip == ip2->ip &&
ip1->ip2 == ip2->ip2 &&
@@ -220,8 +219,7 @@ struct hash_ipportip6_elem {
static bool
hash_ipportip6_data_equal(const struct hash_ipportip6_elem *ip1,
- const struct hash_ipportip6_elem *ip2,
- u32 *multi)
+ const struct hash_ipportip6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
ipv6_addr_equal(&ip1->ip2.in6, &ip2->ip2.in6) &&
diff --git a/net/netfilter/ipset/ip_set_hash_ipportnet.c b/net/netfilter/ipset/ip_set_hash_ipportnet.c
index 58a47a0f89f0..01950dbff27d 100644
--- a/net/netfilter/ipset/ip_set_hash_ipportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_ipportnet.c
@@ -62,8 +62,7 @@ struct hash_ipportnet4_elem {
static bool
hash_ipportnet4_data_equal(const struct hash_ipportnet4_elem *ip1,
- const struct hash_ipportnet4_elem *ip2,
- u32 *multi)
+ const struct hash_ipportnet4_elem *ip2)
{
return ip1->ip == ip2->ip &&
ip1->ip2 == ip2->ip2 &&
@@ -320,8 +319,7 @@ struct hash_ipportnet6_elem {
static bool
hash_ipportnet6_data_equal(const struct hash_ipportnet6_elem *ip1,
- const struct hash_ipportnet6_elem *ip2,
- u32 *multi)
+ const struct hash_ipportnet6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
ipv6_addr_equal(&ip1->ip2.in6, &ip2->ip2.in6) &&
diff --git a/net/netfilter/ipset/ip_set_hash_mac.c b/net/netfilter/ipset/ip_set_hash_mac.c
index 4c195db349f7..0e7b30faddee 100644
--- a/net/netfilter/ipset/ip_set_hash_mac.c
+++ b/net/netfilter/ipset/ip_set_hash_mac.c
@@ -40,8 +40,7 @@ struct hash_mac4_elem {
static bool
hash_mac4_data_equal(const struct hash_mac4_elem *e1,
- const struct hash_mac4_elem *e2,
- u32 *multi)
+ const struct hash_mac4_elem *e2)
{
return ether_addr_equal(e1->ether, e2->ether);
}
diff --git a/net/netfilter/ipset/ip_set_hash_net.c b/net/netfilter/ipset/ip_set_hash_net.c
index 157660674f1d..431f0ae56054 100644
--- a/net/netfilter/ipset/ip_set_hash_net.c
+++ b/net/netfilter/ipset/ip_set_hash_net.c
@@ -50,8 +50,7 @@ struct hash_net4_elem {
static bool
hash_net4_data_equal(const struct hash_net4_elem *ip1,
- const struct hash_net4_elem *ip2,
- u32 *multi)
+ const struct hash_net4_elem *ip2)
{
return ip1->ip == ip2->ip &&
ip1->cidr == ip2->cidr;
@@ -221,8 +220,7 @@ struct hash_net6_elem {
static bool
hash_net6_data_equal(const struct hash_net6_elem *ip1,
- const struct hash_net6_elem *ip2,
- u32 *multi)
+ const struct hash_net6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
ip1->cidr == ip2->cidr;
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index 82b00337cfef..0869a62ef200 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -74,11 +74,9 @@ hash_netiface4_key_equal(const struct hash_netiface4_elem *ip1,
static bool
hash_netiface4_data_equal(const struct hash_netiface4_elem *ip1,
- const struct hash_netiface4_elem *ip2,
- u32 *multi)
+ const struct hash_netiface4_elem *ip2)
{
return hash_netiface4_key_equal(ip1, ip2) &&
- (++*multi) &&
(ip1->wildcard ?
strncmp(ip1->iface, ip2->iface, strlen(ip1->iface)) == 0 :
strcmp(ip1->iface, ip2->iface) == 0);
@@ -314,11 +312,9 @@ hash_netiface6_key_equal(const struct hash_netiface6_elem *ip1,
static bool
hash_netiface6_data_equal(const struct hash_netiface6_elem *ip1,
- const struct hash_netiface6_elem *ip2,
- u32 *multi)
+ const struct hash_netiface6_elem *ip2)
{
return hash_netiface6_key_equal(ip1, ip2) &&
- (++*multi) &&
(ip1->wildcard ?
strncmp(ip1->iface, ip2->iface, strlen(ip1->iface)) == 0 :
strcmp(ip1->iface, ip2->iface) == 0);
diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c
index 4e77e40ebc2c..a6bd24e3b1ac 100644
--- a/net/netfilter/ipset/ip_set_hash_netnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netnet.c
@@ -58,8 +58,7 @@ struct hash_netnet4_elem {
static bool
hash_netnet4_data_equal(const struct hash_netnet4_elem *ip1,
- const struct hash_netnet4_elem *ip2,
- u32 *multi)
+ const struct hash_netnet4_elem *ip2)
{
return ip1->ipcmp == ip2->ipcmp &&
ip1->ccmp == ip2->ccmp;
@@ -295,8 +294,7 @@ struct hash_netnet6_elem {
static bool
hash_netnet6_data_equal(const struct hash_netnet6_elem *ip1,
- const struct hash_netnet6_elem *ip2,
- u32 *multi)
+ const struct hash_netnet6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip[0].in6, &ip2->ip[0].in6) &&
ipv6_addr_equal(&ip1->ip[1].in6, &ip2->ip[1].in6) &&
diff --git a/net/netfilter/ipset/ip_set_hash_netport.c b/net/netfilter/ipset/ip_set_hash_netport.c
index dfe8f995abbe..a301aff67c6d 100644
--- a/net/netfilter/ipset/ip_set_hash_netport.c
+++ b/net/netfilter/ipset/ip_set_hash_netport.c
@@ -60,8 +60,7 @@ struct hash_netport4_elem {
static bool
hash_netport4_data_equal(const struct hash_netport4_elem *ip1,
- const struct hash_netport4_elem *ip2,
- u32 *multi)
+ const struct hash_netport4_elem *ip2)
{
return ip1->ip == ip2->ip &&
ip1->port == ip2->port &&
@@ -277,8 +276,7 @@ struct hash_netport6_elem {
static bool
hash_netport6_data_equal(const struct hash_netport6_elem *ip1,
- const struct hash_netport6_elem *ip2,
- u32 *multi)
+ const struct hash_netport6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6) &&
ip1->port == ip2->port &&
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index 0dc79d37beac..8575a2c5e215 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -59,8 +59,7 @@ struct hash_netportnet4_elem {
static bool
hash_netportnet4_data_equal(const struct hash_netportnet4_elem *ip1,
- const struct hash_netportnet4_elem *ip2,
- u32 *multi)
+ const struct hash_netportnet4_elem *ip2)
{
return ip1->ipcmp == ip2->ipcmp &&
ip1->ccmp == ip2->ccmp &&
@@ -352,8 +351,7 @@ struct hash_netportnet6_elem {
static bool
hash_netportnet6_data_equal(const struct hash_netportnet6_elem *ip1,
- const struct hash_netportnet6_elem *ip2,
- u32 *multi)
+ const struct hash_netportnet6_elem *ip2)
{
return ipv6_addr_equal(&ip1->ip[0].in6, &ip2->ip[0].in6) &&
ipv6_addr_equal(&ip1->ip[1].in6, &ip2->ip[1].in6) &&
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 10/13] netfilter: ipset: remove resize completely
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (8 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 09/13] netfilter: ipset: remove multi-flag Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 11/13] netfilter: ipset: remove trivial kvfree wrapper Florian Westphal
` (2 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
From: Jozsef Kadlecsik <kadlec@netfilter.org>
It is not needed anymore.
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/linux/netfilter/ipset/ip_set.h | 2 --
net/netfilter/ipset/ip_set_core.c | 5 +----
net/netfilter/ipset/ip_set_hash_gen.h | 1 -
3 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index 37cfda517c1b..9a0a2944700b 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -166,8 +166,6 @@ struct ip_set_type_variant {
/* Low level add/del/test functions */
ipset_adtfn adt[IPSET_ADT_MAX];
- /* When adding entries and set is full, try to resize the set */
- int (*resize)(struct ip_set *set, bool retried);
/* Destroy the set */
void (*destroy)(struct ip_set *set);
/* Flush the elements */
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 339726b33342..f1378e9540a1 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -1744,10 +1744,7 @@ call_ad(struct net *net, struct sock *ctnl, struct sk_buff *skb,
ret = set->variant->uadt(set, tb, adt, &lineno, flags, retried);
retried = true;
- } while (ret == -ERANGE ||
- (ret == -EAGAIN &&
- set->variant->resize &&
- (ret = set->variant->resize(set, retried)) == 0));
+ } while (ret == -ERANGE);
if (!ret || (ret == -IPSET_ERR_EXIST && eexist))
return 0;
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 973adb854082..225f30e5b749 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -1302,7 +1302,6 @@ static const struct ip_set_type_variant mtype_variant = {
.head = mtype_head,
.list = mtype_list,
.uref = mtype_uref,
- .resize = NULL,
.same_set = mtype_same_set,
.cancel_gc = mtype_cancel_gc,
};
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 11/13] netfilter: ipset: remove trivial kvfree wrapper
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (9 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 10/13] netfilter: ipset: remove resize completely Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 12/13] netfilter: ipset: use plain rcu_read_lock Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 13/13] netfilter: ipset: improve lockdep coverage Florian Westphal
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
This is a leftover from the days when kvmalloc/kvfree did not exist.
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/linux/netfilter/ipset/ip_set.h | 7 +++++--
net/netfilter/ipset/ip_set_bitmap_gen.h | 4 ++--
net/netfilter/ipset/ip_set_bitmap_ip.c | 2 +-
net/netfilter/ipset/ip_set_bitmap_ipmac.c | 2 +-
net/netfilter/ipset/ip_set_bitmap_port.c | 2 +-
net/netfilter/ipset/ip_set_core.c | 17 -----------------
6 files changed, 10 insertions(+), 24 deletions(-)
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index 9a0a2944700b..b2ff881e2ec1 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -318,8 +318,6 @@ extern int ip_set_test(ip_set_id_t id, const struct sk_buff *skb,
struct ip_set_adt_opt *opt);
/* Utility functions */
-extern void *ip_set_alloc(size_t size);
-extern void ip_set_free(void *members);
extern int ip_set_get_ipaddr4(struct nlattr *nla, __be32 *ipaddr);
extern int ip_set_get_ipaddr6(struct nlattr *nla, union nf_inet_addr *ipaddr);
extern size_t ip_set_elem_len(struct ip_set *set, struct nlattr *tb[],
@@ -333,6 +331,11 @@ extern bool ip_set_match_extensions(struct ip_set *set,
struct ip_set_ext *mext,
u32 flags, void *data);
+static inline void *ip_set_alloc(size_t size)
+{
+ return kvzalloc(size, GFP_KERNEL_ACCOUNT);
+}
+
static inline int
ip_set_get_hostipaddr4(struct nlattr *nla, u32 *ipaddr)
{
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index 0b6dd2e13433..409a7d07fa8d 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -62,8 +62,8 @@ mtype_destroy(struct ip_set *set)
if (set->dsize && set->extensions & IPSET_EXT_DESTROY)
mtype_ext_cleanup(set);
- ip_set_free(map->members);
- ip_set_free(map);
+ kvfree(map->members);
+ kvfree(map);
set->data = NULL;
}
diff --git a/net/netfilter/ipset/ip_set_bitmap_ip.c b/net/netfilter/ipset/ip_set_bitmap_ip.c
index 9307c89d2837..9252eb45d705 100644
--- a/net/netfilter/ipset/ip_set_bitmap_ip.c
+++ b/net/netfilter/ipset/ip_set_bitmap_ip.c
@@ -333,7 +333,7 @@ bitmap_ip_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
set->variant = &bitmap_ip;
if (!init_map_ip(set, map, first_ip, last_ip,
elements, hosts, netmask)) {
- ip_set_free(map);
+ kvfree(map);
return -ENOMEM;
}
if (tb[IPSET_ATTR_TIMEOUT]) {
diff --git a/net/netfilter/ipset/ip_set_bitmap_ipmac.c b/net/netfilter/ipset/ip_set_bitmap_ipmac.c
index 720650d0c3f1..ec8d93e8d2db 100644
--- a/net/netfilter/ipset/ip_set_bitmap_ipmac.c
+++ b/net/netfilter/ipset/ip_set_bitmap_ipmac.c
@@ -375,7 +375,7 @@ bitmap_ipmac_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
map->memsize = BITS_TO_LONGS(elements) * sizeof(unsigned long);
set->variant = &bitmap_ipmac;
if (!init_map_ipmac(set, map, first_ip, last_ip, elements)) {
- ip_set_free(map);
+ kvfree(map);
return -ENOMEM;
}
if (tb[IPSET_ATTR_TIMEOUT]) {
diff --git a/net/netfilter/ipset/ip_set_bitmap_port.c b/net/netfilter/ipset/ip_set_bitmap_port.c
index ea644bd420c5..6c5d31305d43 100644
--- a/net/netfilter/ipset/ip_set_bitmap_port.c
+++ b/net/netfilter/ipset/ip_set_bitmap_port.c
@@ -284,7 +284,7 @@ bitmap_port_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
map->memsize = BITS_TO_LONGS(elements) * sizeof(unsigned long);
set->variant = &bitmap_port;
if (!init_map_port(set, map, first_port, last_port)) {
- ip_set_free(map);
+ kvfree(map);
return -ENOMEM;
}
if (tb[IPSET_ATTR_TIMEOUT]) {
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index f1378e9540a1..632e30c7f35d 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -255,23 +255,6 @@ ip_set_type_unregister(struct ip_set_type *type)
}
EXPORT_SYMBOL_GPL(ip_set_type_unregister);
-/* Utility functions */
-void *
-ip_set_alloc(size_t size)
-{
- return kvzalloc(size, GFP_KERNEL_ACCOUNT);
-}
-EXPORT_SYMBOL_GPL(ip_set_alloc);
-
-void
-ip_set_free(void *members)
-{
- pr_debug("%p: free with %s\n", members,
- is_vmalloc_addr(members) ? "vfree" : "kfree");
- kvfree(members);
-}
-EXPORT_SYMBOL_GPL(ip_set_free);
-
static bool
flag_nested(const struct nlattr *nla)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 12/13] netfilter: ipset: use plain rcu_read_lock
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (10 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 11/13] netfilter: ipset: remove trivial kvfree wrapper Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 13/13] netfilter: ipset: improve lockdep coverage Florian Westphal
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
No need to disable/reenable softirqs.
Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_core.c | 4 ++--
net/netfilter/ipset/ip_set_hash_gen.h | 19 ++++++++-----------
net/netfilter/ipset/ip_set_hash_netnet.c | 8 ++++----
net/netfilter/ipset/ip_set_hash_netportnet.c | 8 ++++----
4 files changed, 18 insertions(+), 21 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 632e30c7f35d..856e53271b38 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -1868,9 +1868,9 @@ static int ip_set_utest(struct sk_buff *skb, const struct nfnl_info *info,
set->type->adt_policy, NULL))
return -IPSET_ERR_PROTOCOL;
- rcu_read_lock_bh();
+ rcu_read_lock();
ret = set->variant->uadt(set, tb, IPSET_TEST, &lineno, 0, 0);
- rcu_read_unlock_bh();
+ rcu_read_unlock();
/* Userspace can't trigger element to be re-added */
if (ret == -EAGAIN)
ret = 1;
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 225f30e5b749..cf48b7c50ca1 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -17,9 +17,6 @@
#define ipset_dereference_nfnl(p) \
rcu_dereference_protected(p, \
lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
-#define ipset_dereference_bh_nfnl(p) \
- rcu_dereference_bh_check(p, \
- lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
/* Kept for backward compatibility */
#define AHASH_INIT_SIZE 2
@@ -729,7 +726,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
int i;
#endif
- rcu_read_lock_bh();
+ rcu_read_lock();
#ifdef IP_SET_HASH_WITH_MULTI
{
struct rhlist_head *tmp, *list;
@@ -906,7 +903,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
ret = flag_exist ? 0 : -IPSET_ERR_EXIST;
out_rcu_unlock:
- rcu_read_unlock_bh();
+ rcu_read_unlock();
return ret;
}
@@ -920,7 +917,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
struct mtype_rht_elem *e;
int ret = -IPSET_ERR_EXIST;
- rcu_read_lock_bh();
+ rcu_read_lock();
#ifdef IP_SET_HASH_WITH_MULTI
{
struct rhlist_head *tmp, *list;
@@ -954,7 +951,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
ip_set_ext_destroy(set, &e->elem);
kfree_rcu(e, rcu);
out_unlock:
- rcu_read_unlock_bh();
+ rcu_read_unlock();
return ret ? -IPSET_ERR_EXIST : 0;
}
@@ -994,10 +991,10 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
pr_debug("test by nets\n");
retry:
multi = 0;
- nets0 = ipset_dereference_bh_nfnl(h->rnets[0]);
+ nets0 = rcu_dereference(h->rnets[0]);
seq0 = read_seqcount_begin(&nets0->seq);
#if IPSET_NET_COUNT == 2
- nets1 = ipset_dereference_bh_nfnl(h->rnets[1]);
+ nets1 = rcu_dereference(h->rnets[1]);
seq1 = read_seqcount_begin(&nets1->seq);
#endif
for (j = 0; j < nets0->len && !multi; j++) {
@@ -1076,7 +1073,7 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
int i;
#endif
- rcu_read_lock_bh();
+ rcu_read_lock();
#ifdef IP_SET_HASH_WITH_NETS
/* If we test an IP address and not a network address,
* try all possible network sizes
@@ -1114,7 +1111,7 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
ret = mtype_data_match(&e->elem, ext, mext, set, flags);
#endif
out:
- rcu_read_unlock_bh();
+ rcu_read_unlock();
return ret;
}
diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c
index a6bd24e3b1ac..6b768725e4cd 100644
--- a/net/netfilter/ipset/ip_set_hash_netnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netnet.c
@@ -148,10 +148,10 @@ hash_netnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netnet4_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- rcu_read_lock_bh();
+ rcu_read_lock();
e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
- rcu_read_unlock_bh();
+ rcu_read_unlock();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK;
@@ -382,10 +382,10 @@ hash_netnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netnet6_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- rcu_read_lock_bh();
+ rcu_read_lock();
e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
- rcu_read_unlock_bh();
+ rcu_read_unlock();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK;
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index 8575a2c5e215..d1e4ce2f2afa 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -155,10 +155,10 @@ hash_netportnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netportnet4_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- rcu_read_lock_bh();
+ rcu_read_lock();
e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
- rcu_read_unlock_bh();
+ rcu_read_unlock();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK;
@@ -444,10 +444,10 @@ hash_netportnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netportnet6_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- rcu_read_lock_bh();
+ rcu_read_lock();
e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
- rcu_read_unlock_bh();
+ rcu_read_unlock();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK;
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH nf-next v4 13/13] netfilter: ipset: improve lockdep coverage
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
` (11 preceding siblings ...)
2026-09-04 18:53 ` [PATCH nf-next v4 12/13] netfilter: ipset: use plain rcu_read_lock Florian Westphal
@ 2026-09-04 18:53 ` Florian Westphal
12 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 18:53 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, Florian Westphal
Avoid always-true arguments to rcu_dereference_protected(), they defeat
lockdep.
Add a few lockdep assertions to ip_set_init_comment() callpaths to have
more confidence in the correctness of the "Called from uadd only" claim.
Uadd implies nfnl mutex is held, make that explicit.
ip_set_comment_free() is called from different contexts, some hold
set->lock spinlock, some do not hold a lock at all but are safe because
the set is being destroyed.
Jozsef suggests to add a "dead" flag, make it so.
Suggested-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/linux/netfilter/ipset/ip_set.h | 5 +++++
net/netfilter/ipset/ip_set_bitmap_gen.h | 4 ++++
net/netfilter/ipset/ip_set_core.c | 22 +++++++++++++++-------
net/netfilter/ipset/ip_set_hash_gen.h | 6 ++----
net/netfilter/ipset/ip_set_list_set.c | 20 ++++++++++++++++++--
5 files changed, 44 insertions(+), 13 deletions(-)
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index b2ff881e2ec1..27270575d7ba 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -258,6 +258,8 @@ struct ip_set {
u8 extensions;
/* Create flags */
u8 flags;
+ /* set is being destroyed */
+ bool dead;
/* Default timeout value, if enabled */
u32 timeout;
/* Number of elements (vs timeout) */
@@ -272,6 +274,9 @@ struct ip_set {
void *data;
};
+#define ipset_dereference_locked(p, set) \
+ rcu_dereference_protected(p, lockdep_is_held(&set->lock))
+
static inline void
ip_set_ext_destroy(struct ip_set *set, void *data)
{
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index 409a7d07fa8d..907273d6c37b 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -137,6 +137,8 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
void *x = get_ext(set, map, e->id);
int ret = mtype_do_add(e, map, flags, set->dsize);
+ lockdep_assert_held(&set->lock);
+
if (ret == IPSET_ADD_FAILED) {
if (SET_WITH_TIMEOUT(set) &&
ip_set_timeout_expired(ext_timeout(x, set))) {
@@ -182,6 +184,8 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
const struct mtype_adt_elem *e = value;
void *x = get_ext(set, map, e->id);
+ lockdep_assert_held(&set->lock);
+
if (mtype_do_del(e, map))
return -IPSET_ERR_EXIST;
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 856e53271b38..9d1c765de86a 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -62,7 +62,7 @@ MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_IPSET);
#define ip_set(inst, id) \
ip_set_dereference(inst)[id]
#define ip_set_ref_netlink(inst,id) \
- rcu_dereference_raw((inst)->ip_set_list)[id]
+ rcu_dereference((inst)->ip_set_list)[id]
#define ip_set_dereference_nfnl(p) \
rcu_dereference_check(p, lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
@@ -323,14 +323,14 @@ ip_set_comment_uget(struct nlattr *tb)
return nla_data(tb);
}
-/* Called from uadd only, protected by the set spinlock.
+/* Called from uadd only, protected by the nfnl subsys mutex.
* The kadt functions don't use the comment extensions in any way.
*/
void
ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment,
const struct ip_set_ext *ext)
{
- struct ip_set_comment_rcu *c = rcu_dereference_protected(comment->c, 1);
+ struct ip_set_comment_rcu *c = ip_set_dereference_nfnl(comment->c);
size_t len = ext->comment ? strlen(ext->comment) : 0;
if (unlikely(c)) {
@@ -373,7 +373,9 @@ ip_set_comment_free(struct ip_set *set, void *ptr)
struct ip_set_comment *comment = ptr;
struct ip_set_comment_rcu *c;
- c = rcu_dereference_protected(comment->c, 1);
+ c = rcu_dereference_check(comment->c,
+ lockdep_is_held(&set->lock) ||
+ set->dead);
if (unlikely(!c))
return;
atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size);
@@ -1014,6 +1016,12 @@ static int ip_set_none(struct sk_buff *skb, const struct nfnl_info *info,
return -EOPNOTSUPP;
}
+static void ip_set_destroy_set(struct ip_set *set)
+{
+ set->dead = true;
+ set->variant->destroy(set);
+}
+
static int ip_set_create(struct sk_buff *skb, const struct nfnl_info *info,
const struct nlattr * const attr[])
{
@@ -1129,7 +1137,7 @@ static int ip_set_create(struct sk_buff *skb, const struct nfnl_info *info,
cleanup:
set->variant->cancel_gc(set);
- set->variant->destroy(set);
+ ip_set_destroy_set(set);
put_out:
module_put(set->type->me);
out:
@@ -1149,7 +1157,7 @@ ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] = {
static void
destroy_and_free_set(struct ip_set *set)
{
- set->variant->destroy(set);
+ ip_set_destroy_set(set);
module_put(set->type->me);
kfree(set);
}
@@ -1189,7 +1197,7 @@ _destroy_all_sets(struct ip_set_net *inst)
set = ip_set(inst, i);
if (set) {
ip_set(inst, i) = NULL;
- set->variant->destroy(set);
+ ip_set_destroy_set(set);
module_put(set->type->me);
kfree(set);
}
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index cf48b7c50ca1..e60602ecbdbd 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -12,8 +12,6 @@
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/ipset/ip_set.h>
-#define __ipset_dereference(p) \
- rcu_dereference_protected(p, 1)
#define ipset_dereference_nfnl(p) \
rcu_dereference_protected(p, \
lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
@@ -353,7 +351,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
struct net_prefix np;
spin_lock_bh(&set->lock);
- nets = __ipset_dereference(h->rnets[n]);
+ nets = ipset_dereference_locked(h->rnets[n], set);
/* Add in increasing prefix order, so larger cidr first */
for (i = 0, found = -1; i < nets->len; i++) {
np = READ_ONCE(nets->nets[i]);
@@ -426,7 +424,7 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
BUILD_BUG_ON(sizeof(struct net_prefix) != sizeof(u32));
spin_lock_bh(&set->lock);
- nets = __ipset_dereference(h->rnets[n]);
+ nets = ipset_dereference_locked(h->rnets[n], set);
for (i = 0, found = -1; i < nets->len; i++) {
np = READ_ONCE(nets->nets[i]);
if (np.count && np.cidr == cidr) {
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index 738f14a73684..ac749afe3801 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -139,15 +139,25 @@ list_set_kadt(struct ip_set *set, const struct sk_buff *skb,
return ret;
}
-/* Userspace interfaces: we are protected by the nfnl mutex */
-
static void
__list_set_del_rcu(struct rcu_head * rcu)
{
struct set_elem *e = container_of(rcu, struct set_elem, rcu);
struct ip_set *set = e->set;
+ /* element is no longer public, extensions can
+ * be removed without lock. This will trip the
+ * rcu_dereference_check() call in ip_set_comment_free(),
+ * so lock/unlock for debug kernels to avoid need to
+ * add a 'dead' flag to the comment extension.
+ */
+#ifdef CONFIG_PROVE_RCU
+ spin_lock_bh(&set->lock);
+#endif
ip_set_ext_destroy(set, e);
+#ifdef CONFIG_PROVE_RCU
+ spin_unlock_bh(&set->lock);
+#endif
kfree(e);
}
@@ -223,6 +233,8 @@ static void
list_set_init_extensions(struct ip_set *set, const struct ip_set_ext *ext,
struct set_elem *e)
{
+ lockdep_assert_held(&set->lock);
+
if (SET_WITH_COUNTER(set))
ip_set_init_counter(ext_counter(e, set), ext);
if (SET_WITH_COMMENT(set))
@@ -243,6 +255,8 @@ list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext,
struct set_elem *e, *n, *prev, *next;
bool flag_exist = flags & IPSET_FLAG_EXIST;
+ lockdep_assert_held(&set->lock);
+
/* Find where to add the new entry */
n = prev = next = NULL;
list_for_each_entry_rcu(e, &map->members, list) {
@@ -327,6 +341,8 @@ list_set_udel(struct ip_set *set, void *value, const struct ip_set_ext *ext,
struct set_adt_elem *d = value;
struct set_elem *e, *n, *next, *prev = NULL;
+ lockdep_assert_held(&set->lock);
+
list_for_each_entry_safe(e, n, &map->members, list) {
if (SET_WITH_TIMEOUT(set) &&
ip_set_timeout_expired(ext_timeout(e, set)))
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
@ 2026-09-04 19:29 ` Florian Westphal
2026-09-08 5:12 ` Herbert Xu
2026-09-09 4:17 ` Herbert Xu
2 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-04 19:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: Jozsef Kadlecsik, herbert, linux-crypto
Florian Westphal <fw@strlen.de> wrote:
> Will be used by upcoming ipset rhashtable conversion.
>
> "walk rht with unlink+free" triggers LLM reject pattern:
> "possible softirq CPU stall".
>
> "walk rht with unlink+free + cond_resched" triggers
> "possibly skipped elements".
>
> Add a helper to detach current hash backend storage from the
> rhashtable, then iterate and flush all contained elements.
As usual, I'm very incompetent and the patch sucks.
So question is merely if this function/idea is desirable/wanted
in the first place.
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
2026-09-04 19:29 ` Florian Westphal
@ 2026-09-08 5:12 ` Herbert Xu
2026-09-08 5:30 ` Florian Westphal
2026-09-09 4:17 ` Herbert Xu
2 siblings, 1 reply; 28+ messages in thread
From: Herbert Xu @ 2026-09-08 5:12 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, fw, linux-crypto
Florian Westphal <fw@strlen.de> wrote:
> Will be used by upcoming ipset rhashtable conversion.
>
> "walk rht with unlink+free" triggers LLM reject pattern:
> "possible softirq CPU stall".
>
> "walk rht with unlink+free + cond_resched" triggers
> "possibly skipped elements".
>
> Add a helper to detach current hash backend storage from the
> rhashtable, then iterate and flush all contained elements.
>
> Cc: herbert@gondor.apana.org.au
> Cc: linux-crypto@vger.kernel.org
> Link: https://sashiko.dev/#/patchset/20260828152256.8759-1-fw%40strlen.de
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
> Herbert: If you prefer to take this via the crypto tree, please
> let me know.
> Otherwise, an explicit Ack would be appreciated, so this can
> be handled via nf-next. Thanks.
>
> net/ipv6/ila/ could be converted to use this helper too.
>
> include/linux/rhashtable.h | 19 ++++++
> lib/rhashtable.c | 127 +++++++++++++++++++++++++++++++++++++
> 2 files changed, 146 insertions(+)
Sorry I wasn't paying attention.
So is the problem that there is no way to remove all elements for
a given key in an rhltable?
If that is what's needed then we should just add it for rhltable
since normal rhashtable's do not contain duplicate objects for a
given key.
Thanks,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-08 5:12 ` Herbert Xu
@ 2026-09-08 5:30 ` Florian Westphal
2026-09-08 9:04 ` Herbert Xu
0 siblings, 1 reply; 28+ messages in thread
From: Florian Westphal @ 2026-09-08 5:30 UTC (permalink / raw)
To: Herbert Xu; +Cc: netfilter-devel, kadlec, linux-crypto
Herbert Xu <herbert@gondor.apana.org.au> wrote:
> Florian Westphal <fw@strlen.de> wrote:
> > Will be used by upcoming ipset rhashtable conversion.
> >
> > "walk rht with unlink+free" triggers LLM reject pattern:
> > "possible softirq CPU stall".
> >
> > "walk rht with unlink+free + cond_resched" triggers
> > "possibly skipped elements".
> >
> > Add a helper to detach current hash backend storage from the
> > rhashtable, then iterate and flush all contained elements.
> >
> > Cc: herbert@gondor.apana.org.au
> > Cc: linux-crypto@vger.kernel.org
> > Link: https://sashiko.dev/#/patchset/20260828152256.8759-1-fw%40strlen.de
> > Assisted-by: Claude:claude-sonnet-5
> > Signed-off-by: Florian Westphal <fw@strlen.de>
> > ---
> > Herbert: If you prefer to take this via the crypto tree, please
> > let me know.
> > Otherwise, an explicit Ack would be appreciated, so this can
> > be handled via nf-next. Thanks.
> >
> > net/ipv6/ila/ could be converted to use this helper too.
> >
> > include/linux/rhashtable.h | 19 ++++++
> > lib/rhashtable.c | 127 +++++++++++++++++++++++++++++++++++++
> > 2 files changed, 146 insertions(+)
>
> Sorry I wasn't paying attention.
>
> So is the problem that there is no way to remove all elements for
> a given key in an rhltable?
No. The problem is that I am too dumb to remove them without having
an LLM tell me to go fuck myself.
> If that is what's needed then we should just add it for rhltable
> since normal rhashtable's do not contain duplicate objects for a
> given key.
I don't understand this response. This isn't about rhashtable vs.
rhltable. This is about my incompetence to flush an rhashtable or
rhashtable. Simple version:
rhashtable_walk_enter();
rhashtable_walk_start();
while ((he = rhashtable_walk_next())) {
if (IS_ERR(he)) {
if (PTR_ERR(he) != -EAGAIN) { .. break; }
continue;
}
rhashtable_remove_fast()
/* free */
}
rhashtable_walk_stop();
rhashtable_walk_exit();
... tells that this causes softirq lockup for huge tables.
Adding a lock-break after N elements via
if (flushed > 64) {
rhashtable_walk_stop();
cond_resched();
rhashtable_walk_start();
}
... tells that this will skip some elements.
... Full restart on atomic_read(->nelems) > 0 post loop
seems wrong to me too.
So, to get out of this I tried to add a 'flush all elements' helper to
the core that just replaces backend storage.
Does adding such a helper make sense or not? Thats the only question
here. If yes, I'll make a v2. If no, I will go back to V1. Unless you
have a better idea.
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-08 5:30 ` Florian Westphal
@ 2026-09-08 9:04 ` Herbert Xu
2026-09-08 9:56 ` Florian Westphal
0 siblings, 1 reply; 28+ messages in thread
From: Herbert Xu @ 2026-09-08 9:04 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, linux-crypto
On Tue, Sep 08, 2026 at 07:30:10AM +0200, Florian Westphal wrote:
>
> I don't understand this response. This isn't about rhashtable vs.
> rhltable. This is about my incompetence to flush an rhashtable or
> rhashtable. Simple version:
>
> rhashtable_walk_enter();
> rhashtable_walk_start();
You should never use rhashtable_walk for real work. It was only ever
intended for the very limited case of netlink dumping where stability
or accuracy was not a requirement.
> while ((he = rhashtable_walk_next())) {
> if (IS_ERR(he)) {
> if (PTR_ERR(he) != -EAGAIN) { .. break; }
> continue;
> }
>
> rhashtable_remove_fast()
So you want to free the entire table, right?
We already have rhashtable_free_and_destroy, any reason why it
doesn't work?
Cheers,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-08 9:04 ` Herbert Xu
@ 2026-09-08 9:56 ` Florian Westphal
2026-09-08 12:39 ` Herbert Xu
0 siblings, 1 reply; 28+ messages in thread
From: Florian Westphal @ 2026-09-08 9:56 UTC (permalink / raw)
To: Herbert Xu; +Cc: netfilter-devel, kadlec, linux-crypto
Herbert Xu <herbert@gondor.apana.org.au> wrote:
> > I don't understand this response. This isn't about rhashtable vs.
> > rhltable. This is about my incompetence to flush an rhashtable or
> > rhashtable. Simple version:
> >
> > rhashtable_walk_enter();
> > rhashtable_walk_start();
>
> You should never use rhashtable_walk for real work. It was only ever
> intended for the very limited case of netlink dumping where stability
> or accuracy was not a requirement.
>
> > while ((he = rhashtable_walk_next())) {
> > if (IS_ERR(he)) {
> > if (PTR_ERR(he) != -EAGAIN) { .. break; }
> > continue;
> > }
> >
> > rhashtable_remove_fast()
>
> So you want to free the entire table, right?
No, remove all elements. Concurrent insertion is not disabled.
> We already have rhashtable_free_and_destroy, any reason why it
> doesn't work?
It requires userspace or kernel don't add new elements.
AFAICS I can't "destroy, then re-init" without some external
mutex. What I could do is add another indirection, i.e.
struct htype {
- struct rhashtable ht; /* the hash table */
+ struct rhashtable *ht; /* the hash table */
struct net_prefixes __rcu *rnets[IPSET_NET_COUNT]; /* cidr prefixes */
};
... and then alloc+init an new ht + free old one.
But its not nice either.
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-08 9:56 ` Florian Westphal
@ 2026-09-08 12:39 ` Herbert Xu
2026-09-08 13:25 ` Florian Westphal
0 siblings, 1 reply; 28+ messages in thread
From: Herbert Xu @ 2026-09-08 12:39 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, linux-crypto
On Tue, Sep 08, 2026 at 11:56:09AM +0200, Florian Westphal wrote:
>
> > So you want to free the entire table, right?
>
> No, remove all elements. Concurrent insertion is not disabled.
So what's the desired semantics for insertions that occur while
you're flushing the table?
Cheers,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-08 12:39 ` Herbert Xu
@ 2026-09-08 13:25 ` Florian Westphal
2026-09-09 3:49 ` Herbert Xu
0 siblings, 1 reply; 28+ messages in thread
From: Florian Westphal @ 2026-09-08 13:25 UTC (permalink / raw)
To: Herbert Xu; +Cc: netfilter-devel, kadlec, linux-crypto
Herbert Xu <herbert@gondor.apana.org.au> wrote:
> > No, remove all elements. Concurrent insertion is not disabled.
>
> So what's the desired semantics for insertions that occur while
> you're flushing the table?
In current ipset home-grown hashes:
If the insertion is into a region not yet scanned -> will be dropped
If the insertion is into a region already scanned -> will exist after flush.
i.e. no guarantee that table is empty after flush, but all
elements that existed before the flush are gone.
For the rhashtable variant I had proposed:
If the insertion is before the moment future_tbl is updated: will be dropped
If the insertion is after future_tbl update: will exist after flush
which I think is matches existing semantics of ipsets homegrown hash
tables (that I'd like to replace with rhashtable/rhltable).
Thanks,
Florian
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-08 13:25 ` Florian Westphal
@ 2026-09-09 3:49 ` Herbert Xu
0 siblings, 0 replies; 28+ messages in thread
From: Herbert Xu @ 2026-09-09 3:49 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, linux-crypto
On Tue, Sep 08, 2026 at 03:25:13PM +0200, Florian Westphal wrote:
>
> In current ipset home-grown hashes:
>
> If the insertion is into a region not yet scanned -> will be dropped
> If the insertion is into a region already scanned -> will exist after flush.
OK now I understand what your patch is trying to do. We can
continue the discussion there.
Thanks,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
2026-09-04 19:29 ` Florian Westphal
2026-09-08 5:12 ` Herbert Xu
@ 2026-09-09 4:17 ` Herbert Xu
2026-09-09 14:45 ` Florian Westphal
2 siblings, 1 reply; 28+ messages in thread
From: Herbert Xu @ 2026-09-09 4:17 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, fw, linux-crypto
Florian Westphal <fw@strlen.de> wrote:
>
> +void rhashtable_flush_and_free(struct rhashtable *ht,
> + void (*free_fn)(void *ptr, void *arg),
> + void *arg)
> +{
> + struct bucket_table *tbl, *old_tbl, *last_tbl, *new_tbl;
> + struct rhashtable_walker *walker;
> + unsigned int i;
> +
> + new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
> + GFP_KERNEL);
> + if (!new_tbl)
> + new_tbl = bucket_table_alloc(ht, ht->p.min_size,
> + GFP_KERNEL | __GFP_NOFAIL);
> +
> + mutex_lock(&ht->mutex);
Before starting the work you need to stop all existing rehashes.
This is where it helps if you impose restrictions on the caller.
For example, if you could guarantee that no insertions or removals
occur during the call to rhashtable_flush_and_free (the duration of
the call does not include the actual freeing, which can occur later),
then it's much easier since you could just call cancel_work_sync on
the rehashes.
If you can't guarantee that, then we'll need some sort of a flag to
stop the rehashes manually.
> + /* Splice the new, empty table onto the tail of the live table ... */
> + old_tbl = rht_dereference(ht->tbl, ht);
> + do {
> + last_tbl = rhashtable_last_table(ht, old_tbl);
> + } while (rhashtable_rehash_attach(ht, last_tbl, new_tbl));
This loop is only needed if you impose no restrictions on the caller.
And if we're going to do this, then you'd need to fix the logic in
rhashtable_insert_rehash as otherwise it may interpret this as a
rehash (as opposed to a resize) which could fail with EBUSY.
In fact I think this could become a lot simpler since the two
tables don't need to mix at all.
Just add an old_tbl field to struct rhashtable, then move the
old table into it and directly write the new table to tbl. That
way none of the complications from overlapping insertions matter
anymore.
Cheers,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-09 4:17 ` Herbert Xu
@ 2026-09-09 14:45 ` Florian Westphal
2026-09-10 9:11 ` Herbert Xu
0 siblings, 1 reply; 28+ messages in thread
From: Florian Westphal @ 2026-09-09 14:45 UTC (permalink / raw)
To: Herbert Xu; +Cc: netfilter-devel, kadlec, linux-crypto
Herbert Xu <herbert@gondor.apana.org.au> wrote:
> Florian Westphal <fw@strlen.de> wrote:
> >
> > +void rhashtable_flush_and_free(struct rhashtable *ht,
> > + void (*free_fn)(void *ptr, void *arg),
> > + void *arg)
> > +{
> > + struct bucket_table *tbl, *old_tbl, *last_tbl, *new_tbl;
> > + struct rhashtable_walker *walker;
> > + unsigned int i;
> > +
> > + new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
> > + GFP_KERNEL);
> > + if (!new_tbl)
> > + new_tbl = bucket_table_alloc(ht, ht->p.min_size,
> > + GFP_KERNEL | __GFP_NOFAIL);
> > +
> > + mutex_lock(&ht->mutex);
>
> Before starting the work you need to stop all existing rehashes.
>
> This is where it helps if you impose restrictions on the caller.
> For example, if you could guarantee that no insertions or removals
> occur during the call to rhashtable_flush_and_free (the duration of
> the call does not include the actual freeing, which can occur later),
> then it's much easier since you could just call cancel_work_sync on
> the rehashes.
>
> If you can't guarantee that, then we'll need some sort of a flag to
> stop the rehashes manually.
Hmm. why? AFAICS the rehash worker holds ht->mutex, i.e. a rehash
might be pending, but it cannot run in parallel.
We called 'rcu_assign_pointer(ht->tbl, new_tbl);' before unlocking the
mutex, so a pending work (resize or rehash) will only see elements
that have been added right after that point.
Am I misreading anything here?
> > + /* Splice the new, empty table onto the tail of the live table ... */
> > + old_tbl = rht_dereference(ht->tbl, ht);
> > + do {
> > + last_tbl = rhashtable_last_table(ht, old_tbl);
> > + } while (rhashtable_rehash_attach(ht, last_tbl, new_tbl));
>
> This loop is only needed if you impose no restrictions on the caller.
Yes, no restrictions are imposed on the caller, at least thats the idea.
> In fact I think this could become a lot simpler since the two
> tables don't need to mix at all.
>
> Just add an old_tbl field to struct rhashtable, then move the
> old table into it and directly write the new table to tbl. That
> way none of the complications from overlapping insertions matter
> anymore.
Hmm. What do you think of this version?
+void rhashtable_flush_and_free(struct rhashtable *ht,
+ void (*free_fn)(void *ptr, void *arg),
+ void *arg)
+{
+ struct bucket_table *tbl, *old_tbl, *new_tbl;
+
+ new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
+ GFP_KERNEL);
+ if (!new_tbl)
+ new_tbl = bucket_table_alloc(ht, ht->p.min_size,
+ GFP_KERNEL | __GFP_NOFAIL);
+
+ /* Make sure we won't race with rhashtable_rehash_table() */
+ mutex_lock(&ht->mutex);
+ old_tbl = rcu_replace_pointer(ht->tbl, new_tbl, lockdep_rht_mutex_is_held(ht));
+ mutex_unlock(&ht->mutex);
+
+ /* Make sure all other CPUs no longer access the old table */
+ synchronize_rcu();
Rest as before. This way, we don't need to worry about concurrency,
no readers or writers can reach old_tbl anymore.
I'll give this a try.
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-09 14:45 ` Florian Westphal
@ 2026-09-10 9:11 ` Herbert Xu
2026-09-10 10:41 ` Florian Westphal
0 siblings, 1 reply; 28+ messages in thread
From: Herbert Xu @ 2026-09-10 9:11 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, linux-crypto
On Wed, Sep 09, 2026 at 04:45:15PM +0200, Florian Westphal wrote:
>
> Hmm. why? AFAICS the rehash worker holds ht->mutex, i.e. a rehash
> might be pending, but it cannot run in parallel.
Because you're chaining the new table onto the end of the old table
in the original patch. So a rehash could start moving entries
from the old table into the new table.
In fact it's still a problem if you set ht->tbl atomically, because
we need to differentiate between rehashes triggered by an insert
or remove that occurred on the old ht->tbl versus one that occured
on the new ht->tbl.
Since there is just one work struct it's impossible to tell the
difference.
> Hmm. What do you think of this version?
>
> +void rhashtable_flush_and_free(struct rhashtable *ht,
> + void (*free_fn)(void *ptr, void *arg),
> + void *arg)
> +{
> + struct bucket_table *tbl, *old_tbl, *new_tbl;
> +
> + new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
> + GFP_KERNEL);
> + if (!new_tbl)
> + new_tbl = bucket_table_alloc(ht, ht->p.min_size,
> + GFP_KERNEL | __GFP_NOFAIL);
> +
> + /* Make sure we won't race with rhashtable_rehash_table() */
> + mutex_lock(&ht->mutex);
> + old_tbl = rcu_replace_pointer(ht->tbl, new_tbl, lockdep_rht_mutex_is_held(ht));
> + mutex_unlock(&ht->mutex);
> +
> + /* Make sure all other CPUs no longer access the old table */
> + synchronize_rcu();
>
> Rest as before. This way, we don't need to worry about concurrency,
> no readers or writers can reach old_tbl anymore.
Yes I think this should work. But rehashes are still tricky if we allow
the caller to do insertions/removals during the flush operation.
It would be a lot simpler if you added the restriction that the
caller must not call insert/remove before the flush call returned.
Otherwise we would need to add a lot of complexity to rhashtable
in order to distinguish between insertions/removals on the table
before or after the flush.
Cheers,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-10 9:11 ` Herbert Xu
@ 2026-09-10 10:41 ` Florian Westphal
2026-09-11 11:56 ` Herbert Xu
0 siblings, 1 reply; 28+ messages in thread
From: Florian Westphal @ 2026-09-10 10:41 UTC (permalink / raw)
To: Herbert Xu; +Cc: netfilter-devel, kadlec, linux-crypto
Herbert Xu <herbert@gondor.apana.org.au> wrote:
> > +void rhashtable_flush_and_free(struct rhashtable *ht,
> > + void (*free_fn)(void *ptr, void *arg),
> > + void *arg)
> > +{
> > + struct bucket_table *tbl, *old_tbl, *new_tbl;
> > +
> > + new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
> > + GFP_KERNEL);
> > + if (!new_tbl)
> > + new_tbl = bucket_table_alloc(ht, ht->p.min_size,
> > + GFP_KERNEL | __GFP_NOFAIL);
> > +
> > + /* Make sure we won't race with rhashtable_rehash_table() */
> > + mutex_lock(&ht->mutex);
> > + old_tbl = rcu_replace_pointer(ht->tbl, new_tbl, lockdep_rht_mutex_is_held(ht));
> > + mutex_unlock(&ht->mutex);
> > +
> > + /* Make sure all other CPUs no longer access the old table */
> > + synchronize_rcu();
> >
> > Rest as before. This way, we don't need to worry about concurrency,
> > no readers or writers can reach old_tbl anymore.
>
> Yes I think this should work. But rehashes are still tricky if we allow
> the caller to do insertions/removals during the flush operation.
What problems do you see? Shouldn't a rehash be a no-op, since it
can't see anything in the hashtable once the flusher releases the
mutex post replace_pointer() call?
If a rehash is in progress, the flusher will block on ht->mutex.
Same for removals, no entries will be found, insertions are ok too as they
add to the new backing store.
> It would be a lot simpler if you added the restriction that the
> caller must not call insert/remove before the flush call returned.
How to assert that? Or should that be an 'external' requirement?
That will, at least afaics, defeat rhashtables purpose, I'd have to
either serialize by external single lock, or add a flag that would
'eat' new insertions while the flush is running.
I could do the latter, if that helps, but I don't see why its
needed with the proposed 'replace, then synchronize_rcu()'.
> Otherwise we would need to add a lot of complexity to rhashtable
> in order to distinguish between insertions/removals on the table
> before or after the flush.
I would like to avoid extra complexity or even new tests in insert/removal
fast paths.
Thanks for reviewing!
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-10 10:41 ` Florian Westphal
@ 2026-09-11 11:56 ` Herbert Xu
2026-09-11 12:54 ` Florian Westphal
0 siblings, 1 reply; 28+ messages in thread
From: Herbert Xu @ 2026-09-11 11:56 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter-devel, kadlec, linux-crypto
On Thu, Sep 10, 2026 at 12:41:27PM +0200, Florian Westphal wrote:
>
> What problems do you see? Shouldn't a rehash be a no-op, since it
> can't see anything in the hashtable once the flusher releases the
> mutex post replace_pointer() call?
The problem is that the rehash is just a deferred work which carries
no state. So it doesn't know why it was triggered.
If we go with the atomic replacement, the issue then comes down
to the fact that if a rehash was triggered before replacement
then it should do nothing, while if a rehash was triggered after
replacement then it should do something. However, because the
rehash doesn't have state it doesn't know.
If you do a rehash when it shouldn't be done, you may end up with
spurious EBUSY errors because it thinks the hashtable is under
attack. While if you skip a rehash when it should've be done,
then it could leave the hashtable in a suboptimal state. But
this should correct itself eventually, so perhaps this would be
the easiest solution.
> > It would be a lot simpler if you added the restriction that the
> > caller must not call insert/remove before the flush call returned.
>
> How to assert that? Or should that be an 'external' requirement?
It's certainly not easy to detect this from within rhashtable.
> That will, at least afaics, defeat rhashtables purpose, I'd have to
> either serialize by external single lock, or add a flag that would
> 'eat' new insertions while the flush is running.
If there is no natural way of expressing it in the caller, then
we might as well add the complexity to rhashtable. Let me look
into this.
Cheers,
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper
2026-09-11 11:56 ` Herbert Xu
@ 2026-09-11 12:54 ` Florian Westphal
0 siblings, 0 replies; 28+ messages in thread
From: Florian Westphal @ 2026-09-11 12:54 UTC (permalink / raw)
To: Herbert Xu; +Cc: netfilter-devel, kadlec, linux-crypto
Herbert Xu <herbert@gondor.apana.org.au> wrote:
> > That will, at least afaics, defeat rhashtables purpose, I'd have to
> > either serialize by external single lock, or add a flag that would
> > 'eat' new insertions while the flush is running.
>
> If there is no natural way of expressing it in the caller, then
> we might as well add the complexity to rhashtable. Let me look
> into this.
Thanks Herbert.
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-09-11 12:54 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
2026-09-04 19:29 ` Florian Westphal
2026-09-08 5:12 ` Herbert Xu
2026-09-08 5:30 ` Florian Westphal
2026-09-08 9:04 ` Herbert Xu
2026-09-08 9:56 ` Florian Westphal
2026-09-08 12:39 ` Herbert Xu
2026-09-08 13:25 ` Florian Westphal
2026-09-09 3:49 ` Herbert Xu
2026-09-09 4:17 ` Herbert Xu
2026-09-09 14:45 ` Florian Westphal
2026-09-10 9:11 ` Herbert Xu
2026-09-10 10:41 ` Florian Westphal
2026-09-11 11:56 ` Herbert Xu
2026-09-11 12:54 ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 02/13] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 03/13] netfilter: ipset: add rhltable " Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 04/13] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 05/13] netfilter: ipset: re-add forceadd support Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 06/13] netfilter: ipset: also report mem size for cidr storage to userspace Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 07/13] netfilter: ipset: remove obsolete data_next stubs Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 08/13] netfilter: ipset: remove last region lock usage Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 09/13] netfilter: ipset: remove multi-flag Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 10/13] netfilter: ipset: remove resize completely Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 11/13] netfilter: ipset: remove trivial kvfree wrapper Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 12/13] netfilter: ipset: use plain rcu_read_lock Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 13/13] netfilter: ipset: improve lockdep coverage Florian Westphal
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox