diff --git a/gio/gio-autocleanups.h b/gio/gio-autocleanups.h index 4c57b4ec0..4c500712b 100644 --- a/gio/gio-autocleanups.h +++ b/gio/gio-autocleanups.h @@ -82,6 +82,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GIcon, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GInetAddress, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GInetAddressMask, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GInetSocketAddress, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVsockSocketAddress, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GInitable, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GInputStream, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GIOModule, g_object_unref) diff --git a/gio/gio.h b/gio/gio.h index 2bcff2b06..2edbdf3eb 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -175,6 +175,7 @@ #include #include #include +#include #include #include #include diff --git a/gio/gioenums.h b/gio/gioenums.h index d0431d494..8c6052121 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -825,10 +825,11 @@ typedef enum /*< flags >*/ { * @G_SOCKET_FAMILY_IPV4: the IPv4 family * @G_SOCKET_FAMILY_IPV6: the IPv6 family * @G_SOCKET_FAMILY_UNIX: the UNIX domain family + * @G_SOCKET_FAMILY_VSOCK: the Linux virtio-vsock family. Since 2.90 * * The protocol family of a #GSocketAddress. (These values are - * identical to the system defines %AF_INET, %AF_INET6 and %AF_UNIX, - * if available.) + * identical to the system defines %AF_INET, %AF_INET6, %AF_UNIX, and + * %AF_VSOCK, if available.) * * Since: 2.22 */ @@ -836,7 +837,8 @@ typedef enum { G_SOCKET_FAMILY_INVALID, G_SOCKET_FAMILY_UNIX = GLIB_SYSDEF_AF_UNIX, G_SOCKET_FAMILY_IPV4 = GLIB_SYSDEF_AF_INET, - G_SOCKET_FAMILY_IPV6 = GLIB_SYSDEF_AF_INET6 + G_SOCKET_FAMILY_IPV6 = GLIB_SYSDEF_AF_INET6, + G_SOCKET_FAMILY_VSOCK GIO_AVAILABLE_ENUMERATOR_IN_2_90 = GLIB_SYSDEF_AF_VSOCK } GSocketFamily; /** diff --git a/gio/giotypes.h b/gio/giotypes.h index e6e67c5c2..55be6b661 100644 --- a/gio/giotypes.h +++ b/gio/giotypes.h @@ -94,6 +94,7 @@ typedef struct _GInetAddress GInetAddress; typedef struct _GInetAddressMask GInetAddressMask; typedef struct _GInetSocketAddress GInetSocketAddress; typedef struct _GNativeSocketAddress GNativeSocketAddress; +typedef struct _GVsockSocketAddress GVsockSocketAddress; typedef struct _GInputStream GInputStream; typedef struct _GInitable GInitable; typedef struct _GIOModule GIOModule; diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c index 1b82a4d36..3df052202 100644 --- a/gio/gnetworkaddress.c +++ b/gio/gnetworkaddress.c @@ -706,6 +706,7 @@ list_split_families (GList *list, break; case G_SOCKET_FAMILY_INVALID: case G_SOCKET_FAMILY_UNIX: + case G_SOCKET_FAMILY_VSOCK: g_assert_not_reached (); } diff --git a/gio/gsocket.c b/gio/gsocket.c index 707b7861e..e88eddc0b 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -593,6 +593,13 @@ g_socket_details_from_fd (GSocket *socket) socket->priv->protocol = G_SOCKET_PROTOCOL_DEFAULT; break; +#ifdef HAVE_LINUX_VM_SOCKETS_H + case G_SOCKET_FAMILY_VSOCK: + socket->priv->family = G_SOCKET_FAMILY_VSOCK; + socket->priv->protocol = G_SOCKET_PROTOCOL_DEFAULT; + break; +#endif + default: socket->priv->family = G_SOCKET_FAMILY_INVALID; break; @@ -750,6 +757,13 @@ g_socket_create_socket (GSocketFamily family, if (family <= 0) { + if (family == G_SOCKET_FAMILY_VSOCK) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Vsock sockets not supported on this system")); + return -1; + } + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Unable to create socket: %s"), _("Unknown family was specified")); return -1; diff --git a/gio/gsocketaddress.c b/gio/gsocketaddress.c index f2ecd5ff6..846e04701 100644 --- a/gio/gsocketaddress.c +++ b/gio/gsocketaddress.c @@ -34,11 +34,16 @@ #include "gproxyaddressenumerator.h" #include "gsocketaddressenumerator.h" #include "gsocketconnectable.h" +#include "gvsocksocketaddress.h" #include "glibintl.h" #include "gioenumtypes.h" #include "gunixsocketaddress.h" +#ifdef HAVE_LINUX_VM_SOCKETS_H +#include +#endif + #ifdef G_OS_WIN32 #include "giowin32-afunix.h" #endif @@ -302,6 +307,20 @@ g_socket_address_new_from_native (gpointer native, return g_unix_socket_address_new (addr->sun_path); } +#ifdef HAVE_LINUX_VM_SOCKETS_H + if (family == AF_VSOCK) + { + struct sockaddr_vm *addr = (struct sockaddr_vm *) native; + + if (len < sizeof (*addr)) + return NULL; + + return g_vsock_socket_address_new_with_flags (addr->svm_cid, + addr->svm_port, + addr->svm_flags); + } +#endif + return g_native_socket_address_new (native, len); } diff --git a/gio/gvsocksocketaddress.c b/gio/gvsocksocketaddress.c new file mode 100644 index 000000000..984d682f9 --- /dev/null +++ b/gio/gvsocksocketaddress.c @@ -0,0 +1,351 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2026 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include "gvsocksocketaddress.h" + +#include + +#include "gioerror.h" +#include "glibintl.h" +#include "gsocketconnectable.h" + +#ifdef HAVE_LINUX_VM_SOCKETS_H +#include +#endif + +/** + * GVsockSocketAddress: + * + * A virtio-vsock socket address, corresponding to Linux `struct sockaddr_vm`. + * + * `GVsockSocketAddress` is available on all platforms, but can only be + * converted to a native socket address on Linux systems with + * `` support. + * + * Since: 2.90 + */ + +enum +{ + PROP_0, + PROP_CID, + PROP_PORT, + PROP_FLAGS +}; + +struct _GVsockSocketAddressPrivate +{ + guint32 cid; + guint32 port; + guint8 flags; +}; + +static void g_vsock_socket_address_connectable_iface_init (GSocketConnectableIface *iface); +static char *g_vsock_socket_address_connectable_to_string (GSocketConnectable *connectable); + +G_DEFINE_TYPE_WITH_CODE (GVsockSocketAddress, g_vsock_socket_address, G_TYPE_SOCKET_ADDRESS, + G_ADD_PRIVATE (GVsockSocketAddress) + G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE, + g_vsock_socket_address_connectable_iface_init)) + +static void +g_vsock_socket_address_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GVsockSocketAddress *address = G_VSOCK_SOCKET_ADDRESS (object); + + switch (prop_id) + { + case PROP_CID: + address->priv->cid = g_value_get_uint (value); + break; + + case PROP_PORT: + address->priv->port = g_value_get_uint (value); + break; + + case PROP_FLAGS: + address->priv->flags = (guint8) g_value_get_uchar (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_vsock_socket_address_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GVsockSocketAddress *address = G_VSOCK_SOCKET_ADDRESS (object); + + switch (prop_id) + { + case PROP_CID: + g_value_set_uint (value, address->priv->cid); + break; + + case PROP_PORT: + g_value_set_uint (value, address->priv->port); + break; + + case PROP_FLAGS: + g_value_set_uchar (value, address->priv->flags); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GSocketFamily +g_vsock_socket_address_get_family (GSocketAddress *address) +{ + return G_SOCKET_FAMILY_VSOCK; +} + +static gssize +g_vsock_socket_address_get_native_size (GSocketAddress *address) +{ +#ifdef HAVE_LINUX_VM_SOCKETS_H + return sizeof (struct sockaddr_vm); +#else + return -1; +#endif +} + +static gboolean +g_vsock_socket_address_to_native (GSocketAddress *address, + gpointer dest, + gsize destlen, + GError **error) +{ +#ifdef HAVE_LINUX_VM_SOCKETS_H + GVsockSocketAddress *addr = G_VSOCK_SOCKET_ADDRESS (address); + struct sockaddr_vm *sock = dest; + + if (destlen < sizeof (*sock)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + _("Not enough space for socket address")); + return FALSE; + } + + memset (sock, 0, sizeof (*sock)); + sock->svm_family = AF_VSOCK; + sock->svm_cid = addr->priv->cid; + sock->svm_port = addr->priv->port; + sock->svm_flags = addr->priv->flags; + + return TRUE; +#else + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Vsock socket addresses not supported on this system")); + return FALSE; +#endif +} + +static void +g_vsock_socket_address_class_init (GVsockSocketAddressClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GSocketAddressClass *gsocketaddress_class = G_SOCKET_ADDRESS_CLASS (klass); + + gobject_class->set_property = g_vsock_socket_address_set_property; + gobject_class->get_property = g_vsock_socket_address_get_property; + + gsocketaddress_class->get_family = g_vsock_socket_address_get_family; + gsocketaddress_class->to_native = g_vsock_socket_address_to_native; + gsocketaddress_class->get_native_size = g_vsock_socket_address_get_native_size; + + /** + * GVsockSocketAddress:cid: + * + * The vsock context ID. + * + * Since: 2.90 + */ + g_object_class_install_property (gobject_class, PROP_CID, + g_param_spec_uint ("cid", NULL, NULL, + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GVsockSocketAddress:port: + * + * The vsock port. + * + * Since: 2.90 + */ + g_object_class_install_property (gobject_class, PROP_PORT, + g_param_spec_uint ("port", NULL, NULL, + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GVsockSocketAddress:flags: + * + * The vsock flags. + * + * Since: 2.90 + */ + g_object_class_install_property (gobject_class, PROP_FLAGS, + g_param_spec_uchar ("flags", NULL, NULL, + 0, + G_MAXUINT8, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +static void +g_vsock_socket_address_connectable_iface_init (GSocketConnectableIface *iface) +{ + GSocketConnectableIface *parent_iface = g_type_interface_peek_parent (iface); + + iface->enumerate = parent_iface->enumerate; + iface->proxy_enumerate = parent_iface->proxy_enumerate; + iface->to_string = g_vsock_socket_address_connectable_to_string; +} + +static char * +g_vsock_socket_address_connectable_to_string (GSocketConnectable *connectable) +{ + GVsockSocketAddress *address = G_VSOCK_SOCKET_ADDRESS (connectable); + + return g_strdup_printf ("%u:%u", address->priv->cid, address->priv->port); +} + +static void +g_vsock_socket_address_init (GVsockSocketAddress *address) +{ + address->priv = g_vsock_socket_address_get_instance_private (address); +} + +/** + * g_vsock_socket_address_new: + * @cid: the vsock context ID + * @port: the vsock port + * + * Creates a new #GVsockSocketAddress for @cid and @port. + * + * Returns: (transfer full): a new #GVsockSocketAddress + * + * Since: 2.90 + */ +GSocketAddress * +g_vsock_socket_address_new (guint32 cid, + guint32 port) +{ + return g_vsock_socket_address_new_with_flags (cid, port, 0); +} + +/** + * g_vsock_socket_address_new_with_flags: + * @cid: the vsock context ID + * @port: the vsock port + * @flags: the vsock flags + * + * Creates a new #GVsockSocketAddress for @cid, @port, and @flags. + * + * Returns: (transfer full): a new #GVsockSocketAddress + * + * Since: 2.90 + */ +GSocketAddress * +g_vsock_socket_address_new_with_flags (guint32 cid, + guint32 port, + guint8 flags) +{ + return g_object_new (G_TYPE_VSOCK_SOCKET_ADDRESS, + "cid", cid, + "port", port, + "flags", flags, + NULL); +} + +/** + * g_vsock_socket_address_get_cid: + * @address: a #GVsockSocketAddress + * + * Gets @address's vsock context ID. + * + * Returns: the vsock context ID + * + * Since: 2.90 + */ +guint32 +g_vsock_socket_address_get_cid (GVsockSocketAddress *address) +{ + g_return_val_if_fail (G_IS_VSOCK_SOCKET_ADDRESS (address), 0); + + return address->priv->cid; +} + +/** + * g_vsock_socket_address_get_port: + * @address: a #GVsockSocketAddress + * + * Gets @address's vsock port. + * + * Returns: the vsock port + * + * Since: 2.90 + */ +guint32 +g_vsock_socket_address_get_port (GVsockSocketAddress *address) +{ + g_return_val_if_fail (G_IS_VSOCK_SOCKET_ADDRESS (address), 0); + + return address->priv->port; +} + +/** + * g_vsock_socket_address_get_flags: + * @address: a #GVsockSocketAddress + * + * Gets @address's vsock flags. + * + * Returns: the vsock flags + * + * Since: 2.90 + */ +guint8 +g_vsock_socket_address_get_flags (GVsockSocketAddress *address) +{ + g_return_val_if_fail (G_IS_VSOCK_SOCKET_ADDRESS (address), 0); + + return address->priv->flags; +} diff --git a/gio/gvsocksocketaddress.h b/gio/gvsocksocketaddress.h new file mode 100644 index 000000000..2cbed308b --- /dev/null +++ b/gio/gvsocksocketaddress.h @@ -0,0 +1,118 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2026 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __G_VSOCK_SOCKET_ADDRESS_H__ +#define __G_VSOCK_SOCKET_ADDRESS_H__ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define G_TYPE_VSOCK_SOCKET_ADDRESS (g_vsock_socket_address_get_type ()) +#define G_VSOCK_SOCKET_ADDRESS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_VSOCK_SOCKET_ADDRESS, GVsockSocketAddress)) +#define G_VSOCK_SOCKET_ADDRESS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_VSOCK_SOCKET_ADDRESS, GVsockSocketAddressClass)) +#define G_IS_VSOCK_SOCKET_ADDRESS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_VSOCK_SOCKET_ADDRESS)) +#define G_IS_VSOCK_SOCKET_ADDRESS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_VSOCK_SOCKET_ADDRESS)) +#define G_VSOCK_SOCKET_ADDRESS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_VSOCK_SOCKET_ADDRESS, GVsockSocketAddressClass)) + +/** + * G_VSOCK_SOCKET_ADDRESS_CID_ANY: + * + * The wildcard VM address, mapping to Linux `VMADDR_CID_ANY`. + * + * Since: 2.90 + */ +#define G_VSOCK_SOCKET_ADDRESS_CID_ANY ((guint32) -1) GIO_AVAILABLE_MACRO_IN_2_90 + +/** + * G_VSOCK_SOCKET_ADDRESS_CID_HYPERVISOR: + * + * The hypervisor VM address, mapping to Linux `VMADDR_CID_HYPERVISOR`. + * + * Since: 2.90 + */ +#define G_VSOCK_SOCKET_ADDRESS_CID_HYPERVISOR ((guint32) 0) GIO_AVAILABLE_MACRO_IN_2_90 + +/** + * G_VSOCK_SOCKET_ADDRESS_CID_LOCAL: + * + * The local VM address, mapping to Linux `VMADDR_CID_LOCAL`. + * + * Since: 2.90 + */ +#define G_VSOCK_SOCKET_ADDRESS_CID_LOCAL ((guint32) 1) GIO_AVAILABLE_MACRO_IN_2_90 + +/** + * G_VSOCK_SOCKET_ADDRESS_CID_HOST: + * + * The host VM address, mapping to Linux `VMADDR_CID_HOST`. + * + * Since: 2.90 + */ +#define G_VSOCK_SOCKET_ADDRESS_CID_HOST ((guint32) 2) GIO_AVAILABLE_MACRO_IN_2_90 + +/** + * G_VSOCK_SOCKET_ADDRESS_PORT_ANY: + * + * The wildcard VM port, mapping to Linux `VMADDR_PORT_ANY`. + * + * Since: 2.90 + */ +#define G_VSOCK_SOCKET_ADDRESS_PORT_ANY ((guint32) -1) GIO_AVAILABLE_MACRO_IN_2_90 + +typedef struct _GVsockSocketAddressClass GVsockSocketAddressClass; +typedef struct _GVsockSocketAddressPrivate GVsockSocketAddressPrivate; + +struct _GVsockSocketAddress +{ + GSocketAddress parent_instance; + + /*< private >*/ + GVsockSocketAddressPrivate *priv; +}; + +struct _GVsockSocketAddressClass +{ + GSocketAddressClass parent_class; +}; + +GIO_AVAILABLE_IN_2_90 +GType g_vsock_socket_address_get_type (void) G_GNUC_CONST; +GIO_AVAILABLE_IN_2_90 +GSocketAddress *g_vsock_socket_address_new (guint32 cid, + guint32 port); +GIO_AVAILABLE_IN_2_90 +GSocketAddress *g_vsock_socket_address_new_with_flags (guint32 cid, + guint32 port, + guint8 flags); +GIO_AVAILABLE_IN_2_90 +guint32 g_vsock_socket_address_get_cid (GVsockSocketAddress *address); +GIO_AVAILABLE_IN_2_90 +guint32 g_vsock_socket_address_get_port (GVsockSocketAddress *address); +GIO_AVAILABLE_IN_2_90 +guint8 g_vsock_socket_address_get_flags (GVsockSocketAddress *address); + +G_END_DECLS + +#endif /* __G_VSOCK_SOCKET_ADDRESS_H__ */ diff --git a/gio/meson.build b/gio/meson.build index ba588b811..a4ac89cc5 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -79,6 +79,14 @@ if host_system != 'windows' endif endif + if host_system == 'linux' and cc.has_header('linux/vm_sockets.h') + if cc.has_type('struct sockaddr_vm', + prefix : '''#include + #include ''') + glib_conf.set('HAVE_LINUX_VM_SOCKETS_H', 1) + endif + endif + # dn_comp() if cc.links('''#include int main (int argc, char ** argv) { @@ -629,6 +637,7 @@ gio_base_sources = files( 'gunixfdlist.c', 'gunixcredentialsmessage.c', 'gunixsocketaddress.c', + 'gvsocksocketaddress.c', 'gvfs.c', 'gvolume.c', 'gvolumemonitor.c', @@ -776,6 +785,7 @@ gio_headers = files( 'gunixcredentialsmessage.h', 'gunixfdlist.h', 'gunixsocketaddress.h', + 'gvsocksocketaddress.h', 'gvfs.h', 'gvolume.h', 'gvolumemonitor.h', diff --git a/gio/tests/socket-address.c b/gio/tests/socket-address.c index b3323c249..3a74f0144 100644 --- a/gio/tests/socket-address.c +++ b/gio/tests/socket-address.c @@ -1,4 +1,10 @@ -#include +#include "config.h" + +#include + +#ifdef HAVE_LINUX_VM_SOCKETS_H +#include +#endif static void test_unix_socket_address_construct (void) @@ -106,6 +112,83 @@ test_unix_socket_address_to_string (void) g_object_unref (addr); } +static void +test_vsock_socket_address_construct (void) +{ + GSocketAddress *addr = NULL; + GVsockSocketAddress *vaddr; + + addr = g_vsock_socket_address_new_with_flags (G_VSOCK_SOCKET_ADDRESS_CID_HOST, + 1234, + 7); + vaddr = G_VSOCK_SOCKET_ADDRESS (addr); + + g_assert_cmpint (g_socket_address_get_family (addr), ==, G_SOCKET_FAMILY_VSOCK); + g_assert_cmpuint (g_vsock_socket_address_get_cid (vaddr), ==, + G_VSOCK_SOCKET_ADDRESS_CID_HOST); + g_assert_cmpuint (g_vsock_socket_address_get_port (vaddr), ==, 1234); + g_assert_cmpuint (g_vsock_socket_address_get_flags (vaddr), ==, 7); + + g_object_unref (addr); +} + +static void +test_vsock_socket_address_to_string (void) +{ + GSocketAddress *addr = NULL; + char *str = NULL; + + addr = g_vsock_socket_address_new (G_VSOCK_SOCKET_ADDRESS_CID_HOST, 1234); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (addr)); + + g_assert_cmpstr (str, ==, "2:1234"); + + g_free (str); + g_object_unref (addr); +} + +static void +test_vsock_socket_address_native (void) +{ + GSocketAddress *addr = NULL; + GSocketAddress *roundtrip = NULL; + GError *error = NULL; + +#ifdef HAVE_LINUX_VM_SOCKETS_H + struct sockaddr_vm native; + + addr = g_vsock_socket_address_new_with_flags (G_VSOCK_SOCKET_ADDRESS_CID_HOST, + 1234, + 7); + g_assert_cmpint (g_socket_address_get_native_size (addr), ==, sizeof (native)); + g_assert_true (g_socket_address_to_native (addr, &native, sizeof (native), &error)); + g_assert_no_error (error); + g_assert_cmpint (native.svm_family, ==, AF_VSOCK); + g_assert_cmpuint (native.svm_cid, ==, G_VSOCK_SOCKET_ADDRESS_CID_HOST); + g_assert_cmpuint (native.svm_port, ==, 1234); + g_assert_cmpuint (native.svm_flags, ==, 7); + + roundtrip = g_socket_address_new_from_native (&native, sizeof (native)); + g_assert_true (G_IS_VSOCK_SOCKET_ADDRESS (roundtrip)); + g_assert_cmpuint (g_vsock_socket_address_get_cid (G_VSOCK_SOCKET_ADDRESS (roundtrip)), + ==, G_VSOCK_SOCKET_ADDRESS_CID_HOST); + g_assert_cmpuint (g_vsock_socket_address_get_port (G_VSOCK_SOCKET_ADDRESS (roundtrip)), + ==, 1234); + g_assert_cmpuint (g_vsock_socket_address_get_flags (G_VSOCK_SOCKET_ADDRESS (roundtrip)), + ==, 7); + + g_object_unref (roundtrip); + g_object_unref (addr); +#else + addr = g_vsock_socket_address_new (G_VSOCK_SOCKET_ADDRESS_CID_HOST, 1234); + g_assert_cmpint (g_socket_address_get_native_size (addr), ==, -1); + g_assert_false (g_socket_address_to_native (addr, NULL, 0, &error)); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + g_object_unref (addr); +#endif +} + int main (int argc, char **argv) @@ -114,6 +197,9 @@ main (int argc, g_test_add_func ("/socket/address/unix/construct", test_unix_socket_address_construct); g_test_add_func ("/socket/address/unix/to-string", test_unix_socket_address_to_string); + g_test_add_func ("/socket/address/vsock/construct", test_vsock_socket_address_construct); + g_test_add_func ("/socket/address/vsock/to-string", test_vsock_socket_address_to_string); + g_test_add_func ("/socket/address/vsock/native", test_vsock_socket_address_native); return g_test_run (); } diff --git a/gio/tests/socket.c b/gio/tests/socket.c index 02f7c72bc..d3e3f981d 100644 --- a/gio/tests/socket.c +++ b/gio/tests/socket.c @@ -18,6 +18,8 @@ * Public License along with this library; if not, see . */ +#include "config.h" + #include #include #include "glib-private.h" @@ -33,6 +35,10 @@ #include #endif +#ifdef HAVE_LINUX_VM_SOCKETS_H +#include +#endif + #ifdef G_OS_WIN32 #include "giowin32-afunix.h" #include @@ -1435,6 +1441,246 @@ test_unix_connection (void) g_object_unref (s); } +static gboolean +vsock_error_is_skip (const GError *error) +{ + return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); +} + +static void +skip_vsock_error (const char *operation, + const GError *error) +{ + char *message = g_strdup_printf ("AF_VSOCK %s unavailable: %s", + operation, + error->message); + + g_test_skip (message); + g_free (message); +} + +static void +test_vsock_new (void) +{ + GSocket *socket = NULL; + GError *error = NULL; + + socket = g_socket_new (G_SOCKET_FAMILY_VSOCK, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + + if (socket == NULL) + { + g_assert_true (vsock_error_is_skip (error)); + skip_vsock_error ("socket creation", error); + g_clear_error (&error); + return; + } + + g_assert_cmpint (g_socket_get_family (socket), ==, G_SOCKET_FAMILY_VSOCK); + g_assert_cmpint (g_socket_get_socket_type (socket), ==, G_SOCKET_TYPE_STREAM); + g_assert_cmpint (g_socket_get_protocol (socket), ==, G_SOCKET_PROTOCOL_DEFAULT); + + g_object_unref (socket); +} + +static void +test_vsock_new_from_fd (void) +{ +#ifdef HAVE_LINUX_VM_SOCKETS_H + GSocket *sock = NULL; + GError *error = NULL; + int fd; + + fd = socket (AF_VSOCK, SOCK_STREAM, 0); + if (fd == -1) + { + g_test_skip ("AF_VSOCK socket creation unavailable"); + return; + } + + sock = g_socket_new_from_fd (fd, &error); + if (sock == NULL) + { + int saved_fd = fd; + + g_assert_true (vsock_error_is_skip (error)); + skip_vsock_error ("fd introspection", error); + g_clear_error (&error); + g_close (saved_fd, NULL); + return; + } + + g_assert_cmpint (g_socket_get_family (sock), ==, G_SOCKET_FAMILY_VSOCK); + g_assert_cmpint (g_socket_get_socket_type (sock), ==, G_SOCKET_TYPE_STREAM); + g_assert_cmpint (g_socket_get_protocol (sock), ==, G_SOCKET_PROTOCOL_DEFAULT); + + g_object_unref (sock); +#else + g_test_skip ("AF_VSOCK not available at compile time"); +#endif +} + +typedef struct +{ + GMainLoop *loop; + GSocketListener *listener; + GSocketConnection *client_connection; + GSocketConnection *server_connection; + GError *client_error; + GError *server_error; + guint pending; +} VsockAsyncData; + +static void +vsock_async_done (VsockAsyncData *data) +{ + data->pending--; + + if (data->pending == 0) + g_main_loop_quit (data->loop); +} + +static void +vsock_client_connected_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + VsockAsyncData *data = user_data; + + data->client_connection = g_socket_client_connect_finish (G_SOCKET_CLIENT (object), + result, + &data->client_error); + if (data->client_connection == NULL) + g_socket_listener_close (data->listener); + + vsock_async_done (data); +} + +static void +vsock_listener_accepted_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + VsockAsyncData *data = user_data; + + data->server_connection = g_socket_listener_accept_finish (G_SOCKET_LISTENER (object), + result, + NULL, + &data->server_error); + vsock_async_done (data); +} + +static gboolean +vsock_async_timeout_cb (gpointer user_data) +{ + VsockAsyncData *data = user_data; + + g_socket_listener_close (data->listener); + g_main_loop_quit (data->loop); + + return G_SOURCE_REMOVE; +} + +static void +test_vsock_listener_client_loopback (void) +{ +#ifdef HAVE_LINUX_VM_SOCKETS_H + GSocketListener *listener = NULL; + GSocketClient *client = NULL; + GSocketAddress *address = NULL; + GSocketAddress *effective_address = NULL; + GSocketConnection *connection = NULL; + GSocketConnection *accepted_connection = NULL; + GError *error = NULL; + VsockAsyncData data = { 0 }; + guint timeout_id; + + listener = g_socket_listener_new (); + address = g_vsock_socket_address_new (G_VSOCK_SOCKET_ADDRESS_CID_LOCAL, + G_VSOCK_SOCKET_ADDRESS_PORT_ANY); + + if (!g_socket_listener_add_address (listener, + address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL, + &effective_address, + &error)) + { + g_assert_true (vsock_error_is_skip (error)); + skip_vsock_error ("loopback listen", error); + g_clear_error (&error); + g_object_unref (address); + g_object_unref (listener); + return; + } + + client = g_socket_client_new (); + connection = g_socket_client_connect (client, + G_SOCKET_CONNECTABLE (effective_address), + NULL, + &error); + if (connection == NULL) + { + g_assert_true (vsock_error_is_skip (error)); + skip_vsock_error ("loopback connect", error); + g_clear_error (&error); + g_object_unref (effective_address); + g_object_unref (address); + g_object_unref (client); + g_object_unref (listener); + return; + } + + g_assert_true (G_IS_SOCKET_CONNECTION (connection)); + accepted_connection = g_socket_listener_accept (listener, NULL, NULL, &error); + g_assert_no_error (error); + g_assert_true (G_IS_SOCKET_CONNECTION (accepted_connection)); + g_clear_object (&accepted_connection); + g_clear_object (&connection); + + data.loop = g_main_loop_new (NULL, FALSE); + data.listener = listener; + data.pending = 2; + + g_socket_listener_accept_async (listener, + NULL, + vsock_listener_accepted_cb, + &data); + g_socket_client_connect_async (client, + G_SOCKET_CONNECTABLE (effective_address), + NULL, + vsock_client_connected_cb, + &data); + + timeout_id = g_timeout_add_seconds (5, vsock_async_timeout_cb, &data); + g_main_loop_run (data.loop); + g_clear_handle_id (&timeout_id, g_source_remove); + + g_assert_no_error (data.client_error); + g_assert_no_error (data.server_error); + g_assert_true (G_IS_SOCKET_CONNECTION (data.client_connection)); + g_assert_true (G_IS_SOCKET_CONNECTION (data.server_connection)); + + g_clear_object (&data.client_connection); + g_clear_object (&data.server_connection); + g_main_loop_unref (data.loop); + g_object_unref (effective_address); + g_object_unref (address); + g_object_unref (client); + g_object_unref (listener); +#else + g_test_skip ("AF_VSOCK not available at compile time"); +#endif +} + #ifdef G_OS_UNIX static GSocketConnection * create_connection_for_fd (int fd) @@ -2813,6 +3059,10 @@ main (int argc, g_test_add_func ("/socket/address", test_sockaddr); g_test_add_func ("/socket/unix-from-fd", test_unix_from_fd); g_test_add_func ("/socket/unix-connection", test_unix_connection); + g_test_add_func ("/socket/vsock-new", test_vsock_new); + g_test_add_func ("/socket/vsock-new-from-fd", test_vsock_new_from_fd); + g_test_add_func ("/socket/vsock-listener-client-loopback", + test_vsock_listener_client_loopback); #ifdef G_OS_UNIX g_test_add_func ("/socket/unix-connection-ancillary-data", test_unix_connection_ancillary_data); #endif diff --git a/glib/glibconfig.h.in b/glib/glibconfig.h.in index 1775619ae..9b6a15dc4 100644 --- a/glib/glibconfig.h.in +++ b/glib/glibconfig.h.in @@ -201,6 +201,7 @@ typedef @g_pid_type@ GPid; #define GLIB_SYSDEF_AF_UNIX @g_af_unix@ #define GLIB_SYSDEF_AF_INET @g_af_inet@ #define GLIB_SYSDEF_AF_INET6 @g_af_inet6@ +#define GLIB_SYSDEF_AF_VSOCK @g_af_vsock@ #define GLIB_SYSDEF_MSG_OOB @g_msg_oob@ #define GLIB_SYSDEF_MSG_PEEK @g_msg_peek@ diff --git a/meson.build b/meson.build index 619d9a2a3..7647ee363 100644 --- a/meson.build +++ b/meson.build @@ -2056,6 +2056,13 @@ foreach d : inet_defines val = cc.compute_int(d[0], prefix: inet_includes, guess: d[2]) glibconfig_conf.set(d[1], val) endforeach +if host_system == 'linux' and cc.has_header_symbol('sys/socket.h', 'AF_VSOCK') + glibconfig_conf.set('g_af_vsock', cc.compute_int('AF_VSOCK', + prefix: inet_includes, + guess: 40)) +else + glibconfig_conf.set('g_af_vsock', -1) +endif if host_system == 'windows' have_ipv6 = true