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
prev 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