All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable
@ 2026-07-14 13:18 Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 01/12] netfilter: ipset: rework cidr bookkeeping Florian Westphal
                   ` (13 more replies)
  0 siblings, 14 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Hi,

This is an initial RFC patchset to convert the hash types to rhashtable.
Main conversion is in patch 8.  First patches contain small drive-by
fixes, patches after 8 contain further simplifications/cleanups.
Last patch adds back the FORCEADD support dropped in the conversion
commit (diff was getting too large...).

The next step is to go through the ipset test failures and figure out
which ones hint at actual bugs and which ones are just harmless cosmetic
issues (that could be suppressed by tinkering with diff.sh in ipset tests).

Notable likely valid remaining bugs:
 - iphash: IP: Compare sorted save and restore: [..] FAILED
 - hash:net,iface.t: Check 10.0.1.1 with eth0:
 Failed test: ../src/ipset [..] 10.0.1.1,eth0
 Warning: 10.0.1.1,eth0 is in set test.

I'd like to eventually get rid of more set->lock places, remove all
usage of rcu_dereference_protected(.. , 1), but thats not too urgent
atm.

1) Rework ipset CIDR bookkeeping.  I had to remove this one from the last
nf batch at the last minute because of a buildbot report. See next patch.

2) Fix a few nits in patch 1), to be squash-merged.

3) Add small wrappers for hash and bucket sizes to reduce noise in the
actual conversion patch.

4) add and use tmtype_del_cidr_all helper to simplify the upcoming
rewrite.

5) Use ip_set_init_comment_slow to prevent race conditions in hash ipset
types. Add lockdep annotations.

6) Same as 5, but for remove: adds ip_set_ext_destroy_slow.

7) Add rhashtable boilerplate stubs to ipset. Initialize and destroy the
rhashtable without ever adding elements.

8) Replace ipset's internal hash table with rhashtable.  FORCEADD is
removed here, and added back in last patch.

9) Use plain rcu_read_lock, not _bh variants.

10) Better lockdep annotations in ipset_dereference. Add assertions to
more places.

11) Remove the last region lock usage in ipset. Move lock responsibility to
kadt, uadt, and flush callbacks.

12) Re-add forceadd support for rhashtable in ipset. Implement
mtype_remove_random() to evict elements when the set is full.

Florian Westphal (11):
  netfilter: ipset: rework cidr bookkeeping fixups
  netfilter: ipset: add small wrappers for hash and bucket sizes
  netfilter: ipset: add and use mtype_del_cidr_all helper
  netfilter: ipset: add and use ip_set_init_comment_slow
  netfilter: ipset: add and use ip_set_ext_destroy_slow
  netfilter: ipset: add rhashtable boilerplate stubs
  netfilter: ipset: replace internal hash table with rhashtable
  netfilter: ipset: use plain rcu_read_lock
  netfilter: ipset: use correct lockdep annotation in ipset_dereference
  netfilter: ipset: remove last region lock usage
  netfilter: ipset: re-add forceadd support for rhashtable

Jozsef Kadlecsik (1):
  netfilter: ipset: rework cidr bookkeeping

 include/linux/netfilter/ipset/ip_set.h       |   44 +-
 net/netfilter/ipset/ip_set_bitmap_gen.h      |    6 +
 net/netfilter/ipset/ip_set_bitmap_ip.c       |   11 +-
 net/netfilter/ipset/ip_set_bitmap_ipmac.c    |    9 +-
 net/netfilter/ipset/ip_set_bitmap_port.c     |   11 +-
 net/netfilter/ipset/ip_set_core.c            |   35 +-
 net/netfilter/ipset/ip_set_hash_gen.h        | 1552 ++++++------------
 net/netfilter/ipset/ip_set_hash_ipportnet.c  |    4 +-
 net/netfilter/ipset/ip_set_hash_net.c        |    4 +-
 net/netfilter/ipset/ip_set_hash_netiface.c   |    4 +-
 net/netfilter/ipset/ip_set_hash_netnet.c     |   12 +-
 net/netfilter/ipset/ip_set_hash_netport.c    |    4 +-
 net/netfilter/ipset/ip_set_hash_netportnet.c |   12 +-
 net/netfilter/ipset/ip_set_list_set.c        |   13 +
 14 files changed, 629 insertions(+), 1092 deletions(-)

-- 
2.54.0

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 01/12] netfilter: ipset: rework cidr bookkeeping
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 02/12] netfilter: ipset: rework cidr bookkeeping fixups Florian Westphal
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

From: Jozsef Kadlecsik <kadlec@netfilter.org>

According to sashiko, the current bookkeeping of cidr values
are unsafe on weakly-ordered architectures. Replace the
in-place updating with an RCU based method: create the new
bookeeping structure, update and replace the old one with
the new. Downside that we need to allocate memory when deleting
a cidr entry - in case of memory pressure fall back to leave
holes which possibility is taken into account at evaluation time.

Thanks to Pablo (Pablo Neira Ayuso <pablo@netfilter.org>) and
Cyntia (Cynthia <cynthia@kosmx.dev>) for helping me in debugging
which resulted the patch "netfilter: ipset: allocate the proper
memory for the generic hash structure" on which this very patch
depends.

Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_hash_gen.h        | 230 ++++++++++++-------
 net/netfilter/ipset/ip_set_hash_ipportnet.c  |   4 +-
 net/netfilter/ipset/ip_set_hash_net.c        |   4 +-
 net/netfilter/ipset/ip_set_hash_netiface.c   |   4 +-
 net/netfilter/ipset/ip_set_hash_netnet.c     |  12 +-
 net/netfilter/ipset/ip_set_hash_netport.c    |   4 +-
 net/netfilter/ipset/ip_set_hash_netportnet.c |  12 +-
 7 files changed, 177 insertions(+), 93 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 8231317b0f1f..a6b282ad8c48 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -99,9 +99,16 @@ struct htable {
 #endif
 
 /* Book-keeping of the prefixes added to the set */
+struct net_prefix {
+	u8 cidr;			/* the cidr value */
+	u32 count;			/* number of elements of this cidr */
+};
+
 struct net_prefixes {
-	u32 nets[IPSET_NET_COUNT]; /* number of elements for this cidr */
-	u8 cidr[IPSET_NET_COUNT];  /* the cidr value */
+	struct rcu_head rcu;
+	u8 len;
+	struct net_prefix nets[]
+		__aligned(__alignof__(u64));
 };
 
 /* Compute the hash table size */
@@ -127,11 +134,6 @@ htable_size(u8 hbits)
 #else
 #define __CIDR(cidr, i)		(cidr)
 #endif
-
-/* cidr + 1 is stored in net_prefixes to support /0 */
-#define NCIDR_PUT(cidr)		((cidr) + 1)
-#define NCIDR_GET(cidr)		((cidr) - 1)
-
 #ifdef IP_SET_HASH_WITH_NETS_PACKED
 /* When cidr is packed with nomatch, cidr - 1 is stored in the data entry */
 #define DCIDR_PUT(cidr)		((cidr) - 1)
@@ -141,21 +143,9 @@ htable_size(u8 hbits)
 #define DCIDR_GET(cidr, i)	__CIDR(cidr, i)
 #endif
 
-#define INIT_CIDR(cidr, host_mask)	\
-	DCIDR_PUT(((cidr) ? NCIDR_GET(cidr) : host_mask))
-
-#ifdef IP_SET_HASH_WITH_NET0
-/* cidr from 0 to HOST_MASK value and c = cidr + 1 */
-#define NLEN			(HOST_MASK + 1)
-#define CIDR_POS(c)		((c) - 1)
-#else
-/* cidr from 1 to HOST_MASK value and c = cidr + 1 */
-#define NLEN			HOST_MASK
-#define CIDR_POS(c)		((c) - 2)
-#endif
+#define INIT_CIDR(n, host_mask)	\
+	DCIDR_PUT((n)->len ? (n)->nets[0].cidr : host_mask)
 
-#else
-#define NLEN			0
 #endif /* IP_SET_HASH_WITH_NETS */
 
 #define SET_ELEM_EXPIRED(set, d)	\
@@ -292,6 +282,7 @@ static const union nf_inet_addr zeromask = {};
 /* The generic hash structure */
 struct htype {
 	struct htable __rcu *table; /* 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 */
 	u32 initval;		/* random jhash init value */
@@ -302,9 +293,6 @@ struct htype {
 #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 */
-#endif
-#ifdef IP_SET_HASH_WITH_NETS
-	struct net_prefixes nets[NLEN]; /* book-keeping of prefixes */
 #endif
 	/* Because 'next' is IPv4/IPv6 dependent, no elements of this
 	 * structure and referred in create() may come after 'next'.
@@ -326,50 +314,88 @@ struct mtype_resize_ad {
 /* Network cidr size book keeping when the hash stores different
  * sized networks. cidr == real cidr + 1 to support /0.
  */
-static void
+static int
 mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 {
-	int i, j;
+	struct net_prefixes *nets, *tmp;
+	int i, j, found, len = 0, ret = 0;
 
 	spin_lock_bh(&set->lock);
+	nets = __ipset_dereference(h->rnets[n]);
 	/* Add in increasing prefix order, so larger cidr first */
-	for (i = 0, j = -1; i < NLEN && h->nets[i].cidr[n]; i++) {
-		if (j != -1) {
+	for (i = 0, found = -1; i < nets->len; i++) {
+		if (nets->nets[i].count)
+			len++;
+		if (found != -1) {
 			continue;
-		} else if (h->nets[i].cidr[n] < cidr) {
-			j = i;
-		} else if (h->nets[i].cidr[n] == cidr) {
-			h->nets[CIDR_POS(cidr)].nets[n]++;
+		} else if (nets->nets[i].cidr < cidr) {
+			found = i;
+		} else if (nets->nets[i].cidr == cidr) {
+			nets->nets[i].count++;
 			goto unlock;
 		}
 	}
-	if (j != -1) {
-		for (; i > j; i--)
-			h->nets[i].cidr[n] = h->nets[i - 1].cidr[n];
+	len++;
+	tmp = kzalloc(sizeof(struct net_prefixes) +
+		      len * sizeof(struct net_prefix), GFP_ATOMIC);
+	if (!tmp)
+		return -ENOMEM;
+	tmp->len = len;
+	for (i = 0, j = 0; i < nets->len; i++) {
+		if (!nets->nets[i].count)
+			continue;
+		if (i == found) {
+			tmp->nets[j].cidr = cidr;
+			tmp->nets[j++].count = 1;
+		}
+		tmp->nets[j].cidr = nets->nets[i].cidr;
+		tmp->nets[j++].count = nets->nets[i].count;
+	}
+	if (found == -1) {
+		tmp->nets[j].cidr = cidr;
+		tmp->nets[j].count = 1;
 	}
-	h->nets[i].cidr[n] = cidr;
-	h->nets[CIDR_POS(cidr)].nets[n] = 1;
+	rcu_assign_pointer(h->rnets[n], tmp);
+	kfree_rcu(nets, rcu);
 unlock:
 	spin_unlock_bh(&set->lock);
+	return ret;
 }
 
 static void
 mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 {
-	u8 i, j, net_end = NLEN - 1;
+	struct net_prefixes *nets, *tmp;
+	u8 i, j, found, len = 0;
 
 	spin_lock_bh(&set->lock);
-	for (i = 0; i < NLEN; i++) {
-		if (h->nets[i].cidr[n] != cidr)
-			continue;
-		h->nets[CIDR_POS(cidr)].nets[n]--;
-		if (h->nets[CIDR_POS(cidr)].nets[n] > 0)
-			goto unlock;
-		for (j = i; j < net_end && h->nets[j].cidr[n]; j++)
-			h->nets[j].cidr[n] = h->nets[j + 1].cidr[n];
-		h->nets[j].cidr[n] = 0;
+	nets = __ipset_dereference(h->rnets[n]);
+	for (i = 0, found = -1; i < nets->len; i++) {
+		if (nets->nets[i].count)
+			len++;
+		if (nets->nets[i].cidr == cidr)
+			found = i;
+	}
+	if (unlikely(found == -1))
+		return;
+	nets->nets[found].count--;
+	if (nets->nets[found].count)
 		goto unlock;
+	len--;
+	tmp = kzalloc(sizeof(struct net_prefixes) +
+		      len * sizeof(struct net_prefix), GFP_ATOMIC);
+	if (!tmp)
+		/* Leave a hole */
+		return;
+	tmp->len = len;
+	for (i = 0, j = 0; i < nets->len; i++) {
+		if (!nets->nets[i].count || i == found)
+			continue;
+		tmp->nets[j].cidr = nets->nets[i].cidr;
+		tmp->nets[j++].count = nets->nets[i].count;
 	}
+	rcu_assign_pointer(h->rnets[n], tmp);
+	kfree_rcu(nets, rcu);
 unlock:
 	spin_unlock_bh(&set->lock);
 }
@@ -402,6 +428,9 @@ static void
 mtype_flush(struct ip_set *set)
 {
 	struct htype *h = set->data;
+#ifdef IP_SET_HASH_WITH_NETS
+	struct net_prefixes *nets, *tmp;
+#endif
 	struct htable *t;
 	struct hbucket *n;
 	u32 r, i;
@@ -425,7 +454,19 @@ mtype_flush(struct ip_set *set)
 		spin_unlock_bh(&t->hregion[r].lock);
 	}
 #ifdef IP_SET_HASH_WITH_NETS
-	memset(h->nets, 0, sizeof(h->nets));
+	for (i = 0; i < IPSET_NET_COUNT; i++) {
+		nets = ipset_dereference_nfnl(h->rnets[i]);
+		tmp = kzalloc(sizeof(struct net_prefixes), GFP_ATOMIC);
+		if (!tmp) {
+			u8 j;
+
+			for (j = 0; j < nets->len; j++)
+				nets->nets[j].count = 0;
+		} else {
+			rcu_assign_pointer(h->rnets[i], tmp);
+			kfree_rcu(nets, rcu);
+		}
+	}
 #endif
 }
 
@@ -433,6 +474,9 @@ mtype_flush(struct ip_set *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;
 
@@ -446,6 +490,11 @@ mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy)
 		kfree(n);
 	}
 
+#ifdef IP_SET_HASH_WITH_NETS
+	if (ext_destroy)
+		for (i = 0; i < IPSET_NET_COUNT; i++)
+			kfree(h->rnets[i]);
+#endif
 	ip_set_free(t->hregion);
 	ip_set_free(t);
 }
@@ -519,8 +568,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
 #ifdef IP_SET_HASH_WITH_NETS
 			for (k = 0; k < IPSET_NET_COUNT; k++)
 				mtype_del_cidr(set, h,
-					NCIDR_PUT(DCIDR_GET(data->cidr, k)),
-					k);
+					DCIDR_GET(data->cidr, k), k);
 #endif
 			t->hregion[r].elements--;
 			ip_set_ext_destroy(set, data);
@@ -950,8 +998,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 #ifdef IP_SET_HASH_WITH_NETS
 			for (i = 0; i < IPSET_NET_COUNT; i++)
 				mtype_del_cidr(set, h,
-					NCIDR_PUT(DCIDR_GET(data->cidr, i)),
-					i);
+					DCIDR_GET(data->cidr, i), i);
 #endif
 			ip_set_ext_destroy(set, data);
 			t->hregion[r].elements--;
@@ -996,7 +1043,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	t->hregion[r].elements++;
 #ifdef IP_SET_HASH_WITH_NETS
 	for (i = 0; i < IPSET_NET_COUNT; i++)
-		mtype_add_cidr(set, h, NCIDR_PUT(DCIDR_GET(d->cidr, i)), i);
+		mtype_add_cidr(set, h, DCIDR_GET(d->cidr, i), i);
 #endif
 	memcpy(data, d, sizeof(struct mtype_elem));
 overwrite_extensions:
@@ -1110,7 +1157,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 #ifdef IP_SET_HASH_WITH_NETS
 		for (j = 0; j < IPSET_NET_COUNT; j++)
 			mtype_del_cidr(set, h,
-				       NCIDR_PUT(DCIDR_GET(d->cidr, j)), j);
+				DCIDR_GET(d->cidr, j), j);
 #endif
 		ip_set_ext_destroy(set, data);
 
@@ -1193,28 +1240,37 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
 {
 	struct htype *h = set->data;
 	struct htable *t = rcu_dereference_bh(h->table);
+	struct net_prefixes *nets0;
 	struct hbucket *n;
 	struct mtype_elem *data;
 #if IPSET_NET_COUNT == 2
+	struct net_prefixes *nets1;
 	struct mtype_elem orig = *d;
-	int ret, i, j = 0, k;
+	int ret, i, j, k;
 #else
-	int ret, i, j = 0;
+	int ret, i, j;
 #endif
 	u32 key, multi = 0;
 	u8 pos;
 
 	pr_debug("test by nets\n");
-	for (; j < NLEN && h->nets[j].cidr[0] && !multi; j++) {
+	rcu_read_lock_bh();
+	nets0 = rcu_dereference_bh(h->rnets[0]);
+#if IPSET_NET_COUNT == 2
+	nets1 = rcu_dereference_bh(h->rnets[1]);
+#endif
+	for (j = 0; j < nets0->len && !multi; j++) {
+		if (!nets0->nets[j].count)
+			continue;
 #if IPSET_NET_COUNT == 2
 		mtype_data_reset_elem(d, &orig);
-		mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]), false);
-		for (k = 0; k < NLEN && h->nets[k].cidr[1] && !multi;
-		     k++) {
-			mtype_data_netmask(d, NCIDR_GET(h->nets[k].cidr[1]),
-					   true);
+		mtype_data_netmask(d, nets0->nets[j].cidr, false);
+		for (k = 0; k < nets1->len && !multi; k++) {
+			if (!nets1->nets[k].count)
+				continue;
+			mtype_data_netmask(d, nets1->nets[k].cidr, true);
 #else
-		mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]));
+		mtype_data_netmask(d, nets0->nets[j].cidr);
 #endif
 		key = HKEY(d, h->initval, t->htable_bits);
 		n = rcu_dereference_bh(hbucket(t, key));
@@ -1229,7 +1285,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
 				continue;
 			ret = mtype_data_match(data, ext, mext, set, flags);
 			if (ret != 0)
-				return ret;
+				goto unlock;
 #ifdef IP_SET_HASH_WITH_MULTI
 			/* No match, reset multiple match flag */
 			multi = 0;
@@ -1239,7 +1295,10 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
 		}
 #endif
 	}
-	return 0;
+	ret = 0;
+unlock:
+	rcu_read_unlock_bh();
+	return ret;
 }
 #endif
 
@@ -1504,6 +1563,9 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 	int ret __attribute__((unused)) = 0;
 	u8 netmask = set->family == NFPROTO_IPV4 ? 32 : 128;
 	union nf_inet_addr bitmask = onesmask;
+#endif
+#ifdef IP_SET_HASH_WITH_NETS
+	struct net_prefixes *nets;
 #endif
 	size_t hsize;
 	struct htype *h;
@@ -1604,21 +1666,25 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 	 */
 	hbits = fls(hashsize - 1);
 	hsize = htable_size(hbits);
-	if (hsize == 0) {
-		kfree(h);
-		return -ENOMEM;
-	}
+	if (hsize == 0)
+		goto free_h;
 	t = ip_set_alloc(hsize);
-	if (!t) {
-		kfree(h);
-		return -ENOMEM;
-	}
+	if (!t)
+		goto free_h;
 	t->hregion = ip_set_alloc(ahash_sizeof_regions(hbits));
-	if (!t->hregion) {
-		ip_set_free(t);
-		kfree(h);
-		return -ENOMEM;
+	if (!t->hregion)
+		goto free_t;
+#ifdef IP_SET_HASH_WITH_NETS
+	for (i = 0; i < IPSET_NET_COUNT; i++) {
+		nets = kzalloc(sizeof(struct net_prefixes), GFP_KERNEL);
+		if (!nets) {
+			while (i > 0)
+				kfree(h->rnets[--i]);
+			goto free_hregion;
+		}
+		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++)
@@ -1682,6 +1748,16 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 		 t->htable_bits, h->maxelem, set->data, t);
 
 	return 0;
+
+#ifdef IP_SET_HASH_WITH_NETS
+free_hregion:
+	ip_set_free(t->hregion);
+#endif
+free_t:
+	ip_set_free(t);
+free_h:
+	kfree(h);
+	return -ENOMEM;
 }
 #endif /* IP_SET_EMIT_CREATE */
 
diff --git a/net/netfilter/ipset/ip_set_hash_ipportnet.c b/net/netfilter/ipset/ip_set_hash_ipportnet.c
index 2d6652d43199..195853a25b06 100644
--- a/net/netfilter/ipset/ip_set_hash_ipportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_ipportnet.c
@@ -138,7 +138,7 @@ hash_ipportnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
 	const struct hash_ipportnet4 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_ipportnet4_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
 
@@ -398,7 +398,7 @@ hash_ipportnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
 	const struct hash_ipportnet6 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_ipportnet6_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
 
diff --git a/net/netfilter/ipset/ip_set_hash_net.c b/net/netfilter/ipset/ip_set_hash_net.c
index ce0a9ce5a91f..092f3c9281b8 100644
--- a/net/netfilter/ipset/ip_set_hash_net.c
+++ b/net/netfilter/ipset/ip_set_hash_net.c
@@ -117,7 +117,7 @@ hash_net4_kadt(struct ip_set *set, const struct sk_buff *skb,
 	const struct hash_net4 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_net4_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
 
@@ -291,7 +291,7 @@ hash_net6_kadt(struct ip_set *set, const struct sk_buff *skb,
 	const struct hash_net6 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_net6_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
 
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index 30a655e5c4fd..b44b95f766b7 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -161,7 +161,7 @@ hash_netiface4_kadt(struct ip_set *set, const struct sk_buff *skb,
 	struct hash_netiface4 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_netiface4_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 		.elem = 1,
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
@@ -382,7 +382,7 @@ hash_netiface6_kadt(struct ip_set *set, const struct sk_buff *skb,
 	struct hash_netiface6 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_netiface6_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 		.elem = 1,
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c
index 8fbe649c9dd3..f7c8a1cc30fc 100644
--- a/net/netfilter/ipset/ip_set_hash_netnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netnet.c
@@ -149,8 +149,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);
 
-	e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
-	e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+	rcu_read_lock_bh();
+	e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+	e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+	rcu_read_unlock_bh();
 	if (adt == IPSET_TEST)
 		e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK;
 
@@ -388,8 +390,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);
 
-	e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
-	e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+	rcu_read_lock_bh();
+	e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+	e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+	rcu_read_unlock_bh();
 	if (adt == IPSET_TEST)
 		e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK;
 
diff --git a/net/netfilter/ipset/ip_set_hash_netport.c b/net/netfilter/ipset/ip_set_hash_netport.c
index d1a0628df4ef..5de4b511de76 100644
--- a/net/netfilter/ipset/ip_set_hash_netport.c
+++ b/net/netfilter/ipset/ip_set_hash_netport.c
@@ -133,7 +133,7 @@ hash_netport4_kadt(struct ip_set *set, const struct sk_buff *skb,
 	const struct hash_netport4 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_netport4_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
 
@@ -353,7 +353,7 @@ hash_netport6_kadt(struct ip_set *set, const struct sk_buff *skb,
 	const struct hash_netport6 *h = set->data;
 	ipset_adtfn adtfn = set->variant->adt[adt];
 	struct hash_netport6_elem e = {
-		.cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+		.cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
 	};
 	struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
 
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index bf4f91b78e1d..6291532be7a5 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -157,8 +157,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);
 
-	e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
-	e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+	rcu_read_lock_bh();
+	e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+	e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+	rcu_read_unlock_bh();
 	if (adt == IPSET_TEST)
 		e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK;
 
@@ -452,8 +454,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);
 
-	e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
-	e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+	rcu_read_lock_bh();
+	e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+	e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+	rcu_read_unlock_bh();
 	if (adt == IPSET_TEST)
 		e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK;
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 02/12] netfilter: ipset: rework cidr bookkeeping fixups
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 01/12] netfilter: ipset: rework cidr bookkeeping Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 03/12] netfilter: ipset: add small wrappers for hash and bucket sizes Florian Westphal
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

should be squash-committed, kept extra for transparency.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_hash_gen.h | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index a6b282ad8c48..49b2d998117e 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -338,8 +338,11 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 	len++;
 	tmp = kzalloc(sizeof(struct net_prefixes) +
 		      len * sizeof(struct net_prefix), GFP_ATOMIC);
-	if (!tmp)
-		return -ENOMEM;
+	if (!tmp) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
 	tmp->len = len;
 	for (i = 0, j = 0; i < nets->len; i++) {
 		if (!nets->nets[i].count)
@@ -366,7 +369,8 @@ static void
 mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 {
 	struct net_prefixes *nets, *tmp;
-	u8 i, j, found, len = 0;
+	u8 i, j, len = 0;
+	int found;
 
 	spin_lock_bh(&set->lock);
 	nets = __ipset_dereference(h->rnets[n]);
@@ -377,7 +381,8 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 			found = i;
 	}
 	if (unlikely(found == -1))
-		return;
+		goto unlock;
+
 	nets->nets[found].count--;
 	if (nets->nets[found].count)
 		goto unlock;
@@ -386,7 +391,8 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 		      len * sizeof(struct net_prefix), GFP_ATOMIC);
 	if (!tmp)
 		/* Leave a hole */
-		return;
+		goto unlock;
+
 	tmp->len = len;
 	for (i = 0, j = 0; i < nets->len; i++) {
 		if (!nets->nets[i].count || i == found)
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 03/12] netfilter: ipset: add small wrappers for hash and bucket sizes
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 01/12] netfilter: ipset: rework cidr bookkeeping Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 02/12] netfilter: ipset: rework cidr bookkeeping fixups Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 04/12] netfilter: ipset: add and use mtype_del_cidr_all helper Florian Westphal
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Preparation patch.  Once the ipset hash table is replaced with
rhashtable these functions are needed, adding them in an extra
commit helps keeping changes in reviewable chunks.

Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_hash_gen.h | 39 +++++++++++++++++++++------
 1 file changed, 31 insertions(+), 8 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 49b2d998117e..ac957a1d5f24 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -200,6 +200,8 @@ static const union nf_inet_addr zeromask = {};
 #undef mtype_same_set
 #undef mtype_kadt
 #undef mtype_uadt
+#undef mtype_bucket_size
+#undef mtype_hash_size
 
 #undef mtype_add
 #undef mtype_del
@@ -245,6 +247,8 @@ static const union nf_inet_addr zeromask = {};
 #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)
@@ -1359,6 +1363,24 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	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)
@@ -1369,21 +1391,20 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
 	size_t memsize;
 	u32 elements = 0;
 	size_t ext_size = 0;
-	u8 htable_bits;
 
 	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 + set->ext_size;
-	htable_bits = t->htable_bits;
 	rcu_read_unlock_bh();
 
 	nested = nla_nest_start(skb, IPSET_ATTR_DATA);
 	if (!nested)
 		goto nla_put_failure;
-	if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE,
-			  htonl(jhash_size(htable_bits))) ||
-	    nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)))
+
+	if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE, htonl(mtype_hash_size(h))))
+		goto nla_put_failure;
+	if (nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)))
 		goto nla_put_failure;
 #ifdef IP_SET_HASH_WITH_BITMASK
 	/* if netmask is set to anything other than HOST_MASK we know that the user supplied netmask
@@ -1407,8 +1428,9 @@ 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, h->bucketsize) ||
-		    nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
+		if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, mtype_bucket_size(h)))
+			goto nla_put_failure;
+		if (nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
 			goto nla_put_failure;
 	}
 	if (nla_put_net32(skb, IPSET_ATTR_REFERENCES, htonl(set->ref)) ||
@@ -1722,6 +1744,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 	INIT_LIST_HEAD(&t->ad);
 	RCU_INIT_POINTER(h->table, t);
 	set->data = h;
+
 #ifndef IP_SET_PROTO_UNDEF
 	if (set->family == NFPROTO_IPV4) {
 #endif
@@ -1750,7 +1773,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 #endif
 	}
 	pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n",
-		 set->name, jhash_size(t->htable_bits),
+		 set->name, mtype_hash_size(h),
 		 t->htable_bits, h->maxelem, set->data, t);
 
 	return 0;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 04/12] netfilter: ipset: add and use mtype_del_cidr_all helper
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (2 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 03/12] netfilter: ipset: add small wrappers for hash and bucket sizes Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 05/12] netfilter: ipset: add and use ip_set_init_comment_slow Florian Westphal
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Makes upcoming rewrite a bit easier on the eyes.

Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_hash_gen.h | 34 +++++++++++++--------------
 1 file changed, 16 insertions(+), 18 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index ac957a1d5f24..129f06ed85f8 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -194,6 +194,7 @@ static const union nf_inet_addr zeromask = {};
 #undef mtype_ext_cleanup
 #undef mtype_add_cidr
 #undef mtype_del_cidr
+#undef mtype_del_cidr_all
 #undef mtype_ahash_memsize
 #undef mtype_flush
 #undef mtype_destroy
@@ -241,6 +242,7 @@ static const union nf_inet_addr zeromask = {};
 #define mtype_ext_cleanup	IPSET_TOKEN(MTYPE, _ext_cleanup)
 #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_ahash_memsize	IPSET_TOKEN(MTYPE, _ahash_memsize)
 #define mtype_flush		IPSET_TOKEN(MTYPE, _flush)
 #define mtype_destroy		IPSET_TOKEN(MTYPE, _destroy)
@@ -411,6 +413,17 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 }
 #endif
 
+static void
+mtype_del_cidr_all(struct ip_set *set, struct htype *h, const struct mtype_elem *data)
+{
+#ifdef IP_SET_HASH_WITH_NETS
+	int k;
+
+	for (k = 0; k < IPSET_NET_COUNT; k++)
+		mtype_del_cidr(set, h, DCIDR_GET(data->cidr, k), k);
+#endif
+}
+
 /* Calculate the actual memory size of the set data */
 static size_t
 mtype_ahash_memsize(const struct htype *h, const struct htable *t)
@@ -552,9 +565,6 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
 	struct mtype_elem *data;
 	u32 i, j, d;
 	size_t dsize = set->dsize;
-#ifdef IP_SET_HASH_WITH_NETS
-	u8 k;
-#endif
 	u8 pos, htable_bits = t->htable_bits;
 
 	spin_lock_bh(&t->hregion[r].lock);
@@ -575,11 +585,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
 			pr_debug("expired %u/%u\n", i, j);
 			clear_bit(j, n->used);
 			smp_mb__after_atomic();
-#ifdef IP_SET_HASH_WITH_NETS
-			for (k = 0; k < IPSET_NET_COUNT; k++)
-				mtype_del_cidr(set, h,
-					DCIDR_GET(data->cidr, k), k);
-#endif
+			mtype_del_cidr_all(set, h, data);
 			t->hregion[r].elements--;
 			ip_set_ext_destroy(set, data);
 			d++;
@@ -1005,11 +1011,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 			j = 0;
 		data = ahash_data(n, j, set->dsize);
 		if (!deleted) {
-#ifdef IP_SET_HASH_WITH_NETS
-			for (i = 0; i < IPSET_NET_COUNT; i++)
-				mtype_del_cidr(set, h,
-					DCIDR_GET(data->cidr, i), i);
-#endif
+			mtype_del_cidr_all(set, h, data);
 			ip_set_ext_destroy(set, data);
 			t->hregion[r].elements--;
 		}
@@ -1164,11 +1166,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 		if (i + 1 == pos)
 			smp_store_release(&n->pos, --pos);
 		t->hregion[r].elements--;
-#ifdef IP_SET_HASH_WITH_NETS
-		for (j = 0; j < IPSET_NET_COUNT; j++)
-			mtype_del_cidr(set, h,
-				DCIDR_GET(d->cidr, j), j);
-#endif
+		mtype_del_cidr_all(set, h, d);
 		ip_set_ext_destroy(set, data);
 
 		if (t->resizing && ext && ext->target) {
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 05/12] netfilter: ipset: add and use ip_set_init_comment_slow
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (3 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 04/12] netfilter: ipset: add and use mtype_del_cidr_all helper Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 06/12] netfilter: ipset: add and use ip_set_ext_destroy_slow Florian Westphal
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

ip_set_init_comment alters set->ext_size, this is racy.
bitmap and list set types serialize on set->lock, but the hash versions
don't do that.

Switch hash to new ip_set_init_comment_slow.
Alternatives would be to avoid set->ext_size altogether or use
atomic_add/sub for this.

Prefer simpler version.  Also add lockdep annotations to document
this.

ip_set_ext_destroy() has the same issue, but this is harder to fix
because there is at least one case where this gets called without
lock where its safe to do so: set is destroyed.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/linux/netfilter/ipset/ip_set.h  | 9 +++++++++
 net/netfilter/ipset/ip_set_bitmap_gen.h | 2 ++
 net/netfilter/ipset/ip_set_core.c       | 3 ++-
 net/netfilter/ipset/ip_set_hash_gen.h   | 2 +-
 net/netfilter/ipset/ip_set_list_set.c   | 4 ++++
 5 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index b98331572ad2..50cd719bc270 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -502,6 +502,15 @@ ip_set_timeout_set(unsigned long *timeout, u32 value)
 void ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment,
 			 const struct ip_set_ext *ext);
 
+static inline void
+ip_set_init_comment_slow(struct ip_set *set, struct ip_set_comment *comment,
+			 const struct ip_set_ext *ext)
+{
+	spin_lock_bh(&set->lock);
+	ip_set_init_comment(set, comment, ext);
+	spin_unlock_bh(&set->lock);
+}
+
 static inline void
 ip_set_init_counter(struct ip_set_counter *counter,
 		    const struct ip_set_ext *ext)
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index bb9b5bed10e1..b13cde902c17 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -135,6 +135,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))) {
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 6cfad152d7d1..3d6a78ad93f5 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -346,7 +346,8 @@ 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 = rcu_dereference_protected(comment->c,
+								 lockdep_is_held(&set->lock));
 	size_t len = ext->comment ? strlen(ext->comment) : 0;
 
 	if (unlikely(c)) {
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 129f06ed85f8..c3dda56d786c 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -1065,7 +1065,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	if (SET_WITH_COUNTER(set))
 		ip_set_init_counter(ext_counter(data, set), ext);
 	if (SET_WITH_COMMENT(set))
-		ip_set_init_comment(set, ext_comment(data, set), ext);
+		ip_set_init_comment_slow(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 */
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index 1cef84f15e8c..d7ddc57a4eca 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -221,6 +221,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))
@@ -241,6 +243,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) {
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 06/12] netfilter: ipset: add and use ip_set_ext_destroy_slow
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (4 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 05/12] netfilter: ipset: add and use ip_set_init_comment_slow Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-14 13:18 ` [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

same rationale as previous patch, however, this time we cannot
add lockdep assertion in ip_set_ext_destroy().

At this time the function has too many call sites where set->lock
is held (e.g. during set destruction).

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/linux/netfilter/ipset/ip_set.h  | 24 ++++++++++++++++++++----
 net/netfilter/ipset/ip_set_bitmap_gen.h |  2 ++
 net/netfilter/ipset/ip_set_hash_gen.h   |  8 ++++----
 3 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index 50cd719bc270..f9003ec21259 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -282,17 +282,33 @@ struct ip_set {
 	void *data;
 };
 
+static inline void
+__ip_set_destroy_comment(struct ip_set *set, void *data)
+{
+	struct ip_set_comment *c = ext_comment(data, set);
+
+	ip_set_extensions[IPSET_EXT_ID_COMMENT].destroy(set, c);
+}
+
 static inline void
 ip_set_ext_destroy(struct ip_set *set, void *data)
 {
 	/* Check that the extension is enabled for the set and
 	 * call it's destroy function for its extension part in data.
 	 */
-	if (SET_WITH_COMMENT(set)) {
-		struct ip_set_comment *c = ext_comment(data, set);
+	if (SET_WITH_COMMENT(set))
+		__ip_set_destroy_comment(set, data);
+}
 
-		ip_set_extensions[IPSET_EXT_ID_COMMENT].destroy(set, c);
-	}
+static inline void
+ip_set_ext_destroy_slow(struct ip_set *set, void *data)
+{
+	if (!SET_WITH_COMMENT(set))
+		return;
+
+	spin_lock_bh(&set->lock);
+	__ip_set_destroy_comment(set, data);
+	spin_unlock_bh(&set->lock);
 }
 
 int ip_set_put_flags(struct sk_buff *skb, struct ip_set *set);
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index b13cde902c17..ca68b6e51214 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -182,6 +182,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_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index c3dda56d786c..1eff2c065bb3 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -443,7 +443,7 @@ mtype_ext_cleanup(struct ip_set *set, struct hbucket *n)
 
 	for (i = 0; i < pos; i++)
 		if (test_bit(i, n->used))
-			ip_set_ext_destroy(set, ahash_data(n, i, set->dsize));
+			ip_set_ext_destroy_slow(set, ahash_data(n, i, set->dsize));
 }
 
 /* Flush a hash type of set: destroy all elements */
@@ -587,7 +587,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
 			smp_mb__after_atomic();
 			mtype_del_cidr_all(set, h, data);
 			t->hregion[r].elements--;
-			ip_set_ext_destroy(set, data);
+			ip_set_ext_destroy_slow(set, data);
 			d++;
 		}
 		if (d >= AHASH_INIT_SIZE) {
@@ -1012,7 +1012,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 		data = ahash_data(n, j, set->dsize);
 		if (!deleted) {
 			mtype_del_cidr_all(set, h, data);
-			ip_set_ext_destroy(set, data);
+			ip_set_ext_destroy_slow(set, data);
 			t->hregion[r].elements--;
 		}
 		goto copy_data;
@@ -1167,7 +1167,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 			smp_store_release(&n->pos, --pos);
 		t->hregion[r].elements--;
 		mtype_del_cidr_all(set, h, d);
-		ip_set_ext_destroy(set, data);
+		ip_set_ext_destroy_slow(set, data);
 
 		if (t->resizing && ext && ext->target) {
 			/* Resize is in process and kernel side del,
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (5 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 06/12] netfilter: ipset: add and use ip_set_ext_destroy_slow Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-16 12:53   ` Jozsef Kadlecsik
  2026-07-14 13:18 ` [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Preparation patch.  Adds an rhashtable to the set and initialised
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 | 104 +++++++++++++++++++++++++-
 1 file changed, 102 insertions(+), 2 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 1eff2c065bb3..8f5f15fdf61b 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/netfilter/nfnetlink.h>
 #include <linux/netfilter/ipset/ip_set.h>
 
@@ -192,9 +193,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
@@ -240,9 +248,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)
@@ -275,6 +291,60 @@ 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 */
+	struct mtype_elem elem;		/* element data; extensions follow */
+};
+
+/* 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),
+	.hashfn		= mtype_rht_hashfn,
+	.obj_hashfn	= mtype_rht_obj_hashfn,
+	.obj_cmpfn	= mtype_rht_cmpfn,
+	.key_len	= HKEY_DATALEN,
+	.automatic_shrinking = true,
+};
+
 #define HKEY(data, initval, htable_bits)			\
 ({								\
 	const u32 *__k = (const u32 *)data;			\
@@ -288,6 +358,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 */
@@ -424,6 +495,16 @@ 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 ip_set *set = arg;
+	struct mtype_rht_elem *e = ptr;
+
+	ip_set_ext_destroy(set, &e->elem);
+	kfree_rcu(e, rcu);
+}
 /* Calculate the actual memory size of the set data */
 static size_t
 mtype_ahash_memsize(const struct htype *h, const struct htable *t)
@@ -458,6 +539,9 @@ mtype_flush(struct ip_set *set)
 	struct hbucket *n;
 	u32 r, i;
 
+	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
+	rhashtable_init(&h->ht, &mtype_rht_params);
+
 	t = ipset_dereference_nfnl(h->table);
 	for (r = 0; r < ahash_numof_locks(t->htable_bits); r++) {
 		spin_lock_bh(&t->hregion[r].lock);
@@ -530,6 +614,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);
@@ -1580,6 +1666,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;
@@ -1596,6 +1683,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",
@@ -1686,6 +1774,16 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 	if (!h)
 		return -ENOMEM;
 
+	/* Initialize rhashtable with the user-requested size as hint */
+	params = mtype_rht_params;
+	params.nelem_hint = hashsize;
+	/* maxsize: maximum bucket table size to expand to */
+	params.max_size = maxelem;
+
+	err = rhashtable_init(&h->ht, &params);
+	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.
@@ -1693,10 +1791,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;
@@ -1782,6 +1880,8 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 #endif
 free_t:
 	ip_set_free(t);
+free_rht:
+	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
 free_h:
 	kfree(h);
 	return -ENOMEM;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (6 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-16 13:22   ` Jozsef Kadlecsik
  2026-07-14 13:18 ` [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock Florian Westphal
                   ` (5 subsequent siblings)
  13 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Not yet ready, too many tests in ipset fail with this,
its possible those are false-positives.

 - existing ip_set_init_comment() alters set->ext_size, I don't
   see how region locking protects this.
   This adds full set->lock serialization, meaning no parallel
   insertion for elements with comment extension.
 - FORCEADD is removed to reduce diff size, its added back later
   in the series.

Sending this early to gather more feedback.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_hash_gen.h | 1252 +++++--------------------
 1 file changed, 259 insertions(+), 993 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 8f5f15fdf61b..e4d26f064c48 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>
@@ -17,84 +16,15 @@
 #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 */
-#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))
-
 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
@@ -112,23 +42,6 @@ struct net_prefixes {
 		__aligned(__alignof__(u64));
 };
 
-/* 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])
@@ -180,7 +93,6 @@ static const union nf_inet_addr zeromask = {};
 
 /* Family dependent templates */
 
-#undef ahash_data
 #undef mtype_data_equal
 #undef mtype_do_data_match
 #undef mtype_data_set_flags
@@ -191,8 +103,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
@@ -203,7 +113,6 @@ 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
@@ -217,12 +126,9 @@ static const union nf_inet_addr zeromask = {};
 #undef mtype_test_cidrs
 #undef mtype_test
 #undef mtype_uref
-#undef mtype_resize
 #undef mtype_ext_size
-#undef mtype_resize_ad
 #undef mtype_head
 #undef mtype_list
-#undef mtype_gc_do
 #undef mtype_gc
 #undef mtype_gc_init
 #undef mtype_cancel_gc
@@ -230,7 +136,7 @@ static const union nf_inet_addr zeromask = {};
 #undef mtype_data_match
 
 #undef htype
-#undef HKEY
+#undef HKEY_DATALEN
 
 #define mtype_data_equal	IPSET_TOKEN(MTYPE, _data_equal)
 #ifdef IP_SET_HASH_WITH_NETS
@@ -246,9 +152,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)
@@ -259,7 +162,6 @@ 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)
@@ -273,12 +175,9 @@ static const union nf_inet_addr zeromask = {};
 #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_head		IPSET_TOKEN(MTYPE, _head)
 #define mtype_list		IPSET_TOKEN(MTYPE, _list)
-#define mtype_gc_do		IPSET_TOKEN(MTYPE, _gc_do)
 #define mtype_gc		IPSET_TOKEN(MTYPE, _gc)
 #define mtype_gc_init		IPSET_TOKEN(MTYPE, _gc_init)
 #define mtype_cancel_gc		IPSET_TOKEN(MTYPE, _cancel_gc)
@@ -345,28 +244,15 @@ static const struct rhashtable_params mtype_rht_params = {
 	.automatic_shrinking = true,
 };
 
-#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 */
 	struct rhashtable ht;	/* the hash table */
+	u32 maxelem;		/* max element limit (user-requested) */
 	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 */
 #ifdef IP_SET_HASH_WITH_MARKMASK
 	u32 markmask;		/* markmask value for mark mask to store */
 #endif
-	u8 bucketsize;		/* max elements in an array block */
 #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 */
@@ -377,16 +263,6 @@ struct htype {
 	struct mtype_elem next; /* temporary storage for uadd */
 };
 
-/* 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
 /* Network cidr size book keeping when the hash stores different
  * sized networks. cidr == real cidr + 1 to support /0.
@@ -505,105 +381,34 @@ mtype_flush_elem(void *ptr, void *arg)
 	ip_set_ext_destroy(set, &e->elem);
 	kfree_rcu(e, rcu);
 }
-/* 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_slow(set, ahash_data(n, i, set->dsize));
-}
 
 /* Flush a hash type of set: destroy all elements */
 static void
 mtype_flush(struct ip_set *set)
 {
 	struct htype *h = set->data;
-#ifdef IP_SET_HASH_WITH_NETS
-	struct net_prefixes *nets, *tmp;
-#endif
-	struct htable *t;
-	struct hbucket *n;
-	u32 r, i;
+	struct rhashtable_iter hti;
+	struct mtype_rht_elem *e;
 
-	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
-	rhashtable_init(&h->ht, &mtype_rht_params);
-
-	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)
+	rhashtable_walk_enter(&h->ht, &hti);
+	rhashtable_walk_start(&hti);
+
+	while ((e = rhashtable_walk_next(&hti))) {
+		if (IS_ERR(e)) {
+			if (PTR_ERR(e) == -EAGAIN)
 				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++) {
-		nets = ipset_dereference_nfnl(h->rnets[i]);
-		tmp = kzalloc(sizeof(struct net_prefixes), GFP_ATOMIC);
-		if (!tmp) {
-			u8 j;
-
-			for (j = 0; j < nets->len; j++)
-				nets->nets[j].count = 0;
-		} else {
-			rcu_assign_pointer(h->rnets[i], tmp);
-			kfree_rcu(nets, rcu);
+			break;
 		}
-	}
-#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;
+		if (rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params))
+			continue; /* Concurrent delete? skip */
 
-	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);
+		mtype_del_cidr_all(set, h, &e->elem);
+		ip_set_ext_destroy_slow(set, &e->elem);
+		kfree_rcu(e, rcu);
 	}
-
-#ifdef IP_SET_HASH_WITH_NETS
-	if (ext_destroy)
-		for (i = 0; i < IPSET_NET_COUNT; i++)
-			kfree(h->rnets[i]);
-#endif
-	ip_set_free(t->hregion);
-	ip_set_free(t);
+	rhashtable_walk_stop(&hti);
+	rhashtable_walk_exit(&hti);
 }
 
 /* Destroy a hash type of set */
@@ -611,16 +416,16 @@ 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
 
 	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
 
-	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(h->rnets[i]);
+#endif
 	kfree(h);
 
 	set->data = NULL;
@@ -632,7 +437,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)
@@ -644,111 +448,45 @@ mtype_same_set(const struct ip_set *a, const struct ip_set *b)
 	       a->extensions == b->extensions;
 }
 
-static void
-mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
-{
-	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)))
-				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_slow(set, data);
-			d++;
-		}
-		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);
-		}
-	}
-	spin_unlock_bh(&t->hregion[r].lock);
-}
-
 static void
 mtype_gc(struct work_struct *work)
 {
 	struct htable_gc *gc;
 	struct ip_set *set;
 	struct htype *h;
-	struct htable *t;
-	u32 r, numof_locks;
+	struct rhashtable_iter hti;
+	struct mtype_rht_elem *e;
 	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;
+
+	rhashtable_walk_enter(&h->ht, &hti);
+	rhashtable_walk_start(&hti);
+	while ((e = rhashtable_walk_next(&hti))) {
+		if (IS_ERR(e)) {
+			if (PTR_ERR(e) == -EAGAIN)
+				continue;
+			break;
+		}
+		if (!ip_set_timeout_expired(ext_timeout(&e->elem, set)))
+			continue;
+		if (rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params))
+			continue; /* Concurrent delete? skip */
+
+		mtype_del_cidr_all(set, h, &e->elem);
+		ip_set_ext_destroy_slow(set, &e->elem);
+		kfree_rcu(e, rcu);
 	}
+	rhashtable_walk_stop(&hti);
+	rhashtable_walk_exit(&hti);
 
 	queue_delayed_work(system_power_efficient_wq, &gc->dwork, next_run);
-
 }
 
 static void
@@ -767,248 +505,15 @@ 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);
-
-/* 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.
- */
-static int
-mtype_resize(struct ip_set *set, bool retried)
-{
-	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;
-
-#ifdef IP_SET_HASH_WITH_NETS
-	tmp = kmalloc(dsize, GFP_KERNEL);
-	if (!tmp)
-		return -ENOMEM;
-#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)
-				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();
-
-	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);
-		}
-		list_del(l);
-		kfree(l);
-	}
-	/* 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);
-	}
-
-out:
-#ifdef IP_SET_HASH_WITH_NETS
-	kfree(tmp);
-#endif
-	return ret;
-
-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;
-
-	/* 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);
-	}
-	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;
-}
-
-/* Get the current number of elements and ext_size in the set  */
+/* Get the current number of elements and per-element memory 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)++;
-			}
-		}
-		*ext_size += t->hregion[r].ext_size;
-	}
+	const struct htype *h = set->data;
+
+	*elements = atomic_read(&h->ht.nelems);
+	*ext_size = *elements *
+		    (offsetof(struct mtype_rht_elem, elem) + set->dsize);
 }
 
 /* Add an element to a hash and update the internal counters when succeeded,
@@ -1019,298 +524,127 @@ 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;
+	struct mtype_rht_elem *e, *old;
 	bool flag_exist = flags & IPSET_FLAG_EXIST;
-	bool deleted = false, forceadd = false, reuse = false;
-	u32 r, key, multi = 0, elements, maxelem;
-	u8 npos = 0;
+	int ret = 0;
+#ifdef IP_SET_HASH_WITH_NETS
+	int i;
+#endif
 
+	/* Check for an existing entry with the same key */
 	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;
-	}
-
-	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;
-			}
-			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;
+	old = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+	if (old) {
+		if (!SET_ELEM_EXPIRED(set, &old->elem)) {
+			if (!flag_exist) {
+				rcu_read_unlock_bh();
+				return -IPSET_ERR_EXIST;
 			}
-			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_slow(set, data);
-			t->hregion[r].elements--;
-		}
-		goto copy_data;
-	}
-	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;
+			/* flag_exist: overwrite extensions in-place.
+			 * Hold set->lock to serialize ext_size accounting in
+			 * ip_set_init_comment against concurrent kernel-side adds.
+			 * rcu_read_lock_bh() must remain held to keep old alive.
+			 */
+			spin_lock_bh(&set->lock);
+#ifdef IP_SET_HASH_WITH_NETS
+			mtype_data_set_flags(&old->elem, flags);
 #endif
-		if (n->size >= AHASH_MAX(h)) {
-			/* Trigger rehashing */
-			mtype_data_next(&h->next, d);
-			ret = -EAGAIN;
-			goto resize;
+			if (SET_WITH_COUNTER(set))
+				ip_set_init_counter(ext_counter(&old->elem, set),
+						    ext);
+			if (SET_WITH_COMMENT(set))
+				ip_set_init_comment(set,
+						    ext_comment(&old->elem, set),
+						    ext);
+			if (SET_WITH_SKBINFO(set))
+				ip_set_init_skbinfo(ext_skbinfo(&old->elem, set),
+						    ext);
+			if (SET_WITH_TIMEOUT(set))
+				ip_set_timeout_set(ext_timeout(&old->elem, set),
+						   ext->timeout);
+			spin_unlock_bh(&set->lock);
+			rcu_read_unlock_bh();
+			return 0;
 		}
-		old = n;
-		n = kzalloc(sizeof(*n) +
-			    (old->size + AHASH_INIT_SIZE) * set->dsize,
-			    GFP_ATOMIC);
-		if (!n) {
-			ret = -ENOMEM;
-			goto unlock;
+		/* Expired entry: remove it to make room */
+		if (rhashtable_remove_fast(&h->ht, &old->node,
+					   mtype_rht_params) == 0) {
+			mtype_del_cidr_all(set, h, &old->elem);
+			ip_set_ext_destroy_slow(set, &old->elem);
+			kfree_rcu(old, rcu);
 		}
-		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);
 	}
+	rcu_read_unlock_bh();
+
+	if (atomic_read(&h->ht.nelems) >= h->maxelem) {
+		if (net_ratelimit())
+			pr_warn("Set %s is full, maxelem %u reached\n",
+				set->name, h->maxelem);
+		mtype_data_next(&h->next, d);
+		return -IPSET_ERR_HASH_FULL;
+	}
+
+	e = kzalloc(offsetof(struct mtype_rht_elem, elem) + set->dsize,
+		    GFP_ATOMIC);
+	if (!e)
+		return -ENOMEM;
+
+	memcpy(&e->elem, d, sizeof(struct mtype_elem));
 
-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);
+
+	mtype_data_set_flags(&e->elem, flags);
 #endif
 	if (SET_WITH_COUNTER(set))
-		ip_set_init_counter(ext_counter(data, set), ext);
+		ip_set_init_counter(ext_counter(&e->elem, set), ext);
 	if (SET_WITH_COMMENT(set))
-		ip_set_init_comment_slow(set, ext_comment(data, set), ext);
+		ip_set_init_comment_slow(set, ext_comment(&e->elem, set), ext);
 	if (SET_WITH_SKBINFO(set))
-		ip_set_init_skbinfo(ext_skbinfo(data, set), ext);
+		ip_set_init_skbinfo(ext_skbinfo(&e->elem, 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;
+		ip_set_timeout_set(ext_timeout(&e->elem, set), ext->timeout);
 
-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);
+	ret = rhashtable_insert_fast(&h->ht, &e->node, mtype_rht_params);
+	if (ret) {
+		mtype_del_cidr_all(set, h, d);
+		ip_set_ext_destroy_slow(set, &e->elem);
+		kfree(e);
+		if (ret == -EEXIST)
+			ret = flag_exist ? 0 : -IPSET_ERR_EXIST;
 	}
 	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.
-	 */
+	struct mtype_rht_elem *e;
+	int ret = -IPSET_ERR_EXIST;
+
 	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);
+	e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+	if (!e) {
+		rcu_read_unlock_bh();
+		return -IPSET_ERR_EXIST;
+	}
+	ret = rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params);
 	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;
-
-		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_slow(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);
-		}
-		goto out;
-	}
+	if (ret)
+		return -IPSET_ERR_EXIST;
 
-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);
-	}
-	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;
+	mtype_del_cidr_all(set, h, d);
+	ip_set_ext_destroy_slow(set, &e->elem);
+	kfree_rcu(e, rcu);
+	return 0;
 }
 
 static int
@@ -1333,25 +667,21 @@ 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;
-	int ret, i, j, k;
+	int ret, j, k;
 #else
-	int ret, i, j;
+	int ret, j;
 #endif
-	u32 key, multi = 0;
-	u8 pos;
+	u32 multi = 0;
 
 	pr_debug("test by nets\n");
-	rcu_read_lock_bh();
-	nets0 = rcu_dereference_bh(h->rnets[0]);
+	nets0 = ipset_dereference_bh_nfnl(h->rnets[0]);
 #if IPSET_NET_COUNT == 2
-	nets1 = rcu_dereference_bh(h->rnets[1]);
+	nets1 = ipset_dereference_bh_nfnl(h->rnets[1]);
 #endif
 	for (j = 0; j < nets0->len && !multi; j++) {
 		if (!nets0->nets[j].count)
@@ -1366,20 +696,11 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
 #else
 		mtype_data_netmask(d, nets0->nets[j].cidr);
 #endif
-		key = HKEY(d, h->initval, t->htable_bits);
-		n = rcu_dereference_bh(hbucket(t, key));
-		if (!n)
-			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))
-				continue;
-			ret = mtype_data_match(data, ext, mext, set, flags);
+		e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+		if (e) {
+			ret = mtype_data_match(&e->elem, ext, mext, set, flags);
 			if (ret != 0)
-				goto unlock;
+				return ret;
 #ifdef IP_SET_HASH_WITH_MULTI
 			/* No match, reset multiple match flag */
 			multi = 0;
@@ -1389,10 +710,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
 		}
 #endif
 	}
-	ret = 0;
-unlock:
-	rcu_read_unlock_bh();
-	return ret;
+	return 0;
 }
 #endif
 
@@ -1402,16 +720,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
@@ -1425,23 +741,13 @@ 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) {
+	e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
+	if (!e || SET_ELEM_EXPIRED(set, &e->elem)) {
 		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)
-			goto out;
-	}
+
+	ret = mtype_data_match(&e->elem, ext, mext, set, flags);
 out:
 	rcu_read_unlock_bh();
 	return ret;
@@ -1449,20 +755,24 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 
 static u32 mtype_hash_size(const struct htype *h)
 {
-	const struct htable *t;
-	u8 htable_bits;
+	const struct bucket_table *tbl;
+	u32 size = 0;
 
 	rcu_read_lock();
-	t = rcu_dereference(h->table);
-	htable_bits = t->htable_bits;
+	tbl = rcu_dereference(h->ht.tbl);
+	if (tbl)
+		size = tbl->size;
 	rcu_read_unlock();
 
-	return jhash_size(htable_bits);
+	return size;
 }
 
 static u32 mtype_bucket_size(const struct htype *h)
 {
-	return h->bucketsize;
+	unsigned int nelems = atomic_read(&h->ht.nelems);
+	u32 size = mtype_hash_size(h);
+
+	return nelems / size;
 }
 
 /* Reply a HEADER request: fill out the header part of the set */
@@ -1470,17 +780,13 @@ 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 + set->ext_size;
-	rcu_read_unlock_bh();
+	memsize = sizeof(*h) + ext_size + set->ext_size;
 
 	nested = nla_nest_start(skb, IPSET_ATTR_DATA);
 	if (!nested)
@@ -1514,7 +820,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
 	if (set->flags & IPSET_CREATE_FLAG_BUCKETSIZE) {
 		if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, mtype_bucket_size(h)))
 			goto nla_put_failure;
-		if (nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
+		if (nla_put_u32(skb, IPSET_ATTR_INITVAL, 0))
 			goto nla_put_failure;
 	}
 	if (nla_put_net32(skb, IPSET_ATTR_REFERENCES, htonl(set->ref)) ||
@@ -1530,25 +836,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(sizeof(*hti), GFP_ATOMIC);
+		if (hti)
+			rhashtable_walk_enter(&h->ht, 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;
 	}
@@ -1559,77 +863,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
@@ -1655,7 +959,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,
@@ -1671,7 +975,6 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
 #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;
@@ -1679,12 +982,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");
@@ -1765,17 +1067,21 @@ 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;
 
 	/* Initialize rhashtable with the user-requested size as hint */
-	params = mtype_rht_params;
 	params.nelem_hint = hashsize;
 	/* maxsize: maximum bucket table size to expand to */
 	params.max_size = maxelem;
@@ -1784,36 +1090,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(sizeof(struct net_prefixes), GFP_KERNEL);
 		if (!nets) {
 			while (i > 0)
 				kfree(h->rnets[--i]);
-			goto free_hregion;
+			goto free_rht;
 		}
 		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;
+	h->gc.set = set;
 #if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
 	h->bitmask = bitmask;
 	h->netmask = netmask;
@@ -1821,24 +1110,6 @@ 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])
-		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]);
-		if (h->bucketsize < AHASH_INIT_SIZE)
-			h->bucketsize = AHASH_INIT_SIZE;
-		else if (h->bucketsize > AHASH_MAX_SIZE)
-			h->bucketsize = AHASH_MAX_SIZE;
-		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);
 	set->data = h;
 
 #ifndef IP_SET_PROTO_UNDEF
@@ -1868,23 +1139,18 @@ 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, mtype_hash_size(h), 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:
 	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
+#endif
 free_h:
 	kfree(h);
-	return -ENOMEM;
+	return err ? err : -ENOMEM;
 }
 #endif /* IP_SET_EMIT_CREATE */
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (7 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-16 13:41   ` Jozsef Kadlecsik
  2026-07-14 13:18 ` [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference Florian Westphal
                   ` (4 subsequent siblings)
  13 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, 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        | 27 +++++++++-----------
 net/netfilter/ipset/ip_set_hash_netnet.c     |  8 +++---
 net/netfilter/ipset/ip_set_hash_netportnet.c |  8 +++---
 4 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 3d6a78ad93f5..6ece5cf305fe 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -1903,9 +1903,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 e4d26f064c48..a0f2cd481b82 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -16,9 +16,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))
 
 struct htable_gc {
 	struct delayed_work dwork;
@@ -533,18 +530,18 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 #endif
 
 	/* Check for an existing entry with the same key */
-	rcu_read_lock_bh();
+	rcu_read_lock();
 	old = rhashtable_lookup(&h->ht, d, mtype_rht_params);
 	if (old) {
 		if (!SET_ELEM_EXPIRED(set, &old->elem)) {
 			if (!flag_exist) {
-				rcu_read_unlock_bh();
+				rcu_read_unlock();
 				return -IPSET_ERR_EXIST;
 			}
 			/* flag_exist: overwrite extensions in-place.
 			 * Hold set->lock to serialize ext_size accounting in
 			 * ip_set_init_comment against concurrent kernel-side adds.
-			 * rcu_read_lock_bh() must remain held to keep old alive.
+			 * rcu_read_lock() must remain held to keep old alive.
 			 */
 			spin_lock_bh(&set->lock);
 #ifdef IP_SET_HASH_WITH_NETS
@@ -564,7 +561,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 				ip_set_timeout_set(ext_timeout(&old->elem, set),
 						   ext->timeout);
 			spin_unlock_bh(&set->lock);
-			rcu_read_unlock_bh();
+			rcu_read_unlock();
 			return 0;
 		}
 		/* Expired entry: remove it to make room */
@@ -575,7 +572,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 			kfree_rcu(old, rcu);
 		}
 	}
-	rcu_read_unlock_bh();
+	rcu_read_unlock();
 
 	if (atomic_read(&h->ht.nelems) >= h->maxelem) {
 		if (net_ratelimit())
@@ -629,14 +626,14 @@ 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();
 	e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
 	if (!e) {
-		rcu_read_unlock_bh();
+		rcu_read_unlock();
 		return -IPSET_ERR_EXIST;
 	}
 	ret = rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params);
-	rcu_read_unlock_bh();
+	rcu_read_unlock();
 
 	if (ret)
 		return -IPSET_ERR_EXIST;
@@ -679,9 +676,9 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
 	u32 multi = 0;
 
 	pr_debug("test by nets\n");
-	nets0 = ipset_dereference_bh_nfnl(h->rnets[0]);
+	nets0 = rcu_dereference(h->rnets[0]);
 #if IPSET_NET_COUNT == 2
-	nets1 = ipset_dereference_bh_nfnl(h->rnets[1]);
+	nets1 = rcu_dereference(h->rnets[1]);
 #endif
 	for (j = 0; j < nets0->len && !multi; j++) {
 		if (!nets0->nets[j].count)
@@ -727,7 +724,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
@@ -749,7 +746,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);
 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 f7c8a1cc30fc..2b874be16f6d 100644
--- a/net/netfilter/ipset/ip_set_hash_netnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netnet.c
@@ -149,10 +149,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;
 
@@ -390,10 +390,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 6291532be7a5..ad171b7cd1f5 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -157,10 +157,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;
 
@@ -454,10 +454,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.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (8 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-16 13:56   ` Jozsef Kadlecsik
  2026-07-14 13:18 ` [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage Florian Westphal
                   ` (3 subsequent siblings)
  13 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Avoid always-true arguments where possible, they defeat lockdep.

ip_set_comment_free() is problematic: called from different contexts,
some hold set->lock spinlock (safe), some do not hold a lock but have other
means of mutual exclusion (e.g., entire set torn down).

Other callers need investigation: ip_set_comment_free() alters
set->ext_size in a non-atomic way.  I don't see how this is safe except
for "entire set is destroyed" case: parallel usage would be a bug.

Add a few lockdep assertions to ip_set_init_comment() callpaths to have
more confidence in the correctness of the
"Called from uadd only, protected by the set spinlock." comment at the
 start of ip_set_init_comment().

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/linux/netfilter/ipset/ip_set.h | 3 +++
 net/netfilter/ipset/ip_set_core.c      | 3 +--
 net/netfilter/ipset/ip_set_hash_gen.h  | 6 ++----
 net/netfilter/ipset/ip_set_list_set.c  | 2 ++
 4 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index f9003ec21259..99bc997914f4 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -282,6 +282,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_destroy_comment(struct ip_set *set, void *data)
 {
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 6ece5cf305fe..a5f77f639d2a 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -346,8 +346,7 @@ 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,
-								 lockdep_is_held(&set->lock));
+	struct ip_set_comment_rcu *c = ipset_dereference_locked(comment->c, set);
 	size_t len = ext->comment ? strlen(ext->comment) : 0;
 
 	if (unlikely(c)) {
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index a0f2cd481b82..e615de2e616b 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -11,8 +11,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))
@@ -271,7 +269,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 	int i, j, found, len = 0, ret = 0;
 
 	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++) {
 		if (nets->nets[i].count)
@@ -323,7 +321,7 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 	int found;
 
 	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++) {
 		if (nets->nets[i].count)
 			len++;
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index d7ddc57a4eca..27bc96458e13 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -326,6 +326,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.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (9 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-16 14:01   ` Jozsef Kadlecsik
  2026-07-14 13:18 ` [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable Florian Westphal
                   ` (2 subsequent siblings)
  13 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

Move lock responsibility into kadt/uadt/flush callbacks and remove the
last .region_lock users.

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    | 11 +++++++--
 net/netfilter/ipset/ip_set_bitmap_ipmac.c |  9 ++++++-
 net/netfilter/ipset/ip_set_bitmap_port.c  | 11 +++++++--
 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, 36 insertions(+), 42 deletions(-)

diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index 99bc997914f4..3d3129118cf2 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 ca68b6e51214..fb964b5613c3 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;
 	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..247cd1b4f4e1 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,11 @@ 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);
+	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 +183,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..a7823dcc2f8e 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,11 @@ 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);
+	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 +278,9 @@ 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..2a868a022b41 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,11 @@ 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);
+	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 +199,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 a5f77f639d2a..bc2434392560 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -722,20 +722,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)
@@ -755,9 +741,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 */
@@ -786,9 +770,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;
 }
@@ -799,7 +781,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);
@@ -808,11 +789,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);
 
@@ -1298,9 +1275,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,
@@ -1754,9 +1729,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 e615de2e616b..12d3bc5acc81 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -957,7 +957,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 27bc96458e13..3669b4c9e575 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;
@@ -404,10 +406,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)
@@ -424,10 +429,12 @@ 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);
 	set->elements = 0;
 	set->ext_size = 0;
+	spin_unlock_bh(&set->lock);
 }
 
 static void
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (10 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage Florian Westphal
@ 2026-07-14 13:18 ` Florian Westphal
  2026-07-16 14:06   ` Jozsef Kadlecsik
  2026-07-14 15:52 ` [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Jozsef Kadlecsik
  2026-07-15  5:54 ` [syzbot ci] " syzbot ci
  13 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-14 13:18 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec, Florian Westphal

The rhashtable conversion removed the SET_WITH_FORCEADD eviction logic.
Sets created with the forceadd flag got IPSET_ERR_HASH_FULL instead of
evicting an existing element to make room.

Add mtype_remove_random() helper that walks the rhashtable to pick an
element to evict, and call it from mtype_add() when the set is full and
forceadd is enabled.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_hash_gen.h | 43 +++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 5 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 12d3bc5acc81..df73c1ebb3f0 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -116,6 +116,7 @@ static const union nf_inet_addr zeromask = {};
 #undef mtype_bucket_size
 #undef mtype_hash_size
 
+#undef mtype_remove_random
 #undef mtype_add
 #undef mtype_del
 #undef mtype_test_cidrs
@@ -165,6 +166,7 @@ static const union nf_inet_addr zeromask = {};
 #define mtype_bucket_size	IPSET_TOKEN(MTYPE, _bucket_size)
 #define mtype_hash_size		IPSET_TOKEN(MTYPE, _hash_size)
 
+#define mtype_remove_random	IPSET_TOKEN(MTYPE, _remove_random)
 #define mtype_add		IPSET_TOKEN(MTYPE, _add)
 #define mtype_del		IPSET_TOKEN(MTYPE, _del)
 #define mtype_test_cidrs	IPSET_TOKEN(MTYPE, _test_cidrs)
@@ -511,6 +513,33 @@ mtype_ext_size(struct ip_set *set, u32 *elements, size_t *ext_size)
 		    (offsetof(struct mtype_rht_elem, elem) + set->dsize);
 }
 
+/* Evict one element from the set to make room for a new one (forceadd) */
+static void __maybe_unused
+mtype_remove_random(struct ip_set *set, struct htype *h)
+{
+	struct rhashtable_iter hti;
+	struct mtype_rht_elem *e;
+	bool removed = false;
+
+	rhashtable_walk_enter(&h->ht, &hti);
+	rhashtable_walk_start(&hti);
+	e = rhashtable_walk_next(&hti);
+	if (IS_ERR(e))
+		e = NULL;
+
+	if (e && !rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params))
+		removed = true;
+
+	rhashtable_walk_stop(&hti);
+	rhashtable_walk_exit(&hti);
+
+	if (removed) {
+		mtype_del_cidr_all(set, h, &e->elem);
+		ip_set_ext_destroy_slow(set, &e->elem);
+		kfree_rcu(e, rcu);
+	}
+}
+
 /* Add an element to a hash and update the internal counters when succeeded,
  * otherwise report the proper error code.
  */
@@ -573,11 +602,15 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	rcu_read_unlock();
 
 	if (atomic_read(&h->ht.nelems) >= h->maxelem) {
-		if (net_ratelimit())
-			pr_warn("Set %s is full, maxelem %u reached\n",
-				set->name, h->maxelem);
-		mtype_data_next(&h->next, d);
-		return -IPSET_ERR_HASH_FULL;
+		if (SET_WITH_FORCEADD(set)) {
+			mtype_remove_random(set, h);
+		} else {
+			if (net_ratelimit())
+				pr_warn("Set %s is full, maxelem %u reached\n",
+					set->name, h->maxelem);
+			mtype_data_next(&h->next, d);
+			return -IPSET_ERR_HASH_FULL;
+		}
 	}
 
 	e = kzalloc(offsetof(struct mtype_rht_elem, elem) + set->dsize,
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (11 preceding siblings ...)
  2026-07-14 13:18 ` [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable Florian Westphal
@ 2026-07-14 15:52 ` Jozsef Kadlecsik
  2026-07-15  5:54 ` [syzbot ci] " syzbot ci
  13 siblings, 0 replies; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-14 15:52 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

Thank you your hard work on converting from hash-array to rhashtable! In 
the next days I'll go through the whole patchset.

Best regards,
Jozsef

On Tue, 14 Jul 2026, Florian Westphal wrote:

> Hi,
>
> This is an initial RFC patchset to convert the hash types to rhashtable.
> Main conversion is in patch 8.  First patches contain small drive-by
> fixes, patches after 8 contain further simplifications/cleanups.
> Last patch adds back the FORCEADD support dropped in the conversion
> commit (diff was getting too large...).
>
> The next step is to go through the ipset test failures and figure out
> which ones hint at actual bugs and which ones are just harmless cosmetic
> issues (that could be suppressed by tinkering with diff.sh in ipset tests).
>
> Notable likely valid remaining bugs:
> - iphash: IP: Compare sorted save and restore: [..] FAILED
> - hash:net,iface.t: Check 10.0.1.1 with eth0:
> Failed test: ../src/ipset [..] 10.0.1.1,eth0
> Warning: 10.0.1.1,eth0 is in set test.
>
> I'd like to eventually get rid of more set->lock places, remove all
> usage of rcu_dereference_protected(.. , 1), but thats not too urgent
> atm.
>
> 1) Rework ipset CIDR bookkeeping.  I had to remove this one from the last
> nf batch at the last minute because of a buildbot report. See next patch.
>
> 2) Fix a few nits in patch 1), to be squash-merged.
>
> 3) Add small wrappers for hash and bucket sizes to reduce noise in the
> actual conversion patch.
>
> 4) add and use tmtype_del_cidr_all helper to simplify the upcoming
> rewrite.
>
> 5) Use ip_set_init_comment_slow to prevent race conditions in hash ipset
> types. Add lockdep annotations.
>
> 6) Same as 5, but for remove: adds ip_set_ext_destroy_slow.
>
> 7) Add rhashtable boilerplate stubs to ipset. Initialize and destroy the
> rhashtable without ever adding elements.
>
> 8) Replace ipset's internal hash table with rhashtable.  FORCEADD is
> removed here, and added back in last patch.
>
> 9) Use plain rcu_read_lock, not _bh variants.
>
> 10) Better lockdep annotations in ipset_dereference. Add assertions to
> more places.
>
> 11) Remove the last region lock usage in ipset. Move lock responsibility to
> kadt, uadt, and flush callbacks.
>
> 12) Re-add forceadd support for rhashtable in ipset. Implement
> mtype_remove_random() to evict elements when the set is full.
>
> Florian Westphal (11):
>  netfilter: ipset: rework cidr bookkeeping fixups
>  netfilter: ipset: add small wrappers for hash and bucket sizes
>  netfilter: ipset: add and use mtype_del_cidr_all helper
>  netfilter: ipset: add and use ip_set_init_comment_slow
>  netfilter: ipset: add and use ip_set_ext_destroy_slow
>  netfilter: ipset: add rhashtable boilerplate stubs
>  netfilter: ipset: replace internal hash table with rhashtable
>  netfilter: ipset: use plain rcu_read_lock
>  netfilter: ipset: use correct lockdep annotation in ipset_dereference
>  netfilter: ipset: remove last region lock usage
>  netfilter: ipset: re-add forceadd support for rhashtable
>
> Jozsef Kadlecsik (1):
>  netfilter: ipset: rework cidr bookkeeping
>
> include/linux/netfilter/ipset/ip_set.h       |   44 +-
> net/netfilter/ipset/ip_set_bitmap_gen.h      |    6 +
> net/netfilter/ipset/ip_set_bitmap_ip.c       |   11 +-
> net/netfilter/ipset/ip_set_bitmap_ipmac.c    |    9 +-
> net/netfilter/ipset/ip_set_bitmap_port.c     |   11 +-
> net/netfilter/ipset/ip_set_core.c            |   35 +-
> net/netfilter/ipset/ip_set_hash_gen.h        | 1552 ++++++------------
> net/netfilter/ipset/ip_set_hash_ipportnet.c  |    4 +-
> net/netfilter/ipset/ip_set_hash_net.c        |    4 +-
> net/netfilter/ipset/ip_set_hash_netiface.c   |    4 +-
> net/netfilter/ipset/ip_set_hash_netnet.c     |   12 +-
> net/netfilter/ipset/ip_set_hash_netport.c    |    4 +-
> net/netfilter/ipset/ip_set_hash_netportnet.c |   12 +-
> net/netfilter/ipset/ip_set_list_set.c        |   13 +
> 14 files changed, 629 insertions(+), 1092 deletions(-)
>
> -- 
> 2.54.0
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [syzbot ci] Re: netfilter: ipset: convert to rhashtable
  2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
                   ` (12 preceding siblings ...)
  2026-07-14 15:52 ` [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Jozsef Kadlecsik
@ 2026-07-15  5:54 ` syzbot ci
  2026-07-16 13:02   ` Florian Westphal
  13 siblings, 1 reply; 29+ messages in thread
From: syzbot ci @ 2026-07-15  5:54 UTC (permalink / raw)
  To: fw, kadlec, netfilter-devel; +Cc: syzbot, syzkaller-bugs

syzbot ci has tested the following series

[v1] netfilter: ipset: convert to rhashtable
https://lore.kernel.org/all/20260714131828.10685-1-fw@strlen.de
* [PATCH RFC nf-next 01/12] netfilter: ipset: rework cidr bookkeeping
* [PATCH RFC nf-next 02/12] netfilter: ipset: rework cidr bookkeeping fixups
* [PATCH RFC nf-next 03/12] netfilter: ipset: add small wrappers for hash and bucket sizes
* [PATCH RFC nf-next 04/12] netfilter: ipset: add and use mtype_del_cidr_all helper
* [PATCH RFC nf-next 05/12] netfilter: ipset: add and use ip_set_init_comment_slow
* [PATCH RFC nf-next 06/12] netfilter: ipset: add and use ip_set_ext_destroy_slow
* [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs
* [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable
* [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock
* [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference
* [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage
* [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable

and found the following issue:
BUG: sleeping function called from invalid context in irq_work_sync

Full report is available here:
https://ci.syzbot.org/series/47a605ae-2607-4da3-aa08-70c5d3d1d824

***

BUG: sleeping function called from invalid context in irq_work_sync

tree:      nf-next
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/netfilter/nf-next.git
base:      f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
arch:      amd64
compiler:  Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
config:    https://ci.syzbot.org/builds/5beb9029-2b0f-4419-ae52-16749b7b6034/config
syz repro: https://ci.syzbot.org/findings/a6144f65-837e-4528-aaf8-77585ab7e425/syz_repro

BUG: sleeping function called from invalid context at kernel/irq_work.c:289
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 5727, name: syz-executor
preempt_count: 101, expected: 0
RCU nest depth: 0, expected: 0
8 locks held by syz-executor/5727:
 #0: ffff88816aad0450 (sb_writers#7){.+.+}-{0:0}, at: file_start_write include/linux/fs.h:2733 [inline]
 #0: ffff88816aad0450 (sb_writers#7){.+.+}-{0:0}, at: vfs_write+0x22b/0xba0 fs/read_write.c:683
 #1: ffff888115bedc80 (&of->mutex){+.+.}-{4:4}, at: kernfs_fop_write_iter+0x1d8/0x540 fs/kernfs/file.c:336
 #2: ffff88810c8b85a8 (kn->active#55){.+.+}-{0:0}, at: kernfs_get_active_of fs/kernfs/file.c:73 [inline]
 #2: ffff88810c8b85a8 (kn->active#55){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x22b/0x540 fs/kernfs/file.c:337
 #3: ffffffff8f66a000 (nsim_bus_dev_list_lock){+.+.}-{4:4}, at: new_device_store+0x13c/0x710 drivers/net/netdevsim/bus.c:184
 #4: ffff88811df84128 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1102 [inline]
 #4: ffff88811df84128 (&dev->mutex){....}-{4:4}, at: __device_attach+0x88/0x450 drivers/base/dd.c:1073
 #5: ffff88811df94258 (&devlink->lock_key#4){+.+.}-{4:4}, at: nsim_drv_probe+0xfc/0xbf0 drivers/net/netdevsim/dev.c:1658
 #6: ffff88811e194ae0 (&sb->s_type->i_mutex_key#4/1){+.+.}-{4:4}, at: inode_lock_nested include/linux/fs.h:1069 [inline]
 #6: ffff88811e194ae0 (&sb->s_type->i_mutex_key#4/1){+.+.}-{4:4}, at: __start_dirop fs/namei.c:2918 [inline]
 #6: ffff88811e194ae0 (&sb->s_type->i_mutex_key#4/1){+.+.}-{4:4}, at: start_dirop+0x4f/0x90 fs/namei.c:2942
 #7: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #7: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_do_batch kernel/rcu/tree.c:2639 [inline]
 #7: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_core+0x70d/0x10a0 kernel/rcu/tree.c:2897
Preemption disabled at:
[<ffffffff8228d9e3>] __pcs_replace_empty_main+0x383/0x6b0 mm/slub.c:4717
CPU: 0 UID: 0 PID: 5727 Comm: syz-executor Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <IRQ>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 __might_resched+0x378/0x4d0 kernel/sched/core.c:9197
 irq_work_sync+0x9b/0x300 kernel/irq_work.c:289
 rhashtable_free_and_destroy+0x39/0x540 lib/rhashtable.c:1295
 hash_netport4_destroy+0x56/0xb0 net/netfilter/ipset/ip_set_hash_gen.h:420
 ip_set_destroy_set_rcu+0x63/0xc0 net/netfilter/ipset/ip_set_core.c:1169
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core+0x78b/0x10a0 kernel/rcu/tree.c:2897
 handle_softirqs+0x225/0x840 kernel/softirq.c:622
 __do_softirq kernel/softirq.c:656 [inline]
 invoke_softirq kernel/softirq.c:496 [inline]
 __irq_exit_rcu+0xca/0x220 kernel/softirq.c:735
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:752
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:stackdepot_memcmp lib/stackdepot.c:585 [inline]
RIP: 0010:find_stack lib/stackdepot.c:618 [inline]
RIP: 0010:stack_depot_save_flags+0x1aa/0x800 lib/stackdepot.c:676
Code: 98 c3 ff 4c 8b 54 24 10 8b 74 24 04 85 db 75 5d 4d 8b 3f 4d 39 e7 74 52 41 39 6f 10 75 f2 45 39 57 14 75 ec 31 c0 49 8b 0c c6 <49> 3b 4c c7 20 75 df 48 ff c0 41 39 c5 75 ed 83 fe 02 72 2f 49 8d
RSP: 0018:ffffc9000603ed48 EFLAGS: 00000202
RAX: 000000000000001a RBX: 0000000000000c01 RCX: ffffffff82463f22
RDX: 0000000046192dc0 RSI: 0000000000000001 RDI: 00000000b3bfa5ab
RBP: 000000008cb0243d R08: 00000000786bc44c R09: 0000000093aed82f
R10: 000000000000001e R11: ffffffff8e959c60 R12: ffff88823b4243d0
R13: 000000000000001e R14: ffffc9000603eda0 R15: ffff8881b8318e00
 kasan_save_stack mm/kasan/common.c:58 [inline]
 kasan_save_track+0x4f/0x80 mm/kasan/common.c:78
 unpoison_slab_object mm/kasan/common.c:340 [inline]
 __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4612 [inline]
 slab_alloc_node mm/slub.c:4945 [inline]
 kmem_cache_alloc_lru_noprof+0x2aa/0x5f0 mm/slub.c:4978
 __d_alloc+0x37/0x6f0 fs/dcache.c:1902
 d_alloc+0x4b/0x190 fs/dcache.c:1981
 lookup_one_qstr_excl+0xd8/0x360 fs/namei.c:1806
 __start_dirop fs/namei.c:2920 [inline]
 start_dirop+0x5c/0x90 fs/namei.c:2942
 simple_start_creating+0xcc/0x110 fs/libfs.c:2305
 debugfs_start_creating+0xdb/0x1a0 fs/debugfs/inode.c:394
 debugfs_create_dir+0x24/0x350 fs/debugfs/inode.c:572
 nsim_ethtool_init+0x28c/0x500 drivers/net/netdevsim/ethtool.c:254
 nsim_create+0x26c/0x1150 drivers/net/netdevsim/netdev.c:1150
 __nsim_dev_port_add+0x7f8/0xcd0 drivers/net/netdevsim/dev.c:1509
 nsim_dev_port_add_all+0x37/0xf0 drivers/net/netdevsim/dev.c:1570
 nsim_drv_probe+0x8ce/0xbf0 drivers/net/netdevsim/dev.c:1731
 call_driver_probe drivers/base/dd.c:-1 [inline]
 really_probe+0x254/0xae0 drivers/base/dd.c:706
 __driver_probe_device+0x1e8/0x360 drivers/base/dd.c:868
 driver_probe_device+0x4f/0x240 drivers/base/dd.c:898
 __device_attach_driver+0x270/0x410 drivers/base/dd.c:1026
 bus_for_each_drv+0x258/0x2f0 drivers/base/bus.c:500
 __device_attach+0x2c4/0x450 drivers/base/dd.c:1098
 device_initial_probe+0xa1/0xd0 drivers/base/dd.c:1153
 bus_probe_device+0x12a/0x220 drivers/base/bus.c:620
 device_add+0x7d7/0xb80 drivers/base/core.c:3772
 nsim_bus_dev_new drivers/net/netdevsim/bus.c:471 [inline]
 new_device_store+0x37b/0x710 drivers/net/netdevsim/bus.c:191
 kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
 new_sync_write fs/read_write.c:595 [inline]
 vfs_write+0x612/0xba0 fs/read_write.c:687
 ksys_write+0x150/0x270 fs/read_write.c:739
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fac41b5d68e
Code: 08 0f 85 a5 a8 ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08
RSP: 002b:00007fff6b0eac58 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 000055556430f500 RCX: 00007fac41b5d68e
RDX: 0000000000000003 RSI: 00007fff6b0eace0 RDI: 0000000000000005
RBP: 00007fac41c33716 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003
R13: 00007fff6b0eace0 R14: 00007fac42944620 R15: 0000000000000003
 </TASK>
BUG: sleeping function called from invalid context at kernel/irq_work.c:289
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 5780, name: kworker/u8:4
preempt_count: 101, expected: 0
RCU nest depth: 0, expected: 0
4 locks held by kworker/u8:4/5780:
 #0: ffff88811326b140 ((wq_completion)ipv6_addrconf){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3297 [inline]
 #0: ffff88811326b140 ((wq_completion)ipv6_addrconf){+.+.}-{0:0}, at: process_scheduled_works+0xa20/0x14e0 kernel/workqueue.c:3405
 #1: ffffc90003edfc40 ((work_completion)(&(&ifa->dad_work)->work)){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3297 [inline]
 #1: ffffc90003edfc40 ((work_completion)(&(&ifa->dad_work)->work)){+.+.}-{0:0}, at: process_scheduled_works+0xa20/0x14e0 kernel/workqueue.c:3405
 #2: ffffffff8fded5c0 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_net_lock include/linux/rtnetlink.h:134 [inline]
 #2: ffffffff8fded5c0 (rtnl_mutex){+.+.}-{4:4}, at: addrconf_dad_work+0x116/0x15c0 net/ipv6/addrconf.c:4229
 #3: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #3: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_do_batch kernel/rcu/tree.c:2639 [inline]
 #3: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_core+0x70d/0x10a0 kernel/rcu/tree.c:2897
Preemption disabled at:
[<0000000000000000>] 0x0
CPU: 0 UID: 0 PID: 5780 Comm: kworker/u8:4 Tainted: G        W           syzkaller #0 PREEMPT(full) 
Tainted: [W]=WARN
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Workqueue: ipv6_addrconf addrconf_dad_work
Call Trace:
 <IRQ>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 __might_resched+0x378/0x4d0 kernel/sched/core.c:9197
 irq_work_sync+0x9b/0x300 kernel/irq_work.c:289
 rhashtable_free_and_destroy+0x39/0x540 lib/rhashtable.c:1295
 hash_netport4_destroy+0x56/0xb0 net/netfilter/ipset/ip_set_hash_gen.h:420
 ip_set_destroy_set_rcu+0x63/0xc0 net/netfilter/ipset/ip_set_core.c:1169
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core+0x78b/0x10a0 kernel/rcu/tree.c:2897
 handle_softirqs+0x225/0x840 kernel/softirq.c:622
 do_softirq+0x76/0xd0 kernel/softirq.c:523
 </IRQ>
 <TASK>
 __local_bh_enable_ip+0xf8/0x130 kernel/softirq.c:450
 spin_unlock_bh include/linux/spinlock.h:396 [inline]
 addrconf_dad_work+0x2d1/0x15c0 net/ipv6/addrconf.c:4259
 process_one_work kernel/workqueue.c:3322 [inline]
 process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>

=============================
WARNING: suspicious RCU usage
syzkaller #0 Tainted: G        W          
-----------------------------
kernel/sched/core.c:9159 Illegal context switch in RCU-sched read-side critical section!

other info that might help us debug this:


rcu_scheduler_active = 2, debug_locks = 1
5 locks held by syz.1.174/6158:
 #0: ffff888111ca7e88 (vm_lock){++++}-{0:0}, at: lock_vma_under_rcu+0x1d1/0x500 mm/mmap_lock.c:310
 #1: ffffffff8e959c60 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffffff8e959c60 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffffff8e959c60 (rcu_read_lock){....}-{1:3}, at: __pte_offset_map+0x29/0x240 mm/pgtable-generic.c:290
 #2: ffff88811429feb8 (ptlock_ptr(ptdesc)#2){+.+.}-{3:3}, at: spin_lock include/linux/spinlock.h:342 [inline]
 #2: ffff88811429feb8 (ptlock_ptr(ptdesc)#2){+.+.}-{3:3}, at: pte_offset_map_lock+0x13d/0x210 mm/pgtable-generic.c:404
 #3: ffffffff8e959d20 (rcu_read_lock_sched){....}-{1:2}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #3: ffffffff8e959d20 (rcu_read_lock_sched){....}-{1:2}, at: rcu_read_lock_sched include/linux/rcupdate.h:938 [inline]
 #3: ffffffff8e959d20 (rcu_read_lock_sched){....}-{1:2}, at: pfn_valid+0xba/0x480 include/linux/mmzone.h:2270
 #4: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #4: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_do_batch kernel/rcu/tree.c:2639 [inline]
 #4: ffffffff8e959d80 (rcu_callback){....}-{0:0}, at: rcu_core+0x70d/0x10a0 kernel/rcu/tree.c:2897

stack backtrace:
CPU: 0 UID: 0 PID: 6158 Comm: syz.1.174 Tainted: G        W           syzkaller #0 PREEMPT(full) 
Tainted: [W]=WARN
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <IRQ>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 lockdep_rcu_suspicious+0x13f/0x1d0 kernel/locking/lockdep.c:6876
 __might_resched+0xbb/0x4d0 kernel/sched/core.c:9159
 irq_work_sync+0x9b/0x300 kernel/irq_work.c:289
 rhashtable_free_and_destroy+0x39/0x540 lib/rhashtable.c:1295
 hash_netport4_destroy+0x56/0xb0 net/netfilter/ipset/ip_set_hash_gen.h:420
 ip_set_destroy_set_rcu+0x63/0xc0 net/netfilter/ipset/ip_set_core.c:1169
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core+0x78b/0x10a0 kernel/rcu/tree.c:2897
 handle_softirqs+0x225/0x840 kernel/softirq.c:622
 __do_softirq kernel/softirq.c:656 [inline]
 invoke_softirq kernel/softirq.c:496 [inline]
 __irq_exit_rcu+0xca/0x220 kernel/softirq.c:735
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:752
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:valid_section include/linux/mmzone.h:2119 [inline]
RIP: 0010:pfn_valid+0x11f/0x480 include/linux/mmzone.h:2271
Code: 74 7d e8 84 ae 86 ff 4d 85 ff 75 11 e8 7a ae 86 ff eb 3d e8 73 ae 86 ff 4d 85 ff 74 ef 4d 89 fd 49 c1 ed 03 43 80 7c 25 00 00 <74> 08 4c 89 ff e8 07 3b f4 ff 49 8b 2f 48 89 ee 48 83 e6 02 31 ff
RSP: 0000:ffffc9000283f8f0 EFLAGS: 00000246
RAX: ffffffff823fb3bc RBX: ffffffff823fb37a RCX: ffff88811b629dc0
RDX: 0000000000000000 RSI: ffffffff8c2ab860 RDI: ffffffff8c2ab820
RBP: 0000000000000001 R08: ffffffff823fb37a R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff8e959d20 R12: dffffc0000000000
R13: 1ffff11029fd668c R14: 000000000011c655 R15: ffff88814feb3460
 page_table_check_set+0x25/0x530 mm/page_table_check.c:105
 page_table_check_ptes_set include/linux/page_table_check.h:83 [inline]
 set_ptes include/linux/pgtable.h:447 [inline]
 set_pte_range+0x7ef/0x840 mm/memory.c:5588
 finish_fault+0xe65/0x1220 mm/memory.c:5724
 do_shared_fault mm/memory.c:5944 [inline]
 do_fault mm/memory.c:5998 [inline]
 do_pte_missing+0xf13/0x34b0 mm/memory.c:4566
 handle_pte_fault mm/memory.c:6379 [inline]
 __handle_mm_fault mm/memory.c:6517 [inline]
 handle_mm_fault+0x1b36/0x3080 mm/memory.c:6686
 do_user_addr_fault+0xa4d/0x1340 arch/x86/mm/fault.c:1343
 handle_page_fault arch/x86/mm/fault.c:1483 [inline]
 exc_page_fault+0x6a/0xc0 arch/x86/mm/fault.c:1536
 asm_exc_page_fault+0x26/0x30 arch/x86/include/asm/idtentry.h:595
RIP: 0033:0x7f04b9c65711
Code: 00 48 89 ef e8 60 52 ff ff 48 8b 05 69 00 ee 00 83 05 52 00 ee 00 01 be 08 00 00 00 48 89 ef 48 8d 50 ff 48 89 15 4f 00 ee 00 <44> 88 78 ff 44 8b 3d 34 00 ee 00 e8 2f 52 ff ff 48 8b 05 40 00 ee
RSP: 002b:00007fffc2849160 EFLAGS: 00010202
RAX: 0000001b32964000 RBX: 0000000000000000 RCX: 000000000003fde8
RDX: 0000001b32963fff RSI: 0000000000000008 RDI: 00007f04bab45720
RBP: 00007f04bab45720 R08: 0000000000000000 R09: 00007f04ba016038
R10: 00007f04bab45700 R11: 0000000000000000 R12: 0000000000000000
R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000003
 </TASK>
------------[ cut here ]------------
do not call blocking ops when !TASK_RUNNING; state=402 set at [<ffffffff819f28bd>] prepare_to_wait_event+0x3dd/0x480 kernel/sched/wait.c:317
WARNING: kernel/sched/core.c:9124 at __might_sleep+0x92/0xf0 kernel/sched/core.c:9120, CPU#0: kworker/u9:0/26
Modules linked in:
CPU: 0 UID: 0 PID: 26 Comm: kworker/u9:0 Tainted: G        W           syzkaller #0 PREEMPT(full) 
Tainted: [W]=WARN
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Workqueue: events_unbound toggle_allocation_gate
RIP: 0010:__might_sleep+0xac/0xf0 kernel/sched/core.c:9120
Code: 00 00 48 89 3c 24 41 89 f5 4c 8d 35 1e c1 9f 0e 43 80 3c 3c 00 74 08 48 89 df e8 6f 7f 9e 00 48 8b 0b 4c 89 f7 89 ee 48 89 ca <67> 48 0f b9 3a 44 89 ee 48 8b 3c 24 eb b5 44 89 f1 80 e1 07 80 c1
RSP: 0000:ffffc90000007c50 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff88810329f1b0 RCX: ffffffff819f28bd
RDX: ffffffff819f28bd RSI: 0000000000000402 RDI: ffffffff90353090
RBP: 0000000000000402 R08: ffffffff81acc8cd R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff8a074b10 R12: 1ffff11020653e36
R13: 0000000000000121 R14: ffffffff90353090 R15: dffffc0000000000
FS:  0000000000000000(0000) GS:ffff88818dc26000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f04ba016018 CR3: 000000000e746000 CR4: 00000000000006f0
Call Trace:
 <IRQ>
 irq_work_sync+0x9b/0x300 kernel/irq_work.c:289
 rhashtable_free_and_destroy+0x39/0x540 lib/rhashtable.c:1295
 hash_netport4_destroy+0x56/0xb0 net/netfilter/ipset/ip_set_hash_gen.h:420
 ip_set_destroy_set_rcu+0x63/0xc0 net/netfilter/ipset/ip_set_core.c:1169
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core+0x78b/0x10a0 kernel/rcu/tree.c:2897
 handle_softirqs+0x225/0x840 kernel/softirq.c:622
 __do_softirq kernel/softirq.c:656 [inline]
 invoke_softirq kernel/softirq.c:496 [inline]
 __irq_exit_rcu+0xca/0x220 kernel/softirq.c:735
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:752
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:__raw_spin_unlock_irqrestore include/linux/spinlock_api_smp.h:179 [inline]
RIP: 0010:_raw_spin_unlock_irqrestore+0x47/0x80 kernel/locking/spinlock.c:198
Code: f7 e8 ad 6c ea f5 f7 c3 00 02 00 00 74 05 e8 10 51 16 f6 9c 58 a9 00 02 00 00 75 27 f7 c3 00 02 00 00 74 01 fb bf 01 00 00 00 <e8> b4 82 db f5 65 8b 05 ad 52 87 07 85 c0 74 18 5b 41 5e e9 91 48
RSP: 0000:ffffc90000a179a8 EFLAGS: 00000206
RAX: 0000000000000002 RBX: 0000000000000286 RCX: 0000000080000001
RDX: 0000000000000006 RSI: ffffffff8dfdcfea RDI: 0000000000000001
RBP: 0000000000000000 R08: ffffffff903237f7 R09: 1ffffffff20646fe
R10: dffffc0000000000 R11: fffffbfff20646ff R12: ffff88810329d940
R13: 0000000000000286 R14: ffffffff8eaa07c0 R15: ffffffff8eaa0800
 spin_unlock_irqrestore include/linux/spinlock.h:408 [inline]
 prepare_to_wait_event+0x436/0x480 kernel/sched/wait.c:319
 toggle_allocation_gate+0x147/0x290 mm/kfence/core.c:913
 process_one_work kernel/workqueue.c:3322 [inline]
 process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
----------------
Code disassembly (best guess):
   0:	98                   	cwtl
   1:	c3                   	ret
   2:	ff 4c 8b 54          	decl   0x54(%rbx,%rcx,4)
   6:	24 10                	and    $0x10,%al
   8:	8b 74 24 04          	mov    0x4(%rsp),%esi
   c:	85 db                	test   %ebx,%ebx
   e:	75 5d                	jne    0x6d
  10:	4d 8b 3f             	mov    (%r15),%r15
  13:	4d 39 e7             	cmp    %r12,%r15
  16:	74 52                	je     0x6a
  18:	41 39 6f 10          	cmp    %ebp,0x10(%r15)
  1c:	75 f2                	jne    0x10
  1e:	45 39 57 14          	cmp    %r10d,0x14(%r15)
  22:	75 ec                	jne    0x10
  24:	31 c0                	xor    %eax,%eax
  26:	49 8b 0c c6          	mov    (%r14,%rax,8),%rcx
* 2a:	49 3b 4c c7 20       	cmp    0x20(%r15,%rax,8),%rcx <-- trapping instruction
  2f:	75 df                	jne    0x10
  31:	48 ff c0             	inc    %rax
  34:	41 39 c5             	cmp    %eax,%r13d
  37:	75 ed                	jne    0x26
  39:	83 fe 02             	cmp    $0x2,%esi
  3c:	72 2f                	jb     0x6d
  3e:	49                   	rex.WB
  3f:	8d                   	.byte 0x8d


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
  Tested-by: syzbot@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.

To test a patch for this bug, please reply with `#syz test`
(should be on a separate line).

The patch should be attached to the email.
Note: arguments like custom git repos and branches are not supported.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs
  2026-07-14 13:18 ` [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
@ 2026-07-16 12:53   ` Jozsef Kadlecsik
  2026-07-16 13:00     ` Florian Westphal
  0 siblings, 1 reply; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 12:53 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> Preparation patch.  Adds an rhashtable to the set and initialised
> 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.
> +/* 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);
> +}

It does not belong to this patch, but the "multi" arg of 
mtype_data_equal() is needed only for the netiface type: it supports the 
same ip/cidr behind multiple interfaces but the element can store a single 
interface only. The "multi" flag made possible to take into account 
multiple interfaces properly when growing the array in the hash.

The interfaces should be stored in a linked list at element level and the 
"multi" arg of mtype_data_equal() can be removed, which is not needed in 
rhashtable anyway.

Best regards,
Jozsef


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs
  2026-07-16 12:53   ` Jozsef Kadlecsik
@ 2026-07-16 13:00     ` Florian Westphal
  0 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 13:00 UTC (permalink / raw)
  To: Jozsef Kadlecsik; +Cc: netfilter-devel, kadlec

Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
> > Preparation patch.  Adds an rhashtable to the set and initialised
> > 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.
> > +/* 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);
> > +}
> 
> It does not belong to this patch, but the "multi" arg of mtype_data_equal()
> is needed only for the netiface type: it supports the same ip/cidr behind
> multiple interfaces but the element can store a single interface only. The
> "multi" flag made possible to take into account multiple interfaces properly
> when growing the array in the hash.

Thanks for providing context, Jozsef.

> The interfaces should be stored in a linked list at element level and the
> "multi" arg of mtype_data_equal() can be removed, which is not needed in
> rhashtable anyway.

Great, I will do this in next version.  This should make things much
simpler.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [syzbot ci] Re: netfilter: ipset: convert to rhashtable
  2026-07-15  5:54 ` [syzbot ci] " syzbot ci
@ 2026-07-16 13:02   ` Florian Westphal
  0 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 13:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: kadlec

syzbot ci <syzbot+ci0695f40f43c0e2d7@syzkaller.appspotmail.com> wrote:
> and found the following issue:
> BUG: sleeping function called from invalid context in irq_work_sync

.destroy will need to support schedule for rhashtable sake.

I made a prep patch to convert from call_rcu to rcu_work api, this
will also allow to move cancel_gc back into destroy callback in
a later / future patch.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable
  2026-07-14 13:18 ` [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
@ 2026-07-16 13:22   ` Jozsef Kadlecsik
  2026-07-16 14:04     ` Florian Westphal
  0 siblings, 1 reply; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 13:22 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> Not yet ready, too many tests in ipset fail with this,
> its possible those are false-positives.
>
> - existing ip_set_init_comment() alters set->ext_size, I don't
>   see how region locking protects this.

That was forgotten so it is not protected...

>   This adds full set->lock serialization, meaning no parallel
>   insertion for elements with comment extension.

The comment extension is restricted to add from userspace which is 
serialized by the mutex anyway.

> - FORCEADD is removed to reduce diff size, its added back later
>   in the series.
>
> Sending this early to gather more feedback.
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
> net/netfilter/ipset/ip_set_hash_gen.h | 1252 +++++--------------------
> 1 file changed, 259 insertions(+), 993 deletions(-)
>
> diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
> index 8f5f15fdf61b..e4d26f064c48 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>
> @@ -17,84 +16,15 @@
> #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 */
> -#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))
> -
> 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
> @@ -112,23 +42,6 @@ struct net_prefixes {
> 		__aligned(__alignof__(u64));
> };
>
> -/* 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])
> @@ -180,7 +93,6 @@ static const union nf_inet_addr zeromask = {};
>
> /* Family dependent templates */
>
> -#undef ahash_data
> #undef mtype_data_equal
> #undef mtype_do_data_match
> #undef mtype_data_set_flags
> @@ -191,8 +103,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
> @@ -203,7 +113,6 @@ 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
> @@ -217,12 +126,9 @@ static const union nf_inet_addr zeromask = {};
> #undef mtype_test_cidrs
> #undef mtype_test
> #undef mtype_uref
> -#undef mtype_resize
> #undef mtype_ext_size
> -#undef mtype_resize_ad
> #undef mtype_head
> #undef mtype_list
> -#undef mtype_gc_do
> #undef mtype_gc
> #undef mtype_gc_init
> #undef mtype_cancel_gc
> @@ -230,7 +136,7 @@ static const union nf_inet_addr zeromask = {};
> #undef mtype_data_match
>
> #undef htype
> -#undef HKEY
> +#undef HKEY_DATALEN
>
> #define mtype_data_equal	IPSET_TOKEN(MTYPE, _data_equal)
> #ifdef IP_SET_HASH_WITH_NETS
> @@ -246,9 +152,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)
> @@ -259,7 +162,6 @@ 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)
> @@ -273,12 +175,9 @@ static const union nf_inet_addr zeromask = {};
> #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_head		IPSET_TOKEN(MTYPE, _head)
> #define mtype_list		IPSET_TOKEN(MTYPE, _list)
> -#define mtype_gc_do		IPSET_TOKEN(MTYPE, _gc_do)
> #define mtype_gc		IPSET_TOKEN(MTYPE, _gc)
> #define mtype_gc_init		IPSET_TOKEN(MTYPE, _gc_init)
> #define mtype_cancel_gc		IPSET_TOKEN(MTYPE, _cancel_gc)
> @@ -345,28 +244,15 @@ static const struct rhashtable_params mtype_rht_params = {
> 	.automatic_shrinking = true,
> };
>
> -#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 */
> 	struct rhashtable ht;	/* the hash table */
> +	u32 maxelem;		/* max element limit (user-requested) */
> 	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 */

Please do not remove initval: the sole reason to keep it is to maintain 
full userspace compatibility. The "ipset" tool supports to specify initval 
and when listing/saving a set, initval is included in the output. Already
existing setups may specify the value and we must be able to return it.

> #ifdef IP_SET_HASH_WITH_MARKMASK
> 	u32 markmask;		/* markmask value for mark mask to store */
> #endif
> -	u8 bucketsize;		/* max elements in an array block */
> #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 */
> @@ -377,16 +263,6 @@ struct htype {
> 	struct mtype_elem next; /* temporary storage for uadd */
> };
>
> -/* 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
> /* Network cidr size book keeping when the hash stores different
>  * sized networks. cidr == real cidr + 1 to support /0.
> @@ -505,105 +381,34 @@ mtype_flush_elem(void *ptr, void *arg)
> 	ip_set_ext_destroy(set, &e->elem);
> 	kfree_rcu(e, rcu);
> }
> -/* 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_slow(set, ahash_data(n, i, set->dsize));
> -}
>
> /* Flush a hash type of set: destroy all elements */
> static void
> mtype_flush(struct ip_set *set)
> {
> 	struct htype *h = set->data;
> -#ifdef IP_SET_HASH_WITH_NETS
> -	struct net_prefixes *nets, *tmp;
> -#endif
> -	struct htable *t;
> -	struct hbucket *n;
> -	u32 r, i;
> +	struct rhashtable_iter hti;
> +	struct mtype_rht_elem *e;
>
> -	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
> -	rhashtable_init(&h->ht, &mtype_rht_params);
> -
> -	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)
> +	rhashtable_walk_enter(&h->ht, &hti);
> +	rhashtable_walk_start(&hti);
> +
> +	while ((e = rhashtable_walk_next(&hti))) {
> +		if (IS_ERR(e)) {
> +			if (PTR_ERR(e) == -EAGAIN)
> 				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++) {
> -		nets = ipset_dereference_nfnl(h->rnets[i]);
> -		tmp = kzalloc(sizeof(struct net_prefixes), GFP_ATOMIC);
> -		if (!tmp) {
> -			u8 j;
> -
> -			for (j = 0; j < nets->len; j++)
> -				nets->nets[j].count = 0;
> -		} else {
> -			rcu_assign_pointer(h->rnets[i], tmp);
> -			kfree_rcu(nets, rcu);
> +			break;
> 		}
> -	}
> -#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;
> +		if (rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params))
> +			continue; /* Concurrent delete? skip */
>
> -	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);
> +		mtype_del_cidr_all(set, h, &e->elem);
> +		ip_set_ext_destroy_slow(set, &e->elem);
> +		kfree_rcu(e, rcu);
> 	}
> -
> -#ifdef IP_SET_HASH_WITH_NETS
> -	if (ext_destroy)
> -		for (i = 0; i < IPSET_NET_COUNT; i++)
> -			kfree(h->rnets[i]);
> -#endif
> -	ip_set_free(t->hregion);
> -	ip_set_free(t);
> +	rhashtable_walk_stop(&hti);
> +	rhashtable_walk_exit(&hti);
> }
>
> /* Destroy a hash type of set */
> @@ -611,16 +416,16 @@ 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
>
> 	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
>
> -	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(h->rnets[i]);
> +#endif
> 	kfree(h);
>
> 	set->data = NULL;
> @@ -632,7 +437,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)
> @@ -644,111 +448,45 @@ mtype_same_set(const struct ip_set *a, const struct ip_set *b)
> 	       a->extensions == b->extensions;
> }
>
> -static void
> -mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
> -{
> -	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)))
> -				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_slow(set, data);
> -			d++;
> -		}
> -		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);
> -		}
> -	}
> -	spin_unlock_bh(&t->hregion[r].lock);
> -}
> -
> static void
> mtype_gc(struct work_struct *work)
> {
> 	struct htable_gc *gc;
> 	struct ip_set *set;
> 	struct htype *h;
> -	struct htable *t;
> -	u32 r, numof_locks;
> +	struct rhashtable_iter hti;
> +	struct mtype_rht_elem *e;
> 	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;
> +
> +	rhashtable_walk_enter(&h->ht, &hti);
> +	rhashtable_walk_start(&hti);
> +	while ((e = rhashtable_walk_next(&hti))) {
> +		if (IS_ERR(e)) {
> +			if (PTR_ERR(e) == -EAGAIN)
> +				continue;
> +			break;
> +		}
> +		if (!ip_set_timeout_expired(ext_timeout(&e->elem, set)))
> +			continue;
> +		if (rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params))
> +			continue; /* Concurrent delete? skip */
> +
> +		mtype_del_cidr_all(set, h, &e->elem);
> +		ip_set_ext_destroy_slow(set, &e->elem);
> +		kfree_rcu(e, rcu);
> 	}
> +	rhashtable_walk_stop(&hti);
> +	rhashtable_walk_exit(&hti);
>
> 	queue_delayed_work(system_power_efficient_wq, &gc->dwork, next_run);
> -
> }
>
> static void
> @@ -767,248 +505,15 @@ 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);
> -
> -/* 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.
> - */
> -static int
> -mtype_resize(struct ip_set *set, bool retried)
> -{
> -	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;
> -
> -#ifdef IP_SET_HASH_WITH_NETS
> -	tmp = kmalloc(dsize, GFP_KERNEL);
> -	if (!tmp)
> -		return -ENOMEM;
> -#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)
> -				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();
> -
> -	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);
> -		}
> -		list_del(l);
> -		kfree(l);
> -	}
> -	/* 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);
> -	}
> -
> -out:
> -#ifdef IP_SET_HASH_WITH_NETS
> -	kfree(tmp);
> -#endif
> -	return ret;
> -
> -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;
> -
> -	/* 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);
> -	}
> -	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;
> -}
> -
> -/* Get the current number of elements and ext_size in the set  */
> +/* Get the current number of elements and per-element memory 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)++;
> -			}
> -		}
> -		*ext_size += t->hregion[r].ext_size;
> -	}
> +	const struct htype *h = set->data;
> +
> +	*elements = atomic_read(&h->ht.nelems);
> +	*ext_size = *elements *
> +		    (offsetof(struct mtype_rht_elem, elem) + set->dsize);
> }
>
> /* Add an element to a hash and update the internal counters when succeeded,
> @@ -1019,298 +524,127 @@ 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;
> +	struct mtype_rht_elem *e, *old;
> 	bool flag_exist = flags & IPSET_FLAG_EXIST;
> -	bool deleted = false, forceadd = false, reuse = false;
> -	u32 r, key, multi = 0, elements, maxelem;
> -	u8 npos = 0;
> +	int ret = 0;
> +#ifdef IP_SET_HASH_WITH_NETS
> +	int i;
> +#endif
>
> +	/* Check for an existing entry with the same key */
> 	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;
> -	}
> -
> -	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;
> -			}
> -			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;
> +	old = rhashtable_lookup(&h->ht, d, mtype_rht_params);
> +	if (old) {
> +		if (!SET_ELEM_EXPIRED(set, &old->elem)) {
> +			if (!flag_exist) {
> +				rcu_read_unlock_bh();
> +				return -IPSET_ERR_EXIST;
> 			}
> -			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_slow(set, data);
> -			t->hregion[r].elements--;
> -		}
> -		goto copy_data;
> -	}
> -	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;
> +			/* flag_exist: overwrite extensions in-place.
> +			 * Hold set->lock to serialize ext_size accounting in
> +			 * ip_set_init_comment against concurrent kernel-side adds.
> +			 * rcu_read_lock_bh() must remain held to keep old alive.
> +			 */
> +			spin_lock_bh(&set->lock);
> +#ifdef IP_SET_HASH_WITH_NETS
> +			mtype_data_set_flags(&old->elem, flags);
> #endif
> -		if (n->size >= AHASH_MAX(h)) {
> -			/* Trigger rehashing */
> -			mtype_data_next(&h->next, d);
> -			ret = -EAGAIN;
> -			goto resize;
> +			if (SET_WITH_COUNTER(set))
> +				ip_set_init_counter(ext_counter(&old->elem, set),
> +						    ext);
> +			if (SET_WITH_COMMENT(set))
> +				ip_set_init_comment(set,
> +						    ext_comment(&old->elem, set),
> +						    ext);
> +			if (SET_WITH_SKBINFO(set))
> +				ip_set_init_skbinfo(ext_skbinfo(&old->elem, set),
> +						    ext);
> +			if (SET_WITH_TIMEOUT(set))
> +				ip_set_timeout_set(ext_timeout(&old->elem, set),
> +						   ext->timeout);
> +			spin_unlock_bh(&set->lock);
> +			rcu_read_unlock_bh();
> +			return 0;
> 		}
> -		old = n;
> -		n = kzalloc(sizeof(*n) +
> -			    (old->size + AHASH_INIT_SIZE) * set->dsize,
> -			    GFP_ATOMIC);
> -		if (!n) {
> -			ret = -ENOMEM;
> -			goto unlock;
> +		/* Expired entry: remove it to make room */
> +		if (rhashtable_remove_fast(&h->ht, &old->node,
> +					   mtype_rht_params) == 0) {
> +			mtype_del_cidr_all(set, h, &old->elem);
> +			ip_set_ext_destroy_slow(set, &old->elem);
> +			kfree_rcu(old, rcu);
> 		}
> -		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);
> 	}
> +	rcu_read_unlock_bh();
> +
> +	if (atomic_read(&h->ht.nelems) >= h->maxelem) {
> +		if (net_ratelimit())
> +			pr_warn("Set %s is full, maxelem %u reached\n",
> +				set->name, h->maxelem);
> +		mtype_data_next(&h->next, d);

Why the mtype_data_next() call is needed here?

> +		return -IPSET_ERR_HASH_FULL;
> +	}
> +
> +	e = kzalloc(offsetof(struct mtype_rht_elem, elem) + set->dsize,
> +		    GFP_ATOMIC);
> +	if (!e)
> +		return -ENOMEM;
> +
> +	memcpy(&e->elem, d, sizeof(struct mtype_elem));
>
> -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);
> +
> +	mtype_data_set_flags(&e->elem, flags);
> #endif
> 	if (SET_WITH_COUNTER(set))
> -		ip_set_init_counter(ext_counter(data, set), ext);
> +		ip_set_init_counter(ext_counter(&e->elem, set), ext);
> 	if (SET_WITH_COMMENT(set))
> -		ip_set_init_comment_slow(set, ext_comment(data, set), ext);
> +		ip_set_init_comment_slow(set, ext_comment(&e->elem, set), ext);
> 	if (SET_WITH_SKBINFO(set))
> -		ip_set_init_skbinfo(ext_skbinfo(data, set), ext);
> +		ip_set_init_skbinfo(ext_skbinfo(&e->elem, 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;
> +		ip_set_timeout_set(ext_timeout(&e->elem, set), ext->timeout);
>
> -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);
> +	ret = rhashtable_insert_fast(&h->ht, &e->node, mtype_rht_params);
> +	if (ret) {
> +		mtype_del_cidr_all(set, h, d);
> +		ip_set_ext_destroy_slow(set, &e->elem);
> +		kfree(e);
> +		if (ret == -EEXIST)
> +			ret = flag_exist ? 0 : -IPSET_ERR_EXIST;
> 	}
> 	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.
> -	 */
> +	struct mtype_rht_elem *e;
> +	int ret = -IPSET_ERR_EXIST;
> +
> 	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);
> +	e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
> +	if (!e) {
> +		rcu_read_unlock_bh();
> +		return -IPSET_ERR_EXIST;
> +	}
> +	ret = rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params);
> 	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;
> -
> -		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_slow(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);
> -		}
> -		goto out;
> -	}
> +	if (ret)
> +		return -IPSET_ERR_EXIST;
>
> -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);
> -	}
> -	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;
> +	mtype_del_cidr_all(set, h, d);
> +	ip_set_ext_destroy_slow(set, &e->elem);
> +	kfree_rcu(e, rcu);
> +	return 0;
> }
>
> static int
> @@ -1333,25 +667,21 @@ 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;
> -	int ret, i, j, k;
> +	int ret, j, k;
> #else
> -	int ret, i, j;
> +	int ret, j;
> #endif
> -	u32 key, multi = 0;
> -	u8 pos;
> +	u32 multi = 0;
>
> 	pr_debug("test by nets\n");
> -	rcu_read_lock_bh();
> -	nets0 = rcu_dereference_bh(h->rnets[0]);
> +	nets0 = ipset_dereference_bh_nfnl(h->rnets[0]);
> #if IPSET_NET_COUNT == 2
> -	nets1 = rcu_dereference_bh(h->rnets[1]);
> +	nets1 = ipset_dereference_bh_nfnl(h->rnets[1]);
> #endif
> 	for (j = 0; j < nets0->len && !multi; j++) {
> 		if (!nets0->nets[j].count)
> @@ -1366,20 +696,11 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
> #else
> 		mtype_data_netmask(d, nets0->nets[j].cidr);
> #endif
> -		key = HKEY(d, h->initval, t->htable_bits);
> -		n = rcu_dereference_bh(hbucket(t, key));
> -		if (!n)
> -			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))
> -				continue;
> -			ret = mtype_data_match(data, ext, mext, set, flags);
> +		e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
> +		if (e) {
> +			ret = mtype_data_match(&e->elem, ext, mext, set, flags);

The code now works as if in the case of the netiface type the interfaces 
were included and matched at element level.

> 			if (ret != 0)
> -				goto unlock;
> +				return ret;
> #ifdef IP_SET_HASH_WITH_MULTI
> 			/* No match, reset multiple match flag */
> 			multi = 0;
> @@ -1389,10 +710,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
> 		}
> #endif
> 	}
> -	ret = 0;
> -unlock:
> -	rcu_read_unlock_bh();
> -	return ret;
> +	return 0;
> }
> #endif
>
> @@ -1402,16 +720,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
> @@ -1425,23 +741,13 @@ 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) {
> +	e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
> +	if (!e || SET_ELEM_EXPIRED(set, &e->elem)) {
> 		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)
> -			goto out;
> -	}
> +
> +	ret = mtype_data_match(&e->elem, ext, mext, set, flags);
> out:
> 	rcu_read_unlock_bh();
> 	return ret;
> @@ -1449,20 +755,24 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
>
> static u32 mtype_hash_size(const struct htype *h)
> {
> -	const struct htable *t;
> -	u8 htable_bits;
> +	const struct bucket_table *tbl;
> +	u32 size = 0;
>
> 	rcu_read_lock();
> -	t = rcu_dereference(h->table);
> -	htable_bits = t->htable_bits;
> +	tbl = rcu_dereference(h->ht.tbl);
> +	if (tbl)
> +		size = tbl->size;
> 	rcu_read_unlock();
>
> -	return jhash_size(htable_bits);
> +	return size;
> }
>
> static u32 mtype_bucket_size(const struct htype *h)
> {
> -	return h->bucketsize;
> +	unsigned int nelems = atomic_read(&h->ht.nelems);
> +	u32 size = mtype_hash_size(h);
> +
> +	return nelems / size;
> }
>
> /* Reply a HEADER request: fill out the header part of the set */
> @@ -1470,17 +780,13 @@ 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 + set->ext_size;
> -	rcu_read_unlock_bh();
> +	memsize = sizeof(*h) + ext_size + set->ext_size;
>
> 	nested = nla_nest_start(skb, IPSET_ATTR_DATA);
> 	if (!nested)
> @@ -1514,7 +820,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
> 	if (set->flags & IPSET_CREATE_FLAG_BUCKETSIZE) {
> 		if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, mtype_bucket_size(h)))
> 			goto nla_put_failure;
> -		if (nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
> +		if (nla_put_u32(skb, IPSET_ATTR_INITVAL, 0))

I know it is just garbage now, but h->initval should be returned.

> 			goto nla_put_failure;
> 	}
> 	if (nla_put_net32(skb, IPSET_ATTR_REFERENCES, htonl(set->ref)) ||
> @@ -1530,25 +836,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);

We don't need uref anymore. Cool!

> -		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(sizeof(*hti), GFP_ATOMIC);
> +		if (hti)
> +			rhashtable_walk_enter(&h->ht, 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;
> 	}
> @@ -1559,77 +863,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
> @@ -1655,7 +959,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,
> @@ -1671,7 +975,6 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
> #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;
> @@ -1679,12 +982,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");
> @@ -1765,17 +1067,21 @@ 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;
>
> 	/* Initialize rhashtable with the user-requested size as hint */
> -	params = mtype_rht_params;
> 	params.nelem_hint = hashsize;
> 	/* maxsize: maximum bucket table size to expand to */
> 	params.max_size = maxelem;
> @@ -1784,36 +1090,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(sizeof(struct net_prefixes), GFP_KERNEL);
> 		if (!nets) {
> 			while (i > 0)
> 				kfree(h->rnets[--i]);
> -			goto free_hregion;
> +			goto free_rht;
> 		}
> 		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;
> +	h->gc.set = set;
> #if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
> 	h->bitmask = bitmask;
> 	h->netmask = netmask;
> @@ -1821,24 +1110,6 @@ 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])
> -		h->initval = ntohl(nla_get_be32(tb[IPSET_ATTR_INITVAL]));
> -	else
> -		get_random_bytes(&h->initval, sizeof(h->initval));

When initval restored, the else branch is still not needed anymore.

Best regards,
Jozsef


> -	h->bucketsize = AHASH_MAX_SIZE;
> -	if (tb[IPSET_ATTR_BUCKETSIZE]) {
> -		h->bucketsize = nla_get_u8(tb[IPSET_ATTR_BUCKETSIZE]);
> -		if (h->bucketsize < AHASH_INIT_SIZE)
> -			h->bucketsize = AHASH_INIT_SIZE;
> -		else if (h->bucketsize > AHASH_MAX_SIZE)
> -			h->bucketsize = AHASH_MAX_SIZE;
> -		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);
> 	set->data = h;
>
> #ifndef IP_SET_PROTO_UNDEF
> @@ -1868,23 +1139,18 @@ 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, mtype_hash_size(h), 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:
> 	rhashtable_free_and_destroy(&h->ht, mtype_flush_elem, set);
> +#endif
> free_h:
> 	kfree(h);
> -	return -ENOMEM;
> +	return err ? err : -ENOMEM;
> }
> #endif /* IP_SET_EMIT_CREATE */
>
> -- 
> 2.54.0
>
>
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock
  2026-07-14 13:18 ` [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock Florian Westphal
@ 2026-07-16 13:41   ` Jozsef Kadlecsik
  2026-07-16 14:05     ` Florian Westphal
  0 siblings, 1 reply; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 13:41 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> No need to disable/reenable softirqs.

Couldn't then similarly simple spinlocking be used instead the _bh() 
variant? I assume then that there's no need to disable/reenable softirqs 
in those cases either.

Best regards,
Jozsef

> 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        | 27 +++++++++-----------
> net/netfilter/ipset/ip_set_hash_netnet.c     |  8 +++---
> net/netfilter/ipset/ip_set_hash_netportnet.c |  8 +++---
> 4 files changed, 22 insertions(+), 25 deletions(-)
>
> diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
> index 3d6a78ad93f5..6ece5cf305fe 100644
> --- a/net/netfilter/ipset/ip_set_core.c
> +++ b/net/netfilter/ipset/ip_set_core.c
> @@ -1903,9 +1903,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 e4d26f064c48..a0f2cd481b82 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
> @@ -16,9 +16,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))
>
> struct htable_gc {
> 	struct delayed_work dwork;
> @@ -533,18 +530,18 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
> #endif
>
> 	/* Check for an existing entry with the same key */
> -	rcu_read_lock_bh();
> +	rcu_read_lock();
> 	old = rhashtable_lookup(&h->ht, d, mtype_rht_params);
> 	if (old) {
> 		if (!SET_ELEM_EXPIRED(set, &old->elem)) {
> 			if (!flag_exist) {
> -				rcu_read_unlock_bh();
> +				rcu_read_unlock();
> 				return -IPSET_ERR_EXIST;
> 			}
> 			/* flag_exist: overwrite extensions in-place.
> 			 * Hold set->lock to serialize ext_size accounting in
> 			 * ip_set_init_comment against concurrent kernel-side adds.
> -			 * rcu_read_lock_bh() must remain held to keep old alive.
> +			 * rcu_read_lock() must remain held to keep old alive.
> 			 */
> 			spin_lock_bh(&set->lock);
> #ifdef IP_SET_HASH_WITH_NETS
> @@ -564,7 +561,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
> 				ip_set_timeout_set(ext_timeout(&old->elem, set),
> 						   ext->timeout);
> 			spin_unlock_bh(&set->lock);
> -			rcu_read_unlock_bh();
> +			rcu_read_unlock();
> 			return 0;
> 		}
> 		/* Expired entry: remove it to make room */
> @@ -575,7 +572,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
> 			kfree_rcu(old, rcu);
> 		}
> 	}
> -	rcu_read_unlock_bh();
> +	rcu_read_unlock();
>
> 	if (atomic_read(&h->ht.nelems) >= h->maxelem) {
> 		if (net_ratelimit())
> @@ -629,14 +626,14 @@ 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();
> 	e = rhashtable_lookup(&h->ht, d, mtype_rht_params);
> 	if (!e) {
> -		rcu_read_unlock_bh();
> +		rcu_read_unlock();
> 		return -IPSET_ERR_EXIST;
> 	}
> 	ret = rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params);
> -	rcu_read_unlock_bh();
> +	rcu_read_unlock();
>
> 	if (ret)
> 		return -IPSET_ERR_EXIST;
> @@ -679,9 +676,9 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
> 	u32 multi = 0;
>
> 	pr_debug("test by nets\n");
> -	nets0 = ipset_dereference_bh_nfnl(h->rnets[0]);
> +	nets0 = rcu_dereference(h->rnets[0]);
> #if IPSET_NET_COUNT == 2
> -	nets1 = ipset_dereference_bh_nfnl(h->rnets[1]);
> +	nets1 = rcu_dereference(h->rnets[1]);
> #endif
> 	for (j = 0; j < nets0->len && !multi; j++) {
> 		if (!nets0->nets[j].count)
> @@ -727,7 +724,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
> @@ -749,7 +746,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);
> 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 f7c8a1cc30fc..2b874be16f6d 100644
> --- a/net/netfilter/ipset/ip_set_hash_netnet.c
> +++ b/net/netfilter/ipset/ip_set_hash_netnet.c
> @@ -149,10 +149,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;
>
> @@ -390,10 +390,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 6291532be7a5..ad171b7cd1f5 100644
> --- a/net/netfilter/ipset/ip_set_hash_netportnet.c
> +++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
> @@ -157,10 +157,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;
>
> @@ -454,10 +454,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.54.0
>
>
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference
  2026-07-14 13:18 ` [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference Florian Westphal
@ 2026-07-16 13:56   ` Jozsef Kadlecsik
  2026-07-16 14:07     ` Florian Westphal
  0 siblings, 1 reply; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 13:56 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> Avoid always-true arguments where possible, they defeat lockdep.
>
> ip_set_comment_free() is problematic: called from different contexts,
> some hold set->lock spinlock (safe), some do not hold a lock but have other
> means of mutual exclusion (e.g., entire set torn down).

Wouldn't something like the following be sufficient?

- Add a bool "deleted" element to struct ip_set.
- The set-specific destroy function would set it true before
   doing anything else.
- Then it'd be safe to use

#define ipset_dereference_locked(p, set)	\
 	rcu_dereference_protected(p, lockdep_is_held(&set->lock) || \
 				     set->deleted))

> Other callers need investigation: ip_set_comment_free() alters
> set->ext_size in a non-atomic way.  I don't see how this is safe except
> for "entire set is destroyed" case: parallel usage would be a bug.

Maybe we should convert ext_size to atomic64_t?

Best regards,
Jozsef
> Add a few lockdep assertions to ip_set_init_comment() callpaths to have
> more confidence in the correctness of the
> "Called from uadd only, protected by the set spinlock." comment at the
> start of ip_set_init_comment().
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
> include/linux/netfilter/ipset/ip_set.h | 3 +++
> net/netfilter/ipset/ip_set_core.c      | 3 +--
> net/netfilter/ipset/ip_set_hash_gen.h  | 6 ++----
> net/netfilter/ipset/ip_set_list_set.c  | 2 ++
> 4 files changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
> index f9003ec21259..99bc997914f4 100644
> --- a/include/linux/netfilter/ipset/ip_set.h
> +++ b/include/linux/netfilter/ipset/ip_set.h
> @@ -282,6 +282,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_destroy_comment(struct ip_set *set, void *data)
> {
> diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
> index 6ece5cf305fe..a5f77f639d2a 100644
> --- a/net/netfilter/ipset/ip_set_core.c
> +++ b/net/netfilter/ipset/ip_set_core.c
> @@ -346,8 +346,7 @@ 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,
> -								 lockdep_is_held(&set->lock));
> +	struct ip_set_comment_rcu *c = ipset_dereference_locked(comment->c, set);
> 	size_t len = ext->comment ? strlen(ext->comment) : 0;
>
> 	if (unlikely(c)) {
> diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
> index a0f2cd481b82..e615de2e616b 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
> @@ -11,8 +11,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))
> @@ -271,7 +269,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
> 	int i, j, found, len = 0, ret = 0;
>
> 	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++) {
> 		if (nets->nets[i].count)
> @@ -323,7 +321,7 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
> 	int found;
>
> 	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++) {
> 		if (nets->nets[i].count)
> 			len++;
> diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
> index d7ddc57a4eca..27bc96458e13 100644
> --- a/net/netfilter/ipset/ip_set_list_set.c
> +++ b/net/netfilter/ipset/ip_set_list_set.c
> @@ -326,6 +326,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.54.0
>
>
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage
  2026-07-14 13:18 ` [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage Florian Westphal
@ 2026-07-16 14:01   ` Jozsef Kadlecsik
  2026-07-16 14:08     ` Florian Westphal
  0 siblings, 1 reply; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 14:01 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> Move lock responsibility into kadt/uadt/flush callbacks and remove the
> last .region_lock users.

Why move the set->lock locking into the callbacks? As now all types use 
it, it'd be easier to keep it in core and not replicate it in all 
callbacks. What do you think?

Best regards,
Jozsef

> 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    | 11 +++++++--
> net/netfilter/ipset/ip_set_bitmap_ipmac.c |  9 ++++++-
> net/netfilter/ipset/ip_set_bitmap_port.c  | 11 +++++++--
> 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, 36 insertions(+), 42 deletions(-)
>
> diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
> index 99bc997914f4..3d3129118cf2 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 ca68b6e51214..fb964b5613c3 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;
> 	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..247cd1b4f4e1 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,11 @@ 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);
> +	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 +183,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..a7823dcc2f8e 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,11 @@ 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);
> +	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 +278,9 @@ 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..2a868a022b41 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,11 @@ 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);
> +	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 +199,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 a5f77f639d2a..bc2434392560 100644
> --- a/net/netfilter/ipset/ip_set_core.c
> +++ b/net/netfilter/ipset/ip_set_core.c
> @@ -722,20 +722,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)
> @@ -755,9 +741,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 */
> @@ -786,9 +770,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;
> }
> @@ -799,7 +781,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);
> @@ -808,11 +789,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);
>
> @@ -1298,9 +1275,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,
> @@ -1754,9 +1729,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 e615de2e616b..12d3bc5acc81 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
> @@ -957,7 +957,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 27bc96458e13..3669b4c9e575 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;
> @@ -404,10 +406,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)
> @@ -424,10 +429,12 @@ 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);
> 	set->elements = 0;
> 	set->ext_size = 0;
> +	spin_unlock_bh(&set->lock);
> }
>
> static void
> -- 
> 2.54.0
>
>
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable
  2026-07-16 13:22   ` Jozsef Kadlecsik
@ 2026-07-16 14:04     ` Florian Westphal
  0 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 14:04 UTC (permalink / raw)
  To: Jozsef Kadlecsik; +Cc: netfilter-devel, kadlec

Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
> >   This adds full set->lock serialization, meaning no parallel
> >   insertion for elements with comment extension.
> 
> The comment extension is restricted to add from userspace which is
> serialized by the mutex anyway.

Ah.  I was worried wrt. FORCEADD and the like evicting this and
messing the counter up.

I will look at this again, maybe this accounting can be switched
to atomic_add/sub to avoid the lock.

> > 	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 */
> 
> Please do not remove initval: the sole reason to keep it is to maintain full
> userspace compatibility. The "ipset" tool supports to specify initval and
> when listing/saving a set, initval is included in the output. Already
> existing setups may specify the value and we must be able to return it.

Fair, I will keep it to dump the value back to userspace.

> > +			pr_warn("Set %s is full, maxelem %u reached\n",
> > +				set->name, h->maxelem);
> > +		mtype_data_next(&h->next, d);
> 
> Why the mtype_data_next() call is needed here?

It's not, merge gone wrong during rebase :-/
Plan was this:

Call mtype_data_next under if (0) guard so compiler does not
complain.

Then figure out if we can remove it together with .resize callback.
But I forgot about that which is why its not mentioned in the commit
message.

Is that feasible?  (removal I mean).

> > -	if (tb[IPSET_ATTR_INITVAL])
> > -		h->initval = ntohl(nla_get_be32(tb[IPSET_ATTR_INITVAL]));
> > -	else
> > -		get_random_bytes(&h->initval, sizeof(h->initval));
> 
> When initval restored, the else branch is still not needed anymore.

Ok, thanks Jozsef, I was about to ask.  I restored the h->initval =
line.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock
  2026-07-16 13:41   ` Jozsef Kadlecsik
@ 2026-07-16 14:05     ` Florian Westphal
  0 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 14:05 UTC (permalink / raw)
  To: Jozsef Kadlecsik; +Cc: netfilter-devel, kadlec

Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
> > No need to disable/reenable softirqs.
> 
> Couldn't then similarly simple spinlocking be used instead the _bh()
> variant? I assume then that there's no need to disable/reenable softirqs in
> those cases either.

I don't think so, the spinlocks are reachable from softirq / timers, so
if you do spin_lock(s) [timer] -> reentry it will deadlock.

rcu_read_lock() can nest, so doesn't have that problem.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable
  2026-07-14 13:18 ` [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable Florian Westphal
@ 2026-07-16 14:06   ` Jozsef Kadlecsik
  0 siblings, 0 replies; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 14:06 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> The rhashtable conversion removed the SET_WITH_FORCEADD eviction logic.
> Sets created with the forceadd flag got IPSET_ERR_HASH_FULL instead of
> evicting an existing element to make room.
>
> Add mtype_remove_random() helper that walks the rhashtable to pick an
> element to evict, and call it from mtype_add() when the set is full and
> forceadd is enabled.

I like it! This is a worst case scenario when we want to add a new entry 
by all means, so deleting a random entry is quite good. However, shouldn't 
mtype_remove_random() return success/failure? mtype_add() should add the 
new entry only if mtype_remove_random() succeeded.

Best regards,
Jozsef

> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
> net/netfilter/ipset/ip_set_hash_gen.h | 43 +++++++++++++++++++++++----
> 1 file changed, 38 insertions(+), 5 deletions(-)
>
> diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
> index 12d3bc5acc81..df73c1ebb3f0 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
> @@ -116,6 +116,7 @@ static const union nf_inet_addr zeromask = {};
> #undef mtype_bucket_size
> #undef mtype_hash_size
>
> +#undef mtype_remove_random
> #undef mtype_add
> #undef mtype_del
> #undef mtype_test_cidrs
> @@ -165,6 +166,7 @@ static const union nf_inet_addr zeromask = {};
> #define mtype_bucket_size	IPSET_TOKEN(MTYPE, _bucket_size)
> #define mtype_hash_size		IPSET_TOKEN(MTYPE, _hash_size)
>
> +#define mtype_remove_random	IPSET_TOKEN(MTYPE, _remove_random)
> #define mtype_add		IPSET_TOKEN(MTYPE, _add)
> #define mtype_del		IPSET_TOKEN(MTYPE, _del)
> #define mtype_test_cidrs	IPSET_TOKEN(MTYPE, _test_cidrs)
> @@ -511,6 +513,33 @@ mtype_ext_size(struct ip_set *set, u32 *elements, size_t *ext_size)
> 		    (offsetof(struct mtype_rht_elem, elem) + set->dsize);
> }
>
> +/* Evict one element from the set to make room for a new one (forceadd) */
> +static void __maybe_unused
> +mtype_remove_random(struct ip_set *set, struct htype *h)
> +{
> +	struct rhashtable_iter hti;
> +	struct mtype_rht_elem *e;
> +	bool removed = false;
> +
> +	rhashtable_walk_enter(&h->ht, &hti);
> +	rhashtable_walk_start(&hti);
> +	e = rhashtable_walk_next(&hti);
> +	if (IS_ERR(e))
> +		e = NULL;
> +
> +	if (e && !rhashtable_remove_fast(&h->ht, &e->node, mtype_rht_params))
> +		removed = true;
> +
> +	rhashtable_walk_stop(&hti);
> +	rhashtable_walk_exit(&hti);
> +
> +	if (removed) {
> +		mtype_del_cidr_all(set, h, &e->elem);
> +		ip_set_ext_destroy_slow(set, &e->elem);
> +		kfree_rcu(e, rcu);
> +	}
> +}
> +
> /* Add an element to a hash and update the internal counters when succeeded,
>  * otherwise report the proper error code.
>  */
> @@ -573,11 +602,15 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
> 	rcu_read_unlock();
>
> 	if (atomic_read(&h->ht.nelems) >= h->maxelem) {
> -		if (net_ratelimit())
> -			pr_warn("Set %s is full, maxelem %u reached\n",
> -				set->name, h->maxelem);
> -		mtype_data_next(&h->next, d);
> -		return -IPSET_ERR_HASH_FULL;
> +		if (SET_WITH_FORCEADD(set)) {
> +			mtype_remove_random(set, h);
> +		} else {
> +			if (net_ratelimit())
> +				pr_warn("Set %s is full, maxelem %u reached\n",
> +					set->name, h->maxelem);
> +			mtype_data_next(&h->next, d);
> +			return -IPSET_ERR_HASH_FULL;
> +		}
> 	}
>
> 	e = kzalloc(offsetof(struct mtype_rht_elem, elem) + set->dsize,
> -- 
> 2.54.0
>
>
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference
  2026-07-16 13:56   ` Jozsef Kadlecsik
@ 2026-07-16 14:07     ` Florian Westphal
  0 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 14:07 UTC (permalink / raw)
  To: Jozsef Kadlecsik; +Cc: netfilter-devel, kadlec

Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
> > ip_set_comment_free() is problematic: called from different contexts,
> > some hold set->lock spinlock (safe), some do not hold a lock but have other
> > means of mutual exclusion (e.g., entire set torn down).
> 
> Wouldn't something like the following be sufficient?
> 
> - Add a bool "deleted" element to struct ip_set.
> - The set-specific destroy function would set it true before
>   doing anything else.
> - Then it'd be safe to use

> #define ipset_dereference_locked(p, set)	\
> 	rcu_dereference_protected(p, lockdep_is_held(&set->lock) || \
> 				     set->deleted))

Yes, that would work.  I'll add this to my todo list.

> > Other callers need investigation: ip_set_comment_free() alters
> > set->ext_size in a non-atomic way.  I don't see how this is safe except
> > for "entire set is destroyed" case: parallel usage would be a bug.
> 
> Maybe we should convert ext_size to atomic64_t?

I was wondering that too.  It would be simpler and avoid these
new helpers -> less code churn.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage
  2026-07-16 14:01   ` Jozsef Kadlecsik
@ 2026-07-16 14:08     ` Florian Westphal
  2026-07-16 14:17       ` Jozsef Kadlecsik
  0 siblings, 1 reply; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 14:08 UTC (permalink / raw)
  To: Jozsef Kadlecsik; +Cc: netfilter-devel, kadlec

Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
> > Move lock responsibility into kadt/uadt/flush callbacks and remove the
> > last .region_lock users.
> 
> Why move the set->lock locking into the callbacks? As now all types use it,
> it'd be easier to keep it in core and not replicate it in all callbacks.

Ideally the hash types would never use it (or only for comment
extension) for delete/add.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage
  2026-07-16 14:08     ` Florian Westphal
@ 2026-07-16 14:17       ` Jozsef Kadlecsik
  2026-07-16 14:52         ` Florian Westphal
  0 siblings, 1 reply; 29+ messages in thread
From: Jozsef Kadlecsik @ 2026-07-16 14:17 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel, kadlec

On Thu, 16 Jul 2026, Florian Westphal wrote:

> Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
>>> Move lock responsibility into kadt/uadt/flush callbacks and remove the
>>> last .region_lock users.
>>
>> Why move the set->lock locking into the callbacks? As now all types use it,
>> it'd be easier to keep it in core and not replicate it in all callbacks.
>
> Ideally the hash types would never use it (or only for comment
> extension) for delete/add.

The comment string is kept in struct ip_set_comment_rcu. The whole mess 
about the extension was due to resize copying the pointer of the wrapper 
struct ip_set_comment while ongoing gc could happen. However resize is 
gone now. Re-add from userspace can change the comment string but it can 
nicely be handled with rcu. So as far as I see ext_size remains to be 
solved and the hash types could remain lockless.

Best regards,
Jozsef



^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage
  2026-07-16 14:17       ` Jozsef Kadlecsik
@ 2026-07-16 14:52         ` Florian Westphal
  0 siblings, 0 replies; 29+ messages in thread
From: Florian Westphal @ 2026-07-16 14:52 UTC (permalink / raw)
  To: Jozsef Kadlecsik; +Cc: netfilter-devel, kadlec

Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> wrote:
> The comment string is kept in struct ip_set_comment_rcu. The whole mess
> about the extension was due to resize copying the pointer of the wrapper
> struct ip_set_comment while ongoing gc could happen. However resize is gone
> now. Re-add from userspace can change the comment string but it can nicely
> be handled with rcu. So as far as I see ext_size remains to be solved and
> the hash types could remain lockless.

Great, thanks Jozsef.  So plan is to make it atomic64_t (which obsoletes
the _slow()) functions added here) which also avoids set->lock for hash
add/del.

^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2026-07-16 14:52 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14 13:18 [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 01/12] netfilter: ipset: rework cidr bookkeeping Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 02/12] netfilter: ipset: rework cidr bookkeeping fixups Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 03/12] netfilter: ipset: add small wrappers for hash and bucket sizes Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 04/12] netfilter: ipset: add and use mtype_del_cidr_all helper Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 05/12] netfilter: ipset: add and use ip_set_init_comment_slow Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 06/12] netfilter: ipset: add and use ip_set_ext_destroy_slow Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 07/12] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
2026-07-16 12:53   ` Jozsef Kadlecsik
2026-07-16 13:00     ` Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 08/12] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
2026-07-16 13:22   ` Jozsef Kadlecsik
2026-07-16 14:04     ` Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 09/12] netfilter: ipset: use plain rcu_read_lock Florian Westphal
2026-07-16 13:41   ` Jozsef Kadlecsik
2026-07-16 14:05     ` Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference Florian Westphal
2026-07-16 13:56   ` Jozsef Kadlecsik
2026-07-16 14:07     ` Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 11/12] netfilter: ipset: remove last region lock usage Florian Westphal
2026-07-16 14:01   ` Jozsef Kadlecsik
2026-07-16 14:08     ` Florian Westphal
2026-07-16 14:17       ` Jozsef Kadlecsik
2026-07-16 14:52         ` Florian Westphal
2026-07-14 13:18 ` [PATCH RFC nf-next 12/12] netfilter: ipset: re-add forceadd support for rhashtable Florian Westphal
2026-07-16 14:06   ` Jozsef Kadlecsik
2026-07-14 15:52 ` [PATCH RFC nf-next 00/12] netfilter: ipset: convert to rhashtable Jozsef Kadlecsik
2026-07-15  5:54 ` [syzbot ci] " syzbot ci
2026-07-16 13:02   ` Florian Westphal

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.