All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/2] vsock: SO_INQ for stream sockets
@ 2026-10-15 15:43 David Carlier
  2026-10-15 15:43 ` [PATCH net-next v2 1/2] vsock: support SO_INQ on " David Carlier
  2026-10-15 15:43 ` [PATCH net-next v2 2/2] vsock/test: cover SO_INQ David Carlier
  0 siblings, 2 replies; 7+ messages in thread
From: David Carlier @ 2026-10-15 15:43 UTC (permalink / raw)
  To: netdev
  Cc: sgarzare, bobbyeshleman, davem, edumazet, kuba, pabeni, horms,
	virtualization, linux-kernel, David Carlier

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.

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:
 - move the SOL_SOCKET options to vsock_sol_socket_setsockopt(),
   with vsock_custom_sockopt() and a switch on optname (Stefano)
 - accepted sockets inherit SO_INQ from the listener, as TCP_INQ
   does (Stefano)
 - use || instead of | in recvmsg (Stefano)
 - test: check MSG_CTRUNC and the SCM_INQ cmsg length (Stefano)
 - test: cover inheritance by accepted sockets

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

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

David Carlier (2):
  vsock: support SO_INQ on stream sockets
  vsock/test: cover SO_INQ

 include/net/af_vsock.h           |   1 +
 net/vmw_vsock/af_vsock.c         | 111 ++++++++----
 tools/testing/vsock/vsock_test.c | 279 +++++++++++++++++++++++++++++++
 3 files changed, 363 insertions(+), 28 deletions(-)


base-commit: a4057f51efc2b74ddf46721e83f45e9efb13649d
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next v2 1/2] vsock: support SO_INQ on stream sockets
  2026-10-15 15:43 [PATCH net-next v2 0/2] vsock: SO_INQ for stream sockets David Carlier
@ 2026-10-15 15:43 ` David Carlier
  2026-10-15 16:02   ` sashiko-bot
  2026-10-15 17:07   ` Bobby Eshleman
  2026-10-15 15:43 ` [PATCH net-next v2 2/2] vsock/test: cover SO_INQ David Carlier
  1 sibling, 2 replies; 7+ messages in thread
From: David Carlier @ 2026-10-15 15:43 UTC (permalink / raw)
  To: netdev
  Cc: sgarzare, bobbyeshleman, davem, edumazet, kuba, pabeni, horms,
	virtualization, linux-kernel, David Carlier

SO_INQ makes recvmsg() return the bytes left in the receive queue in an
SCM_INQ cmsg, saving a SIOCINQ call per receive. AF_UNIX stream sockets
support it, AF_VSOCK fails with ENOPROTOOPT.

Add it for SOCK_STREAM, reusing the hint already computed for io_uring.
As with TCP_INQ, 1 is reported once the stream is finished so the caller
reads again and sees EOF, and accepted sockets inherit the setting from
the listener. No cmsg is sent when the transport cannot report the queue
length.

SOCK_SEQPACKET is refused, as on AF_UNIX: its transport callback counts
messages, not bytes.

Signed-off-by: David Carlier <devnexen@gmail.com>
---
 include/net/af_vsock.h   |   1 +
 net/vmw_vsock/af_vsock.c | 111 +++++++++++++++++++++++++++++----------
 2 files changed, 84 insertions(+), 28 deletions(-)

diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6..e8481f12312e 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -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;
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 1c2d1cea67f4..186ed57fd797 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -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);
@@ -2014,6 +2015,66 @@ static void vsock_update_buffer_size(struct vsock_sock *vsk,
 	vsk->buffer_size = val;
 }
 
+static bool vsock_custom_sockopt(int optname)
+{
+	switch (optname) {
+	case SO_ZEROCOPY:
+	case SO_INQ:
+		return true;
+	default:
+		return false;
+	}
+}
+
+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 = 0;
+	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;
+
+	lock_sock(sk);
+
+	switch (optname) {
+	case SO_ZEROCOPY:
+		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;
+		}
+
+		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,
@@ -2045,36 +2106,16 @@ static int vsock_connectible_setsockopt(struct socket *sock,
 	sk = sock->sk;
 	vsk = vsock_sk(sk);
 
-	lock_sock(sk);
-
-	transport = vsk->transport;
-
 	if (level == SOL_SOCKET) {
-		int zerocopy;
-
-		if (optname != SO_ZEROCOPY) {
-			release_sock(sk);
+		if (!vsock_custom_sockopt(optname))
 			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;
-		}
+		return vsock_sol_socket_setsockopt(sk, optname, optval, optlen);
+	}
 
-		if (transport && !vsock_msgzerocopy_allow(transport)) {
-			err = -EOPNOTSUPP;
-			goto exit;
-		}
+	lock_sock(sk);
 
-		sock_valbool_flag(sk, SOCK_ZEROCOPY, zerocopy);
-		goto exit;
-	}
+	transport = vsk->transport;
 
 	switch (optname) {
 	case SO_VM_SOCKETS_BUFFER_SIZE:
@@ -2581,6 +2622,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;
@@ -2637,13 +2679,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;
 }
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH net-next v2 2/2] vsock/test: cover SO_INQ
  2026-10-15 15:43 [PATCH net-next v2 0/2] vsock: SO_INQ for stream sockets David Carlier
  2026-10-15 15:43 ` [PATCH net-next v2 1/2] vsock: support SO_INQ on " David Carlier
@ 2026-10-15 15:43 ` David Carlier
  2026-10-15 16:02   ` sashiko-bot
  2026-10-15 17:12   ` Bobby Eshleman
  1 sibling, 2 replies; 7+ messages in thread
From: David Carlier @ 2026-10-15 15:43 UTC (permalink / raw)
  To: netdev
  Cc: sgarzare, bobbyeshleman, davem, edumazet, kuba, pabeni, horms,
	virtualization, linux-kernel, David Carlier

Check the SCM_INQ value after a partial receive, after draining the
queue and at EOF, that no cmsg is sent while the option is off, that
accepted sockets inherit it from the listener, and that out of range
values and SOCK_SEQPACKET are rejected.

Signed-off-by: David Carlier <devnexen@gmail.com>
---
 tools/testing/vsock/vsock_test.c | 279 +++++++++++++++++++++++++++++++
 1 file changed, 279 insertions(+)

diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index b4ff9f946565..10b9074fb87f 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -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,
+	},
 	{},
 };
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH net-next v2 1/2] vsock: support SO_INQ on stream sockets
  2026-10-15 15:43 ` [PATCH net-next v2 1/2] vsock: support SO_INQ on " David Carlier
@ 2026-10-15 16:02   ` sashiko-bot
  2026-10-15 17:07   ` Bobby Eshleman
  1 sibling, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-15 16:02 UTC (permalink / raw)
  To: David Carlier; +Cc: virtualization, Michael S. Tsirkin, Eugenio Perez

> SO_INQ makes recvmsg() return the bytes left in the receive queue in an
> SCM_INQ cmsg, saving a SIOCINQ call per receive. AF_UNIX stream sockets
> support it, AF_VSOCK fails with ENOPROTOOPT.
> 
> Add it for SOCK_STREAM, reusing the hint already computed for io_uring.
> As with TCP_INQ, 1 is reported once the stream is finished so the caller
> reads again and sees EOF, and accepted sockets inherit the setting from
> the listener. No cmsg is sent when the transport cannot report the queue
> length.
> 
> SOCK_SEQPACKET is refused, as on AF_UNIX: its transport callback counts
> messages, not bytes.
> 
> Signed-off-by: David Carlier <devnexen@gmail.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015154334.3217358-1-devnexen@gmail.com?part=1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH net-next v2 2/2] vsock/test: cover SO_INQ
  2026-10-15 15:43 ` [PATCH net-next v2 2/2] vsock/test: cover SO_INQ David Carlier
@ 2026-10-15 16:02   ` sashiko-bot
  2026-10-15 17:12   ` Bobby Eshleman
  1 sibling, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-15 16:02 UTC (permalink / raw)
  To: David Carlier; +Cc: virtualization, Michael S. Tsirkin, Eugenio Perez

> Check the SCM_INQ value after a partial receive, after draining the
> queue and at EOF, that no cmsg is sent while the option is off, that
> accepted sockets inherit it from the listener, and that out of range
> values and SOCK_SEQPACKET are rejected.
> 
> Signed-off-by: David Carlier <devnexen@gmail.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015154334.3217358-1-devnexen@gmail.com?part=2


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH net-next v2 1/2] vsock: support SO_INQ on stream sockets
  2026-10-15 15:43 ` [PATCH net-next v2 1/2] vsock: support SO_INQ on " David Carlier
  2026-10-15 16:02   ` sashiko-bot
@ 2026-10-15 17:07   ` Bobby Eshleman
  1 sibling, 0 replies; 7+ messages in thread
From: Bobby Eshleman @ 2026-10-15 17:07 UTC (permalink / raw)
  To: David Carlier
  Cc: netdev, sgarzare, davem, edumazet, kuba, pabeni, horms,
	virtualization, linux-kernel

On Thu, Oct 15, 2026 at 04:43:33PM +0100, David Carlier wrote:
> SO_INQ makes recvmsg() return the bytes left in the receive queue in an
> SCM_INQ cmsg, saving a SIOCINQ call per receive. AF_UNIX stream sockets
> support it, AF_VSOCK fails with ENOPROTOOPT.
> 
> Add it for SOCK_STREAM, reusing the hint already computed for io_uring.
> As with TCP_INQ, 1 is reported once the stream is finished so the caller
> reads again and sees EOF, and accepted sockets inherit the setting from
> the listener. No cmsg is sent when the transport cannot report the queue
> length.
> 
> SOCK_SEQPACKET is refused, as on AF_UNIX: its transport callback counts
> messages, not bytes.
> 
> Signed-off-by: David Carlier <devnexen@gmail.com>
> ---
>  include/net/af_vsock.h   |   1 +
>  net/vmw_vsock/af_vsock.c | 111 +++++++++++++++++++++++++++++----------
>  2 files changed, 84 insertions(+), 28 deletions(-)
> 
> diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
> index 5549298c1ec6..e8481f12312e 100644
> --- a/include/net/af_vsock.h
> +++ b/include/net/af_vsock.h
> @@ -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;
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index 1c2d1cea67f4..186ed57fd797 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
> @@ -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);
> @@ -2014,6 +2015,66 @@ static void vsock_update_buffer_size(struct vsock_sock *vsk,
>  	vsk->buffer_size = val;
>  }
>  
> +static bool vsock_custom_sockopt(int optname)
> +{
> +	switch (optname) {
> +	case SO_ZEROCOPY:
> +	case SO_INQ:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +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 = 0;
> +	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;
> +
> +	lock_sock(sk);
> +
> +	switch (optname) {
> +	case SO_ZEROCOPY:
> +		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;
> +		}
> +
> +		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,
> @@ -2045,36 +2106,16 @@ static int vsock_connectible_setsockopt(struct socket *sock,
>  	sk = sock->sk;
>  	vsk = vsock_sk(sk);
>  
> -	lock_sock(sk);
> -
> -	transport = vsk->transport;
> -
>  	if (level == SOL_SOCKET) {
> -		int zerocopy;
> -
> -		if (optname != SO_ZEROCOPY) {
> -			release_sock(sk);
> +		if (!vsock_custom_sockopt(optname))
>  			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;
> -		}
> +		return vsock_sol_socket_setsockopt(sk, optname, optval, optlen);
> +	}
>  
> -		if (transport && !vsock_msgzerocopy_allow(transport)) {
> -			err = -EOPNOTSUPP;
> -			goto exit;
> -		}
> +	lock_sock(sk);
>  
> -		sock_valbool_flag(sk, SOCK_ZEROCOPY, zerocopy);
> -		goto exit;
> -	}
> +	transport = vsk->transport;
>  
>  	switch (optname) {
>  	case SO_VM_SOCKETS_BUFFER_SIZE:
> @@ -2581,6 +2622,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;
> @@ -2637,13 +2679,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;
>  }
>  
> -- 
> 2.55.0
> 

All LGTM.

Acked-by: Bobby Eshleman <bobbyeshleman@meta.com>

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH net-next v2 2/2] vsock/test: cover SO_INQ
  2026-10-15 15:43 ` [PATCH net-next v2 2/2] vsock/test: cover SO_INQ David Carlier
  2026-10-15 16:02   ` sashiko-bot
@ 2026-10-15 17:12   ` Bobby Eshleman
  1 sibling, 0 replies; 7+ messages in thread
From: Bobby Eshleman @ 2026-10-15 17:12 UTC (permalink / raw)
  To: David Carlier
  Cc: netdev, sgarzare, davem, edumazet, kuba, pabeni, horms,
	virtualization, linux-kernel

On Thu, Oct 15, 2026 at 04:43:34PM +0100, David Carlier wrote:
> Check the SCM_INQ value after a partial receive, after draining the
> queue and at EOF, that no cmsg is sent while the option is off, that
> accepted sockets inherit it from the listener, and that out of range
> values and SOCK_SEQPACKET are rejected.
> 
> Signed-off-by: David Carlier <devnexen@gmail.com>
> ---
>  tools/testing/vsock/vsock_test.c | 279 +++++++++++++++++++++++++++++++
>  1 file changed, 279 insertions(+)
> 
> diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
> index b4ff9f946565..10b9074fb87f 100644
> --- a/tools/testing/vsock/vsock_test.c
> +++ b/tools/testing/vsock/vsock_test.c
> @@ -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,
> +	},
>  	{},
>  };
>  
> -- 
> 2.55.0
> 

Acked-by: Bobby Eshleman <bobbyeshleman@meta.com>

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-15 17:12 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 15:43 [PATCH net-next v2 0/2] vsock: SO_INQ for stream sockets David Carlier
2026-10-15 15:43 ` [PATCH net-next v2 1/2] vsock: support SO_INQ on " David Carlier
2026-10-15 16:02   ` sashiko-bot
2026-10-15 17:07   ` Bobby Eshleman
2026-10-15 15:43 ` [PATCH net-next v2 2/2] vsock/test: cover SO_INQ David Carlier
2026-10-15 16:02   ` sashiko-bot
2026-10-15 17:12   ` Bobby Eshleman

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.