mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-12-11 19:13:02 +01:00
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.
348 lines
12 KiB
C
348 lines
12 KiB
C
/*
|
|
* 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, ¶ms);
|
|
|
|
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 ();
|
|
}
|