From: Zhewei Yuan <yzwlhm@gmail.com>
To: netdev@vger.kernel.org
Cc: Zhewei Yuan <yzwlhm@gmail.com>,
linux-kernel@vger.kernel.org,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>,
Neal Cardwell <ncardwell@google.com>,
Kuniyuki Iwashima <kuniyu@google.com>
Subject: [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches
Date: Sun, 25 Oct 2026 09:00:48 +0000 [thread overview]
Message-ID: <20261025090048.27870-3-yzwlhm@gmail.com> (raw)
In-Reply-To: <20261025090048.27870-1-yzwlhm@gmail.com>
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
next prev parent reply other threads:[~2026-10-25 9:01 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Zhewei Yuan [this message]
2026-10-25 11:08 ` [PATCH net 2/2] tcp: Add KUnit tests for bind fastreuse caches Eric Dumazet
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261025090048.27870-3-yzwlhm@gmail.com \
--to=yzwlhm@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®