Merge branch 'vsock-so_inq-for-stream-sockets'

David Carlier says:

====================
vsock: SO_INQ for stream sockets

This is the follow-up announced in the receive queue hint series [1]:
SO_INQ and SCM_INQ for AF_VSOCK stream sockets, as AF_UNIX has.

Patch 1 moves the SOL_SOCKET option handling to a helper without
functional change, patch 2 adds SO_INQ and patch 3 covers it in
vsock_test.

The value is the one SIOCINQ returns, so on Hyper-V it covers the
current packet only. Accepted sockets inherit the flag from the
listener, and the sockmap receive path does not report it.

Tested over loopback with vsock_test and vsock_uring_test.

v2: https://lore.kernel.org/netdev/20261015154334.3217358-1-devnexen@gmail.com/
v1: https://lore.kernel.org/netdev/20261012235524.41468-1-devnexen@gmail.com/

[1] https://lore.kernel.org/netdev/20260923184008.153541-1-devnexen@gmail.com/
====================

Link: https://patch.msgid.link/20261016132716.272018-1-devnexen@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-10-21 11:58:24 +02:00
commit 7dca8514c5
3 changed files with 379 additions and 30 deletions

View File

@ -68,6 +68,7 @@ struct vsock_sock {
u64 buffer_size;
u64 buffer_min_size;
u64 buffer_max_size;
bool recvmsg_inq;
/* Private to transport. */
void *trans;

View File

@ -984,6 +984,7 @@ static struct sock *__vsock_create(struct net *net,
vsk->buffer_size = psk->buffer_size;
vsk->buffer_min_size = psk->buffer_min_size;
vsk->buffer_max_size = psk->buffer_max_size;
vsk->recvmsg_inq = psk->recvmsg_inq;
security_sk_clone(parent, sk);
} else {
vsk->trusted = ns_capable_noaudit(&init_user_ns, CAP_NET_ADMIN);
@ -2013,6 +2014,80 @@ static void vsock_update_buffer_size(struct vsock_sock *vsk,
vsk->buffer_size = val;
}
static bool vsock_sol_socket_custom(int optname)
{
switch (optname) {
case SO_ZEROCOPY:
case SO_INQ:
return true;
default:
return false;
}
}
static int vsock_sol_socket_get_bool(sockptr_t optval, unsigned int optlen,
int *val)
{
/* Use 'int' type here, because variable to set these options usually
* has this type.
*/
if (optlen < sizeof(*val))
return -EINVAL;
if (copy_from_sockptr(val, optval, sizeof(*val)))
return -EFAULT;
if (*val < 0 || *val > 1)
return -EINVAL;
return 0;
}
static int vsock_sol_socket_setsockopt(struct sock *sk, int optname,
sockptr_t optval, unsigned int optlen)
{
struct vsock_sock *vsk = vsock_sk(sk);
int err;
int val;
lock_sock(sk);
switch (optname) {
case SO_ZEROCOPY:
err = vsock_sol_socket_get_bool(optval, optlen, &val);
if (err)
break;
if (vsk->transport && !vsock_msgzerocopy_allow(vsk->transport)) {
err = -EOPNOTSUPP;
break;
}
sock_valbool_flag(sk, SOCK_ZEROCOPY, val);
break;
case SO_INQ:
if (sk->sk_type != SOCK_STREAM) {
err = -ENOPROTOOPT;
break;
}
err = vsock_sol_socket_get_bool(optval, optlen, &val);
if (err)
break;
vsk->recvmsg_inq = val;
break;
default:
err = -ENOPROTOOPT;
break;
}
release_sock(sk);
return err;
}
static int vsock_connectible_setsockopt(struct socket *sock,
int level,
int optname,
@ -2044,37 +2119,17 @@ static int vsock_connectible_setsockopt(struct socket *sock,
sk = sock->sk;
vsk = vsock_sk(sk);
if (level == SOL_SOCKET) {
if (!vsock_sol_socket_custom(optname))
return sock_setsockopt(sock, level, optname, optval, optlen);
return vsock_sol_socket_setsockopt(sk, optname, optval, optlen);
}
lock_sock(sk);
transport = vsk->transport;
if (level == SOL_SOCKET) {
int zerocopy;
if (optname != SO_ZEROCOPY) {
release_sock(sk);
return sock_setsockopt(sock, level, optname, optval, optlen);
}
/* Use 'int' type here, because variable to
* set this option usually has this type.
*/
COPY_IN(zerocopy);
if (zerocopy < 0 || zerocopy > 1) {
err = -EINVAL;
goto exit;
}
if (transport && !vsock_msgzerocopy_allow(transport)) {
err = -EOPNOTSUPP;
goto exit;
}
sock_valbool_flag(sk, SOCK_ZEROCOPY, zerocopy);
goto exit;
}
switch (optname) {
case SO_VM_SOCKETS_BUFFER_SIZE:
COPY_IN(val);
@ -2580,6 +2635,7 @@ __vsock_connectible_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
struct sock *sk;
struct vsock_sock *vsk;
const struct vsock_transport *transport;
bool do_cmsg = false;
int err;
sk = sock->sk;
@ -2636,13 +2692,26 @@ __vsock_connectible_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
err = __vsock_seqpacket_recvmsg(sk, msg, len, flags);
out:
/* Seqpacket has_data counts messages, while io_uring treats msg_inq as
* a byte length when sizing retries, so only streams report a hint.
/* Seqpacket has_data counts messages, while SCM_INQ reports bytes and
* io_uring treats msg_inq as a byte length when sizing retries, so
* only streams report it.
*/
if (msg->msg_get_inq && err >= 0 && sk->sk_type == SOCK_STREAM)
if ((vsk->recvmsg_inq || msg->msg_get_inq) && err >= 0 &&
sk->sk_type == SOCK_STREAM) {
msg->msg_inq = vsock_stream_inq_hint(sk);
/* A negative hint means the transport could not tell. SCM_INQ
* has no encoding for that, so report nothing.
*/
do_cmsg = vsk->recvmsg_inq && msg->msg_inq >= 0;
}
release_sock(sk);
if (do_cmsg)
put_cmsg(msg, SOL_SOCKET, SCM_INQ, sizeof(msg->msg_inq),
&msg->msg_inq);
return err;
}

View File

@ -2429,6 +2429,271 @@ static void test_stream_collapse_server(const struct test_opts *opts)
close(fd);
}
#define SO_INQ_BUF_LEN 64
#define SO_INQ_CHUNK_LEN 16
static int so_inq_set(int fd, int val)
{
return setsockopt(fd, SOL_SOCKET, SO_INQ, &val, sizeof(val));
}
/* Receive @len bytes and return the SCM_INQ value, or -1 if the kernel did
* not attach one.
*/
static int so_inq_recv(int fd, size_t len, int expected_ret)
{
char control[CMSG_SPACE(sizeof(int))] = { 0 };
unsigned char buf[SO_INQ_BUF_LEN];
struct iovec iov = {
.iov_base = buf,
.iov_len = len,
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
ssize_t ret;
int inq;
ret = recvmsg(fd, &msg, 0);
if (ret < 0) {
perror("recvmsg");
exit(EXIT_FAILURE);
}
expect_res(ret, expected_ret, "recvmsg");
if (msg.msg_flags & MSG_CTRUNC) {
fprintf(stderr, "SCM_INQ control message truncated\n");
exit(EXIT_FAILURE);
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_INQ)
continue;
if (cmsg->cmsg_len != CMSG_LEN(sizeof(inq))) {
fprintf(stderr, "unexpected SCM_INQ length %zu\n",
(size_t)cmsg->cmsg_len);
exit(EXIT_FAILURE);
}
memcpy(&inq, CMSG_DATA(cmsg), sizeof(inq));
return inq;
}
return -1;
}
static void test_stream_so_inq_client(const struct test_opts *opts)
{
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
control_expectln("SENT");
/* Wait for the whole payload to be queued, so the values below do not
* depend on how the transport delivers it.
*/
if (!vsock_ioctl_int(fd, SIOCINQ, SO_INQ_BUF_LEN)) {
fprintf(stderr, "Test skipped, SIOCINQ not supported.\n");
goto out;
}
/* No ancillary data unless the option is enabled. */
expect_res(so_inq_recv(fd, SO_INQ_CHUNK_LEN, SO_INQ_CHUNK_LEN), -1,
"SCM_INQ with SO_INQ disabled");
if (so_inq_set(fd, 1)) {
if (errno != ENOPROTOOPT) {
perror("setsockopt(SO_INQ)");
exit(EXIT_FAILURE);
}
fprintf(stderr, "Test skipped, SO_INQ not supported.\n");
goto out;
}
/* Partial read: the rest of the payload is still queued. */
expect_res(so_inq_recv(fd, SO_INQ_CHUNK_LEN, SO_INQ_CHUNK_LEN),
SO_INQ_BUF_LEN - 2 * SO_INQ_CHUNK_LEN,
"SCM_INQ after partial receive");
/* Draining read while the peer stays connected. */
expect_res(so_inq_recv(fd, SO_INQ_BUF_LEN - 2 * SO_INQ_CHUNK_LEN,
SO_INQ_BUF_LEN - 2 * SO_INQ_CHUNK_LEN), 0,
"SCM_INQ after draining receive");
control_writeln("DRAINED");
vsock_wait_remote_close(fd);
/* Nothing is queued once the peer is gone, but 1 is reported so the
* caller keeps reading until it observes EOF, as TCP_INQ does.
*/
expect_res(so_inq_recv(fd, SO_INQ_CHUNK_LEN, 0), 1, "SCM_INQ at EOF");
if (so_inq_set(fd, 0)) {
perror("setsockopt(SO_INQ)");
exit(EXIT_FAILURE);
}
expect_res(so_inq_recv(fd, SO_INQ_CHUNK_LEN, 0), -1,
"SCM_INQ after disabling SO_INQ");
close(fd);
return;
out:
control_writeln("DRAINED");
close(fd);
}
static void test_stream_so_inq_server(const struct test_opts *opts)
{
unsigned char buf[SO_INQ_BUF_LEN];
int fd;
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
memset(buf, 'A', sizeof(buf));
send_buf(fd, buf, sizeof(buf), 0, sizeof(buf));
control_writeln("SENT");
/* Keep the connection open until the peer has drained the queue: an
* early close would turn its last SCM_INQ value into the EOF one.
*/
control_expectln("DRAINED");
close(fd);
}
static void test_stream_so_inq_inherit_client(const struct test_opts *opts)
{
unsigned char buf[SO_INQ_BUF_LEN];
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
memset(buf, 'A', sizeof(buf));
send_buf(fd, buf, sizeof(buf), 0, sizeof(buf));
control_writeln("SENT");
control_expectln("DONE");
close(fd);
}
static void test_stream_so_inq_inherit_server(const struct test_opts *opts)
{
bool supported = true;
int listen_fd, fd;
listen_fd = vsock_stream_listen(VMADDR_CID_ANY, opts->peer_port);
/* Enable the option on the listener only. */
if (so_inq_set(listen_fd, 1)) {
if (errno != ENOPROTOOPT) {
perror("setsockopt(SO_INQ)");
exit(EXIT_FAILURE);
}
fprintf(stderr, "Test skipped, SO_INQ not supported.\n");
supported = false;
}
control_writeln("LISTENING");
timeout_begin(TIMEOUT);
do {
fd = accept(listen_fd, NULL, NULL);
timeout_check("accept");
} while (fd < 0 && errno == EINTR);
timeout_end();
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
close(listen_fd);
control_expectln("SENT");
if (!supported)
goto out;
if (!vsock_ioctl_int(fd, SIOCINQ, SO_INQ_BUF_LEN)) {
fprintf(stderr, "Test skipped, SIOCINQ not supported.\n");
goto out;
}
/* The accepted socket reports SCM_INQ without its own setsockopt(). */
expect_res(so_inq_recv(fd, SO_INQ_CHUNK_LEN, SO_INQ_CHUNK_LEN),
SO_INQ_BUF_LEN - SO_INQ_CHUNK_LEN,
"SCM_INQ inherited from the listener");
out:
control_writeln("DONE");
close(fd);
}
static void test_so_inq_validation_client(const struct test_opts *opts)
{
int fd;
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (so_inq_set(fd, 1)) {
if (errno != ENOPROTOOPT) {
perror("setsockopt(SO_INQ)");
exit(EXIT_FAILURE);
}
fprintf(stderr, "Test skipped, SO_INQ not supported.\n");
close(fd);
return;
}
if (!so_inq_set(fd, 2) || errno != EINVAL) {
fprintf(stderr, "SO_INQ accepted an out of range value\n");
exit(EXIT_FAILURE);
}
close(fd);
/* SCM_INQ counts bytes, which a message based socket cannot report. */
fd = socket(AF_VSOCK, SOCK_SEQPACKET, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (!so_inq_set(fd, 1) || errno != ENOPROTOOPT) {
fprintf(stderr, "SO_INQ not rejected on SOCK_SEQPACKET\n");
exit(EXIT_FAILURE);
}
close(fd);
}
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM connection reset",
@ -2633,6 +2898,20 @@ static struct test_case test_cases[] = {
.run_client = test_stream_collapse_client,
.run_server = test_stream_collapse_server,
},
{
.name = "SOCK_STREAM SO_INQ functionality",
.run_client = test_stream_so_inq_client,
.run_server = test_stream_so_inq_server,
},
{
.name = "SOCK_STREAM SO_INQ inherited by accepted socket",
.run_client = test_stream_so_inq_inherit_client,
.run_server = test_stream_so_inq_inherit_server,
},
{
.name = "SO_INQ option validation",
.run_client = test_so_inq_validation_client,
},
{},
};