Merge branch 'vsock-sock_linger-rework'

Michal Luczaj says:

====================
vsock: SOCK_LINGER rework

Change vsock's lingerning to wait on close() until all data is sent, i.e.
until workers picked all the packets for processing.

v5: https://lore.kernel.org/r/20250521-vsock-linger-v5-0-94827860d1d6@rbox.co
v4: https://lore.kernel.org/r/20250501-vsock-linger-v4-0-beabbd8a0847@rbox.co
v3: https://lore.kernel.org/r/20250430-vsock-linger-v3-0-ddbe73b53457@rbox.co
v2: https://lore.kernel.org/r/20250421-vsock-linger-v2-0-fe9febd64668@rbox.co
v1: https://lore.kernel.org/r/20250407-vsock-linger-v1-0-1458038e3492@rbox.co

Signed-off-by: Michal Luczaj <mhal@rbox.co>
====================

Link: https://patch.msgid.link/20250522-vsock-linger-v6-0-2ad00b0e447e@rbox.co
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2025-05-27 11:05:25 +02:00
commit 35a7a2f555
6 changed files with 135 additions and 45 deletions

View File

@ -221,6 +221,7 @@ void vsock_for_each_connected_socket(struct vsock_transport *transport,
void (*fn)(struct sock *sk));
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
bool vsock_find_cid(unsigned int cid);
void vsock_linger(struct sock *sk);
/**** TAP ****/

View File

@ -1013,6 +1013,39 @@ static int vsock_getname(struct socket *sock,
return err;
}
void vsock_linger(struct sock *sk)
{
DEFINE_WAIT_FUNC(wait, woken_wake_function);
ssize_t (*unsent)(struct vsock_sock *vsk);
struct vsock_sock *vsk = vsock_sk(sk);
long timeout;
if (!sock_flag(sk, SOCK_LINGER))
return;
timeout = sk->sk_lingertime;
if (!timeout)
return;
/* Transports must implement `unsent_bytes` if they want to support
* SOCK_LINGER through `vsock_linger()` since we use it to check when
* the socket can be closed.
*/
unsent = vsk->transport->unsent_bytes;
if (!unsent)
return;
add_wait_queue(sk_sleep(sk), &wait);
do {
if (sk_wait_event(sk, &timeout, unsent(vsk) == 0, &wait))
break;
} while (!signal_pending(current) && timeout);
remove_wait_queue(sk_sleep(sk), &wait);
}
EXPORT_SYMBOL_GPL(vsock_linger);
static int vsock_shutdown(struct socket *sock, int mode)
{
int err;

View File

@ -1191,23 +1191,6 @@ static void virtio_transport_remove_sock(struct vsock_sock *vsk)
vsock_remove_sock(vsk);
}
static void virtio_transport_wait_close(struct sock *sk, long timeout)
{
if (timeout) {
DEFINE_WAIT_FUNC(wait, woken_wake_function);
add_wait_queue(sk_sleep(sk), &wait);
do {
if (sk_wait_event(sk, &timeout,
sock_flag(sk, SOCK_DONE), &wait))
break;
} while (!signal_pending(current) && timeout);
remove_wait_queue(sk_sleep(sk), &wait);
}
}
static void virtio_transport_cancel_close_work(struct vsock_sock *vsk,
bool cancel_timeout)
{
@ -1277,8 +1260,8 @@ static bool virtio_transport_close(struct vsock_sock *vsk)
if ((sk->sk_shutdown & SHUTDOWN_MASK) != SHUTDOWN_MASK)
(void)virtio_transport_shutdown(vsk, SHUTDOWN_MASK);
if (sock_flag(sk, SOCK_LINGER) && !(current->flags & PF_EXITING))
virtio_transport_wait_close(sk, sk->sk_lingertime);
if (!(current->flags & PF_EXITING))
vsock_linger(sk);
if (sock_flag(sk, SOCK_DONE)) {
return true;

View File

@ -17,6 +17,7 @@
#include <assert.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <linux/sockios.h>
#include "timeout.h"
#include "control.h"
@ -96,6 +97,30 @@ void vsock_wait_remote_close(int fd)
close(epollfd);
}
/* Wait until transport reports no data left to be sent.
* Return false if transport does not implement the unsent_bytes() callback.
*/
bool vsock_wait_sent(int fd)
{
int ret, sock_bytes_unsent;
timeout_begin(TIMEOUT);
do {
ret = ioctl(fd, SIOCOUTQ, &sock_bytes_unsent);
if (ret < 0) {
if (errno == EOPNOTSUPP)
break;
perror("ioctl(SIOCOUTQ)");
exit(EXIT_FAILURE);
}
timeout_check("SIOCOUTQ");
} while (sock_bytes_unsent != 0);
timeout_end();
return !ret;
}
/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
int vsock_bind(unsigned int cid, unsigned int port, int type)
{
@ -798,3 +823,16 @@ void enable_so_zerocopy_check(int fd)
setsockopt_int_check(fd, SOL_SOCKET, SO_ZEROCOPY, 1,
"setsockopt SO_ZEROCOPY");
}
void enable_so_linger(int fd, int timeout)
{
struct linger optval = {
.l_onoff = 1,
.l_linger = timeout
};
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
perror("setsockopt(SO_LINGER)");
exit(EXIT_FAILURE);
}
}

View File

@ -54,6 +54,7 @@ int vsock_stream_listen(unsigned int cid, unsigned int port);
int vsock_seqpacket_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp);
void vsock_wait_remote_close(int fd);
bool vsock_wait_sent(int fd);
void send_buf(int fd, const void *buf, size_t len, int flags,
ssize_t expected_ret);
void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret);
@ -79,4 +80,5 @@ void setsockopt_int_check(int fd, int level, int optname, int val,
void setsockopt_timeval_check(int fd, int level, int optname,
struct timeval val, char const *errmsg);
void enable_so_zerocopy_check(int fd);
void enable_so_linger(int fd, int timeout);
#endif /* UTIL_H */

View File

@ -21,7 +21,6 @@
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <linux/time64.h>
#include "vsock_test_zerocopy.h"
@ -1280,7 +1279,7 @@ static void test_unsent_bytes_server(const struct test_opts *opts, int type)
static void test_unsent_bytes_client(const struct test_opts *opts, int type)
{
unsigned char buf[MSG_BUF_IOCTL_LEN];
int ret, fd, sock_bytes_unsent;
int fd;
fd = vsock_connect(opts->peer_cid, opts->peer_port, type);
if (fd < 0) {
@ -1297,22 +1296,12 @@ static void test_unsent_bytes_client(const struct test_opts *opts, int type)
/* SIOCOUTQ isn't guaranteed to instantly track sent data. Even though
* the "RECEIVED" message means that the other side has received the
* data, there can be a delay in our kernel before updating the "unsent
* bytes" counter. Repeat SIOCOUTQ until it returns 0.
* bytes" counter. vsock_wait_sent() will repeat SIOCOUTQ until it
* returns 0.
*/
timeout_begin(TIMEOUT);
do {
ret = ioctl(fd, SIOCOUTQ, &sock_bytes_unsent);
if (ret < 0) {
if (errno == EOPNOTSUPP) {
fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
break;
}
perror("ioctl");
exit(EXIT_FAILURE);
}
timeout_check("SIOCOUTQ");
} while (sock_bytes_unsent != 0);
timeout_end();
if (!vsock_wait_sent(fd))
fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
close(fd);
}
@ -1824,10 +1813,6 @@ static void test_stream_connect_retry_server(const struct test_opts *opts)
static void test_stream_linger_client(const struct test_opts *opts)
{
struct linger optval = {
.l_onoff = 1,
.l_linger = 1
};
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
@ -1836,11 +1821,7 @@ static void test_stream_linger_client(const struct test_opts *opts)
exit(EXIT_FAILURE);
}
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
perror("setsockopt(SO_LINGER)");
exit(EXIT_FAILURE);
}
enable_so_linger(fd, 1);
close(fd);
}
@ -1858,6 +1839,53 @@ static void test_stream_linger_server(const struct test_opts *opts)
close(fd);
}
/* Half of the default to not risk timing out the control channel */
#define LINGER_TIMEOUT (TIMEOUT / 2)
static void test_stream_nolinger_client(const struct test_opts *opts)
{
bool waited;
time_t ns;
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
enable_so_linger(fd, LINGER_TIMEOUT);
send_byte(fd, 1, 0); /* Left unread to expose incorrect behaviour. */
waited = vsock_wait_sent(fd);
ns = current_nsec();
close(fd);
ns = current_nsec() - ns;
if (!waited) {
fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
} else if (DIV_ROUND_UP(ns, NSEC_PER_SEC) >= LINGER_TIMEOUT) {
fprintf(stderr, "Unexpected lingering\n");
exit(EXIT_FAILURE);
}
control_writeln("DONE");
}
static void test_stream_nolinger_server(const struct test_opts *opts)
{
int fd;
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
control_expectln("DONE");
close(fd);
}
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM connection reset",
@ -2018,6 +2046,11 @@ static struct test_case test_cases[] = {
.run_client = test_stream_linger_client,
.run_server = test_stream_linger_server,
},
{
.name = "SOCK_STREAM SO_LINGER close() on unread",
.run_client = test_stream_nolinger_client,
.run_server = test_stream_nolinger_server,
},
{},
};