mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
7dca8514c5
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user