mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
selftests: net: rds: add getsockopt() conversion test
Add a kselftest that exercises the RDS getsockopt() paths converted to the getsockopt_iter() / sockopt_t callback: - RDS_RECVERR and SO_RDS_TRANSPORT, which return their int value through copy_to_iter() and report the written length in opt->optlen. - RDS_INFO_*, which obtains the userspace buffer pages with iov_iter_extract_pages() (including a non-zero starting page offset) and lets the info producers copy the snapshot in under a spinlock. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Breno Leitao <leitao@debian.org> Reviewed-by: Allison Henderson <achender@kernel.org> Link: https://patch.msgid.link/20260608-getsock_more-v3-1-706ecf2ea332@debian.org Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
6b669e6a2a
commit
b74360369e
1
tools/testing/selftests/net/rds/.gitignore
vendored
1
tools/testing/selftests/net/rds/.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
include.sh
|
||||
getsockopt
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ all:
|
|||
|
||||
TEST_PROGS := rds_run.sh
|
||||
|
||||
TEST_GEN_PROGS := getsockopt
|
||||
|
||||
TEST_FILES := \
|
||||
include.sh \
|
||||
settings \
|
||||
|
|
@ -16,4 +18,6 @@ EXTRA_CLEAN := \
|
|||
/tmp/rds_logs \
|
||||
# end of EXTRA_CLEAN
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
|
||||
include ../../lib.mk
|
||||
|
|
|
|||
208
tools/testing/selftests/net/rds/getsockopt.c
Normal file
208
tools/testing/selftests/net/rds/getsockopt.c
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Exercise the RDS getsockopt() paths that were converted to the
|
||||
* getsockopt_iter() / sockopt_t callback.
|
||||
*
|
||||
* Three distinct paths are covered:
|
||||
*
|
||||
* - RDS_RECVERR and SO_RDS_TRANSPORT, which now return their int value
|
||||
* through copy_to_iter() and report the written length in opt->optlen.
|
||||
*
|
||||
* - RDS_INFO_*, which pins the userspace buffer with
|
||||
* iov_iter_extract_pages() (including a non-zero starting page offset)
|
||||
* and lets the info producers memcpy the snapshot in under a spinlock.
|
||||
*
|
||||
* The kvec (in-kernel buffer) -> -EOPNOTSUPP path of rds_info_getsockopt()
|
||||
* is not reachable from a userspace getsockopt() and so is not tested here.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/rds.h>
|
||||
|
||||
#include "../../kselftest_harness.h"
|
||||
|
||||
#ifndef AF_RDS
|
||||
#define AF_RDS 21
|
||||
#endif
|
||||
|
||||
FIXTURE(rds) {
|
||||
int fd;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(rds)
|
||||
{
|
||||
self->fd = socket(AF_RDS, SOCK_SEQPACKET, 0);
|
||||
if (self->fd < 0)
|
||||
SKIP(return, "AF_RDS unavailable (errno %d) - load the rds module",
|
||||
errno);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(rds)
|
||||
{
|
||||
if (self->fd >= 0)
|
||||
close(self->fd);
|
||||
}
|
||||
|
||||
/* RDS_RECVERR defaults to 0 and is reported back as a 4-byte int. */
|
||||
TEST_F(rds, recverr_default)
|
||||
{
|
||||
socklen_t len = sizeof(int);
|
||||
int val = 0xdeadbeef;
|
||||
|
||||
ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len));
|
||||
EXPECT_EQ(sizeof(int), len);
|
||||
EXPECT_EQ(0, val);
|
||||
}
|
||||
|
||||
/* A value set via setsockopt() must be readable back unchanged. */
|
||||
TEST_F(rds, recverr_set_get)
|
||||
{
|
||||
socklen_t len = sizeof(int);
|
||||
int val = 1;
|
||||
|
||||
ASSERT_EQ(0, setsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, len));
|
||||
|
||||
val = 0;
|
||||
ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len));
|
||||
EXPECT_EQ(sizeof(int), len);
|
||||
EXPECT_EQ(1, val);
|
||||
}
|
||||
|
||||
/* A buffer smaller than an int is rejected with EINVAL, not silently. */
|
||||
TEST_F(rds, recverr_short_buffer)
|
||||
{
|
||||
socklen_t len = sizeof(int) - 1;
|
||||
char buf[sizeof(int)];
|
||||
|
||||
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, buf, &len));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
/* An unbound socket reports RDS_TRANS_NONE for SO_RDS_TRANSPORT. */
|
||||
TEST_F(rds, transport_unbound)
|
||||
{
|
||||
socklen_t len = sizeof(int);
|
||||
int val = 0;
|
||||
|
||||
ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, &val,
|
||||
&len));
|
||||
EXPECT_EQ(sizeof(int), len);
|
||||
EXPECT_EQ(RDS_TRANS_NONE, (unsigned int)val);
|
||||
}
|
||||
|
||||
TEST_F(rds, transport_short_buffer)
|
||||
{
|
||||
socklen_t len = sizeof(int) - 1;
|
||||
char buf[sizeof(int)];
|
||||
|
||||
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, buf,
|
||||
&len));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* RDS_INFO_COUNTERS with a zero-length buffer is the "probe" call: it must
|
||||
* fail with ENOSPC and report the required snapshot size in optlen.
|
||||
*/
|
||||
TEST_F(rds, info_counters_probe)
|
||||
{
|
||||
socklen_t len = 0;
|
||||
|
||||
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL,
|
||||
&len));
|
||||
EXPECT_EQ(ENOSPC, errno);
|
||||
EXPECT_GT(len, 0);
|
||||
/* The snapshot is an array of fixed-size counter records. */
|
||||
EXPECT_EQ(0, len % (socklen_t)sizeof(struct rds_info_counter));
|
||||
}
|
||||
|
||||
/*
|
||||
* A real snapshot into an unaligned userspace buffer exercises the
|
||||
* iov_iter_extract_pages() path, including the non-zero offset0 handling
|
||||
* that the patch reworked. Place the buffer at a non-page-aligned address
|
||||
* spanning into the next page to make sure multi-page pinning works too.
|
||||
*/
|
||||
TEST_F(rds, info_counters_snapshot)
|
||||
{
|
||||
struct rds_info_counter *ctr;
|
||||
socklen_t need = 0, len;
|
||||
long pagesz = sysconf(_SC_PAGESIZE);
|
||||
size_t offset, map_len;
|
||||
unsigned int i, n;
|
||||
char *region, *buf;
|
||||
int ret;
|
||||
|
||||
/* Probe for the required size. */
|
||||
getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need);
|
||||
ASSERT_GT(need, 0);
|
||||
|
||||
/*
|
||||
* Place the buffer at a non-page-aligned offset that runs past the
|
||||
* first page boundary, and size the mapping from the probed length so
|
||||
* the test keeps working if the counter set grows.
|
||||
*/
|
||||
offset = pagesz - 64;
|
||||
map_len = ((offset + need + pagesz - 1) / pagesz) * pagesz;
|
||||
|
||||
region = mmap(NULL, map_len, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_NE(MAP_FAILED, region);
|
||||
|
||||
buf = region + offset;
|
||||
|
||||
/*
|
||||
* On success the RDS_INFO path returns the positive per-element size
|
||||
* (lens.each) rather than 0, and writes the full snapshot length back
|
||||
* into optlen.
|
||||
*/
|
||||
len = need;
|
||||
ret = getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, buf, &len);
|
||||
ASSERT_GE(ret, 0) {
|
||||
TH_LOG("getsockopt snapshot failed: errno %d", errno);
|
||||
}
|
||||
EXPECT_EQ(sizeof(struct rds_info_counter), ret);
|
||||
EXPECT_EQ(need, len);
|
||||
|
||||
/* The counter names must be NUL-terminated, non-empty strings. */
|
||||
ctr = (struct rds_info_counter *)buf;
|
||||
n = len / sizeof(*ctr);
|
||||
ASSERT_GT(n, 0);
|
||||
for (i = 0; i < n; i++) {
|
||||
size_t namelen = strnlen((char *)ctr[i].name,
|
||||
sizeof(ctr[i].name));
|
||||
|
||||
EXPECT_GT(namelen, 0);
|
||||
EXPECT_LT(namelen, sizeof(ctr[i].name));
|
||||
}
|
||||
|
||||
munmap(region, map_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* A non-zero but too-small buffer must report ENOSPC and the full required
|
||||
* length, without corrupting memory past the buffer.
|
||||
*/
|
||||
TEST_F(rds, info_counters_short_buffer)
|
||||
{
|
||||
socklen_t need = 0, len;
|
||||
char small[sizeof(struct rds_info_counter)];
|
||||
|
||||
getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need);
|
||||
ASSERT_GT(need, 0);
|
||||
|
||||
/* Ask with a buffer guaranteed smaller than the full snapshot. */
|
||||
if (need <= (socklen_t)sizeof(small))
|
||||
SKIP(return, "snapshot fits in one record; nothing to test");
|
||||
|
||||
len = 1; /* < sizeof(struct rds_info_counter) */
|
||||
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, small,
|
||||
&len));
|
||||
EXPECT_EQ(ENOSPC, errno);
|
||||
EXPECT_EQ(need, len);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Loading…
Reference in New Issue
Block a user