Netdev List
 help / color / mirror / Atom feed
From: Breno Leitao <leitao@debian.org>
To: "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>,
	 Kuniyuki Iwashima <kuniyu@google.com>,
	 Willem de Bruijn <willemb@google.com>,
	Neal Cardwell <ncardwell@google.com>,
	 Daniel Borkmann <daniel@iogearbox.net>,
	 John Fastabend <john.fastabend@gmail.com>,
	 Stanislav Fomichev <sdf@fomichev.me>,
	 Martin KaFai Lau <martin.lau@linux.dev>,
	 Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	 Eduard Zingerman <eddyz87@gmail.com>,
	 Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Song Liu <song@kernel.org>,
	 Yonghong Song <yonghong.song@linux.dev>,
	Jiri Olsa <jolsa@kernel.org>,
	 Emil Tsalapatis <emil@etsalapatis.com>,
	 Ihor Solodrai <ihor.solodrai@linux.dev>,
	Shuah Khan <shuah@kernel.org>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	 bpf@vger.kernel.org, linux-kselftest@vger.kernel.org,
	 Breno Leitao <leitao@debian.org>,
	kernel-team@meta.com
Subject: [PATCH net-next 3/3] selftests: net: getsockopt_iter: cover tcp
Date: Thu, 12 Nov 2026 09:05:27 -0800	[thread overview]
Message-ID: <20261112-getsockopt_phase7-v1-3-1eb759dd0929@debian.org> (raw)
In-Reply-To: <20261112-getsockopt_phase7-v1-0-1eb759dd0929@debian.org>

Add a fixture for do_tcp_getsockopt(). The setup keeps both ends of a
loopback connection, with TCP_SAVE_SYN set on the listener, so the
accepted socket has a saved SYN to report.

TCP_INFO and TCP_CONGESTION cover clamping the reply down to what fits,
TCP_ULP covers reporting zero and writing nothing, and TCP_SAVED_SYN
covers the length-then-EINVAL path, which the second half of that test
uses to read the SYN back.

TCP_ZEROCOPY_RECEIVE reads its struct from optval and writes it back.
With nothing queued it maps nothing and succeeds, so the tests cover
the 16-byte struct glibc still declares, a larger buffer cut down to
the kernel's struct, and one whose extra tail is not zero.

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 tools/testing/selftests/net/getsockopt_iter.c | 245 ++++++++++++++++++++++++++
 1 file changed, 245 insertions(+)

diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c
index 2367fce226d4cc..4e51c8aa6a8555 100644
--- a/tools/testing/selftests/net/getsockopt_iter.c
+++ b/tools/testing/selftests/net/getsockopt_iter.c
@@ -55,6 +55,9 @@
 #ifndef TCP_ULP
 #define TCP_ULP 31
 #endif
+#ifndef TCP_CA_NAME_MAX
+#define TCP_CA_NAME_MAX 16
+#endif
 /* linux/mroute.h does not coexist with netinet/in.h here. */
 #ifndef MRT_VERSION
 #define MRT_VERSION 206
@@ -1005,4 +1008,246 @@ TEST_F(tls, bad_optname)
 	ASSERT_EQ(ENOPROTOOPT, errno);
 }
 
+/* ---------- tcp ---------- */
+
+FIXTURE(tcp)
+{
+	int fd;		/* connected client */
+	int sfd;	/* accepted server side, its SYN was saved */
+};
+
+FIXTURE_SETUP(tcp)
+{
+	struct sockaddr_in a = {
+		.sin_family = AF_INET,
+		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+	};
+	socklen_t alen = sizeof(a);
+	int one = 1;
+	int lfd;
+
+	self->fd = -1;
+	self->sfd = -1;
+
+	lfd = socket(AF_INET, SOCK_STREAM, 0);
+	if (lfd < 0)
+		SKIP(return, "TCP socket: %s", strerror(errno));
+	setsockopt(lfd, IPPROTO_TCP, TCP_SAVE_SYN, &one, sizeof(one));
+	if (bind(lfd, (struct sockaddr *)&a, sizeof(a)) || listen(lfd, 1) ||
+	    getsockname(lfd, (struct sockaddr *)&a, &alen)) {
+		close(lfd);
+		SKIP(return, "listener setup: %s", strerror(errno));
+	}
+	self->fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (self->fd < 0) {
+		close(lfd);
+		SKIP(return, "TCP socket: %s", strerror(errno));
+	}
+	if (connect(self->fd, (struct sockaddr *)&a, sizeof(a))) {
+		close(lfd);
+		SKIP(return, "connect: %s", strerror(errno));
+	}
+	self->sfd = accept(lfd, NULL, NULL);
+	close(lfd);
+	if (self->sfd < 0)
+		SKIP(return, "accept: %s", strerror(errno));
+}
+
+FIXTURE_TEARDOWN(tcp)
+{
+	if (self->fd >= 0)
+		close(self->fd);
+	if (self->sfd >= 0)
+		close(self->sfd);
+}
+
+TEST_F(tcp, maxseg_exact)
+{
+	socklen_t optlen = sizeof(int);
+	int val = -1;
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_MAXSEG, &val,
+				&optlen));
+	ASSERT_EQ(sizeof(int), optlen);
+	ASSERT_GT(val, 0);
+}
+
+/* Unlike IPv4, TCP clamps the reply rather than shrinking it to a byte. */
+TEST_F(tcp, nodelay_undersize_clamped)
+{
+	socklen_t optlen = 1;
+	int val = 0;
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, &val,
+				&optlen));
+	ASSERT_EQ(1, optlen);
+}
+
+TEST_F(tcp, info_exact)
+{
+	struct tcp_info info;
+	socklen_t optlen = sizeof(info);
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_INFO, &info,
+				&optlen));
+	ASSERT_EQ(sizeof(info), optlen);
+	ASSERT_EQ(TCP_ESTABLISHED, info.tcpi_state);
+}
+
+TEST_F(tcp, info_short_clamped)
+{
+	struct tcp_info info;
+	socklen_t optlen = 8;
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_INFO, &info,
+				&optlen));
+	ASSERT_EQ(8, optlen);
+	ASSERT_EQ(TCP_ESTABLISHED, info.tcpi_state);
+}
+
+TEST_F(tcp, congestion_exact)
+{
+	socklen_t optlen = TCP_CA_NAME_MAX;
+	char name[TCP_CA_NAME_MAX] = {};
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_CONGESTION, name,
+				&optlen));
+	ASSERT_EQ(TCP_CA_NAME_MAX, optlen);
+	ASSERT_NE(0, name[0]);
+}
+
+TEST_F(tcp, congestion_short_clamped)
+{
+	socklen_t optlen = 4;
+	char name[4] = {};
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_CONGESTION, name,
+				&optlen));
+	ASSERT_EQ(4, optlen);
+	ASSERT_NE(0, name[0]);
+}
+
+/* No ULP attached: length reported as zero, nothing written. */
+TEST_F(tcp, ulp_none)
+{
+	socklen_t optlen = 16;
+	char buf[16];
+
+	memset(buf, 0x5a, sizeof(buf));
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_ULP, buf, &optlen));
+	ASSERT_EQ(0, optlen);
+	ASSERT_EQ(0x5a, (unsigned char)buf[0]);
+}
+
+TEST_F(tcp, repair_window_wrong_len)
+{
+	socklen_t optlen = 4;
+	char buf[4];
+
+	ASSERT_EQ(-1, getsockopt(self->fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, buf,
+				 &optlen));
+	ASSERT_EQ(EINVAL, errno);
+}
+
+TEST_F(tcp, zerocopy_receive_short)
+{
+	socklen_t optlen = 4;
+	char buf[4] = {};
+
+	ASSERT_EQ(-1, getsockopt(self->fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE,
+				 buf, &optlen));
+	ASSERT_EQ(EINVAL, errno);
+}
+
+/* Nothing queued: the call maps nothing and succeeds. */
+TEST_F(tcp, zerocopy_receive_old_struct)
+{
+	struct tcp_zerocopy_receive zc = {};
+	socklen_t optlen = sizeof(zc);
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE,
+				&zc, &optlen));
+	ASSERT_EQ(sizeof(zc), optlen);
+	ASSERT_EQ(0, zc.length);
+}
+
+/* A larger struct is cut down to the kernel's, if its tail is zero. */
+TEST_F(tcp, zerocopy_receive_oversize_clamped)
+{
+	char buf[256] = {};
+	socklen_t optlen = sizeof(buf);
+
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE,
+				buf, &optlen));
+	ASSERT_GE(optlen, sizeof(struct tcp_zerocopy_receive));
+	ASSERT_LT(optlen, sizeof(buf));
+}
+
+TEST_F(tcp, zerocopy_receive_oversize_dirty)
+{
+	char buf[256] = {};
+	socklen_t optlen = sizeof(buf);
+
+	buf[sizeof(buf) - 1] = 1;
+	ASSERT_EQ(-1, getsockopt(self->fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE,
+				 buf, &optlen));
+	ASSERT_EQ(EINVAL, errno);
+	ASSERT_EQ(sizeof(buf), optlen);
+}
+
+/* The client never saved a SYN, so there is nothing to report. */
+TEST_F(tcp, saved_syn_none)
+{
+	socklen_t optlen = 128;
+	char buf[128];
+
+	memset(buf, 0x5a, sizeof(buf));
+	ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_SAVED_SYN, buf,
+				&optlen));
+	ASSERT_EQ(0, optlen);
+	ASSERT_EQ(0x5a, (unsigned char)buf[0]);
+}
+
+/* A short buffer gets the required length back along with EINVAL, and the
+ * SYN is still there for the retry.
+ */
+TEST_F(tcp, saved_syn_short_reports_len)
+{
+	socklen_t optlen = 1;
+	char buf[256];
+	socklen_t need;
+
+	ASSERT_EQ(-1, getsockopt(self->sfd, IPPROTO_TCP, TCP_SAVED_SYN, buf,
+				 &optlen));
+	ASSERT_EQ(EINVAL, errno);
+	ASSERT_GT(optlen, 1);
+	ASSERT_LE(optlen, sizeof(buf));
+	need = optlen;
+
+	ASSERT_EQ(0, getsockopt(self->sfd, IPPROTO_TCP, TCP_SAVED_SYN, buf,
+				&optlen));
+	ASSERT_EQ(need, optlen);
+}
+
+TEST_F(tcp, negative_optlen)
+{
+	socklen_t optlen = (socklen_t)-1;
+	int val;
+
+	ASSERT_EQ(-1, getsockopt(self->fd, IPPROTO_TCP, TCP_MAXSEG, &val,
+				 &optlen));
+	ASSERT_EQ(EINVAL, errno);
+}
+
+TEST_F(tcp, bad_optname)
+{
+	socklen_t optlen = sizeof(int);
+	int val;
+
+	ASSERT_EQ(-1, getsockopt(self->fd, IPPROTO_TCP, 0x7fff, &val,
+				 &optlen));
+	ASSERT_EQ(ENOPROTOOPT, errno);
+	ASSERT_EQ(sizeof(int), optlen);
+}
+
 TEST_HARNESS_MAIN

-- 
2.53.0-Meta


      parent reply	other threads:[~2026-11-12 17:07 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-12 17:05 [PATCH net-next 0/3] tcp: convert the SOL_TCP getsockopt path to sockopt_t Breno Leitao
2026-11-12 17:05 ` [PATCH net-next 1/3] net/tcp-ao: convert the getsockopt helpers " Breno Leitao
2026-11-12 17:05 ` [PATCH net-next 2/3] tcp: convert do_tcp_getsockopt() " Breno Leitao
2026-11-12 17:05 ` Breno Leitao [this message]

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=20261112-getsockopt_phase7-v1-3-1eb759dd0929@debian.org \
    --to=leitao@debian.org \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=eddyz87@gmail.com \
    --cc=edumazet@kernel.org \
    --cc=emil@etsalapatis.com \
    --cc=horms@kernel.org \
    --cc=ihor.solodrai@linux.dev \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=kernel-team@meta.com \
    --cc=kuba@kernel.org \
    --cc=kuniyu@google.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=ncardwell@google.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sdf@fomichev.me \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=willemb@google.com \
    --cc=yonghong.song@linux.dev \
    /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