[glib/pgriffis/wip/resolver-https] gresolver: Add support for the HTTPS DNS type
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/pgriffis/wip/resolver-https] gresolver: Add support for the HTTPS DNS type
- Date: Fri, 4 Jun 2021 18:53:19 +0000 (UTC)
commit 26a55665527c56bfedfa1b52bc77f053a3d3ee45
Author: Patrick Griffis <pgriffis igalia com>
Date: Mon Apr 19 12:17:58 2021 -0500
gresolver: Add support for the HTTPS DNS type
This is a new type used to request information about an HTTPS
service and contains information like alternative hosts, ports,
EncryptedClientHello keys, and protocols supported.
gio/gioenums.h | 18 +++-
gio/gthreadedresolver.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++
gio/tests/resolver.c | 57 +++++++++-
3 files changed, 354 insertions(+), 3 deletions(-)
---
diff --git a/gio/gioenums.h b/gio/gioenums.h
index d81ada416..5af655947 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -722,6 +722,7 @@ typedef enum {
* @G_RESOLVER_RECORD_TXT: look up DNS TXT records for a name
* @G_RESOLVER_RECORD_SOA: look up DNS SOA records for a zone
* @G_RESOLVER_RECORD_NS: look up DNS NS records for a domain
+ * @G_RESOLVER_RECORD_HTTPS: look up DNS HTTPS records for a zone
*
* The type of record that g_resolver_lookup_records() or
* g_resolver_lookup_records_async() should retrieve. The records are returned
@@ -754,6 +755,20 @@ typedef enum {
* %G_RESOLVER_RECORD_NS records are returned as variants with the signature
* `(s)`, representing a string of the hostname of the name server.
*
+ * %G_RESOLVER_RECORD_HTTPS records are returned as variants with the signature
+ * `(qsa{sv})`, representing the priority, target host, and params of the domain.
+ * The keys of the params dictionary are:
+ * "alpn": array of strings
+ * "no-default-alpn": an empty string
+ * "port": uint16
+ * "ipv4hint": array of strings of addresses
+ * "ipv6hint": array of strings of addresses
+ * "ech": string of base64 data
+ * "mandatory": array of strings matching these keys
+ * "keyNNNN": for unknown keys, NNNN is the key number, the value is a bytestring of unparsed data
+ * See the [RFC](https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/) for more information.
+ * Since: 2.70
+ *
* Since: 2.34
*/
typedef enum {
@@ -761,7 +776,8 @@ typedef enum {
G_RESOLVER_RECORD_MX,
G_RESOLVER_RECORD_TXT,
G_RESOLVER_RECORD_SOA,
- G_RESOLVER_RECORD_NS
+ G_RESOLVER_RECORD_NS,
+ G_RESOLVER_RECORD_HTTPS
} GResolverRecordType;
/**
diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c
index 93794b5b3..ecf95b9dc 100644
--- a/gio/gthreadedresolver.c
+++ b/gio/gthreadedresolver.c
@@ -633,6 +633,283 @@ parse_res_txt (guchar *answer,
return record;
}
+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 (guchar *src, guchar *end, guchar **out)
+{
+ GString *target = g_string_new (NULL);
+
+ /* Defined in RFC 1035 section 3.1 */
+ do
+ {
+ guint16 length = *(src++);
+
+ if (length > (gsize) (end - src))
+ break;
+
+ /* End of string */
+ if (length == 0)
+ {
+ if (target->len == 0)
+ g_string_append_c (target, '.');
+ break;
+ }
+
+ g_string_append_len (target, src, length);
+ g_string_append_c (target, '.');
+ src += length;
+ }
+ while (src < end);
+
+ *out = src;
+ return g_string_free (target, FALSE);
+}
+
+static GVariant *
+get_svcb_ipv4_hint_value (guchar *src, guchar *end, 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));
+ }
+
+ return g_variant_builder_end (&builder);
+}
+
+static GVariant *
+get_svcb_ipv6_hint_value (guchar *src, guchar *end, 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));
+ }
+#endif
+
+ return g_variant_builder_end (&builder);
+}
+
+static GVariant *
+get_svcb_alpn_value (guchar *src, guchar *end, guint16 length)
+{
+ GVariantBuilder builder;
+ GString *alpn_id = NULL;
+ guchar alpn_id_remaning = 0;
+
+ /* Format defined in Section 6.1 */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+
+ /* length prefixed strings */
+ for (; src <= end && length; src++, length--)
+ {
+ guchar c = *src;
+
+ if (alpn_id && !alpn_id_remaning)
+ {
+ 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)
+ {
+ /* First byte is length */
+ alpn_id_remaning = c;
+ alpn_id = g_string_sized_new (c + 1);
+ continue;
+ }
+
+ g_string_append_c (alpn_id, c);
+ alpn_id_remaning--;
+ }
+
+ /* Trailing value */
+ if (alpn_id)
+ {
+ 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 (guchar *src, guchar *end, guint16 length)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+
+ for (; length; 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));
+ }
+
+ return g_variant_builder_end (&builder);
+}
+
+static gboolean
+get_svcb_value (SVCBKey key, guint16 value_length, guchar *src, guchar *end, GVariant **value)
+{
+ switch (key)
+ {
+ case SVCB_KEY_MANDATORY:
+ *value = get_svcb_mandatory_value (src, end, value_length);
+ return TRUE;
+ case SVCB_KEY_ALPN:
+ *value = get_svcb_alpn_value (src, end, value_length);
+ return TRUE;
+ case SVCB_KEY_PORT:
+ {
+ guint16 port;
+ GETSHORT (port, src);
+ *value = g_variant_new_uint16 (port);
+ return TRUE;
+ }
+ case SVCB_KEY_ECH:
+ {
+ guint8 length;
+ gchar *base64_str;
+ GETSHORT (length, src);
+
+ if (length > (gsize) (end - src))
+ {
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ base64_str = g_base64_encode (src, length);
+ *value = g_variant_new_take_string (base64_str);
+ return TRUE;
+ }
+ case SVCB_KEY_IPV4HINT:
+ *value = get_svcb_ipv4_hint_value (src, end, value_length);
+ return TRUE;
+ case SVCB_KEY_IPV6HINT:
+ *value = get_svcb_ipv6_hint_value (src, end, value_length);
+ return TRUE;
+ case SVCB_KEY_NO_DEFAULT_ALPN:
+ G_GNUC_FALLTHROUGH;
+ default:
+ {
+ gchar *string = g_strndup (src, value_length);
+ *value = g_variant_new_bytestring (string);
+ g_free (string);
+ return TRUE;
+ }
+ }
+}
+
+static GVariant *
+parse_res_https (guchar *answer,
+ guchar *end,
+ guchar **p)
+{
+ 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.
+ */
+
+ GETSHORT (priority, *p);
+ target = get_uncompressed_domain (*p, end, p);
+
+ /* 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))
+ {
+ g_warn_if_reached ();
+ *p = end;
+ break;
+ }
+
+ key_str = svcb_key_to_string (key);
+
+ if (get_svcb_value (key, value_length, *p, end, &value))
+ 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;
+}
+
static gint
g_resolver_record_type_to_rrtype (GResolverRecordType type)
{
@@ -648,6 +925,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 65;
}
g_return_val_if_reached (-1);
}
@@ -739,6 +1018,9 @@ g_resolver_records_from_res_query (const gchar *rrname,
case T_TXT:
record = parse_res_txt (answer, p + rdlength, &p);
break;
+ case 65:
+ record = parse_res_https (answer, p + rdlength, &p);
+ break;
default:
g_warn_if_reached ();
record = NULL;
diff --git a/gio/tests/resolver.c b/gio/tests/resolver.c
index 6e0c4d73b..e6b2330aa 100644
--- a/gio/tests/resolver.c
+++ b/gio/tests/resolver.c
@@ -307,6 +307,51 @@ print_resolved_ns (const char *rrname,
G_UNLOCK (response);
}
+static void
+print_resolved_https (const char *rrname,
+ GList *records,
+ GError *error)
+{
+ GList *t;
+
+ G_LOCK (response);
+ printf ("Zone: %s\n", rrname);
+ if (error)
+ {
+ printf ("Error: %s\n", error->message);
+ g_error_free (error);
+ }
+ else if (!records)
+ {
+ printf ("no HTTPS records\n");
+ }
+ else
+ {
+ for (t = records; t; t = t->next)
+ {
+ guint16 priority;
+ gchar *target, *params_str;
+ GVariant *params;
+
+ g_variant_get (t->data, "(qs@a{sv})", &priority, &target, ¶ms);
+
+ printf ("Priority: %u\nTarget: %s\n", priority, target);
+
+ params_str = g_variant_print (params, FALSE);
+ printf ("Params: %s\n", params_str);
+
+ g_free (params_str);
+ g_free (target);
+ g_variant_unref (t->data);
+ }
+ g_list_free (records);
+ }
+ printf ("\n");
+
+ done_lookup ();
+ G_UNLOCK (response);
+}
+
static void
lookup_one_sync (const char *arg)
{
@@ -331,6 +376,9 @@ lookup_one_sync (const char *arg)
case G_RESOLVER_RECORD_TXT:
print_resolved_txt (arg, records, error);
break;
+ case G_RESOLVER_RECORD_HTTPS:
+ print_resolved_https (arg, records, error);
+ break;
default:
g_warn_if_reached ();
break;
@@ -449,6 +497,9 @@ lookup_records_callback (GObject *source,
case G_RESOLVER_RECORD_TXT:
print_resolved_txt (arg, records, error);
break;
+ case G_RESOLVER_RECORD_HTTPS:
+ print_resolved_https (arg, records, error);
+ break;
default:
g_warn_if_reached ();
break;
@@ -659,9 +710,11 @@ record_type_arg (const gchar *option_name,
record_type = G_RESOLVER_RECORD_SOA;
} else if (g_ascii_strcasecmp (value, "NS") == 0) {
record_type = G_RESOLVER_RECORD_NS;
+ } else if (g_ascii_strcasecmp (value, "HTTPS") == 0) {
+ record_type = G_RESOLVER_RECORD_HTTPS;
} else {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
- "Specify MX, TXT, NS or SOA for the special record lookup types");
+ "Specify MX, TXT, NS, SOA, or HTTPS for the special record lookup types");
return FALSE;
}
@@ -671,7 +724,7 @@ record_type_arg (const gchar *option_name,
static const GOptionEntry option_entries[] = {
{ "synchronous", 's', 0, G_OPTION_ARG_NONE, &synchronous, "Synchronous connections", NULL },
{ "connectable", 'c', 0, G_OPTION_ARG_INT, &connectable_count, "Connectable count", "C" },
- { "special-type", 't', 0, G_OPTION_ARG_CALLBACK, record_type_arg, "Record type like MX, TXT, NS or SOA",
"RR" },
+ { "special-type", 't', 0, G_OPTION_ARG_CALLBACK, record_type_arg, "Record type like MX, TXT, NS, SOA, or
HTTPS", "RR" },
G_OPTION_ENTRY_NULL,
};
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]