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 <[email protected]> > --- > 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 <[email protected]>

