network: Introduce sd_dns_resolver

This type will be used to represent a "designated resolver", and the
necessary info for communicating with it. Beyond and address endpoint,
we may need to know the dns transport, authenticated domain name, DoH
path, etc.
This commit is contained in:
Ronan Pigott
2024-02-23 17:12:46 -07:00
parent 427166c3b0
commit 1e2ead52e1
10 changed files with 479 additions and 40 deletions

View File

@@ -4,6 +4,8 @@
# include <gcrypt.h>
#endif
#include "dns-resolver-internal.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "escape.h"
@@ -2955,27 +2957,6 @@ const char* format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) {
return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
}
static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
[DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory",
[DNS_SVC_PARAM_KEY_ALPN] = "alpn",
[DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
[DNS_SVC_PARAM_KEY_PORT] = "port",
[DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint",
[DNS_SVC_PARAM_KEY_ECH] = "ech",
[DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint",
[DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath",
[DNS_SVC_PARAM_KEY_OHTTP] = "ohttp",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
const char *p = dns_svc_param_key_to_string(i);
if (p)
return p;
return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
}
static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
[DNS_PROTOCOL_DNS] = "dns",
[DNS_PROTOCOL_MDNS] = "mdns",

View File

@@ -362,25 +362,6 @@ const char* format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
const char* dns_protocol_to_string(DnsProtocol p) _const_;
DnsProtocol dns_protocol_from_string(const char *s) _pure_;
/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
enum {
DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 section 8 */
DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 section 7.1 */
DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 Section 7.1 */
DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 section 7.2 */
DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 section 7.3 */
DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */
DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 section 7.3 */
DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */
DNS_SVC_PARAM_KEY_OHTTP = 8,
_DNS_SVC_PARAM_KEY_MAX_DEFINED,
DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */
};
const char* dns_svc_param_key_to_string(int i) _const_;
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })

View File

@@ -2,6 +2,8 @@
#include <math.h>
#include "dns-resolver-internal.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "dns-type.h"

View File

@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <errno.h>
#include "sd-dns-resolver.h"
#include "macro.h"
#include "list.h"
#include "socket-netlink.h"
/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
enum {
DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 § 8 */
DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 § 7.1 */
DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 § 7.1 */
DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 § 7.2 */
DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 § 7.3 */
DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */
DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 § 7.3 */
DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */
DNS_SVC_PARAM_KEY_OHTTP = 8,
_DNS_SVC_PARAM_KEY_MAX_DEFINED,
DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */
};
const char* dns_svc_param_key_to_string(int i) _const_;
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
/* Represents a "designated resolver" */
/* typedef struct sd_dns_resolver sd_dns_resolver; */
struct sd_dns_resolver {
uint16_t priority;
char *auth_name;
int family;
union in_addr_union *addrs;
size_t n_addrs;
sd_dns_alpn_flags transports;
uint16_t port;
char *dohpath;
};
void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state);
int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers,
struct in_addr_full ***ret_addrs, size_t *ret_n_addrs);
int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b);
int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver);
int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolvers, char ***ret_names);
void sd_dns_resolver_done(sd_dns_resolver *res);
void dns_resolver_done_many(sd_dns_resolver *resolvers, size_t n);

View File

@@ -154,6 +154,7 @@ shared_sources = files(
'resize-fs.c',
'resolve-util.c',
'rm-rf.c',
'sd-dns-resolver.c',
'securebits-util.c',
'selinux-util.c',
'serialize.c',

View File

@@ -0,0 +1,362 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dns-resolver-internal.h"
#include "macro.h"
#include "unaligned.h"
#include "socket-netlink.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
void sd_dns_resolver_done(sd_dns_resolver *res) {
assert(res);
res->auth_name = mfree(res->auth_name);
res->addrs = mfree(res->addrs);
res->dohpath = mfree(res->dohpath);
}
sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res) {
if (!res)
return NULL;
sd_dns_resolver_done(res);
return mfree(res);
}
void dns_resolver_done_many(sd_dns_resolver resolvers[], size_t n) {
assert(resolvers || n == 0);
FOREACH_ARRAY(res, resolvers, n)
sd_dns_resolver_done(res);
free(resolvers);
}
int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b) {
return CMP(ASSERT_PTR(a)->priority, ASSERT_PTR(b)->priority);
}
int sd_dns_resolver_get_priority(sd_dns_resolver *res, uint16_t *ret_priority) {
assert_return(res, -EINVAL);
assert_return(ret_priority, -EINVAL);
*ret_priority = res->priority;
return 0;
}
int sd_dns_resolver_get_adn(sd_dns_resolver *res, const char **ret_adn) {
assert_return(res, -EINVAL);
assert_return(ret_adn, -EINVAL);
/* Without adn only Do53 can be supported */
if (!res->auth_name)
return -ENODATA;
*ret_adn = res->auth_name;
return 0;
}
int sd_dns_resolver_get_inet_addresses(sd_dns_resolver *res, const struct in_addr **ret_addrs, size_t
*ret_n_addrs) {
assert_return(res, -EINVAL);
assert_return(ret_addrs, -EINVAL);
assert_return(ret_n_addrs, -EINVAL);
assert_return(res->family == AF_INET, -EINVAL);
/* ADN-only mode has no addrs */
if (res->n_addrs == 0)
return -ENODATA;
struct in_addr *addrs = new(struct in_addr, res->n_addrs);
if (!addrs)
return -ENOMEM;
for (size_t i = 0; i < res->n_addrs; i++)
addrs[i] = res->addrs[i].in;
*ret_addrs = addrs;
*ret_n_addrs = res->n_addrs;
return 0;
}
int sd_dns_resolver_get_inet6_addresses(sd_dns_resolver *res, const struct in6_addr **ret_addrs, size_t
*ret_n_addrs) {
assert_return(res, -EINVAL);
assert_return(ret_addrs, -EINVAL);
assert_return(ret_n_addrs, -EINVAL);
assert_return(res->family == AF_INET6, -EINVAL);
/* ADN-only mode has no addrs */
if (res->n_addrs == 0)
return -ENODATA;
struct in6_addr *addrs = new(struct in6_addr, res->n_addrs);
if (!addrs)
return -ENOMEM;
for (size_t i = 0; i < res->n_addrs; i++)
addrs[i] = res->addrs[i].in6;
*ret_addrs = addrs;
*ret_n_addrs = res->n_addrs;
return 0;
}
int sd_dns_resolver_get_alpn(sd_dns_resolver *res, sd_dns_alpn_flags *ret_alpn) {
assert_return(res, -EINVAL);
assert_return(ret_alpn, -EINVAL);
/* ADN-only mode has no transports */
if (!res->transports)
return -ENODATA;
*ret_alpn = res->transports;
return 0;
}
int sd_dns_resolver_get_port(sd_dns_resolver *res, uint16_t *ret_port) {
assert_return(res, -EINVAL);
assert_return(ret_port, -EINVAL);
/* port = 0 is the default port */
*ret_port = res->port;
return 0;
}
int sd_dns_resolver_get_dohpath(sd_dns_resolver *res, const char **ret_dohpath) {
assert_return(res, -EINVAL);
assert_return(ret_dohpath, -EINVAL);
/* only present in DoH resolvers */
if (!res->dohpath)
return -ENODATA;
*ret_dohpath = res->dohpath;
return 0;
}
void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state) {
assert(res);
siphash24_compress_typesafe(res->priority, state);
siphash24_compress_typesafe(res->transports, state);
siphash24_compress_typesafe(res->port, state);
siphash24_compress_string(res->auth_name, state);
siphash24_compress_string(res->dohpath, state);
siphash24_compress_typesafe(res->family, state);
FOREACH_ARRAY(addr, res->addrs, res->n_addrs)
siphash24_compress_typesafe(*addr, state);
}
static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
[DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory",
[DNS_SVC_PARAM_KEY_ALPN] = "alpn",
[DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
[DNS_SVC_PARAM_KEY_PORT] = "port",
[DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint",
[DNS_SVC_PARAM_KEY_ECH] = "ech",
[DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint",
[DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath",
[DNS_SVC_PARAM_KEY_OHTTP] = "ohttp",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
assert(buf);
const char *p = dns_svc_param_key_to_string(i);
if (p)
return p;
return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
}
int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver) {
size_t offset = 0;
int r;
assert(option || len == 0);
assert(resolver);
sd_dns_alpn_flags transports = 0;
uint16_t port = 0;
_cleanup_free_ char *dohpath = NULL;
bool alpn = false;
uint16_t lastkey = 0;
while (offset < len) {
if (offset + 4 > len)
return -EBADMSG;
uint16_t key = unaligned_read_be16(&option[offset]);
offset += 2;
/* RFC9460 § 2.2 SvcParam MUST appear in strictly increasing numeric order */
if (lastkey >= key)
return -EBADMSG;
lastkey = key;
uint16_t plen = unaligned_read_be16(&option[offset]);
offset += 2;
if (offset + plen > len)
return -EBADMSG;
switch (key) {
/* Mandatory keys must be understood by the client, otherwise the record should be discarded.
* Automatic mandatory keys must not appear in the mandatory parameter, so these are all
* supplementary. We don't understand any supplementary keys, so if the mandatory parameter
* is present, we cannot use this record.*/
case DNS_SVC_PARAM_KEY_MANDATORY:
if (plen > 0)
return -EBADMSG;
break;
case DNS_SVC_PARAM_KEY_ALPN:
if (plen == 0)
return 0;
alpn = true; /* alpn is required. Record that the requirement is met. */
size_t poff = offset;
size_t pend = offset + plen;
while (poff < pend) {
uint8_t alen = option[poff++];
if (poff + alen > len)
return -EBADMSG;
if (memcmp_nn(&option[poff], alen, "dot", STRLEN("dot")) == 0)
transports |= SD_DNS_ALPN_DOT;
if (memcmp_nn(&option[poff], alen, "h2", STRLEN("h2")) == 0)
transports |= SD_DNS_ALPN_HTTP_2_TLS;
if (memcmp_nn(&option[poff], alen, "h3", STRLEN("h3")) == 0)
transports |= SD_DNS_ALPN_HTTP_3;
if (memcmp_nn(&option[poff], alen, "doq", STRLEN("doq")) == 0)
transports |= SD_DNS_ALPN_DOQ;
poff += alen;
}
if (poff != pend)
return -EBADMSG;
break;
case DNS_SVC_PARAM_KEY_PORT:
if (plen != sizeof(uint16_t))
return -EBADMSG;
port = unaligned_read_be16(&option[offset]);
/* Server should indicate default port by omitting this param */
if (port == 0)
return -EBADMSG;
break;
/* RFC9463 § 5.1 service params MUST NOT include ipv4hint/ipv6hint */
case DNS_SVC_PARAM_KEY_IPV4HINT:
case DNS_SVC_PARAM_KEY_IPV6HINT:
return -EBADMSG;
case DNS_SVC_PARAM_KEY_DOHPATH:
r = make_cstring((const char*) &option[offset], plen,
MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath);
if (ERRNO_IS_NEG_RESOURCE(r))
return r;
if (r < 0)
return -EBADMSG;
/* dohpath is a RFC6750 URI template. We don't parse these, but at least check the
* charset is reasonable. */
if (!in_charset(dohpath, URI_VALID "{}"))
return -EBADMSG;
break;
default:
break;
}
offset += plen;
}
if (offset != len)
return -EBADMSG;
/* DNR cannot be used without alpn */
if (!alpn)
return -EBADMSG;
/* RFC9461 § 5: If the [SvcParam] indicates support for HTTP, "dohpath" MUST be present. */
if (!dohpath && (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_2_TLS) ||
FLAGS_SET(transports, SD_DNS_ALPN_HTTP_3)))
return -EBADMSG;
/* No useful transports */
if (!transports)
return 0;
resolver->transports = transports;
resolver->port = port;
free_and_replace(resolver->dohpath, dohpath);
return transports;
}
int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers,
struct in_addr_full ***ret_addrs, size_t *ret_n_addrs) {
assert(ret_addrs);
assert(ret_n_addrs);
assert(resolvers || n_resolvers == 0);
struct in_addr_full **addrs = NULL;
size_t n = 0;
CLEANUP_ARRAY(addrs, n, in_addr_full_array_free);
FOREACH_ARRAY(res, resolvers, n_resolvers) {
if (!FLAGS_SET(res->transports, SD_DNS_ALPN_DOT))
continue;
FOREACH_ARRAY(i, res->addrs, res->n_addrs) {
_cleanup_(in_addr_full_freep) struct in_addr_full *addr = NULL;
int r;
addr = new0(struct in_addr_full, 1);
if (!addr)
return -ENOMEM;
if (!GREEDY_REALLOC(addrs, n+1))
return -ENOMEM;
r = free_and_strdup(&addr->server_name, res->auth_name);
if (r < 0)
return r;
addr->family = res->family;
addr->port = res->port;
addr->address = *i;
addrs[n++] = TAKE_PTR(addr);
}
}
*ret_addrs = TAKE_PTR(addrs);
*ret_n_addrs = n;
return n;
}
int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolvers, char ***ret_names) {
assert(ret_names);
int r;
_cleanup_strv_free_ char **names = NULL;
size_t len = 0;
struct in_addr_full **addrs = NULL;
size_t n = 0;
CLEANUP_ARRAY(addrs, n, in_addr_full_array_free);
r = dns_resolvers_to_dot_addrs(resolvers, n_resolvers, &addrs, &n);
if (r < 0)
return r;
FOREACH_ARRAY(addr, addrs, n) {
const char *name = in_addr_full_to_string(*addr);
if (!name)
return -ENOMEM;
r = strv_extend_with_size(&names, &len, name);
if (r < 0)
return r;
}
*ret_names = TAKE_PTR(names);
return len;
}

View File

@@ -347,6 +347,15 @@ struct in_addr_full *in_addr_full_free(struct in_addr_full *a) {
return mfree(a);
}
void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n) {
assert(addrs || n == 0);
FOREACH_ARRAY(a, addrs, n)
in_addr_full_freep(a);
free(addrs);
}
int in_addr_full_new(
int family,
const union in_addr_union *a,

View File

@@ -39,6 +39,7 @@ struct in_addr_full {
struct in_addr_full *in_addr_full_free(struct in_addr_full *a);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free);
void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n);
int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret);
int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret);
const char* in_addr_full_to_string(struct in_addr_full *a);

View File

@@ -35,6 +35,7 @@ _not_installed_headers = [
'sd-dhcp6-lease.h',
'sd-dhcp6-option.h',
'sd-dhcp6-protocol.h',
'sd-dns-resolver.h',
'sd-ipv4acd.h',
'sd-ipv4ll.h',
'sd-lldp-rx.h',

View File

@@ -0,0 +1,44 @@
#ifndef SD_DNS_RESOLVER_H
#define SD_DNS_RESOLVER_H
#include <errno.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdint.h>
#include "_sd-common.h"
_SD_BEGIN_DECLARATIONS;
typedef struct sd_dns_resolver sd_dns_resolver;
/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids */
typedef enum sd_dns_alpn_flags {
/* There isn't really an alpn reserved for Do53 service, but designated resolvers may or may not offer
* Do53 service, so we should probably have a flag to represent this capability. Unfortunately DNR
* does not indicate the status to us.*/
SD_DNS_ALPN_DO53 = 1 << 0,
/* SD_DNS_ALPN_HTTP_1_1, "http/1.1" [RFC9112] */
SD_DNS_ALPN_HTTP_2_TLS = 1 << 1, /* "h2" [RFC9113] [RFC9461] */
/* SD_DNS_ALPN_HTTP_2_TCP, "h2c" [RFC9113] */
SD_DNS_ALPN_HTTP_3 = 1 << 2, /* "h3" [RFC9114] [RFC9461] */
SD_DNS_ALPN_DOT = 1 << 3, /* "dot" [RFC7858] [RFC9461] */
SD_DNS_ALPN_DOQ = 1 << 4, /* "doq" [RFC9250] [RFC9461] */
_SD_ENUM_FORCE_S64(SD_DNS_ALPN)
} sd_dns_alpn_flags;
int sd_dns_resolver_get_priority(sd_dns_resolver *res, uint16_t *ret_priority);
int sd_dns_resolver_get_adn(sd_dns_resolver *res, const char **ret_adn);
int sd_dns_resolver_get_inet_addresses(sd_dns_resolver *res, const struct in_addr **ret_addrs, size_t *n);
int sd_dns_resolver_get_inet6_addresses(sd_dns_resolver *res, const struct in6_addr **ret_addrs, size_t *n);
int sd_dns_resolver_get_alpn(sd_dns_resolver *res, sd_dns_alpn_flags *ret_alpn);
int sd_dns_resolver_get_port(sd_dns_resolver *res, uint16_t *ret_port);
int sd_dns_resolver_get_dohpath(sd_dns_resolver *res, const char **ret_dohpath);
sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dns_resolver, sd_dns_resolver_unref);
_SD_END_DECLARATIONS;
#endif /* SD_DNS_RESOLVER_H */