Merge branch 'pgriffis/ipv6-scope-id' into 'main'

Fix IPv6 scope-id from DNS responses being lost

See merge request GNOME/glib!4676
This commit is contained in:
Philip Withnall
2025-07-09 14:00:18 +00:00
8 changed files with 273 additions and 88 deletions

View File

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

View File

@@ -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__ */

View File

@@ -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.
*/
iaddr = g_inet_address_new_from_string (address);
if (!iaddr)
return NULL;
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;
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);
}

View File

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

View File

@@ -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");

View File

@@ -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 },

View File

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

View File

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