mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net 0/2] tcp: Fix fastreuse cache initialization on first bind
@ 2026-10-25  9:00 Zhewei Yuan
  2026-10-25  9:00 ` [PATCH net 1/2] " Zhewei Yuan
  2026-10-25  9:00 ` [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches Zhewei Yuan
  0 siblings, 2 replies; 5+ messages in thread
From: Zhewei Yuan @ 2026-10-25  9:00 UTC (permalink / raw)
  To: netdev
  Cc: Zhewei Yuan, linux-kernel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Neal Cardwell,
	Kuniyuki Iwashima

This series fixes TCP bind reuse cache initialization and adds eight
KUnit regression cases.

Since commit 8002d44fe84d, tb2 is already linked to tb->bhash2 when
inet_csk_update_fastreuse() checks for an empty list. First binds miss
fastreuse=1 for SO_REUSEADDR and FASTREUSEPORT_ANY for SO_REUSEPORT,
causing unnecessary conflict scans. Patch 1 recognizes the sole,
ownerless address bucket; patch 2 tests initialization and conflicts.

Bind-only results on historical net f4377bf85887 (not the submission
base): TCP/IPv4, one-vCPU KVM, GCC 11.5.0, identical configs/programs.
Means of 3 guest boots x 3 rounds; only bind() is timed, with all sockets
kept open. Each case uses one reuse option, the same UID and port.
"same" uses 127.0.0.1; "alternate" interleaves 127.0.0.1/127.0.0.2.

  16000 sockets                 before (ms)    after (ms)
  SO_REUSEADDR, same                 4256.956        20.004
  SO_REUSEADDR, alternate            2476.914        19.983
  SO_REUSEPORT, same                   20.600        19.557
  SO_REUSEPORT, alternate            2491.294        19.830

Same-IP SO_REUSEPORT measurements overlap, so no stable gain is claimed
there. These are bind microbenchmarks, not application throughput.
A minimal reproducer is included at the end of this letter.

Validation on submission base 7076f684d513:
- KUnit: six expected first-bind failures and two passing conflict
  guards before the fix; all eight pass after it (same tests/config).
- bind_wildcard, reuseaddr_conflict, reuseport_addr_any,
  reuseaddr_ports_exhausted and tcp_port_share: pass without skips.
- Full x86_64 allmodconfig W=1: baseline and patched builds pass with
  identical configs, GCC 11.5.0 and CONFIG_WERROR=n. Existing warnings
  remain, but normalized diagnostics and counts match: no new warnings.
- Full allyesconfig is blocked by the MCTP KUnit static_assert at
  drivers/net/mctp/mctp-usblib-test.c:819. A targeted unpatched build
  reproduces the same error; this is not a full allyesconfig pass.

KUnit A/B and patched allyesconfig/allmodconfig W=1 builds also passed
on historical f4377bf85887 (GCC 11.5.0, CONFIG_WERROR=n); those builds
are auxiliary evidence, not substitutes for the submission-base checks.

Found during manual code inspection and GDB tracing. Codex assisted
with analysis, test/benchmark development, validation and patch text.
Requests covered real first binds, cache flags, automatic ports,
conflict preservation and separate reuseaddr/reuseport measurements.
No failure injection was used; KUnit inspects state after real binds.

Reproducer (not for the merge commit). It gives matching results on
the submission base 7076f684d513 (16000 sockets, same order as the
table, before/after in ms: 4178/19.4, 2416/19.4, 19.8/19.4,
2428/19.5). It needs loopback up and an fd limit above N, e.g.
"ulimit -n 70000; ./bind_bench reuseaddr same 16000".

  // Time bind() of N TCP sockets with one reuse option to one port.
  // Usage: bind_bench reuseaddr|reuseport same|alternate N
  // same: 127.0.0.1; alternate: 127.0.0.1/127.0.0.2. Sockets stay open.
  #include <netinet/in.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/socket.h>
  #include <time.h>

  int main(int argc, char **argv)
  {
  	struct sockaddr_in sa = { .sin_family = AF_INET,
  				  .sin_port = htons(40000) };
  	int opt, alt, n, one = 1, i, fd;
  	struct timespec a, b;
  	long long ns = 0;

  	if (argc != 4)
  		return fprintf(stderr, "usage: %s reuseaddr|reuseport same|alternate N\n",
  			       argv[0]), 1;
  	opt = strcmp(argv[1], "reuseport") ? SO_REUSEADDR : SO_REUSEPORT;
  	alt = !strcmp(argv[2], "alternate");
  	n = atoi(argv[3]);

  	for (i = 0; i < n; i++) {
  		fd = socket(AF_INET, SOCK_STREAM, 0);
  		if (fd < 0 || setsockopt(fd, SOL_SOCKET, opt, &one, sizeof(one)))
  			return perror("socket/setsockopt"), 1;
  		sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK + (alt ? i % 2 : 0));
  		clock_gettime(CLOCK_MONOTONIC, &a);
  		if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)))
  			return perror("bind"), 1;
  		clock_gettime(CLOCK_MONOTONIC, &b);
  		ns += (b.tv_sec - a.tv_sec) * 1000000000LL + b.tv_nsec - a.tv_nsec;
  	}
  	printf("%s %s %d: %.3f ms\n", argv[1], argv[2], n, ns / 1e6);
  	return 0;
  }

Zhewei Yuan (2):
  tcp: Fix fastreuse cache initialization on first bind
  tcp: Add KUnit tests for bind fastreuse caches

 net/ipv4/.kunitconfig                |   8 +
 net/ipv4/Kconfig                     |  14 ++
 net/ipv4/Makefile                    |   1 +
 net/ipv4/inet_connection_sock.c      |   3 +-
 net/ipv4/inet_connection_sock_test.c | 303 +++++++++++++++++++++++++++
 5 files changed, 328 insertions(+), 1 deletion(-)
 create mode 100644 net/ipv4/.kunitconfig
 create mode 100644 net/ipv4/inet_connection_sock_test.c


base-commit: 7076f684d5132e6a6145e1da2fa74771ecd052e5
-- 
2.52.0


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

* [PATCH net 1/2] tcp: Fix fastreuse cache initialization on first bind
  2026-10-25  9:00 [PATCH net 0/2] tcp: Fix fastreuse cache initialization on first bind Zhewei Yuan
@ 2026-10-25  9:00 ` Zhewei Yuan
  2026-10-25 11:06   ` Eric Dumazet
  2026-10-25  9:00 ` [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches Zhewei Yuan
  1 sibling, 1 reply; 5+ messages in thread
From: Zhewei Yuan @ 2026-10-25  9:00 UTC (permalink / raw)
  To: netdev
  Cc: Zhewei Yuan, linux-kernel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Neal Cardwell,
	Kuniyuki Iwashima

inet_csk_update_fastreuse() initializes the reuse caches only when
tb->bhash2 is empty. Both inet_csk_get_port() and __inet_inherit_port()
link tb2 before calling it, so this condition is never met.

Fresh buckets therefore miss fastreuse=1 for SO_REUSEADDR, forcing
repeated binds to scan existing owners. SO_REUSEPORT starts in STRICT
instead of ANY, causing repeated fast-path misses with alternating
addresses.

Initialize the caches when tb2 is the sole entry in tb->bhash2 and has
no owners. Both checks prevent reinitializing an already-used bucket.

On historical net f4377bf85887, 16000 SO_REUSEADDR binds to one IPv4
address/port take 4256.956 ms before and 20.004 ms after the fix in a
one-vCPU KVM guest (bind-only means of 3 boots x 3 rounds).

Found by manual inspection and GDB tracing. Codex assisted with
analysis, validation and wording; test/benchmark assistance is
described in the cover letter.

Fixes: 8002d44fe84d ("tcp: Check hlist_empty(&tb->bhash2) instead of hlist_empty(&tb->owners).")
Signed-off-by: Zhewei Yuan <yzwlhm@gmail.com>

---
 net/ipv4/inet_connection_sock.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 8151c638a24..93b306d91b6 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -446,7 +446,8 @@ void inet_csk_update_fastreuse(const struct sock *sk,
 {
 	bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN;
 
-	if (hlist_empty(&tb->bhash2)) {
+	if (hlist_is_singular_node(&tb2->bhash_node, &tb->bhash2) &&
+	    hlist_empty(&tb2->owners)) {
 		tb->fastreuse = reuse;
 		if (sk->sk_reuseport) {
 			tb->fastreuseport = FASTREUSEPORT_ANY;
-- 
2.52.0


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

* [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches
  2026-10-25  9:00 [PATCH net 0/2] tcp: Fix fastreuse cache initialization on first bind Zhewei Yuan
  2026-10-25  9:00 ` [PATCH net 1/2] " Zhewei Yuan
@ 2026-10-25  9:00 ` Zhewei Yuan
  2026-10-25 11:08   ` Eric Dumazet
  1 sibling, 1 reply; 5+ messages in thread
From: Zhewei Yuan @ 2026-10-25  9:00 UTC (permalink / raw)
  To: netdev
  Cc: Zhewei Yuan, linux-kernel, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Neal Cardwell,
	Kuniyuki Iwashima

Add eight KUnit cases using real TCP binds in isolated network namespaces.
Six check the tb/tb2 reuse caches for SO_REUSEADDR, SO_REUSEPORT and both,
with explicit or automatic ports. Two guard against reinitialization
when an owner or another address bucket already exists.

The six first-bind cases fail before the fix and all eight pass after
it, using matching tests/configs; both conflict guards pass before and
after.

Run with:

  ./tools/testing/kunit/kunit.py run --arch=x86_64 \
    --kunitconfig=net/ipv4/.kunitconfig --jobs=8 --timeout=120 \
    inet_csk_fastreuse

Codex assisted with test development and review, following requests for
real binds, cache inspection, automatic ports and conflict preservation.

Signed-off-by: Zhewei Yuan <yzwlhm@gmail.com>
---
 net/ipv4/.kunitconfig                |   8 +
 net/ipv4/Kconfig                     |  14 ++
 net/ipv4/Makefile                    |   1 +
 net/ipv4/inet_connection_sock_test.c | 303 +++++++++++++++++++++++++++
 4 files changed, 326 insertions(+)
 create mode 100644 net/ipv4/.kunitconfig
 create mode 100644 net/ipv4/inet_connection_sock_test.c

diff --git a/net/ipv4/.kunitconfig b/net/ipv4/.kunitconfig
new file mode 100644
index 00000000000..11ba2dd7bea
--- /dev/null
+++ b/net/ipv4/.kunitconfig
@@ -0,0 +1,8 @@
+CONFIG_KUNIT=y
+# CONFIG_KUNIT_ALL_TESTS is not set
+CONFIG_MULTIUSER=y
+CONFIG_NAMESPACES=y
+CONFIG_NET=y
+CONFIG_NET_NS=y
+CONFIG_INET=y
+CONFIG_INET_CONNECTION_SOCK_KUNIT_TEST=y
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index 3545fb0a045..913bf9a6f43 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -778,3 +778,17 @@ config TCP_MD5SIG
 	  on the Internet.
 
 	  If unsure, say N.
+
+config INET_CONNECTION_SOCK_KUNIT_TEST
+	bool "KUnit tests for INET connection sockets" if !KUNIT_ALL_TESTS
+	depends on INET && NET_NS && KUNIT=y
+	default KUNIT_ALL_TESTS
+	help
+	  Test initialization and updates of the INET reuse caches through
+	  real TCP socket bind operations in isolated network namespaces.
+	  Exercise SO_REUSEADDR, SO_REUSEPORT and their combination, checking
+	  the fast reuse flags in both the port and address binding buckets.
+	  Cover both explicit and automatically allocated local ports.
+	  Verify that cache updates preserve conflicts with existing sockets.
+
+	  If unsure, say N.
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 69e4baaed12..2194bf2a3a4 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -16,6 +16,7 @@ obj-y     := route.o inetpeer.o protocol.o \
 	     inet_fragment.o ping.o ip_tunnel_core.o gre_offload.o \
 	     metrics.o netlink.o nexthop.o udp_tunnel_stub.o
 
+obj-$(CONFIG_INET_CONNECTION_SOCK_KUNIT_TEST) += inet_connection_sock_test.o
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
 obj-$(CONFIG_PROC_FS) += proc.o
diff --git a/net/ipv4/inet_connection_sock_test.c b/net/ipv4/inet_connection_sock_test.c
new file mode 100644
index 00000000000..7b210859de6
--- /dev/null
+++ b/net/ipv4/inet_connection_sock_test.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <kunit/test.h>
+#include <linux/in.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/user_namespace.h>
+#include <net/inet_connection_sock.h>
+#include <net/inet_hashtables.h>
+#include <net/net_namespace.h>
+
+struct inet_csk_fastreuse_param {
+	const char *name;
+	bool reuseaddr;
+	bool reuseport;
+	u16 port;
+	int fastreuse;
+	int fastreuseport;
+};
+
+struct inet_csk_fastreuse_cache {
+	int tb_fastreuse;
+	int tb_fastreuseport;
+	int tb2_fastreuse;
+	int tb2_fastreuseport;
+};
+
+static const struct inet_csk_fastreuse_param inet_csk_fastreuse_params[] = {
+	{
+		.name = "reuseaddr_only",
+		.reuseaddr = true,
+		.reuseport = false,
+		.port = 40000,
+		.fastreuse = 1,
+		.fastreuseport = 0,
+	},
+	{
+		.name = "reuseport_only",
+		.reuseaddr = false,
+		.reuseport = true,
+		.port = 40000,
+		.fastreuse = 0,
+		.fastreuseport = FASTREUSEPORT_ANY,
+	},
+	{
+		.name = "reuseaddr_and_reuseport",
+		.reuseaddr = true,
+		.reuseport = true,
+		.port = 40000,
+		.fastreuse = 1,
+		.fastreuseport = FASTREUSEPORT_ANY,
+	},
+	{
+		.name = "reuseaddr_only_port_zero",
+		.reuseaddr = true,
+		.reuseport = false,
+		.port = 0,
+		.fastreuse = 1,
+		.fastreuseport = 0,
+	},
+	{
+		.name = "reuseport_only_port_zero",
+		.reuseaddr = false,
+		.reuseport = true,
+		.port = 0,
+		.fastreuse = 0,
+		.fastreuseport = FASTREUSEPORT_ANY,
+	},
+	{
+		.name = "reuseaddr_and_reuseport_port_zero",
+		.reuseaddr = true,
+		.reuseport = true,
+		.port = 0,
+		.fastreuse = 1,
+		.fastreuseport = FASTREUSEPORT_ANY,
+	},
+};
+
+static void
+inet_csk_fastreuse_get_desc(const struct inet_csk_fastreuse_param *param,
+			    char *desc)
+{
+	strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(inet_csk_fastreuse, inet_csk_fastreuse_params,
+		  inet_csk_fastreuse_get_desc);
+
+static void inet_csk_test_put_net(void *data)
+{
+	put_net(data);
+}
+
+static void inet_csk_test_release_socket(void *data)
+{
+	sock_release(data);
+}
+
+static struct net *inet_csk_test_create_net(struct kunit *test)
+{
+	struct net *net;
+	int ret;
+
+	net = copy_net_ns(CLONE_NEWNET, &init_user_ns, &init_net);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, net);
+
+	ret = kunit_add_action_or_reset(test, inet_csk_test_put_net, net);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return net;
+}
+
+static struct socket *
+inet_csk_test_create_socket(struct kunit *test, struct net *net)
+{
+	struct socket *sock;
+	int ret;
+
+	ret = sock_create_kern(net, AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	/* Close all sockets before the namespace cleanup action runs. */
+	ret = kunit_add_action_or_reset(test, inet_csk_test_release_socket,
+					sock);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return sock;
+}
+
+static int inet_csk_test_bind(struct socket *sock, u32 address, u16 port)
+{
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_addr.s_addr = htonl(address),
+		.sin_port = htons(port),
+	};
+
+	return kernel_bind(sock, (struct sockaddr_unsized *)&addr, sizeof(addr));
+}
+
+static struct inet_csk_fastreuse_cache
+inet_csk_test_read_cache(struct kunit *test, struct sock *sk)
+{
+	struct inet_bind_hashbucket *head, *head2;
+	struct inet_csk_fastreuse_cache cache;
+	struct inet_bind2_bucket *tb2;
+	struct inet_bind_bucket *tb;
+	struct inet_hashinfo *hinfo;
+	struct net *net = sock_net(sk);
+
+	tb = inet_csk(sk)->icsk_bind_hash;
+	tb2 = inet_csk(sk)->icsk_bind2_hash;
+	KUNIT_ASSERT_NOT_NULL(test, tb);
+	KUNIT_ASSERT_NOT_NULL(test, tb2);
+
+	hinfo = tcp_get_hashinfo(sk);
+	head = &hinfo->bhash[inet_bhashfn(net, inet_sk(sk)->inet_num,
+					  hinfo->bhash_size)];
+	head2 = inet_bhashfn_portaddr(hinfo, sk, net, inet_sk(sk)->inet_num);
+	spin_lock_bh(&head->lock);
+	spin_lock(&head2->lock);
+	cache.tb_fastreuse = tb->fastreuse;
+	cache.tb_fastreuseport = tb->fastreuseport;
+	cache.tb2_fastreuse = tb2->fastreuse;
+	cache.tb2_fastreuseport = tb2->fastreuseport;
+	spin_unlock(&head2->lock);
+	spin_unlock_bh(&head->lock);
+
+	return cache;
+}
+
+static void inet_csk_fastreuse_first_bind(struct kunit *test)
+{
+	const struct inet_csk_fastreuse_param *param = test->param_value;
+	struct inet_csk_fastreuse_cache cache;
+	struct sockaddr_in bound = {};
+	struct socket *sock;
+	struct net *net;
+	int ret;
+
+	net = inet_csk_test_create_net(test);
+	sock = inet_csk_test_create_socket(test, net);
+	if (param->reuseaddr)
+		sock_set_reuseaddr(sock->sk);
+	if (param->reuseport)
+		sock_set_reuseport(sock->sk);
+
+	ret = inet_csk_test_bind(sock, INADDR_ANY, param->port);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	ret = kernel_getsockname(sock, (struct sockaddr *)&bound);
+	KUNIT_ASSERT_EQ(test, ret, (int)sizeof(bound));
+	if (param->port)
+		KUNIT_EXPECT_EQ(test, ntohs(bound.sin_port), param->port);
+	else
+		KUNIT_EXPECT_NE(test, ntohs(bound.sin_port), 0);
+
+	cache = inet_csk_test_read_cache(test, sock->sk);
+	KUNIT_EXPECT_EQ(test, cache.tb_fastreuse, param->fastreuse);
+	KUNIT_EXPECT_EQ(test, cache.tb_fastreuseport, param->fastreuseport);
+	KUNIT_EXPECT_EQ(test, cache.tb2_fastreuse, param->fastreuse);
+	KUNIT_EXPECT_EQ(test, cache.tb2_fastreuseport, param->fastreuseport);
+}
+
+static void inet_csk_fastreuse_existing_owner(struct kunit *test)
+{
+	struct inet_csk_fastreuse_cache cache;
+	struct socket *sock_a, *sock_b, *sock_c;
+	struct net *net;
+	int ret;
+
+	net = inet_csk_test_create_net(test);
+	sock_a = inet_csk_test_create_socket(test, net);
+	sock_set_reuseport(sock_a->sk);
+	ret = inet_csk_test_bind(sock_a, INADDR_ANY, 40000);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	sock_b = inet_csk_test_create_socket(test, net);
+	sock_set_reuseaddr(sock_b->sk);
+	sock_set_reuseport(sock_b->sk);
+	ret = inet_csk_test_bind(sock_b, INADDR_ANY, 40000);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	KUNIT_ASSERT_PTR_EQ(test, inet_csk(sock_a->sk)->icsk_bind_hash,
+			    inet_csk(sock_b->sk)->icsk_bind_hash);
+	KUNIT_ASSERT_PTR_EQ(test, inet_csk(sock_a->sk)->icsk_bind2_hash,
+			    inet_csk(sock_b->sk)->icsk_bind2_hash);
+
+	/* The sole address bucket already owns A, which has no SO_REUSEADDR. */
+	cache = inet_csk_test_read_cache(test, sock_b->sk);
+	KUNIT_EXPECT_EQ(test, cache.tb_fastreuse, 0);
+	KUNIT_EXPECT_EQ(test, cache.tb2_fastreuse, 0);
+
+	/* An incorrect reset to fastreuse=1 would bypass the conflict with A. */
+	sock_c = inet_csk_test_create_socket(test, net);
+	sock_set_reuseaddr(sock_c->sk);
+	ret = inet_csk_test_bind(sock_c, INADDR_ANY, 40000);
+	KUNIT_EXPECT_EQ(test, ret, -EADDRINUSE);
+}
+
+static void inet_csk_fastreuse_new_address(struct kunit *test)
+{
+	struct inet_csk_fastreuse_cache cache;
+	struct socket *sock_a, *sock_b, *sock_c;
+	struct net *net;
+	int ret;
+
+	net = inet_csk_test_create_net(test);
+	/* Make both 127.0.0.1 and 127.0.0.2 local to this test namespace. */
+	rtnl_lock();
+	ret = dev_open(net->loopback_dev, NULL);
+	rtnl_unlock();
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	sock_a = inet_csk_test_create_socket(test, net);
+	ret = inet_csk_test_bind(sock_a, INADDR_LOOPBACK, 40000);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	sock_b = inet_csk_test_create_socket(test, net);
+	sock_set_reuseport(sock_b->sk);
+	ret = inet_csk_test_bind(sock_b, INADDR_LOOPBACK + 1, 40000);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	KUNIT_ASSERT_PTR_EQ(test, inet_csk(sock_a->sk)->icsk_bind_hash,
+			    inet_csk(sock_b->sk)->icsk_bind_hash);
+	KUNIT_ASSERT_PTR_NE(test, inet_csk(sock_a->sk)->icsk_bind2_hash,
+			    inet_csk(sock_b->sk)->icsk_bind2_hash);
+
+	/* B creates an empty address bucket, but A still owns the port. */
+	cache = inet_csk_test_read_cache(test, sock_b->sk);
+	KUNIT_EXPECT_EQ(test, cache.tb_fastreuse, 0);
+	KUNIT_EXPECT_EQ(test, cache.tb_fastreuseport, FASTREUSEPORT_STRICT);
+	KUNIT_EXPECT_EQ(test, cache.tb2_fastreuse, 0);
+	KUNIT_EXPECT_EQ(test, cache.tb2_fastreuseport, FASTREUSEPORT_STRICT);
+
+	/* ANY would wrongly admit C despite A not having SO_REUSEPORT. */
+	sock_c = inet_csk_test_create_socket(test, net);
+	sock_set_reuseport(sock_c->sk);
+	ret = inet_csk_test_bind(sock_c, INADDR_LOOPBACK, 40000);
+	KUNIT_EXPECT_EQ(test, ret, -EADDRINUSE);
+}
+
+static struct kunit_case inet_csk_fastreuse_cases[] = {
+	KUNIT_CASE_PARAM(inet_csk_fastreuse_first_bind,
+			 inet_csk_fastreuse_gen_params),
+	KUNIT_CASE(inet_csk_fastreuse_existing_owner),
+	KUNIT_CASE(inet_csk_fastreuse_new_address),
+	{}
+};
+
+static struct kunit_suite inet_csk_fastreuse_suite = {
+	.name = "inet_csk_fastreuse",
+	.test_cases = inet_csk_fastreuse_cases,
+};
+
+kunit_test_suite(inet_csk_fastreuse_suite);
+
+MODULE_DESCRIPTION("KUnit tests for INET bind reuse caching");
+MODULE_LICENSE("GPL");
-- 
2.52.0


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

* Re: [PATCH net 1/2] tcp: Fix fastreuse cache initialization on first bind
  2026-10-25  9:00 ` [PATCH net 1/2] " Zhewei Yuan
@ 2026-10-25 11:06   ` Eric Dumazet
  0 siblings, 0 replies; 5+ messages in thread
From: Eric Dumazet @ 2026-10-25 11:06 UTC (permalink / raw)
  To: Zhewei Yuan
  Cc: netdev, linux-kernel, David S. Miller, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Neal Cardwell, Kuniyuki Iwashima

Le dim. 25 oct. 2026 à 10:01, Zhewei Yuan <yzwlhm@gmail.com> a écrit :
>
> inet_csk_update_fastreuse() initializes the reuse caches only when
> tb->bhash2 is empty. Both inet_csk_get_port() and __inet_inherit_port()
> link tb2 before calling it, so this condition is never met.
>
> Fresh buckets therefore miss fastreuse=1 for SO_REUSEADDR, forcing
> repeated binds to scan existing owners. SO_REUSEPORT starts in STRICT
> instead of ANY, causing repeated fast-path misses with alternating
> addresses.
>
> Initialize the caches when tb2 is the sole entry in tb->bhash2 and has
> no owners. Both checks prevent reinitializing an already-used bucket.
>
> On historical net f4377bf85887, 16000 SO_REUSEADDR binds to one IPv4
> address/port take 4256.956 ms before and 20.004 ms after the fix in a
> one-vCPU KVM guest (bind-only means of 3 boots x 3 rounds).
>
> Found by manual inspection and GDB tracing. Codex assisted with
> analysis, validation and wording; test/benchmark assistance is
> described in the cover letter.
>
> Fixes: 8002d44fe84d ("tcp: Check hlist_empty(&tb->bhash2) instead of hlist_empty(&tb->owners).")
> Signed-off-by: Zhewei Yuan <yzwlhm@gmail.com>
>
> ---
>  net/ipv4/inet_connection_sock.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
> index 8151c638a24..93b306d91b6 100644
> --- a/net/ipv4/inet_connection_sock.c
> +++ b/net/ipv4/inet_connection_sock.c
> @@ -446,7 +446,8 @@ void inet_csk_update_fastreuse(const struct sock *sk,
>  {
>         bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN;
>
> -       if (hlist_empty(&tb->bhash2)) {
> +       if (hlist_is_singular_node(&tb2->bhash_node, &tb->bhash2) &&
> +           hlist_empty(&tb2->owners)) {
>                 tb->fastreuse = reuse;
>                 if (sk->sk_reuseport) {
>                         tb->fastreuseport = FASTREUSEPORT_ANY;
> --
> 2.52.0
>

Indeed, inet_bind2_bucket_init() adds tb2 to tb->bhash2, so the
first-bind branch has been dead since 8002d44fe84d.

Since timewait sockets are also linked on tb2->owners, this is
equivalent to the old hlist_empty(&tb->owners) test.

Reviewed-by: Eric Dumazet <edumazet@edumazet@kernel.org>

Please send this patch alone (no cover letter). Patch 2 is not net
material, I doubt we want it
(see my separate feedback on it)

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

* Re: [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches
  2026-10-25  9:00 ` [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches Zhewei Yuan
@ 2026-10-25 11:08   ` Eric Dumazet
  0 siblings, 0 replies; 5+ messages in thread
From: Eric Dumazet @ 2026-10-25 11:08 UTC (permalink / raw)
  To: Zhewei Yuan
  Cc: netdev, linux-kernel, David S. Miller, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Neal Cardwell, Kuniyuki Iwashima

Le dim. 25 oct. 2026 à 10:01, Zhewei Yuan <yzwlhm@gmail.com> a écrit :
>
> Add eight KUnit cases using real TCP binds in isolated network namespaces.
> Six check the tb/tb2 reuse caches for SO_REUSEADDR, SO_REUSEPORT and both,
> with explicit or automatic ports. Two guard against reinitialization
> when an owner or another address bucket already exists.
>
> The six first-bind cases fail before the fix and all eight pass after
> it, using matching tests/configs; both conflict guards pass before and
> after.
>
> Run with:
>
>   ./tools/testing/kunit/kunit.py run --arch=x86_64 \
>     --kunitconfig=net/ipv4/.kunitconfig --jobs=8 --timeout=120 \
>     inet_csk_fastreuse
>
> Codex assisted with test development and review, following requests for
> real binds, cache inspection, automatic ports and conflict preservation.
>
> Signed-off-by: Zhewei Yuan <yzwlhm@gmail.com>

I do not think we want this LLM stuff.

300 lines, a new Kconfig option and a .kunitconfig, all to peek at
tb->fastreuse and tb2->fastreuseport. These are implementation
details, and this test will break the next time someone touches bhash.

Bind conflict behavior is already covered by selftests
(reuseaddr_conflict, bind_wildcard, reuseport_addr_any...),
and they keep passing with or without patch 1, since the bug only
causes extra conflict scans.

If you really want coverage of the performance issue, a selftest in
tools/testing/selftests/net would be better, targeting net-next.

Also, please keep the changelogs and cover letters short.

pw-bot: cr

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

end of thread, other threads:[~2026-10-25 11:08 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-25  9:00 [PATCH net 0/2] tcp: Fix fastreuse cache initialization on first bind Zhewei Yuan
2026-10-25  9:00 ` [PATCH net 1/2] " Zhewei Yuan
2026-10-25 11:06   ` Eric Dumazet
2026-10-25  9:00 ` [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches Zhewei Yuan
2026-10-25 11:08   ` Eric Dumazet

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®