diff --git a/gio/ginetaddress.c b/gio/ginetaddress.c index c0ba2507ad6744297510a7947c95dd26d40c72be..c5274a0f59698eedfad0ed5f624a7bd20f22a5d1 100644 --- a/gio/ginetaddress.c +++ b/gio/ginetaddress.c @@ -42,6 +42,10 @@ struct _GInetAddressPrivate struct in6_addr ipv6; #endif } addr; +#ifdef HAVE_IPV6 + guint32 flowinfo; + guint32 scope_id; +#endif }; /** @@ -78,6 +82,8 @@ enum PROP_IS_MC_NODE_LOCAL, PROP_IS_MC_ORG_LOCAL, PROP_IS_MC_SITE_LOCAL, + PROP_FLOWINFO, + PROP_SCOPE_ID, }; static void @@ -107,6 +113,18 @@ g_inet_address_set_property (GObject *object, #endif break; + case PROP_SCOPE_ID: +#ifdef HAVE_IPV6 + address->priv->scope_id = g_value_get_uint (value); +#endif + break; + + case PROP_FLOWINFO: +#ifdef HAVE_IPV6 + address->priv->flowinfo = g_value_get_uint (value); +#endif + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -172,6 +190,14 @@ g_inet_address_get_property (GObject *object, g_value_set_boolean (value, g_inet_address_get_is_mc_site_local (address)); break; + case PROP_FLOWINFO: + g_value_set_uint (value, g_inet_address_get_flowinfo (address)); + break; + + case PROP_SCOPE_ID: + g_value_set_uint (value, g_inet_address_get_scope_id (address)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -353,6 +379,38 @@ g_inet_address_class_init (GInetAddressClass *klass) FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GInetAddress:flowinfo: + * + * The flowinfo for an IPv6 address. + * See [method@Gio.InetAddress.get_flowinfo]. + * + * Since: 2.86 + */ + g_object_class_install_property (gobject_class, PROP_FLOWINFO, + g_param_spec_uint ("flowinfo", NULL, NULL, + 0, G_MAXUINT32, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GInetAddress:scope-id: + * + * The scope-id for an IPv6 address. + * See [method@Gio.InetAddress.get_scope_id]. + * + * Since: 2.86 + */ + g_object_class_install_property (gobject_class, PROP_SCOPE_ID, + g_param_spec_uint ("scope-id", NULL, NULL, + 0, G_MAXUINT32, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); } static void @@ -367,6 +425,10 @@ g_inet_address_init (GInetAddress *address) * * Parses @string as an IP address and creates a new #GInetAddress. * + * If @address is an IPv6 address, it can also contain a scope ID + * (separated from the address by a `%`). Note that currently this + * behavior is platform specific. This may change in a future release. + * * Returns: (nullable) (transfer full): a new #GInetAddress corresponding * to @string, or %NULL if @string could not be parsed. * Free the returned object with g_object_unref(). @@ -377,9 +439,6 @@ GInetAddress * g_inet_address_new_from_string (const gchar *string) { struct in_addr in_addr; -#ifdef HAVE_IPV6 - struct in6_addr in6_addr; -#endif g_return_val_if_fail (string != NULL, NULL); @@ -389,13 +448,54 @@ g_inet_address_new_from_string (const gchar *string) */ g_networking_init (); - if (inet_pton (AF_INET, string, &in_addr) > 0) - return g_inet_address_new_from_bytes ((guint8 *)&in_addr, AF_INET); #ifdef HAVE_IPV6 - else if (inet_pton (AF_INET6, string, &in6_addr) > 0) - return g_inet_address_new_from_bytes ((guint8 *)&in6_addr, AF_INET6); + /* IPv6 address (or it's invalid). We use getaddrinfo() because + * it will handle parsing a scope_id as well. + */ + if (strchr (string, ':')) + { + struct addrinfo *res; + struct addrinfo hints = { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_NUMERICHOST, + }; + int status; + GInetAddress *address = NULL; + + status = getaddrinfo (string, NULL, &hints, &res); + if (status == 0) + { + g_assert (res->ai_addrlen == sizeof (struct sockaddr_in6)); + struct sockaddr_in6 *sockaddr6 = (struct sockaddr_in6 *)res->ai_addr; + address = g_inet_address_new_from_bytes_with_ipv6_info (((guint8 *)&sockaddr6->sin6_addr), + G_SOCKET_FAMILY_IPV6, + sockaddr6->sin6_flowinfo, + sockaddr6->sin6_scope_id); + freeaddrinfo (res); + } + else + { + struct in6_addr in6_addr; + g_debug ("getaddrinfo failed to resolve host string %s", string); + + if (inet_pton (AF_INET6, string, &in6_addr) > 0) + address = g_inet_address_new_from_bytes ((guint8 *)&in6_addr, G_SOCKET_FAMILY_IPV6); + } + + return address; + } #endif + /* IPv4 (or invalid). We don't want to use getaddrinfo() here, + * because it accepts the stupid "IPv4 numbers-and-dots + * notation" addresses that are never used for anything except + * phishing. Since we don't have to worry about scope IDs for + * IPv4, we can just use inet_pton(). + */ + if (inet_pton (AF_INET, string, &in_addr) > 0) + return g_inet_address_new_from_bytes ((guint8 *)&in_addr, G_SOCKET_FAMILY_IPV4); + return NULL; } @@ -490,6 +590,38 @@ g_inet_address_new_any (GSocketFamily family) #endif } +/** + * g_inet_address_new_from_bytes_with_ipv6_info: + * @bytes: (array) (element-type guint8): raw address data + * @family: the address family of @bytes + * @scope_id: the scope-id of the address + * + * Creates a new [class@Gio.InetAddress] from the given @family, @bytes + * and @scope_id. + * + * @bytes must be 4 bytes for [enum@Gio.SocketFamily.IPV4] and 16 bytes for + * [enum@Gio.SocketFamily.IPV6]. + * + * Returns: (transfer full): a new internet address corresponding to + * @family, @bytes and @scope_id + * + * Since: 2.86 + */ +GInetAddress * +g_inet_address_new_from_bytes_with_ipv6_info (const guint8 *bytes, + GSocketFamily family, + guint32 flowinfo, + guint32 scope_id) +{ + g_return_val_if_fail (G_INET_ADDRESS_FAMILY_IS_VALID (family), NULL); + + return g_object_new (G_TYPE_INET_ADDRESS, + "family", family, + "bytes", bytes, + "flowinfo", flowinfo, + "scope-id", scope_id, + NULL); +} /** * g_inet_address_to_string: @@ -866,6 +998,49 @@ g_inet_address_get_is_mc_site_local (GInetAddress *address) #endif } +/** + * g_inet_address_get_scope_id: + * @address: a #GInetAddress + * + * Gets the value of [property@Gio.InetAddress:scope-id]. + * + * Returns: The scope-id for the address, `0` if unset or not IPv6 address. + * Since: 2.86 + */ +guint32 +g_inet_address_get_scope_id (GInetAddress *address) +{ + g_return_val_if_fail (G_IS_INET_ADDRESS (address), 0); + +#ifdef HAVE_IPV6 + if (address->priv->family == AF_INET6) + return address->priv->scope_id; +#endif + return 0; +} + +/** + * g_inet_address_get_flowinfo: + * @address: a #GInetAddress + * + * Gets the value of [property@Gio.InetAddress:flowinfo]. + * + * Returns: The flowinfo for the address, `0` if unset or not IPv6 address. + * Since: 2.86 + */ +guint32 +g_inet_address_get_flowinfo (GInetAddress *address) +{ + g_return_val_if_fail (G_IS_INET_ADDRESS (address), 0); + +#ifdef HAVE_IPV6 + if (address->priv->family == AF_INET6) + return address->priv->flowinfo; +#endif + return 0; +} + + /** * g_inet_address_equal: * @address: A #GInetAddress. diff --git a/gio/ginetaddress.h b/gio/ginetaddress.h index ea503a9278c949a93a8316ec2cab19e01aca0038..9d2f21b1e6f59a7cdc518548799dbaf41a4850c6 100644 --- a/gio/ginetaddress.h +++ b/gio/ginetaddress.h @@ -71,6 +71,12 @@ GInetAddress * g_inet_address_new_from_bytes (const guint8 GIO_AVAILABLE_IN_ALL GInetAddress * g_inet_address_new_loopback (GSocketFamily family); +GIO_AVAILABLE_IN_2_86 +GInetAddress * g_inet_address_new_from_bytes_with_ipv6_info (const guint8 *bytes, + GSocketFamily family, + guint32 flowinfo, + guint32 scope_id); + GIO_AVAILABLE_IN_ALL GInetAddress * g_inet_address_new_any (GSocketFamily family); @@ -120,6 +126,12 @@ gboolean g_inet_address_get_is_mc_org_local (GInetAddress GIO_AVAILABLE_IN_ALL gboolean g_inet_address_get_is_mc_site_local (GInetAddress *address); +GIO_AVAILABLE_IN_2_86 +guint32 g_inet_address_get_scope_id (GInetAddress *address); + +GIO_AVAILABLE_IN_2_86 +guint32 g_inet_address_get_flowinfo (GInetAddress *address); + G_END_DECLS #endif /* __G_INET_ADDRESS_H__ */ diff --git a/gio/ginetsocketaddress.c b/gio/ginetsocketaddress.c index c17bd1497e136b912d5d398c26d00f0c74a9848a..95a4eb1649cb31c7063d4570800144d1523d8fbd 100644 --- a/gio/ginetsocketaddress.c +++ b/gio/ginetsocketaddress.c @@ -97,12 +97,12 @@ g_inet_socket_address_get_property (GObject *object, case PROP_FLOWINFO: g_return_if_fail (g_inet_address_get_family (address->priv->address) == G_SOCKET_FAMILY_IPV6); - g_value_set_uint (value, address->priv->flowinfo); + g_value_set_uint (value, g_inet_socket_address_get_flowinfo (address)); break; case PROP_SCOPE_ID: g_return_if_fail (g_inet_address_get_family (address->priv->address) == G_SOCKET_FAMILY_IPV6); - g_value_set_uint (value, address->priv->scope_id); + g_value_set_uint (value, g_inet_socket_address_get_scope_id (address)); break; default: @@ -220,8 +220,8 @@ g_inet_socket_address_to_native (GSocketAddress *address, memset (sock, 0, sizeof (*sock)); sock->sin6_family = AF_INET6; sock->sin6_port = g_htons (addr->priv->port); - sock->sin6_flowinfo = addr->priv->flowinfo; - sock->sin6_scope_id = addr->priv->scope_id; + sock->sin6_flowinfo = g_inet_socket_address_get_flowinfo (addr); + sock->sin6_scope_id = g_inet_socket_address_get_scope_id (addr); memcpy (&(sock->sin6_addr.s6_addr), g_inet_address_to_bytes (addr->priv->address), sizeof (sock->sin6_addr)); return TRUE; } @@ -282,6 +282,8 @@ g_inet_socket_address_class_init (GInetSocketAddressClass *klass) * * The `sin6_flowinfo` field, for IPv6 addresses. * + * If unset this property is inherited from [property@Gio.InetSocketAddress:address]. + * * Since: 2.32 */ g_object_class_install_property (gobject_class, PROP_FLOWINFO, @@ -298,6 +300,8 @@ g_inet_socket_address_class_init (GInetSocketAddressClass *klass) * * The `sin6_scope_id` field, for IPv6 addresses. * + * If unset this property is inherited from [property@Gio.InetSocketAddress:address]. + * * Since: 2.32 */ g_object_class_install_property (gobject_class, PROP_SCOPE_ID, @@ -398,7 +402,8 @@ g_inet_socket_address_new (GInetAddress *address, * Creates a new #GInetSocketAddress for @address and @port. * * If @address is an IPv6 address, it can also contain a scope ID - * (separated from the address by a `%`). + * (separated from the address by a `%`). Note that currently this + * behavior is platform specific. This may change in a future release. * * Returns: (nullable) (transfer full): a new #GInetSocketAddress, * or %NULL if @address cannot be parsed. @@ -409,60 +414,16 @@ GSocketAddress * g_inet_socket_address_new_from_string (const char *address, guint port) { - static struct addrinfo *hints, hints_struct; GSocketAddress *saddr; GInetAddress *iaddr; - struct addrinfo *res; - gint status; - - if (strchr (address, ':')) - { - /* IPv6 address (or it's invalid). We use getaddrinfo() because - * it will handle parsing a scope_id as well. - */ - - if (G_UNLIKELY (g_once_init_enter_pointer (&hints))) - { - hints_struct.ai_family = AF_UNSPEC; - hints_struct.ai_socktype = SOCK_STREAM; - hints_struct.ai_protocol = 0; - hints_struct.ai_flags = AI_NUMERICHOST; - g_once_init_leave_pointer (&hints, &hints_struct); - } - - status = getaddrinfo (address, NULL, hints, &res); - if (status != 0) - return NULL; - if (res->ai_family == AF_INET6 && - res->ai_addrlen == sizeof (struct sockaddr_in6)) - { - ((struct sockaddr_in6 *)res->ai_addr)->sin6_port = g_htons (port); - saddr = g_socket_address_new_from_native (res->ai_addr, res->ai_addrlen); - } - else - saddr = NULL; + iaddr = g_inet_address_new_from_string (address); + if (!iaddr) + return NULL; - freeaddrinfo (res); - } - else - { - /* IPv4 (or invalid). We don't want to use getaddrinfo() here, - * because it accepts the stupid "IPv4 numbers-and-dots - * notation" addresses that are never used for anything except - * phishing. Since we don't have to worry about scope IDs for - * IPv4, we can just use g_inet_address_new_from_string(). - */ - iaddr = g_inet_address_new_from_string (address); - if (!iaddr) - return NULL; - - g_warn_if_fail (g_inet_address_get_family (iaddr) == G_SOCKET_FAMILY_IPV4); - - saddr = g_inet_socket_address_new (iaddr, port); - g_object_unref (iaddr); - } + saddr = g_inet_socket_address_new (iaddr, port); + g_object_unref (iaddr); return saddr; } @@ -511,6 +472,8 @@ g_inet_socket_address_get_port (GInetSocketAddress *address) * Gets the `sin6_flowinfo` field from @address, * which must be an IPv6 address. * + * If not overridden this value will be inherited from [property@Gio.InetSocketAddress:address]. + * * Returns: the flowinfo field * * Since: 2.32 @@ -521,7 +484,7 @@ g_inet_socket_address_get_flowinfo (GInetSocketAddress *address) g_return_val_if_fail (G_IS_INET_SOCKET_ADDRESS (address), 0); g_return_val_if_fail (g_inet_address_get_family (address->priv->address) == G_SOCKET_FAMILY_IPV6, 0); - return address->priv->flowinfo; + return address->priv->flowinfo ? address->priv->flowinfo : g_inet_address_get_flowinfo (address->priv->address); } /** @@ -531,6 +494,8 @@ g_inet_socket_address_get_flowinfo (GInetSocketAddress *address) * Gets the `sin6_scope_id` field from @address, * which must be an IPv6 address. * + * If not overridden this value will be inherited from [property@Gio.InetSocketAddress:address]. + * * Returns: the scope id field * * Since: 2.32 @@ -541,5 +506,5 @@ g_inet_socket_address_get_scope_id (GInetSocketAddress *address) g_return_val_if_fail (G_IS_INET_SOCKET_ADDRESS (address), 0); g_return_val_if_fail (g_inet_address_get_family (address->priv->address) == G_SOCKET_FAMILY_IPV6, 0); - return address->priv->scope_id; + return address->priv->scope_id ? address->priv->scope_id : g_inet_address_get_scope_id (address->priv->address); } diff --git a/gio/gsocketaddress.c b/gio/gsocketaddress.c index defd965e0727f3369960b62c66c299029d2cd38d..195644b5322ecd288bbefe23cab1689ec1497460 100644 --- a/gio/gsocketaddress.c +++ b/gio/gsocketaddress.c @@ -252,15 +252,13 @@ g_socket_address_new_from_native (gpointer native, iaddr = g_inet_address_new_from_bytes ((guint8 *) &(sin_addr.sin_addr), G_SOCKET_FAMILY_IPV4); } else - { - iaddr = g_inet_address_new_from_bytes ((guint8 *) &(addr->sin6_addr), G_SOCKET_FAMILY_IPV6); - } + { + iaddr = g_inet_address_new_from_bytes_with_ipv6_info ((guint8 *) &(addr->sin6_addr), G_SOCKET_FAMILY_IPV6, addr->sin6_flowinfo, addr->sin6_scope_id); + } sockaddr = g_object_new (G_TYPE_INET_SOCKET_ADDRESS, "address", iaddr, "port", g_ntohs (addr->sin6_port), - "flowinfo", addr->sin6_flowinfo, - "scope_id", addr->sin6_scope_id, NULL); g_object_unref (iaddr); return sockaddr; diff --git a/gio/tests/inet-address.c b/gio/tests/inet-address.c index 5c663fd67101e3229c88e41c59bccaf4df0377de..799f66b5fdeaef425aeba5fb5ce824682734cf4b 100644 --- a/gio/tests/inet-address.c +++ b/gio/tests/inet-address.c @@ -53,6 +53,11 @@ test_parse (void) addr = g_inet_address_new_from_string ("204.152.189.116"); g_assert (addr != NULL); g_object_unref (addr); +#ifndef G_OS_WIN32 /* getaddrinfo on Windows does not support scope-id */ + addr = g_inet_address_new_from_string ("::1%0"); + g_assert (addr != NULL); + g_object_unref (addr); +#endif addr = g_inet_address_new_from_string ("::1::2"); g_assert (addr == NULL); @@ -60,10 +65,12 @@ test_parse (void) g_assert (addr == NULL); addr = g_inet_address_new_from_string ("[2001:1:2:3:4:5:6:7"); g_assert (addr == NULL); +#ifndef G_OS_WIN32 /* getaddrinfo on Windows is more forgiving about format and accepts these strings */ addr = g_inet_address_new_from_string ("[2001:1:2:3:4:5:6:7]"); g_assert (addr == NULL); addr = g_inet_address_new_from_string ("[2001:1:2:3:4:5:6:7]:80"); g_assert (addr == NULL); +#endif addr = g_inet_address_new_from_string ("0:1:2:3:4:5:6:7:8:9"); g_assert (addr == NULL); addr = g_inet_address_new_from_string ("::FFFFFFF"); diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index 61fa41468ceb10d6984f804e69378e7cba6653fa..81dcda7a92b486dd7436476e94370a25d5cb2a13 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -176,8 +176,13 @@ static ResolveTest address_tests[] = { * (just) IP addresses. */ { "192.168.1.2:80", TRUE, FALSE, FALSE }, +#ifndef G_OS_WIN32 /* getaddrinfo on Windows is more forgiving about format and accepts these strings */ { "[fe80::42]", TRUE, FALSE, FALSE }, { "[fe80::42]:80", TRUE, FALSE, FALSE }, +#else + { "[fe80::42]", TRUE, TRUE, FALSE }, + { "[fe80::42]:80", TRUE, TRUE, FALSE }, +#endif /* These should not be considered IP addresses by anyone. */ { "192.168.258", FALSE, FALSE, FALSE }, diff --git a/gio/tests/socket-common.c b/gio/tests/socket-common.c index b740f68e795109231905ff93f2a44715b666cd2a..fb59f3f6de794612948a37ffe5063f449ac65991 100644 --- a/gio/tests/socket-common.c +++ b/gio/tests/socket-common.c @@ -17,14 +17,23 @@ socket_address_to_string (GSocketAddress *address) if (G_IS_INET_SOCKET_ADDRESS (address)) { + GInetSocketAddress *socket_address; GInetAddress *inet_address; char *str; int port; + guint32 scope_id; - inet_address = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)); + socket_address = G_INET_SOCKET_ADDRESS (address); + scope_id = g_inet_socket_address_get_scope_id (socket_address); + inet_address = g_inet_socket_address_get_address (socket_address); str = g_inet_address_to_string (inet_address); - port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address)); - res = g_strdup_printf ("%s:%d", str, port); + port = g_inet_socket_address_get_port (socket_address); + if (scope_id) + res = g_strdup_printf ("[%s%%%u]:%d", str, scope_id, port); + else if (g_inet_address_get_family (inet_address) == G_SOCKET_FAMILY_IPV6) + res = g_strdup_printf ("[%s]:%d", str, port); + else + res = g_strdup_printf ("%s:%d", str, port); g_free (str); } #ifdef G_OS_UNIX diff --git a/gio/tests/socket-testclient.c b/gio/tests/socket-testclient.c index 02563276797be379146e742359f2985b50dc2fb0..4b8aa65821014653f5c21dfc0f574f82793bc4ad 100644 --- a/gio/tests/socket-testclient.c +++ b/gio/tests/socket-testclient.c @@ -103,6 +103,17 @@ lookup_client_certificate (GTlsClientConnection *conn, return certificate; } +static GSocket * +make_socket (GSocketFamily socket_family, GSocketType socket_type, GError **error) +{ + GSocket *socket = g_socket_new (socket_family, socket_type, 0, error); + + if (socket && read_timeout) + g_socket_set_timeout (socket, read_timeout); + + return socket; +} + static gboolean make_connection (const char *argument, GTlsCertificate *certificate, @@ -115,29 +126,18 @@ make_connection (const char *argument, GError **error) { GSocketType socket_type; - GSocketFamily socket_family; GSocketAddressEnumerator *enumerator; GSocketConnectable *connectable; GSocketAddress *src_address; GTlsInteraction *interaction; GError *err = NULL; + char *socket_string; if (use_udp) socket_type = G_SOCKET_TYPE_DATAGRAM; else socket_type = G_SOCKET_TYPE_STREAM; - if (unix_socket) - socket_family = G_SOCKET_FAMILY_UNIX; - else - socket_family = G_SOCKET_FAMILY_IPV4; - - *socket = g_socket_new (socket_family, socket_type, 0, error); - if (*socket == NULL) - return FALSE; - - if (read_timeout) - g_socket_set_timeout (*socket, read_timeout); if (unix_socket) { @@ -171,17 +171,30 @@ make_connection (const char *argument, return FALSE; } + *socket = make_socket (unix_socket ? G_SOCKET_FAMILY_UNIX : g_socket_address_get_family (*address), + socket_type, error); + if (*socket == NULL) + { + g_object_unref (*address); + g_object_unref (enumerator); + return FALSE; + } + if (g_socket_connect (*socket, *address, cancellable, &err)) break; - g_message ("Connection to %s failed: %s, trying next", socket_address_to_string (*address), err->message); + socket_string = socket_address_to_string (*address); + g_message ("Connection to %s failed: %s, trying next", socket_string, err->message); + g_free (socket_string); g_clear_error (&err); + g_object_unref (*socket); g_object_unref (*address); } g_object_unref (enumerator); - g_print ("Connected to %s\n", - socket_address_to_string (*address)); + socket_string = socket_address_to_string (*address); + g_print ("Connected to %s\n", socket_string); + g_free (socket_string); src_address = g_socket_get_local_address (*socket, error); if (!src_address) @@ -190,8 +203,9 @@ make_connection (const char *argument, return FALSE; } - g_print ("local address: %s\n", - socket_address_to_string (src_address)); + socket_string = socket_address_to_string (src_address); + g_print ("local address: %s\n", socket_string); + g_free (socket_string); g_object_unref (src_address); if (use_udp)