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

Reply via email to