summaryrefslogtreecommitdiff
path: root/gio/tests
diff options
context:
space:
mode:
authorPatrick Griffis <pgriffis@igalia.com>2021-04-19 12:17:58 -0500
committerPatrick Griffis <pgriffis@igalia.com>2021-12-22 22:23:00 -0600
commite11e4e34c94a8da65eee03eca89380f135173b89 (patch)
tree7d1ea435354fecfa91bdda32cc47ac3a46a8a8ad /gio/tests
parenteaa52ddfc5b201377d5df3ef5b735396c5433bf3 (diff)
downloadglib-pgriffis/wip/resolver-https.tar.gz
gresolver: Add support for the HTTPS DNS typepgriffis/wip/resolver-https
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.
Diffstat (limited to 'gio/tests')
-rw-r--r--gio/tests/gresolver.c347
-rw-r--r--gio/tests/meson.build1
-rw-r--r--gio/tests/resolver.c58
3 files changed, 404 insertions, 2 deletions
diff --git a/gio/tests/gresolver.c b/gio/tests/gresolver.c
new file mode 100644
index 000000000..a81f01681
--- /dev/null
+++ b/gio/tests/gresolver.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2021 Igalia S.L.
+ *
+ * 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 library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Patrick Griffis <pgriffis@igalia.com>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <gio/gnetworking.h>
+
+#define GIO_COMPILATION
+#include "gthreadedresolver.h"
+#undef GIO_COMPILATION
+
+#ifdef HAVE_DN_COMP
+static void
+dns_builder_add_uint8 (GByteArray *builder,
+ guint8 value)
+{
+ g_byte_array_append (builder, &value, 1);
+}
+
+static void
+dns_builder_add_uint16 (GByteArray *builder,
+ guint16 value)
+{
+ dns_builder_add_uint8 (builder, (value >> 8) & 0xFF);
+ dns_builder_add_uint8 (builder, (value) & 0xFF);
+}
+
+static void
+dns_builder_add_uint32 (GByteArray *builder,
+ guint32 value)
+{
+ dns_builder_add_uint8 (builder, (value >> 24) & 0xFF);
+ dns_builder_add_uint8 (builder, (value >> 16) & 0xFF);
+ dns_builder_add_uint8 (builder, (value >> 8) & 0xFF);
+ dns_builder_add_uint8 (builder, (value) & 0xFF);
+}
+
+static void
+dns_builder_add_length_prefixed_string (GByteArray *builder,
+ const char *string)
+{
+ guint8 length;
+
+ g_assert (strlen (string) <= G_MAXUINT8);
+
+ length = (guint8) strlen (string);
+ dns_builder_add_uint8 (builder, length);
+
+ /* Don't include trailing NUL */
+ g_byte_array_append (builder, (const guchar *)string, length);
+}
+
+static void
+dns_builder_add_domain (GByteArray *builder,
+ const char *string)
+{
+ int ret;
+ guchar buffer[256];
+
+ ret = dn_comp (string, buffer, sizeof (buffer), NULL, NULL);
+ g_assert (ret != -1);
+
+ g_byte_array_append (builder, buffer, ret);
+}
+
+static void
+dns_builder_add_answer_data (GByteArray *builder,
+ GByteArray *answer)
+{
+ dns_builder_add_uint16 (builder, answer->len); /* rdlength */
+ g_byte_array_append (builder, answer->data, answer->len);
+}
+#endif /* HAVE_DN_COMP */
+
+typedef struct
+{
+ GByteArray *answer;
+} TestData;
+
+static void
+dns_test_setup (TestData *fixture,
+ gconstpointer user_data)
+{
+ fixture->answer = g_byte_array_sized_new (2046);
+
+#ifdef HAVE_DN_COMP
+ /* Start with a header, we ignore everything except ancount.
+ https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1 */
+ dns_builder_add_uint16 (fixture->answer, 0); /* ID */
+ dns_builder_add_uint16 (fixture->answer, 0); /* |QR| Opcode |AA|TC|RD|RA| Z | RCODE | */
+ dns_builder_add_uint16 (fixture->answer, 0); /* QDCOUNT */
+ dns_builder_add_uint16 (fixture->answer, 1); /* ANCOUNT (1 answer) */
+ dns_builder_add_uint16 (fixture->answer, 0); /* NSCOUNT */
+ dns_builder_add_uint16 (fixture->answer, 0); /* ARCOUNT */
+
+ /* Answer section */
+ dns_builder_add_domain (fixture->answer, "example.org");
+ dns_builder_add_uint16 (fixture->answer, 65); /* type=HTTPS */
+ dns_builder_add_uint16 (fixture->answer, 1); /* qclass=C_IN */
+ dns_builder_add_uint32 (fixture->answer, 0); /* ttl (ignored) */
+ /* Next one will be rdlength which is test specific. */
+#endif
+}
+
+static void
+dns_test_teardown (TestData *fixture,
+ gconstpointer user_data)
+{
+ g_byte_array_free (fixture->answer, TRUE);
+}
+
+static void
+test_https_alias (TestData *fixture,
+ gconstpointer user_data)
+{
+#ifndef HAVE_DN_COMP
+ g_test_skip ("The dn_comp() function was not available.");
+ return;
+#else
+ GList *records;
+ GByteArray *https_answer = g_byte_array_sized_new (1024);
+ guint16 priority;
+ const char *alias;
+ GError *error = NULL;
+
+ dns_builder_add_uint16 (https_answer, 0); /* priority */
+ dns_builder_add_length_prefixed_string (https_answer, "foo.example.org"); /* alias target */
+
+ dns_builder_add_answer_data (fixture->answer, https_answer);
+ records = g_resolver_records_from_res_query ("example.org", 65,
+ (guchar*)fixture->answer->data,
+ fixture->answer->len,
+ 0, &error);
+
+ g_assert_no_error (error);
+ g_assert_cmpuint (g_list_length (records), ==, 1);
+ g_variant_get (records->data, "(q&sa{sv})", &priority, &alias, NULL);
+
+ g_assert_cmpuint (priority, ==, 0);
+ g_assert_cmpstr (alias, ==, "foo.example.org.");
+
+ g_list_free_full (records, (GDestroyNotify)g_variant_unref);
+ g_byte_array_free (https_answer, TRUE);
+#endif /* HAVE_DN_COMP */
+}
+
+static void
+test_https_service (TestData *fixture,
+ gconstpointer user_data)
+{
+#ifndef HAVE_DN_COMP
+ g_test_skip ("The dn_comp() function was not available.");
+ return;
+#else
+ GList *records;
+ GByteArray *https_answer = g_byte_array_sized_new (1024);
+ guint16 priority;
+ const char *target;
+ GVariant *params;
+ guint16 port;
+ GVariantDict *dict;
+ GError *error = NULL;
+ const char **alpn, **mandatory;
+ GVariant *key123_variant;
+ const char *key123;
+ gsize key123_size;
+
+ dns_builder_add_uint16 (https_answer, 1); /* priority */
+ dns_builder_add_length_prefixed_string (https_answer, ""); /* target */
+
+ dns_builder_add_uint16 (https_answer, 3); /* SVCB key "port" */
+ dns_builder_add_uint16 (https_answer, 2); /* Value length */
+ dns_builder_add_uint16 (https_answer, 4443); /* SVCB value */
+
+ dns_builder_add_uint16 (https_answer, 0); /* SVCB key "mandatory" */
+ dns_builder_add_uint16 (https_answer, 2); /* Value length */
+ dns_builder_add_uint16 (https_answer, 3); /* SVCB value */
+
+ dns_builder_add_uint16 (https_answer, 1); /* SVCB key "alpn" */
+ dns_builder_add_uint16 (https_answer, 3); /* Value length */
+ dns_builder_add_length_prefixed_string (https_answer, "h2");
+
+ dns_builder_add_uint16 (https_answer, 123); /* SVCB key "key123" */
+ dns_builder_add_uint16 (https_answer, 4); /* Value length */
+ dns_builder_add_length_prefixed_string (https_answer, "idk");
+
+ dns_builder_add_answer_data (fixture->answer, https_answer);
+ records = g_resolver_records_from_res_query ("example.org", 65,
+ (guchar *)fixture->answer->data,
+ fixture->answer->len,
+ 0, &error);
+
+ g_assert_no_error (error);
+ g_assert_cmpuint (g_list_length (records), ==, 1);
+ g_variant_get (records->data, "(q&s@a{sv})", &priority, &target, &params);
+
+ g_assert_cmpuint (priority, ==, 1);
+ g_assert_cmpstr (target, ==, ".");
+ g_assert_true (g_variant_is_of_type (params, G_VARIANT_TYPE_VARDICT));
+ dict = g_variant_dict_new (params);
+ g_assert_true (g_variant_dict_lookup (dict, "port", "q", &port));
+ g_assert_cmpuint (port, ==, 4443);
+ g_assert_true (g_variant_dict_lookup (dict, "alpn", "^a&s", &alpn));
+ g_assert_cmpstr (alpn[0], ==, "h2");
+ g_assert_true (g_variant_dict_lookup (dict, "mandatory", "^a&s", &mandatory));
+ g_assert_cmpstr (mandatory[0], ==, "port");
+ g_assert_true (g_variant_dict_lookup (dict, "key123", "@ay", &key123_variant));
+ key123 = g_variant_get_fixed_array (key123_variant, &key123_size, 1);
+ g_assert_true (!strncmp (key123 + 1, "idk", key123_size - 1));
+
+ g_variant_dict_unref (dict);
+ g_variant_unref (params);
+ g_list_free_full (records, (GDestroyNotify)g_variant_unref);
+ g_byte_array_free (https_answer, TRUE);
+#endif
+}
+
+static void
+test_https_invalid_1 (TestData *fixture,
+ gconstpointer user_data)
+{
+#ifndef HAVE_DN_COMP
+ g_test_skip ("The dn_comp() function was not available.");
+ return;
+#else
+ GList *records;
+ GByteArray *https_answer = g_byte_array_sized_new (1024);
+ GError *error = NULL;
+
+ dns_builder_add_uint16 (https_answer, 1); /* priority */
+ dns_builder_add_length_prefixed_string (https_answer, ""); /* target */
+
+ /* Invalid value length is too long and will be caught. */
+ dns_builder_add_uint16 (https_answer, 3); /* SVCB key "port" */
+ dns_builder_add_uint16 (https_answer, 100); /* Value length */
+ dns_builder_add_uint16 (https_answer, 4443); /* SVCB value */
+
+ dns_builder_add_answer_data (fixture->answer, https_answer);
+ records = g_resolver_records_from_res_query ("example.org", 65,
+ (guchar *)fixture->answer->data,
+ fixture->answer->len,
+ 0, &error);
+
+ g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL);
+ g_assert_null (records);
+
+ g_error_free (error);
+ g_byte_array_free (https_answer, TRUE);
+#endif
+}
+
+static void
+test_https_invalid_2 (TestData *fixture,
+ gconstpointer user_data)
+{
+#ifndef HAVE_DN_COMP
+ g_test_skip ("The dn_comp() function was not available.");
+ return;
+#else
+ GList *records;
+ GByteArray *https_answer = g_byte_array_sized_new (1024);
+ GError *error = NULL;
+
+ dns_builder_add_uint16 (https_answer, 1); /* priority */
+ dns_builder_add_length_prefixed_string (https_answer, ""); /* target */
+
+ /* Within a SVCB value, having invalid length will also be caught. */
+ dns_builder_add_uint16 (https_answer, 5); /* SVCB key "ECH" */
+ dns_builder_add_uint16 (https_answer, 2); /* Value length */
+ dns_builder_add_uint16 (https_answer, 1000); /* SVCB value (prefixed string, invalid length) */
+
+ dns_builder_add_answer_data (fixture->answer, https_answer);
+ records = g_resolver_records_from_res_query ("example.org", 65,
+ (guchar *)fixture->answer->data,
+ fixture->answer->len,
+ 0, &error);
+
+ g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL);
+ g_assert_null (records);
+
+ g_error_free (error);
+ g_byte_array_free (https_answer, TRUE);
+#endif
+}
+
+static void
+test_https_invalid_3 (TestData *fixture,
+ gconstpointer user_data)
+{
+#ifndef HAVE_DN_COMP
+ g_test_skip ("The dn_comp() function was not available.");
+ return;
+#else
+ GList *records;
+ GByteArray *https_answer = g_byte_array_sized_new (1024);
+ GError *error = NULL;
+
+ dns_builder_add_uint16 (https_answer, 0); /* priority */
+
+ /* Creating an invalid target string will be caught. */
+ dns_builder_add_uint8 (https_answer, 100);
+ g_byte_array_append (https_answer, (const guchar *)"test", 4);
+
+ dns_builder_add_answer_data (fixture->answer, https_answer);
+ records = g_resolver_records_from_res_query ("example.org", 65,
+ (guchar *)fixture->answer->data,
+ fixture->answer->len,
+ 0, &error);
+
+ g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL);
+ g_assert_null (records);
+
+ g_error_free (error);
+ g_byte_array_free (https_answer, TRUE);
+#endif
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add ("/gresolver/https/alias", TestData, NULL, dns_test_setup, test_https_alias, dns_test_teardown);
+ g_test_add ("/gresolver/https/service", TestData, NULL, dns_test_setup, test_https_service, dns_test_teardown);
+ g_test_add ("/gresolver/https/invalid/1", TestData, NULL, dns_test_setup, test_https_invalid_1, dns_test_teardown);
+ g_test_add ("/gresolver/https/invalid/2", TestData, NULL, dns_test_setup, test_https_invalid_2, dns_test_teardown);
+ g_test_add ("/gresolver/https/invalid/3", TestData, NULL, dns_test_setup, test_https_invalid_3, dns_test_teardown);
+
+ return g_test_run ();
+}
diff --git a/gio/tests/meson.build b/gio/tests/meson.build
index f8cf7b655..88607510c 100644
--- a/gio/tests/meson.build
+++ b/gio/tests/meson.build
@@ -67,6 +67,7 @@ gio_tests = {
'g-icon' : {},
'gdbus-addresses' : {},
'gdbus-message' : {},
+ 'gresolver' : {'dependencies' : [network_libs]},
'inet-address' : {},
'io-stream' : {},
'memory-input-stream' : {},
diff --git a/gio/tests/resolver.c b/gio/tests/resolver.c
index 6e0c4d73b..2052c9c32 100644
--- a/gio/tests/resolver.c
+++ b/gio/tests/resolver.c
@@ -308,6 +308,52 @@ print_resolved_ns (const char *rrname,
}
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, &params);
+
+ 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 (params);
+ g_variant_unref (t->data);
+ }
+ g_list_free (records);
+ }
+ printf ("\n");
+
+ done_lookup ();
+ G_UNLOCK (response);
+}
+
+static void
lookup_one_sync (const char *arg)
{
GError *error = NULL;
@@ -331,6 +377,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 +498,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 +711,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 +725,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,
};