Merge branch 'net-rds-convert-rds-to-getsockopt_iter'

Breno Leitao says:

====================
net: rds: convert rds to getsockopt_iter

This series continues the conversion of the remaining proto_ops getsockopt
callbacks to the new getsockopt_iter callback introduced in commit
67fab22a7a ("net: add getsockopt_iter callback to proto_ops"), this time
for RDS.

RDS is a little more involved than the protocols converted so far, because
the RDS_INFO_* options snapshot kernel state directly into the destination
buffer: the info producers memcpy into the pages under a spinlock via
kmap_atomic() and so must not fault.

The conversion preserves that model — it obtains the same page array and
starting offset from opt->iter_out with iov_iter_extract_pages(),
preallocating the array so the iterator fills it in place, and leaves
the rds_info_iterator / rds_info_copy machinery and all producer
callbacks unchanged; kernel (ITER_KVEC) buffers remain unsupported on
the RDS_INFO path, as before.

I've vibe-coded a kselftest exercising both the simple options and the
RDS_INFO_* snapshot path, feel free to drop it in case this is not
useful.

Signed-off-by: Breno Leitao <leitao@debian.org>
====================

Link: https://patch.msgid.link/20260608-getsock_more-v3-0-706ecf2ea332@debian.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-06-11 12:19:07 +02:00
commit e6d6a68f1d
6 changed files with 278 additions and 50 deletions

View File

@ -37,6 +37,7 @@
#include <linux/in.h>
#include <linux/ipv6.h>
#include <linux/poll.h>
#include <linux/uio.h>
#include <net/sock.h>
#include "rds.h"
@ -485,35 +486,36 @@ static int rds_setsockopt(struct socket *sock, int level, int optname,
}
static int rds_getsockopt(struct socket *sock, int level, int optname,
char __user *optval, int __user *optlen)
sockopt_t *opt)
{
struct rds_sock *rs = rds_sk_to_rs(sock->sk);
int ret = -ENOPROTOOPT, len;
int trans;
int val;
if (level != SOL_RDS)
goto out;
if (get_user(len, optlen)) {
ret = -EFAULT;
goto out;
}
len = opt->optlen;
switch (optname) {
case RDS_INFO_FIRST ... RDS_INFO_LAST:
ret = rds_info_getsockopt(sock, optname, optval,
optlen);
ret = rds_info_getsockopt(sock, optname, opt);
break;
case RDS_RECVERR:
if (len < sizeof(int))
if (len < sizeof(int)) {
ret = -EINVAL;
else
if (put_user(rs->rs_recverr, (int __user *) optval) ||
put_user(sizeof(int), optlen))
break;
}
val = rs->rs_recverr;
if (copy_to_iter(&val, sizeof(int), &opt->iter_out) !=
sizeof(int)) {
ret = -EFAULT;
else
} else {
opt->optlen = sizeof(int);
ret = 0;
}
break;
case SO_RDS_TRANSPORT:
if (len < sizeof(int)) {
@ -522,11 +524,13 @@ static int rds_getsockopt(struct socket *sock, int level, int optname,
}
trans = (rs->rs_transport ? rs->rs_transport->t_type :
RDS_TRANS_NONE); /* unbound */
if (put_user(trans, (int __user *)optval) ||
put_user(sizeof(int), optlen))
if (copy_to_iter(&trans, sizeof(int), &opt->iter_out) !=
sizeof(int)) {
ret = -EFAULT;
else
} else {
opt->optlen = sizeof(int);
ret = 0;
}
break;
default:
break;
@ -653,7 +657,7 @@ static const struct proto_ops rds_proto_ops = {
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = rds_setsockopt,
.getsockopt = rds_getsockopt,
.getsockopt_iter = rds_getsockopt,
.sendmsg = rds_sendmsg,
.recvmsg = rds_recvmsg,
.mmap = sock_no_mmap,

View File

@ -35,6 +35,7 @@
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/export.h>
#include <linux/uio.h>
#include "rds.h"
@ -144,39 +145,41 @@ void rds_info_copy(struct rds_info_iterator *iter, void *data,
EXPORT_SYMBOL_GPL(rds_info_copy);
/*
* @optval points to the userspace buffer that the information snapshot
* will be copied into.
*
* @optlen on input is the size of the buffer in userspace. @optlen
* on output is the size of the requested snapshot in bytes.
* @opt->iter_out describes the buffer that the information snapshot will be
* copied into, and @opt->optlen is the size of that buffer on input. On
* output @opt->optlen is set to the size of the requested snapshot in bytes.
*
* This function returns -errno if there is a failure, particularly -ENOSPC
* if the given userspace buffer was not large enough to fit the snapshot.
* On success it returns the positive number of bytes of each array element
* in the snapshot.
* if the given buffer was not large enough to fit the snapshot. On success
* it returns the positive number of bytes of each array element in the
* snapshot.
*/
int rds_info_getsockopt(struct socket *sock, int optname, char __user *optval,
int __user *optlen)
int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt)
{
struct rds_info_iterator iter;
struct rds_info_lengths lens;
unsigned long nr_pages = 0;
unsigned long start;
rds_info_func func;
struct page **pages = NULL;
size_t offset0 = 0;
int npages = 0;
int ret;
int len;
int total;
if (get_user(len, optlen)) {
ret = -EFAULT;
len = opt->optlen;
/* check for all kinds of wrapping and the like */
if (len < 0 || len > INT_MAX - PAGE_SIZE + 1) {
ret = -EINVAL;
goto out;
}
/* check for all kinds of wrapping and the like */
start = (unsigned long)optval;
if (len < 0 || len > INT_MAX - PAGE_SIZE + 1 || start + len < start) {
ret = -EINVAL;
/* The info producers write into the pages with kmap_atomic() while
* holding a spinlock, so they need a genuine page-backed user buffer.
*/
if (!user_backed_iter(&opt->iter_out)) {
ret = -EOPNOTSUPP;
goto out;
}
@ -184,20 +187,26 @@ int rds_info_getsockopt(struct socket *sock, int optname, char __user *optval,
if (len == 0)
goto call_func;
nr_pages = (PAGE_ALIGN(start + len) - (start & PAGE_MASK))
>> PAGE_SHIFT;
pages = kmalloc_objs(struct page *, nr_pages);
/*
* Preallocate the page array and pass it in so that
* iov_iter_extract_pages() fills it in place rather than allocating
* one for us. Handing it a non-NULL array keeps ownership of the
* array with us on every return path, instead of depending on the
* iterator code to allocate and hand it back.
*/
npages = iov_iter_npages(&opt->iter_out, INT_MAX);
pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL);
if (!pages) {
ret = -ENOMEM;
goto out;
}
ret = pin_user_pages_fast(start, nr_pages, FOLL_WRITE, pages);
if (ret != nr_pages) {
if (ret > 0)
nr_pages = ret;
else
nr_pages = 0;
ret = iov_iter_extract_pages(&opt->iter_out, &pages, len, npages,
0, &offset0);
if (ret < 0)
goto out;
nr_pages = DIV_ROUND_UP(offset0 + ret, PAGE_SIZE);
if (ret != len) {
ret = -EAGAIN; /* XXX ? */
goto out;
}
@ -213,7 +222,7 @@ int rds_info_getsockopt(struct socket *sock, int optname, char __user *optval,
iter.pages = pages;
iter.addr = NULL;
iter.offset = start & (PAGE_SIZE - 1);
iter.offset = offset0;
func(sock, len, &iter, &lens);
BUG_ON(lens.each == 0);
@ -230,13 +239,16 @@ int rds_info_getsockopt(struct socket *sock, int optname, char __user *optval,
ret = lens.each;
}
if (put_user(len, optlen))
ret = -EFAULT;
opt->optlen = len;
out:
if (pages)
/*
* iov_iter_extract_pages() pins only user-backed (ubuf) iters;
* iov_iter_extract_will_pin() reports whether an unpin is owed here.
*/
if (pages && iov_iter_extract_will_pin(&opt->iter_out))
unpin_user_pages(pages, nr_pages);
kfree(pages);
kvfree(pages);
return ret;
}

View File

@ -21,8 +21,7 @@ typedef void (*rds_info_func)(struct socket *sock, unsigned int len,
void rds_info_register_func(int optname, rds_info_func func);
void rds_info_deregister_func(int optname, rds_info_func func);
int rds_info_getsockopt(struct socket *sock, int optname, char __user *optval,
int __user *optlen);
int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt);
void rds_info_copy(struct rds_info_iterator *iter, void *data,
unsigned long bytes);
void rds_info_iter_unmap(struct rds_info_iterator *iter);

View File

@ -1 +1,2 @@
include.sh
getsockopt

View File

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

View 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