On Tue, Oct 13, 2026 at 12:55:24AM +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, and
that out of range values and SOCK_SEQPACKET are rejected.

Signed-off-by: David Carlier <[email protected]>
---
tools/testing/vsock/vsock_test.c | 191 +++++++++++++++++++++++++++++++
1 file changed, 191 insertions(+)

diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index b4ff9f946565..00bb68c00c07 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -2429,6 +2429,188 @@ 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");
+
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != SOL_SOCKET ||
+                   cmsg->cmsg_type != SCM_INQ)
+                       continue;

Should we check CMSG_LEN and/or MSG_CTRUNC ?

The rest LGTM.

Thanks,
Stefano

+
+               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_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 +2815,15 @@ 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 = "SO_INQ option validation",
+               .run_client = test_so_inq_validation_client,
+       },
        {},
};

--
2.55.0



Reply via email to