Mock the three syscalls so its address construction, connect-timeout option and errno preservation are checked without a real AF_VSOCK socket.
Signed-off-by: Polina Vishneva <[email protected]> --- tests/meson.build | 2 + tests/virvsockmock.c | 143 +++++++++++++++++++++++++++++++++++++++++++ tests/virvsocktest.c | 133 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 tests/virvsockmock.c create mode 100644 tests/virvsocktest.c diff --git a/tests/meson.build b/tests/meson.build index ea50f89fb5..63e0474a29 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -94,6 +94,7 @@ if host_machine.system() == 'linux' { 'name': 'virnetdevbandwidthmock' }, { 'name': 'virtestmock' }, { 'name': 'virusbmock' }, + { 'name': 'virvsockmock' }, ] endif @@ -337,6 +338,7 @@ if host_machine.system() == 'linux' { 'name': 'virresctrltest', 'link_whole': [ test_file_wrapper_lib ] }, { 'name': 'virscsitest' }, { 'name': 'virusbtest' }, + { 'name': 'virvsocktest' }, ] if conf.has('WITH_JSON') tests += [ diff --git a/tests/virvsockmock.c b/tests/virvsockmock.c new file mode 100644 index 0000000000..a951ad5845 --- /dev/null +++ b/tests/virvsockmock.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2026 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "virmock.h" +#include "virstring.h" + +#include <sys/socket.h> +#include <stdlib.h> +#include <errno.h> + +#ifdef __linux__ +# include <linux/time_types.h> +# include <linux/vm_sockets.h> +# ifndef SO_VM_SOCKETS_CONNECT_TIMEOUT_OLD +# define SO_VM_SOCKETS_CONNECT_TIMEOUT_OLD SO_VM_SOCKETS_CONNECT_TIMEOUT +# endif +#endif + +#if defined(__linux__) && defined(RTLD_NEXT) + +static int (*real_socket)(int domain, int type, int protocol); +static int (*real_setsockopt)(int sockfd, int level, int optname, + const void *optval, socklen_t optlen); +static int (*real_connect)(int sockfd, const struct sockaddr *addr, + socklen_t addrlen); + +/* Microseconds passed to the connect-timeout setsockopt() during the current + * attempt, or -1 if it wasn't set. Reset by socket() and checked by connect() + * so a dropped timeout can't pass on a stale value from an earlier attempt. */ +static long long appliedTimeoutUs = -1; + +static void +init_syms(void) +{ + if (real_socket) + return; + + VIR_MOCK_REAL_INIT(socket); + VIR_MOCK_REAL_INIT(setsockopt); + VIR_MOCK_REAL_INIT(connect); +} + +/* Read a base-10 long long from environment variable @name into @out, + * returning false (leaving @out untouched) when it is unset or unparseable. */ +static bool +get_expected_ll(const char *name, long long *out) +{ + const char *s = getenv(name); + + return s && virStrToLong_ll(s, NULL, 10, out) == 0; +} + +int +socket(int domain, int type, int protocol) +{ + init_syms(); + + /* Hand back a real, closable fd, and start a fresh attempt by forgetting + * any previously applied timeout. */ + if (domain == AF_VSOCK) { + appliedTimeoutUs = -1; + return real_socket(AF_UNIX, type, protocol); + } + + return real_socket(domain, type, protocol); +} + +int +setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen) +{ + init_syms(); + + /* vsock options use AF_VSOCK as the level. Decode only the connect + * timeout; swallow any other vsock option so it can't reach the AF_UNIX + * fd we handed back from socket(). */ + if (level == AF_VSOCK) { + if (optname == SO_VM_SOCKETS_CONNECT_TIMEOUT_OLD && optval) { + const struct __kernel_old_timeval *tv = optval; + + appliedTimeoutUs = tv->tv_sec * 1000000LL + tv->tv_usec; + } + return 0; + } + + return real_setsockopt(sockfd, level, optname, optval, optlen); +} + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + init_syms(); + + if (addr && addr->sa_family == AF_VSOCK) { + const struct sockaddr_vm *svm = (const struct sockaddr_vm *)(const void *)addr; + const char *errstr = getenv("LIBVIRT_VSOCK_MOCK_CONNECT_ERRNO"); + long long expected; + int err = 0; + + /* The test declares, via the environment, what production must have put + * on the wire; a mismatch fails the connect just like a bad request + * would, so the test only needs to check the return value. */ + if ((get_expected_ll("LIBVIRT_VSOCK_MOCK_EXPECT_CID", &expected) && + svm->svm_cid != expected) || + (get_expected_ll("LIBVIRT_VSOCK_MOCK_EXPECT_PORT", &expected) && + svm->svm_port != expected) || + (get_expected_ll("LIBVIRT_VSOCK_MOCK_EXPECT_TIMEOUT_US", &expected) && + appliedTimeoutUs != expected)) { + errno = EINVAL; + return -1; + } + + if (errstr) + ignore_value(virStrToLong_i(errstr, NULL, 10, &err)); + + if (err != 0) { + errno = err; + return -1; + } + return 0; + } + + return real_connect(sockfd, addr, addrlen); +} + +#endif /* __linux__ && RTLD_NEXT */ diff --git a/tests/virvsocktest.c b/tests/virvsocktest.c new file mode 100644 index 0000000000..2571db0107 --- /dev/null +++ b/tests/virvsocktest.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2026 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <sys/socket.h> + +#include "testutils.h" +#include "virvsock.h" +#include "virerror.h" +#include "virfile.h" +#include "virlog.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("tests.vsocktest"); + +static int +testVsockConnectAddr(const void *opaque G_GNUC_UNUSED) +{ + const unsigned int cid = 42; + const unsigned int port = 1234; + g_autofree char *cidStr = g_strdup_printf("%u", cid); + g_autofree char *portStr = g_strdup_printf("%u", port); + VIR_AUTOCLOSE fd = -1; + + /* The mock fails the connect unless production reaches it with exactly + * this cid/port, so a successful connect is the assertion. */ + g_setenv("LIBVIRT_VSOCK_MOCK_EXPECT_CID", cidStr, true); + g_setenv("LIBVIRT_VSOCK_MOCK_EXPECT_PORT", portStr, true); + + fd = virVsockConnectQuiet(cid, port); + + g_unsetenv("LIBVIRT_VSOCK_MOCK_EXPECT_CID"); + g_unsetenv("LIBVIRT_VSOCK_MOCK_EXPECT_PORT"); + + if (fd < 0) { + fprintf(stderr, "virVsockConnectQuiet failed: wrong cid/port reached the kernel\n"); + return -1; + } + + return 0; +} + + +static int +testVsockConnectTimeout(const void *opaque G_GNUC_UNUSED) +{ + g_autofree char *timeoutStr = + g_strdup_printf("%lld", VIR_VSOCK_CONNECT_TIMEOUT_MS * 1000LL); + VIR_AUTOCLOSE fd = -1; + + /* The mock fails the connect unless the timeout was applied via + * setsockopt() beforehand. */ + g_setenv("LIBVIRT_VSOCK_MOCK_EXPECT_TIMEOUT_US", timeoutStr, true); + + fd = virVsockConnectQuiet(VIR_VSOCK_GUEST_CID_MIN, 1234); + + g_unsetenv("LIBVIRT_VSOCK_MOCK_EXPECT_TIMEOUT_US"); + + if (fd < 0) { + fprintf(stderr, "virVsockConnectQuiet failed: connect timeout not applied\n"); + return -1; + } + + return 0; +} + + +static int +testVsockConnectErrno(const void *opaque G_GNUC_UNUSED) +{ + g_autofree char *errstr = g_strdup_printf("%d", ECONNREFUSED); + int fd; + + g_setenv("LIBVIRT_VSOCK_MOCK_CONNECT_ERRNO", errstr, true); + + virResetLastError(); + errno = 0; + fd = virVsockConnectQuiet(VIR_VSOCK_GUEST_CID_MIN, 1234); + g_unsetenv("LIBVIRT_VSOCK_MOCK_CONNECT_ERRNO"); + + if (fd != -1) { + VIR_FORCE_CLOSE(fd); + fprintf(stderr, "virVsockConnectQuiet should have failed\n"); + return -1; + } + + if (errno != ECONNREFUSED) { + fprintf(stderr, "expected errno %d, got %d\n", ECONNREFUSED, errno); + return -1; + } + + if (virGetLastError()) { + fprintf(stderr, "virVsockConnectQuiet reported a libvirt error\n"); + return -1; + } + + return 0; +} + + +static int +mymain(void) +{ + int ret = 0; + + if (virTestRun("vsock connect address", testVsockConnectAddr, NULL) < 0) + ret = -1; + if (virTestRun("vsock connect timeout", testVsockConnectTimeout, NULL) < 0) + ret = -1; + if (virTestRun("vsock connect errno preserved", testVsockConnectErrno, NULL) < 0) + ret = -1; + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN_PRELOAD(mymain, VIR_TEST_MOCK("virvsock")) -- 2.54.0
