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 <polina.vishneva@virtuozzo.com>
---
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
© 2016 - 2026 Red Hat, Inc.