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

Reply via email to