mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-12-10 10:33:42 +01:00
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.
This commit is contained in:
@@ -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. Since: 2.72
|
||||
*
|
||||
* The type of record that g_resolver_lookup_records() or
|
||||
* g_resolver_lookup_records_async() should retrieve. The records are returned
|
||||
@@ -754,6 +755,19 @@ 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 of protocol names
|
||||
* - `no-default-alpn`: an empty string if present
|
||||
* - `port`: uint16 (0-65535)
|
||||
* - `ipv4hint`: array of strings of addresses
|
||||
* - `ipv6hint`: array of strings of addresses
|
||||
* - `ech`: byte array of data containing an ECHConfigList defined [here](https://datatracker.ietf.org/doc/draft-ietf-tls-esni/)
|
||||
* - `mandatory`: array of strings matching these keys
|
||||
* - `keyN`: for unknown keys, N is the numeric value of the key type, the value is a byte array of unparsed data
|
||||
* See the [RFC](https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/) for more information.
|
||||
*
|
||||
* Since: 2.34
|
||||
*/
|
||||
typedef enum {
|
||||
@@ -761,7 +775,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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,14 @@ if host_system != 'windows'
|
||||
endif
|
||||
endif
|
||||
|
||||
# dn_comp()
|
||||
if cc.links('''#include <resolv.h>
|
||||
int main (int argc, char ** argv) {
|
||||
return dn_comp(NULL, NULL, 0, NULL, NULL) == -1;
|
||||
} ''', args : network_args, name : 'dn_comp()')
|
||||
glib_conf.set('HAVE_DN_COMP', 1)
|
||||
endif
|
||||
|
||||
# res_nclose()
|
||||
if cc.links('''#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
347
gio/tests/gresolver.c
Normal file
347
gio/tests/gresolver.c
Normal file
@@ -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, ¶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 ();
|
||||
}
|
||||
@@ -67,6 +67,7 @@ gio_tests = {
|
||||
'g-icon' : {},
|
||||
'gdbus-addresses' : {},
|
||||
'gdbus-message' : {},
|
||||
'gresolver' : {'dependencies' : [network_libs]},
|
||||
'inet-address' : {},
|
||||
'io-stream' : {},
|
||||
'memory-input-stream' : {},
|
||||
|
||||
@@ -307,6 +307,52 @@ 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 (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)
|
||||
{
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user