Hi Polina,

First of all, thanks for your patches.

Before reviewing this patch, I want to ask: why is it needed?

I understand the patch "qemu-sockets: Enable AF_VSOCK on Windows",
but AF_VSOCK has the same behavior on Linux and Windows.
I expected that we could totally reuse the POSIX part, so just move it to
common with
some defines like:
```
#ifdef G_OS_WIN32
        in_ch = g_io_channel_win32_new_fd(in_fd);
#else
        in_ch = g_io_channel_unix_new(in_fd);
#endif
```

We tested our driver with SSH over VSOCK, and standard Linux SSH works
well.
So, what is the difference in this case that you add win32-specific code
for VSOCK?

Best Regards,
Kostiantyn Kostiuk.


On Fri, Jun 19, 2026 at 9:03 PM Polina Vishneva <
[email protected]> wrote:

> Add vsock-listen channel mode to the Windows guest agent.
>
> The listen/accept path uses the standard QEMU socket helpers
> (socket_parse(), socket_listen(), qemu_accept()) and plugs the resulting
> SOCKET into the GMainLoop via g_io_channel_win32_new_socket().
>
> Signed-off-by: Polina Vishneva <[email protected]>
> ---
>  docs/interop/qemu-ga.rst |   4 +
>  qga/channel-win32.c      | 257 ++++++++++++++++++++++++++++++++-------
>  qga/main.c               |   6 +
>  3 files changed, 222 insertions(+), 45 deletions(-)
>
> diff --git a/docs/interop/qemu-ga.rst b/docs/interop/qemu-ga.rst
> index d16cc1b9f0..9c415fc8f8 100644
> --- a/docs/interop/qemu-ga.rst
> +++ b/docs/interop/qemu-ga.rst
> @@ -59,6 +59,10 @@ Options
>    Transport method: one of ``unix-listen``, ``virtio-serial``, or
>    ``isa-serial``, or ``vsock-listen`` (``virtio-serial`` is the default).
>
> +  .. note::
> +     On Windows, ``vsock-listen`` requires the viosock driver from
> +     kvm-guest-drivers-windows recent enough to include commit
> ``687c776``.
> +
>  .. option:: -p, --path=PATH
>
>    Device/socket path (the default for virtio-serial is
> diff --git a/qga/channel-win32.c b/qga/channel-win32.c
> index 2bd6fc1538..257576815a 100644
> --- a/qga/channel-win32.c
> +++ b/qga/channel-win32.c
> @@ -1,6 +1,8 @@
>  #include "qemu/osdep.h"
>  #include <windows.h>
>  #include <io.h>
> +#include "qapi/error.h"
> +#include "qemu/sockets.h"
>  #include "guest-agent-core.h"
>  #include "channel-common.h"
>
> @@ -17,6 +19,9 @@ typedef struct GAChannelReadState {
>  struct GAChannel {
>      GAChannelCommon common;
>
> +    int listen_fd;
> +    int client_fd;
> +
>      HANDLE handle;
>      GAChannelReadState rstate;
>      GIOCondition pending_events; /* TODO: use GAWatch.pollfd.revents */
> @@ -198,7 +203,116 @@ static GSource *ga_channel_create_watch(GAChannel *c)
>      return source;
>  }
>
> -GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize
> *count)
> +/*
> + * Socket mode: GIOChannel-based listen/accept/client helpers
> + */
> +
> +static gboolean ga_channel_socket_client_event(GIOChannel *channel,
> +                                               GIOCondition condition,
> +                                               gpointer data);
> +static gboolean ga_channel_socket_listen_accept(GIOChannel *channel,
> +                                                GIOCondition condition,
> +                                                gpointer data);
> +
> +static void ga_channel_socket_listen_add(GAChannel *c, bool create)
> +{
> +    if (create) {
> +        c->common.listen_channel =
> +            g_io_channel_win32_new_socket(_get_osfhandle(c->listen_fd));
> +        g_assert(c->common.listen_channel);
> +    }
> +    g_io_add_watch(c->common.listen_channel, G_IO_IN,
> +                   ga_channel_socket_listen_accept, c);
> +}
> +
> +static gboolean ga_channel_socket_client_close(GAChannel *c)
> +{
> +    /* Free the CRT fd wrapper while the SOCKET is still valid. */
> +    if (c->client_fd >= 0) {
> +        qemu_close_socket_osfhandle(c->client_fd);
> +        c->client_fd = -1;
> +    }
> +    ga_channel_common_client_close(&c->common);
> +    if (c->common.listen_channel) {
> +        ga_channel_socket_listen_add(c, false);
> +    }
> +    return false;
> +}
> +
> +static gboolean ga_channel_socket_listen_accept(GIOChannel *channel,
> +                                                GIOCondition condition,
> +                                                gpointer data)
> +{
> +    GAChannel *c = data;
> +    int client_fd;
> +    GIOChannel *client_ch;
> +    bool accepted = false;
> +
> +    client_fd = qemu_accept(c->listen_fd, NULL, NULL);
> +    if (client_fd < 0) {
> +        g_warning("error accepting connection: %s", strerror(errno));
> +        goto out;
> +    }
> +
> +    /*
> +     * g_io_channel_win32_new_socket() makes the socket nonblocking via
> +     * WSAEventSelect.
> +     */
> +    client_ch = g_io_channel_win32_new_socket(_get_osfhandle(client_fd));
> +    g_assert(client_ch);
> +
> +    if (ga_channel_common_client_add(&c->common, client_ch,
> +                                     ga_channel_socket_client_event, c)) {
> +        g_warning("error setting up connection");
> +        /*
> +         * Release the CRT fd wrapper while its SOCKET is still open -
> only
> +         * then qemu_close_socket_osfhandle() can detach the fd and let
> the
> +         * channel teardown perform the single closesocket().
> +         */
> +        qemu_close_socket_osfhandle(client_fd);
> +        ga_channel_common_gio_destroy(client_ch);
> +        goto out;
> +    }
> +    c->client_fd = client_fd;
> +    accepted = true;
> +
> +out:
> +    /* only accept 1 connection at a time */
> +    return !accepted;
> +}
> +
> +static gboolean ga_channel_socket_client_event(GIOChannel *channel,
> +                                               GIOCondition condition,
> +                                               gpointer data)
> +{
> +    GAChannel *c = data;
> +
> +    g_assert(c);
> +    if (!c->common.event_cb(condition, c->common.user_data)) {
> +        return ga_channel_socket_client_close(c);
> +    }
> +    /*
> +     * A peer that sends data and disconnects at once is never reported:
> +     * GLib turns the coalesced FD_READ|FD_CLOSE into a plain G_IO_IN,
> +     * and Winsock records FD_CLOSE only once, so no later event follows.
> +     * Peek after each event to detect the disconnect.
> +     */
> +    if (c->client_fd >= 0) {
> +        char peek;
> +        ssize_t r = recv(c->client_fd, &peek, 1, MSG_PEEK);
> +        if (r == 0 || (r < 0 && errno != EAGAIN)) {
> +            return ga_channel_socket_client_close(c);
> +        }
> +    }
> +    return true;
> +}
> +
> +/*
> + * Handle mode: overlapped I/O read/write
> + */
> +
> +static GIOStatus ga_channel_handle_read(GAChannel *c, char *buf, size_t
> size,
> +                                        gsize *count)
>  {
>      GAChannelReadState *rs = &c->rstate;
>      GIOStatus status;
> @@ -221,8 +335,8 @@ GIOStatus ga_channel_read(GAChannel *c, char *buf,
> size_t size, gsize *count)
>      return status;
>  }
>
> -static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t
> size,
> -                                  size_t *count)
> +static GIOStatus ga_channel_handle_write(GAChannel *c, const char *buf,
> +                                         size_t size, size_t *count)
>  {
>      GIOStatus status;
>      OVERLAPPED ov = {0};
> @@ -262,13 +376,25 @@ static GIOStatus ga_channel_write(GAChannel *c,
> const char *buf, size_t size,
>      return status;
>  }
>
> +GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize
> *count)
> +{
> +    if (c->common.method == GA_CHANNEL_VSOCK_LISTEN) {
> +        return ga_channel_common_read(&c->common, buf, size, count);
> +    }
> +    return ga_channel_handle_read(c, buf, size, count);
> +}
> +
>  GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size)
>  {
>      GIOStatus status = G_IO_STATUS_NORMAL;
>      size_t count = 0;
>
> +    if (c->common.method == GA_CHANNEL_VSOCK_LISTEN) {
> +        return ga_channel_common_write_all(&c->common, buf, size);
> +    }
> +
>      while (size) {
> -        status = ga_channel_write(c, buf, size, &count);
> +        status = ga_channel_handle_write(c, buf, size, &count);
>          if (status == G_IO_STATUS_NORMAL) {
>              size -= count;
>              buf += count;
> @@ -276,86 +402,127 @@ GIOStatus ga_channel_write_all(GAChannel *c, const
> char *buf, size_t size)
>              break;
>          }
>      }
> -
>      return status;
>  }
>
>  static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method,
> -                                const gchar *path)
> +                                const gchar *path, Error **errp)
>  {
>      COMMTIMEOUTS comTimeOut = {0};
>      gchar newpath[MAXPATHLEN] = {0};
>      comTimeOut.ReadIntervalTimeout = 1;
>
> -    if (method != GA_CHANNEL_VIRTIO_SERIAL && method !=
> GA_CHANNEL_ISA_SERIAL) {
> -        g_critical("unsupported communication method");
> -        return false;
> -    }
> +    c->common.method = method;
>
> -    if (method == GA_CHANNEL_ISA_SERIAL) {
> -        snprintf(newpath, sizeof(newpath), "\\\\.\\%s", path);
> -    } else {
> -        g_strlcpy(newpath, path, sizeof(newpath));
> -    }
> +    switch (method) {
> +    case GA_CHANNEL_VIRTIO_SERIAL:
> +    case GA_CHANNEL_ISA_SERIAL:
> +        if (method == GA_CHANNEL_ISA_SERIAL) {
> +            snprintf(newpath, sizeof(newpath), "\\\\.\\%s", path);
> +        } else {
> +            g_strlcpy(newpath, path, sizeof(newpath));
> +        }
>
> -    c->handle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, 0, NULL,
> -                           OPEN_EXISTING,
> -                           FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
> NULL);
> -    if (c->handle == INVALID_HANDLE_VALUE) {
> -        g_autofree gchar *emsg = g_win32_error_message(GetLastError());
> -        g_critical("error opening path %s: %s", newpath, emsg);
> -        return false;
> +        c->handle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, 0,
> NULL,
> +                               OPEN_EXISTING,
> +                               FILE_FLAG_NO_BUFFERING |
> FILE_FLAG_OVERLAPPED,
> +                               NULL);
> +        if (c->handle == INVALID_HANDLE_VALUE) {
> +            g_autofree gchar *emsg =
> g_win32_error_message(GetLastError());
> +            error_setg(errp, "error opening path %s: %s", newpath, emsg);
> +            c->handle = NULL;
> +            return false;
> +        }
> +
> +        if (method == GA_CHANNEL_ISA_SERIAL
> +                && !SetCommTimeouts(c->handle, &comTimeOut)) {
> +            g_autofree gchar *emsg =
> g_win32_error_message(GetLastError());
> +            error_setg(errp, "error setting timeout for com port: %s",
> emsg);
> +            CloseHandle(c->handle);
> +            c->handle = NULL;
> +            return false;
> +        }
> +        return true;
> +
> +#ifdef CONFIG_AF_VSOCK
> +    case GA_CHANNEL_VSOCK_LISTEN: {
> +        int fd = ga_channel_common_vsock_listen(path, errp);
> +        if (fd < 0) {
> +            return false;
> +        }
> +
> +        c->listen_fd = fd;
> +        ga_channel_socket_listen_add(c, true);
> +        return true;
>      }
> +#endif
>
> -    if (method == GA_CHANNEL_ISA_SERIAL
> -            && !SetCommTimeouts(c->handle, &comTimeOut)) {
> -        g_autofree gchar *emsg = g_win32_error_message(GetLastError());
> -        g_critical("error setting timeout for com port: %s", emsg);
> -        CloseHandle(c->handle);
> +    default:
> +        error_setg(errp, "unsupported communication method");
>          return false;
>      }
> -
> -    return true;
>  }
>
>  GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
>                            int listen_fd, GAChannelCallback cb, gpointer
> opaque)
>  {
> +    Error *err = NULL;
>      GAChannel *c = g_new0(GAChannel, 1);
> -    SECURITY_ATTRIBUTES sec_attrs;
>
> -    if (!ga_channel_open(c, method, path)) {
> -        g_critical("error opening channel");
> -        g_free(c);
> +    /* listen_fd is for systemd socket activation; not applicable on
> Windows. */
> +    (void)listen_fd;
> +    c->listen_fd = -1;
> +    c->client_fd = -1;
> +    c->common.event_cb = cb;
> +    c->common.user_data = opaque;
> +
> +    if (!ga_channel_open(c, method, path, &err)) {
> +        g_critical("%s", error_get_pretty(err));
> +        error_free(err);
> +        ga_channel_free(c);
>          return NULL;
>      }
>
> -    c->common.event_cb = cb;
> -    c->common.user_data = opaque;
> +    if (c->common.method != GA_CHANNEL_VSOCK_LISTEN) {
> +        SECURITY_ATTRIBUTES sec_attrs;
> +
> +        sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
> +        sec_attrs.lpSecurityDescriptor = NULL;
> +        sec_attrs.bInheritHandle = false;
>
> -    sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
> -    sec_attrs.lpSecurityDescriptor = NULL;
> -    sec_attrs.bInheritHandle = false;
> +        c->rstate.buf_size = QGA_READ_COUNT_DEFAULT;
> +        c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT);
> +        c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL);
>
> -    c->rstate.buf_size = QGA_READ_COUNT_DEFAULT;
> -    c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT);
> -    c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL);
> +        c->source = ga_channel_create_watch(c);
> +        g_source_attach(c->source, NULL);
> +    }
>
> -    c->source = ga_channel_create_watch(c);
> -    g_source_attach(c->source, NULL);
>      return c;
>  }
>
>  void ga_channel_free(GAChannel *c)
>  {
> +    if (c->common.method == GA_CHANNEL_VSOCK_LISTEN) {
> +        /* Free the CRT fd wrappers before the GIOChannels close the
> SOCKETs. */
> +        if (c->listen_fd >= 0) {
> +            qemu_close_socket_osfhandle(c->listen_fd);
> +        }
> +        if (c->client_fd >= 0) {
> +            qemu_close_socket_osfhandle(c->client_fd);
> +        }
> +        ga_channel_common_free(&c->common);
> +        g_free(c);
> +        return;
> +    }
> +
> +    /* Handle mode */
>      if (c->source) {
>          g_source_destroy(c->source);
>      }
>      if (c->rstate.ov.hEvent) {
>          CloseHandle(c->rstate.ov.hEvent);
>      }
> -
> -    ga_channel_common_free(&c->common);
>      g_free(c->rstate.buf);
>      g_free(c);
>  }
> diff --git a/qga/main.c b/qga/main.c
> index fd19c7037d..35068ac7bb 100644
> --- a/qga/main.c
> +++ b/qga/main.c
> @@ -1675,6 +1675,12 @@ int main(int argc, char **argv)
>
>      config->log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
>
> +    /* Required for Windows, harmless otherwise. */
> +    if (socket_init() < 0) {
> +        g_critical("failed to initialize sockets");
> +        goto end;
> +    }
> +
>      qemu_init_exec_dir(argv[0]);
>      qga_qmp_init_marshal(&ga_commands);
>
> --
> 2.54.0
>
>
>

Reply via email to