diff options
Diffstat (limited to 'gio/gthreadedresolver.c')
-rw-r--r-- | gio/gthreadedresolver.c | 351 |
1 files changed, 350 insertions, 1 deletions
diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c index 48545d6ad..4c9fdf60f 100644 --- a/gio/gthreadedresolver.c +++ b/gio/gthreadedresolver.c @@ -394,6 +394,10 @@ lookup_by_address_finish (GResolver *resolver, #if defined(G_OS_UNIX) +#ifndef T_HTTPS +#define T_HTTPS 65 +#endif + #if defined __BIONIC__ && !defined BIND_4_COMPAT /* Copy from bionic/libc/private/arpa_nameser_compat.h * and bionic/libc/private/arpa_nameser.h */ @@ -633,6 +637,330 @@ parse_res_txt (const guint8 *answer, return record; } +/* Defined in Section 6 (https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/) */ +typedef enum { + SVCB_KEY_MANDATORY = 0, + SVCB_KEY_ALPN, + SVCB_KEY_NO_DEFAULT_ALPN, + SVCB_KEY_PORT, + SVCB_KEY_IPV4HINT, + SVCB_KEY_ECH, + SVCB_KEY_IPV6HINT +} SVCBKey; + +static char * +svcb_key_to_string (SVCBKey key) +{ + switch (key) + { + case SVCB_KEY_MANDATORY: + return g_strdup ("mandatory"); + case SVCB_KEY_ALPN: + return g_strdup ("alpn"); + case SVCB_KEY_NO_DEFAULT_ALPN: + return g_strdup ("no-default-alpn"); + case SVCB_KEY_PORT: + return g_strdup ("port"); + case SVCB_KEY_IPV4HINT: + return g_strdup ("ipv4hint"); + case SVCB_KEY_ECH: + return g_strdup ("ech"); + case SVCB_KEY_IPV6HINT: + return g_strdup ("ipv6hint"); + default: + return g_strdup_printf ("key%u", key); + } +} + +static gchar * +get_uncompressed_domain (const guint8 *src, + const guint8 *end, + const guint8 **out) +{ + /* Length is same as input (all length octets replaced with '.') plus trailing NUL */ + GString *target = g_string_sized_new (end - src + 1); + guint n_labels = 0; + +#define MAX_LABEL_LENGTH 63 +#define MAX_LABEL_NUMBER 255 + + /* Defined in RFC 1035 section 3.1 */ + do + { + guint8 length = *(src++); + + if (length > (gsize) (end - src) + || length > MAX_LABEL_LENGTH + || n_labels == MAX_LABEL_NUMBER) + { + g_debug ("Received invalid length compressed domain."); + return g_string_free (target, TRUE); + } + + /* End of string */ + if (length == 0) + { + if (target->len == 0) + g_string_append_c (target, '.'); + break; + } + + g_string_append_len (target, (char *)src, length); + g_string_append_c (target, '.'); + src += length; + n_labels++; + } + while (src < end); + + *out = src; + return g_string_free (target, FALSE); +} + +static GVariant * +get_svcb_ipv4_hint_value (const guint8 *src, + guint16 length) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY); + + for (; length >= 4; length -= 4, src += 4) + { + char buffer[INET_ADDRSTRLEN]; + + if (inet_ntop (AF_INET, src, buffer, sizeof (buffer))) + g_variant_builder_add_value (&builder, g_variant_new_string (buffer)); + } + + if (length != 0) + { + g_debug ("Received invalid length ipv4hint."); + g_variant_builder_clear (&builder); + return NULL; + } + + return g_variant_builder_end (&builder); +} + +static GVariant * +get_svcb_ipv6_hint_value (const guint8 *src, + guint16 length) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY); + +#ifdef HAVE_IPV6 + for (; length >= 16; length -= 16, src += 16) + { + char buffer[INET6_ADDRSTRLEN]; + + if (inet_ntop (AF_INET6, src, buffer, sizeof (buffer))) + g_variant_builder_add_value (&builder, g_variant_new_string (buffer)); + } + + if (length != 0) + { + g_debug ("Received invalid length ipv6hint."); + g_variant_builder_clear (&builder); + return NULL; + } + +#endif + + return g_variant_builder_end (&builder); +} + +static GVariant * +get_svcb_alpn_value (const guint8 *src, + guint16 length) +{ + GVariantBuilder builder; + GString *alpn_id = NULL; + guchar alpn_id_remaining = 0; + + /* Format defined in Section 6.1 */ + g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY); + + /* length prefixed strings */ + for (; length > 0; src++, length--) + { + guchar c = *src; + + if (alpn_id != NULL && alpn_id_remaining == 0) + { + g_variant_builder_add_value (&builder, + g_variant_new_take_string (g_string_free (g_steal_pointer (&alpn_id), FALSE))); + /* This drops through as the current char is the new length */ + } + + if (alpn_id == NULL) + { + /* First byte is length */ + alpn_id_remaining = c; + alpn_id = g_string_sized_new (((gsize)c) + 1); + continue; + } + + g_string_append_c (alpn_id, c); + alpn_id_remaining--; + } + + /* Trailing value */ + if (alpn_id != NULL) + { + g_variant_builder_add_value (&builder, + g_variant_new_take_string (g_string_free (g_steal_pointer (&alpn_id), FALSE))); + } + + return g_variant_builder_end (&builder); +} + +static GVariant * +get_svcb_mandatory_value (const guint8 *src, + guint16 length) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY); + + for (; length > 1; length -= 2) + { + guint16 key; + gchar *key_str; + + GETSHORT (key, src); + key_str = svcb_key_to_string (key); + + g_variant_builder_add_value (&builder, + g_variant_new_take_string (key_str)); + } + + if (length != 0) + { + g_debug ("Received invalid length mandatory."); + g_variant_builder_clear (&builder); + return NULL; + } + + return g_variant_builder_end (&builder); +} + +static gboolean +get_svcb_value (SVCBKey key, + guint16 value_length, + const guint8 *src, + GVariant **value) +{ + switch (key) + { + case SVCB_KEY_MANDATORY: + *value = get_svcb_mandatory_value (src, value_length); + return *value != NULL; + case SVCB_KEY_ALPN: + *value = get_svcb_alpn_value (src, value_length); + return TRUE; + case SVCB_KEY_PORT: + { + guint16 port; + GETSHORT (port, src); + /* 0-65535 is valid */ + *value = g_variant_new_uint16 (port); + return TRUE; + } + case SVCB_KEY_ECH: + { + guint8 length; + GETSHORT (length, src); + + if (length > (value_length - 1)) + return FALSE; + + *value = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, src, length, 1); + return TRUE; + } + case SVCB_KEY_IPV4HINT: + *value = get_svcb_ipv4_hint_value (src, value_length); + return *value != NULL; + case SVCB_KEY_IPV6HINT: + *value = get_svcb_ipv6_hint_value (src, value_length); + return *value != NULL; + case SVCB_KEY_NO_DEFAULT_ALPN: + *value = g_variant_new_string (""); + return TRUE; + default: + { + *value = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, src, value_length, 1); + return TRUE; + } + } +} + +static GVariant *parse_res_https (const guint8 *answer, + const guint8 *end, + const guint8 **p, + GError **error) +{ + GVariant *variant; + gchar *target; + guint16 priority; + GVariantBuilder params_builder; + + /* This response has two forms: + * if priority is 0 it is AliasForm and the string is + * the alias target with nothing else. + * otherwise it is ServiceForm which is an alternative endpoint + * and extra params are included. + * + * https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/ + */ + + GETSHORT (priority, *p); + target = get_uncompressed_domain (*p, end, p); + + if (target == NULL) + goto invalid_input; + + /* For AliasForm we just include an empty dict. */ + g_variant_builder_init (¶ms_builder, G_VARIANT_TYPE ("a{sv}")); + if (priority != 0) + { + while (*p < end) + { + gchar *key_str; + GVariant *value = NULL; + guint16 key; + guint16 value_length; + + GETSHORT (key, *p); + GETSHORT (value_length, *p); + + if (value_length > (gsize) (end - *p)) + goto invalid_input; + + key_str = svcb_key_to_string (key); + + if (!get_svcb_value (key, value_length, *p, &value)) + goto invalid_input; + + g_variant_builder_add (¶ms_builder, "{sv}", key_str, value); + + *p += value_length; + g_free (key_str); + } + } + + variant = g_variant_new ("(qsa{sv})", priority, target, ¶ms_builder); + g_free (target); + return variant; + +invalid_input: + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, "Invalid HTTPS response data"); + *p = end; + g_free (target); + return NULL; +} + static gint g_resolver_record_type_to_rrtype (GResolverRecordType type) { @@ -648,6 +976,8 @@ g_resolver_record_type_to_rrtype (GResolverRecordType type) return T_NS; case G_RESOLVER_RECORD_MX: return T_MX; + case G_RESOLVER_RECORD_HTTPS: + return T_HTTPS; } g_return_val_if_reached (-1); } @@ -667,6 +997,7 @@ g_resolver_records_from_res_query (const gchar *rrname, const HEADER *header; GList *records; GVariant *record; + GError *parsing_error = NULL; if (len <= 0) { @@ -739,6 +1070,9 @@ g_resolver_records_from_res_query (const gchar *rrname, case T_TXT: record = parse_res_txt (answer, p + rdlength, &p); break; + case T_HTTPS: + record = parse_res_https (answer, p + rdlength, &p, &parsing_error); + break; default: g_warn_if_reached (); record = NULL; @@ -747,9 +1081,18 @@ g_resolver_records_from_res_query (const gchar *rrname, if (record != NULL) records = g_list_prepend (records, record); + + if (parsing_error) + break; } - if (records == NULL) + if (parsing_error) + { + g_propagate_prefixed_error (error, parsing_error, _("Failed to parse DNS response for ā%sā: "), rrname); + g_list_free_full (records, (GDestroyNotify)g_variant_unref); + return NULL; + } + else if (!records) { g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND, _("No DNS record of the requested type for ā%sā"), rrname); @@ -762,6 +1105,10 @@ g_resolver_records_from_res_query (const gchar *rrname, #elif defined(G_OS_WIN32) +#ifndef DNS_TYPE_HTTPS +#define DNS_TYPE_HTTPS 65 +#endif + static GVariant * parse_dns_srv (DNS_RECORD *rec) { @@ -830,6 +1177,8 @@ g_resolver_record_type_to_dnstype (GResolverRecordType type) return DNS_TYPE_NS; case G_RESOLVER_RECORD_MX: return DNS_TYPE_MX; + case G_RESOLVER_RECORD_HTTPS: + return DNS_TYPE_HTTPS; } g_return_val_if_reached (-1); } |