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
>
>
>