summaryrefslogtreecommitdiffstats
path: root/src/resolve
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:42 +0000
commit78e9bb837c258ac0ec7712b3d612cc2f407e731e (patch)
treef515d16b6efd858a9aeb5b0ef5d6f90bf288283d /src/resolve
parentAdding debian version 255.5-1. (diff)
downloadsystemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.tar.xz
systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.zip
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/dns-type.c2
-rw-r--r--src/resolve/dns-type.h2
-rw-r--r--src/resolve/fuzz-resource-record.c10
-rw-r--r--src/resolve/meson.build15
-rw-r--r--src/resolve/resolvectl.c124
-rw-r--r--src/resolve/resolved-bus.c55
-rw-r--r--src/resolve/resolved-conf.c44
-rw-r--r--src/resolve/resolved-conf.h9
-rw-r--r--src/resolve/resolved-def.h4
-rw-r--r--src/resolve/resolved-dns-answer.c2
-rw-r--r--src/resolve/resolved-dns-cache.c22
-rw-r--r--src/resolve/resolved-dns-cache.h2
-rw-r--r--src/resolve/resolved-dns-dnssec.c26
-rw-r--r--src/resolve/resolved-dns-dnssec.h3
-rw-r--r--src/resolve/resolved-dns-packet.c312
-rw-r--r--src/resolve/resolved-dns-packet.h133
-rw-r--r--src/resolve/resolved-dns-query.c17
-rw-r--r--src/resolve/resolved-dns-query.h2
-rw-r--r--src/resolve/resolved-dns-rr.c435
-rw-r--r--src/resolve/resolved-dns-rr.h33
-rw-r--r--src/resolve/resolved-dns-scope.c92
-rw-r--r--src/resolve/resolved-dns-scope.h7
-rw-r--r--src/resolve/resolved-dns-server.c23
-rw-r--r--src/resolve/resolved-dns-server.h9
-rw-r--r--src/resolve/resolved-dns-stream.c4
-rw-r--r--src/resolve/resolved-dns-stub.c27
-rw-r--r--src/resolve/resolved-dns-synthesize.c19
-rw-r--r--src/resolve/resolved-dns-synthesize.h2
-rw-r--r--src/resolve/resolved-dns-transaction.c282
-rw-r--r--src/resolve/resolved-dns-transaction.h2
-rw-r--r--src/resolve/resolved-dns-trust-anchor.c12
-rw-r--r--src/resolve/resolved-dns-zone.c4
-rw-r--r--src/resolve/resolved-dnssd-bus.c16
-rw-r--r--src/resolve/resolved-dnssd-gperf.gperf15
-rw-r--r--src/resolve/resolved-dnssd.c116
-rw-r--r--src/resolve/resolved-dnssd.h11
-rw-r--r--src/resolve/resolved-dnstls-openssl.c3
-rw-r--r--src/resolve/resolved-etc-hosts.c19
-rw-r--r--src/resolve/resolved-link-bus.c91
-rw-r--r--src/resolve/resolved-link.c2
-rw-r--r--src/resolve/resolved-manager.c136
-rw-r--r--src/resolve/resolved-manager.h3
-rw-r--r--src/resolve/resolved-mdns.c43
-rw-r--r--src/resolve/resolved-util.c2
-rw-r--r--src/resolve/resolved-varlink.c786
-rw-r--r--src/resolve/resolved.c2
-rw-r--r--src/resolve/test-resolved-dummy-server.c450
-rw-r--r--src/resolve/test-resolved-packet.c187
-rw-r--r--src/resolve/test-resolved-stream.c19
49 files changed, 3158 insertions, 478 deletions
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c
index da68b41..1af370d 100644
--- a/src/resolve/dns-type.c
+++ b/src/resolve/dns-type.c
@@ -80,7 +80,7 @@ bool dns_type_is_valid_query(uint16_t type) {
DNS_TYPE_RRSIG);
}
-bool dns_type_is_zone_transer(uint16_t type) {
+bool dns_type_is_zone_transfer(uint16_t type) {
/* Zone transfers, either normal or incremental */
diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h
index c6be190..404256c 100644
--- a/src/resolve/dns-type.h
+++ b/src/resolve/dns-type.h
@@ -136,7 +136,7 @@ bool dns_type_is_obsolete(uint16_t type);
bool dns_type_may_wildcard(uint16_t type);
bool dns_type_apex_only(uint16_t type);
bool dns_type_needs_authentication(uint16_t type);
-bool dns_type_is_zone_transer(uint16_t type);
+bool dns_type_is_zone_transfer(uint16_t type);
int dns_type_to_af(uint16_t type);
bool dns_class_is_pseudo(uint16_t class);
diff --git a/src/resolve/fuzz-resource-record.c b/src/resolve/fuzz-resource-record.c
index 358a5c7..f792167 100644
--- a/src/resolve/fuzz-resource-record.c
+++ b/src/resolve/fuzz-resource-record.c
@@ -26,12 +26,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
assert_se(f = memstream_init(&m));
(void) fprintf(f, "%s", strna(dns_resource_record_to_string(rr)));
- if (dns_resource_record_to_json(rr, &v) < 0)
- return 0;
-
- (void) json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, f, NULL);
- (void) dns_resource_record_to_wire_format(rr, false);
- (void) dns_resource_record_to_wire_format(rr, true);
+ assert_se(dns_resource_record_to_json(rr, &v) >= 0);
+ assert_se(json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, f, NULL) >= 0);
+ assert_se(dns_resource_record_to_wire_format(rr, false) >= 0);
+ assert_se(dns_resource_record_to_wire_format(rr, true) >= 0);
return 0;
}
diff --git a/src/resolve/meson.build b/src/resolve/meson.build
index e7867e2..d336b2c 100644
--- a/src/resolve/meson.build
+++ b/src/resolve/meson.build
@@ -118,7 +118,6 @@ if conf.get('ENABLE_DNS_OVER_TLS') == 1
endif
link_with = [
- libbasic_gcrypt,
libshared,
libsystemd_resolve_core,
]
@@ -196,6 +195,20 @@ executables += [
],
'include_directories' : resolve_includes,
},
+ test_template + {
+ 'sources' : [
+ files('test-resolved-dummy-server.c'),
+ basic_dns_sources,
+ systemd_resolved_sources,
+ ],
+ 'dependencies' : [
+ lib_openssl_or_gcrypt,
+ libm,
+ systemd_resolved_dependencies,
+ ],
+ 'include_directories' : resolve_includes,
+ 'type' : 'manual',
+ },
resolve_fuzz_template + {
'sources' : files('fuzz-dns-packet.c'),
},
diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c
index afa537f..f2e9e7a 100644
--- a/src/resolve/resolvectl.c
+++ b/src/resolve/resolvectl.c
@@ -184,6 +184,9 @@ static void print_source(uint64_t flags, usec_t rtt) {
if (!arg_legend)
return;
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return;
+
if (flags == 0)
return;
@@ -250,6 +253,9 @@ static int resolve_host(sd_bus *bus, const char *name) {
assert(name);
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type=A or --type=AAAA to acquire address record information in JSON format.");
+
log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname);
r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveHostname");
@@ -348,6 +354,9 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a
assert(IN_SET(family, AF_INET, AF_INET6));
assert(address);
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format.");
+
if (ifindex <= 0)
ifindex = arg_ifindex;
@@ -433,11 +442,26 @@ static int output_rr_packet(const void *d, size_t l, int ifindex) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
int r;
+ assert(d || l == 0);
+
r = dns_resource_record_new_from_raw(&rr, d, l);
if (r < 0)
return log_error_errno(r, "Failed to parse RR: %m");
- if (arg_raw == RAW_PAYLOAD) {
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
+ _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+ r = dns_resource_record_to_json(rr, &j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert RR to JSON: %m");
+
+ if (!j)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "JSON formatting for records of type %s (%u) not available.", dns_type_to_string(rr->key->type), rr->key->type);
+
+ r = json_variant_dump(j, arg_json_format_flags, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ } else if (arg_raw == RAW_PAYLOAD) {
void *data;
ssize_t k;
@@ -946,6 +970,9 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
static int verb_service(int argc, char **argv, void *userdata) {
sd_bus *bus = userdata;
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format.");
+
if (argc == 2)
return resolve_service(bus, NULL, NULL, argv[1]);
else if (argc == 3)
@@ -1005,6 +1032,9 @@ static int verb_openpgp(int argc, char **argv, void *userdata) {
sd_bus *bus = userdata;
int q, r = 0;
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format.");
+
STRV_FOREACH(p, argv + 1) {
q = resolve_openpgp(bus, *p);
if (q < 0)
@@ -1056,6 +1086,9 @@ static int verb_tlsa(int argc, char **argv, void *userdata) {
const char *family = "tcp";
int q, r = 0;
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format.");
+
if (service_family_is_valid(argv[1])) {
family = argv[1];
args++;
@@ -1080,9 +1113,9 @@ static int show_statistics(int argc, char **argv, void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpStatistics", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpStatistics", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue DumpStatistics() varlink call: %m");
+ return r;
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
@@ -1238,9 +1271,9 @@ static int reset_statistics(int argc, char **argv, void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.ResetStatistics", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.ResetStatistics", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue ResetStatistics() varlink call: %m");
+ return r;
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
return json_variant_dump(reply, arg_json_format_flags, NULL, NULL);
@@ -2715,24 +2748,26 @@ static int print_answer(JsonVariant *answer) {
static void monitor_query_dump(JsonVariant *v) {
_cleanup_(json_variant_unrefp) JsonVariant *question = NULL, *answer = NULL, *collected_questions = NULL;
- int rcode = -1, error = 0, r;
- const char *state = NULL;
+ int rcode = -1, error = 0, ede_code = -1;
+ const char *state = NULL, *result = NULL, *ede_msg = NULL;
assert(v);
JsonDispatch dispatch_table[] = {
- { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
- { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
- { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
- { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
- { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
- { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
+ { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY },
+ { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 },
+ { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 },
+ { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY },
+ { "result", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&result), 0 },
+ { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 },
+ { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 },
+ { "extendedDNSErrorCode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&ede_code), 0 },
+ { "extendedDNSErrorMessage", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&ede_msg), 0 },
{}
};
- r = json_dispatch(v, dispatch_table, 0, NULL);
- if (r < 0)
- return (void) log_warning("Received malformed monitor message, ignoring.");
+ if (json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, NULL) < 0)
+ return;
/* First show the current question */
print_question('Q', ansi_highlight_cyan(), question);
@@ -2740,7 +2775,7 @@ static void monitor_query_dump(JsonVariant *v) {
/* And then show the questions that led to this one in case this was a CNAME chain */
print_question('C', ansi_highlight_grey(), collected_questions);
- printf("%s%s S%s: %s\n",
+ printf("%s%s S%s: %s",
streq_ptr(state, "success") ? ansi_highlight_green() : ansi_highlight_red(),
special_glyph(SPECIAL_GLYPH_ARROW_LEFT),
ansi_normal(),
@@ -2748,6 +2783,17 @@ static void monitor_query_dump(JsonVariant *v) {
streq_ptr(state, "rcode-failure") ? dns_rcode_to_string(rcode) :
state));
+ if (!isempty(result))
+ printf(": %s", result);
+
+ if (ede_code >= 0)
+ printf(" (%s%s%s)",
+ FORMAT_DNS_EDE_RCODE(ede_code),
+ !isempty(ede_msg) ? ": " : "",
+ strempty(ede_msg));
+
+ puts("");
+
print_answer(answer);
}
@@ -2856,7 +2902,7 @@ static int dump_cache_item(JsonVariant *item) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
int r, c = 0;
- r = json_dispatch(item, dispatch_table, JSON_LOG, &item_info);
+ r = json_dispatch(item, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &item_info);
if (r < 0)
return r;
@@ -2918,7 +2964,7 @@ static int dump_cache_scope(JsonVariant *scope) {
{},
};
- r = json_dispatch(scope, dispatch_table, JSON_LOG, &scope_info);
+ r = json_dispatch(scope, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &scope_info);
if (r < 0)
return r;
@@ -2959,9 +3005,9 @@ static int verb_show_cache(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpCache", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpCache", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue DumpCache() varlink call: %m");
+ return r;
d = json_variant_by_key(reply, "dump");
if (!d)
@@ -3034,7 +3080,7 @@ static int dump_server_state(JsonVariant *server) {
{},
};
- r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_PERMISSIVE, &server_state);
+ r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &server_state);
if (r < 0)
return r;
@@ -3133,9 +3179,9 @@ static int verb_show_server_state(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
- r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpServerState", NULL, &reply, NULL, 0);
+ r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpServerState", /* parameters= */ NULL, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to issue DumpServerState() varlink call: %m");
+ return r;
d = json_variant_by_key(reply, "dump");
if (!d)
@@ -3252,11 +3298,11 @@ static int native_help(void) {
if (r < 0)
return log_oom();
- printf("%s [OPTIONS...] COMMAND ...\n"
+ printf("%1$s [OPTIONS...] COMMAND ...\n"
"\n"
- "%sSend control commands to the network name resolution manager, or%s\n"
- "%sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%s\n"
- "\nCommands:\n"
+ "%5$sSend control commands to the network name resolution manager, or%6$s\n"
+ "%5$sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%6$s\n"
+ "\n%3$sCommands:%4$s\n"
" query HOSTNAME|ADDRESS... Resolve domain names, IPv4 and IPv6 addresses\n"
" service [[NAME] TYPE] DOMAIN Resolve service (SRV)\n"
" openpgp EMAIL@DOMAIN... Query OpenPGP public key\n"
@@ -3279,7 +3325,7 @@ static int native_help(void) {
" nta [LINK [DOMAIN...]] Get/set per-interface DNSSEC NTA\n"
" revert LINK Revert per-interface configuration\n"
" log-level [LEVEL] Get/set logging threshold for systemd-resolved\n"
- "\nOptions:\n"
+ "\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
@@ -3296,6 +3342,7 @@ static int native_help(void) {
" --synthesize=BOOL Allow synthetic response (default: yes)\n"
" --cache=BOOL Allow response from cache (default: yes)\n"
" --stale-data=BOOL Allow response from cache with stale data (default: yes)\n"
+ " --relax-single-label=BOOL Allow single label lookups to go upstream (default: no)\n"
" --zone=BOOL Allow response from locally registered mDNS/LLMNR\n"
" records (default: yes)\n"
" --trust-anchor=BOOL Allow response from local trust anchor (default:\n"
@@ -3308,13 +3355,13 @@ static int native_help(void) {
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short\n"
" otherwise\n"
- "\nSee the %s for details.\n",
+ "\nSee the %2$s for details.\n",
program_invocation_short_name,
- ansi_highlight(),
+ link,
+ ansi_underline(),
ansi_normal(),
ansi_highlight(),
- ansi_normal(),
- link);
+ ansi_normal());
return 0;
}
@@ -3655,7 +3702,8 @@ static int native_parse_argv(int argc, char *argv[]) {
ARG_SEARCH,
ARG_NO_PAGER,
ARG_JSON,
- ARG_STALE_DATA
+ ARG_STALE_DATA,
+ ARG_RELAX_SINGLE_LABEL,
};
static const struct option options[] = {
@@ -3680,6 +3728,7 @@ static int native_parse_argv(int argc, char *argv[]) {
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "json", required_argument, NULL, ARG_JSON },
{ "stale-data", required_argument, NULL, ARG_STALE_DATA },
+ { "relax-single-label", required_argument, NULL, ARG_RELAX_SINGLE_LABEL },
{}
};
@@ -3866,6 +3915,13 @@ static int native_parse_argv(int argc, char *argv[]) {
SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0);
break;
+ case ARG_RELAX_SINGLE_LABEL:
+ r = parse_boolean_argument("--relax-single-label=", optarg, NULL);
+ if (r < 0)
+ return r;
+ SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0);
+ break;
+
case ARG_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index 75ba29c..d6d2273 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -11,6 +11,7 @@
#include "format-util.h"
#include "memory-util.h"
#include "missing_capability.h"
+#include "path-util.h"
#include "resolved-bus.h"
#include "resolved-def.h"
#include "resolved-dns-stream.h"
@@ -146,8 +147,13 @@ static int reply_query_state(DnsQuery *q) {
return reply_method_errorf(q, BUS_ERROR_ABORTED, "Query aborted");
case DNS_TRANSACTION_DNSSEC_FAILED:
- return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s",
- dnssec_result_to_string(q->answer_dnssec_result));
+ return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s%s%s%s%s%s",
+ dnssec_result_to_string(q->answer_dnssec_result),
+ q->answer_ede_rcode >= 0 ? " (" : "",
+ q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
+ (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
+ q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
+ q->answer_ede_rcode >= 0 ? ")" : "");
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return reply_method_errorf(q, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known");
@@ -184,7 +190,13 @@ static int reply_query_state(DnsQuery *q) {
rc = FORMAT_DNS_RCODE(q->answer_rcode);
n = strjoina(_BUS_ERROR_DNS, rc);
- sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc);
+ sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error: %s%s%s%s%s%s",
+ dns_query_string(q), rc,
+ q->answer_ede_rcode >= 0 ? " (" : "",
+ q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "",
+ (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "",
+ q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "",
+ q->answer_ede_rcode >= 0 ? ")" : "");
}
return sd_bus_reply_method_error(req, &error);
@@ -362,6 +374,7 @@ static int validate_and_mangle_flags(
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
SD_RESOLVED_NO_STALE|
+ SD_RESOLVED_RELAX_SINGLE_LABEL|
ok))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
@@ -807,7 +820,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (!dns_type_is_valid_query(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type);
- if (dns_type_is_zone_transer(type))
+ if (dns_type_is_zone_transfer(type))
return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Zone transfers not permitted via this programming interface.");
if (dns_type_is_obsolete(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type);
@@ -1854,7 +1867,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata,
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
_cleanup_(dnssd_service_freep) DnssdService *service = NULL;
_cleanup_(sd_bus_track_unrefp) sd_bus_track *bus_track = NULL;
- const char *name, *name_template, *type;
+ const char *id, *name_template, *type;
_cleanup_free_ char *path = NULL;
DnssdService *s = NULL;
Manager *m = ASSERT_PTR(userdata);
@@ -1878,22 +1891,26 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata,
if (r < 0)
return r;
service->originator = euid;
+ service->config_source = RESOLVE_CONFIG_SOURCE_DBUS;
- r = sd_bus_message_read(message, "sssqqq", &name, &name_template, &type,
+ r = sd_bus_message_read(message, "sssqqq", &id, &name_template, &type,
&service->port, &service->priority,
&service->weight);
if (r < 0)
return r;
- s = hashmap_get(m->dnssd_services, name);
- if (s)
- return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", name);
+ if (!filename_part_is_valid(id))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service identifier '%s' is invalid", id);
if (!dnssd_srv_type_is_valid(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service type '%s' is invalid", type);
- service->name = strdup(name);
- if (!service->name)
+ s = hashmap_get(m->dnssd_services, id);
+ if (s)
+ return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", id);
+
+ service->id = strdup(id);
+ if (!service->id)
return log_oom();
service->name_template = strdup(name_template);
@@ -1986,20 +2003,22 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata,
txt_data = NULL;
}
- r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->name, &path);
+ r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->id, &path);
if (r < 0)
return r;
- r = bus_verify_polkit_async(message, CAP_SYS_ADMIN,
- "org.freedesktop.resolve1.register-service",
- NULL, false, UID_INVALID,
- &m->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.register-service",
+ /* details= */ NULL,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Polkit will call us back */
- r = hashmap_ensure_put(&m->dnssd_services, &string_hash_ops, service->name, service);
+ r = hashmap_ensure_put(&m->dnssd_services, &string_hash_ops, service->id, service);
if (r < 0)
return r;
@@ -2163,7 +2182,7 @@ static const sd_bus_vtable resolve_vtable[] = {
bus_method_revert_link,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("RegisterService",
- SD_BUS_ARGS("s", name,
+ SD_BUS_ARGS("s", id,
"s", name_template,
"s", type,
"q", service_port,
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c
index 2f08ed0..4441ee2 100644
--- a/src/resolve/resolved-conf.c
+++ b/src/resolve/resolved-conf.c
@@ -55,7 +55,7 @@ static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, cons
return 0;
}
- return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name);
+ return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name, RESOLVE_CONFIG_SOURCE_FILE);
}
int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
@@ -299,6 +299,37 @@ int config_parse_dnssd_service_type(
return 0;
}
+int config_parse_dnssd_service_subtype(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ DnssdService *s = ASSERT_PTR(userdata);
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ s->subtype = mfree(s->subtype);
+ return 0;
+ }
+
+ if (!dns_subtype_name_is_valid(rvalue)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Service subtype is invalid. Ignoring.");
+ return 0;
+ }
+
+ return free_and_strdup_warn(&s->subtype, rvalue);
+}
+
int config_parse_dnssd_txt(
const char *unit,
const char *filename,
@@ -362,7 +393,7 @@ int config_parse_dnssd_txt(
case DNS_TXT_ITEM_DATA:
if (value) {
- r = unbase64mem(value, strlen(value), &decoded, &length);
+ r = unbase64mem(value, &decoded, &length);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
@@ -570,9 +601,12 @@ int manager_parse_config_file(Manager *m) {
assert(m);
- r = config_parse_config_file("resolved.conf", "Resolve\0",
- config_item_perf_lookup, resolved_gperf_lookup,
- CONFIG_PARSE_WARN, m);
+ r = config_parse_standard_file_with_dropins(
+ "systemd/resolved.conf",
+ "Resolve\0",
+ config_item_perf_lookup, resolved_gperf_lookup,
+ CONFIG_PARSE_WARN,
+ /* userdata= */ m);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h
index 07ce259..5eea6bd 100644
--- a/src/resolve/resolved-conf.h
+++ b/src/resolve/resolved-conf.h
@@ -3,6 +3,14 @@
#include "conf-parser.h"
+typedef enum ResolveConfigSource {
+ RESOLVE_CONFIG_SOURCE_FILE,
+ RESOLVE_CONFIG_SOURCE_NETWORKD,
+ RESOLVE_CONFIG_SOURCE_DBUS,
+ _RESOLVE_CONFIG_SOURCE_MAX,
+ _RESOLVE_CONFIG_SOURCE_INVALID = -EINVAL,
+} ResolveConfigSource;
+
#include "resolved-dns-server.h"
int manager_parse_config_file(Manager *m);
@@ -17,6 +25,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dns_servers);
CONFIG_PARSER_PROTOTYPE(config_parse_search_domains);
CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_name);
+CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_subtype);
CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_type);
CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_txt);
CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_extra);
diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h
index b7a44f9..f702a0a 100644
--- a/src/resolve/resolved-def.h
+++ b/src/resolve/resolved-def.h
@@ -73,6 +73,10 @@
/* Input: Don't answer request with stale data */
#define SD_RESOLVED_NO_STALE (UINT64_C(1) << 24)
+/* Input: Allow single-label lookups to Internet DNS servers */
+#define SD_RESOLVED_RELAX_SINGLE_LABEL \
+ (UINT64_C(1) << 25)
+
#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6)
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c
index bf023a7..254f836 100644
--- a/src/resolve/resolved-dns-answer.c
+++ b/src/resolve/resolved-dns-answer.c
@@ -26,7 +26,7 @@ static void dns_answer_item_hash_func(const DnsAnswerItem *a, struct siphash *st
assert(a);
assert(state);
- siphash24_compress(&a->ifindex, sizeof(a->ifindex), state);
+ siphash24_compress_typesafe(a->ifindex, state);
dns_resource_record_hash_func(a->rr, state);
}
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index e90915e..9061908 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -164,8 +164,8 @@ void dns_cache_flush(DnsCache *c) {
while ((key = hashmap_first_key(c->by_key)))
dns_cache_remove_by_key(c, key);
- assert(hashmap_size(c->by_key) == 0);
- assert(prioq_size(c->by_expiry) == 0);
+ assert(hashmap_isempty(c->by_key));
+ assert(prioq_isempty(c->by_expiry));
c->by_key = hashmap_free(c->by_key);
c->by_expiry = prioq_free(c->by_expiry);
@@ -186,7 +186,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
DnsCacheItem *i;
- if (prioq_size(c->by_expiry) <= 0)
+ if (prioq_isempty(c->by_expiry))
break;
if (prioq_size(c->by_expiry) + add < CACHE_MAX)
@@ -243,6 +243,22 @@ void dns_cache_prune(DnsCache *c) {
}
}
+bool dns_cache_expiry_in_one_second(DnsCache *c, usec_t t) {
+ DnsCacheItem *i;
+
+ assert(c);
+
+ /* Check if any items expire within the next second */
+ i = prioq_peek(c->by_expiry);
+ if (!i)
+ return false;
+
+ if (i->until <= usec_add(t, USEC_PER_SEC))
+ return true;
+
+ return false;
+}
+
static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
const DnsCacheItem *x = a, *y = b;
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index d078ae9..6a45b95 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -58,3 +58,5 @@ bool dns_cache_is_empty(DnsCache *cache);
unsigned dns_cache_size(DnsCache *cache);
int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p, usec_t ts, unsigned max_rr);
+
+bool dns_cache_expiry_in_one_second(DnsCache *c, usec_t t);
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index a192d82..233418a 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -890,8 +890,11 @@ static int dnssec_rrset_verify_sig(
_cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL;
void *hash;
size_t hash_size;
+ int r;
- initialize_libgcrypt(false);
+ r = initialize_libgcrypt(false);
+ if (r < 0)
+ return r;
#endif
switch (rrsig->rrsig.algorithm) {
@@ -1334,6 +1337,7 @@ static hash_md_t digest_to_hash_md(uint8_t algorithm) {
int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
uint8_t wire_format[DNS_WIRE_FORMAT_HOSTNAME_MAX];
+ size_t encoded_length;
int r;
assert(dnskey);
@@ -1360,6 +1364,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds,
r = dns_name_to_wire_format(dns_resource_key_name(dnskey->key), wire_format, sizeof wire_format, true);
if (r < 0)
return r;
+ encoded_length = r;
hash_md_t md_algorithm = digest_to_hash_md(ds->ds.digest_type);
@@ -1383,7 +1388,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds,
if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0)
return -EIO;
- if (EVP_DigestUpdate(ctx, wire_format, r) <= 0)
+ if (EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0)
return -EIO;
if (mask_revoke)
@@ -1407,7 +1412,9 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds,
if (md_algorithm < 0)
return -EOPNOTSUPP;
- initialize_libgcrypt(false);
+ r = initialize_libgcrypt(false);
+ if (r < 0)
+ return r;
_cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL;
@@ -1421,7 +1428,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds,
if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md)
return -EIO;
- gcry_md_write(md, wire_format, r);
+ gcry_md_write(md, wire_format, encoded_length);
if (mask_revoke)
md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE);
else
@@ -1552,8 +1559,11 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
if (algorithm < 0)
return algorithm;
- initialize_libgcrypt(false);
+ r = initialize_libgcrypt(false);
+ if (r < 0)
+ return r;
+ size_t encoded_length;
unsigned hash_size = gcry_md_get_algo_dlen(algorithm);
assert(hash_size > 0);
@@ -1563,13 +1573,14 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
if (r < 0)
return r;
+ encoded_length = r;
_cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL;
gcry_error_t err = gcry_md_open(&md, algorithm, 0);
if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md)
return -EIO;
- gcry_md_write(md, wire_format, r);
+ gcry_md_write(md, wire_format, encoded_length);
gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
void *result = gcry_md_read(md, 0);
@@ -1963,7 +1974,7 @@ found_closest_encloser:
}
static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) {
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
const char *n;
int r;
@@ -2576,6 +2587,7 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
+ [DNSSEC_UPSTREAM_FAILURE] = "upstream-failure",
[DNSSEC_TOO_MANY_VALIDATIONS] = "too-many-validations",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index 29b9013..d8ff3ba 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -21,11 +21,12 @@ enum DnssecResult {
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
- /* These two are added by the DnsTransaction logic */
+ /* These five are added by the DnsTransaction logic */
DNSSEC_UNSIGNED,
DNSSEC_FAILED_AUXILIARY,
DNSSEC_NSEC_MISMATCH,
DNSSEC_INCOMPATIBLE_SERVER,
+ DNSSEC_UPSTREAM_FAILURE,
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -EINVAL,
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 426711b..e626740 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "escape.h"
#include "memory-util.h"
#include "resolved-dns-packet.h"
#include "set.h"
@@ -569,7 +570,7 @@ int dns_packet_append_name(
while (!dns_name_is_root(name)) {
const char *z = name;
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
size_t n = 0;
if (allow_compression)
@@ -792,7 +793,7 @@ int dns_packet_append_opt(
static const uint8_t rfc6975[] = {
- 0, 5, /* OPTION_CODE: DAU */
+ 0, DNS_EDNS_OPT_DAU, /* OPTION_CODE */
#if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600)
0, 7, /* LIST_LENGTH */
#else
@@ -808,13 +809,13 @@ int dns_packet_append_opt(
DNSSEC_ALGORITHM_ED25519,
#endif
- 0, 6, /* OPTION_CODE: DHU */
+ 0, DNS_EDNS_OPT_DHU, /* OPTION_CODE */
0, 3, /* LIST_LENGTH */
DNSSEC_DIGEST_SHA1,
DNSSEC_DIGEST_SHA256,
DNSSEC_DIGEST_SHA384,
- 0, 7, /* OPTION_CODE: N3U */
+ 0, DNS_EDNS_OPT_N3U, /* OPTION_CODE */
0, 1, /* LIST_LENGTH */
NSEC3_ALGORITHM_SHA1,
};
@@ -1182,6 +1183,31 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns
r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL);
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ r = dns_packet_append_uint16(p, rr->svcb.priority, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_name(p, rr->svcb.target_name, false, false, NULL);
+ if (r < 0)
+ goto fail;
+
+ LIST_FOREACH(params, i, rr->svcb.params) {
+ r = dns_packet_append_uint16(p, i->key, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint16(p, i->length, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_blob(p, i->value, i->length, NULL);
+ if (r < 0)
+ goto fail;
+ }
+ break;
+
case DNS_TYPE_CAA:
r = dns_packet_append_uint8(p, rr->caa.flags, NULL);
if (r < 0)
@@ -1194,6 +1220,30 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns
r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL);
break;
+ case DNS_TYPE_NAPTR:
+ r = dns_packet_append_uint16(p, rr->naptr.order, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint16(p, rr->naptr.preference, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_string(p, rr->naptr.flags, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_string(p, rr->naptr.services, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_string(p, rr->naptr.regexp, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_name(p, rr->naptr.replacement, /* allow_compression= */ false, /* canonical_candidate= */ true, NULL);
+ break;
+
case DNS_TYPE_OPT:
case DNS_TYPE_OPENPGPKEY:
case _DNS_TYPE_INVALID: /* unparsable */
@@ -1688,6 +1738,41 @@ static bool loc_size_ok(uint8_t size) {
return m <= 9 && e <= 9 && (m > 0 || e == 0);
}
+static bool dns_svc_param_is_valid(DnsSvcParam *i) {
+ if (!i)
+ return false;
+
+ switch (i->key) {
+ /* RFC 9460, section 7.1.1: alpn-ids must exactly fill SvcParamValue */
+ case DNS_SVC_PARAM_KEY_ALPN: {
+ size_t sz = 0;
+ if (i->length <= 0)
+ return false;
+ while (sz < i->length)
+ sz += 1 + i->value[sz]; /* N.B. will not overflow */
+ return sz == i->length;
+ }
+
+ /* RFC 9460, section 7.1.1: value must be empty */
+ case DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN:
+ return i->length == 0;
+
+ /* RFC 9460, section 7.2 */
+ case DNS_SVC_PARAM_KEY_PORT:
+ return i->length == 2;
+
+ /* RFC 9460, section 7.3: addrs must exactly fill SvcParamValue */
+ case DNS_SVC_PARAM_KEY_IPV4HINT:
+ return i->length % (sizeof (struct in_addr)) == 0;
+ case DNS_SVC_PARAM_KEY_IPV6HINT:
+ return i->length % (sizeof (struct in6_addr)) == 0;
+
+ /* Otherwise, permit any value */
+ default:
+ return true;
+ }
+}
+
int dns_packet_read_rr(
DnsPacket *p,
DnsResourceRecord **ret,
@@ -2122,6 +2207,52 @@ int dns_packet_read_rr(
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ r = dns_packet_read_uint16(p, &rr->svcb.priority, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_name(p, &rr->svcb.target_name, false /* uncompressed */, NULL);
+ if (r < 0)
+ return r;
+
+ DnsSvcParam *last = NULL;
+ while (p->rindex - offset < rdlength) {
+ _cleanup_free_ DnsSvcParam *i = NULL;
+ uint16_t svc_param_key;
+ uint16_t sz;
+
+ r = dns_packet_read_uint16(p, &svc_param_key, NULL);
+ if (r < 0)
+ return r;
+ /* RFC 9460, section 2.2 says we must consider an RR malformed if SvcParamKeys are
+ * not in strictly increasing order */
+ if (last && last->key >= svc_param_key)
+ return -EBADMSG;
+
+ r = dns_packet_read_uint16(p, &sz, NULL);
+ if (r < 0)
+ return r;
+
+ i = malloc0(offsetof(DnsSvcParam, value) + sz);
+ if (!i)
+ return -ENOMEM;
+
+ i->key = svc_param_key;
+ i->length = sz;
+ r = dns_packet_read_blob(p, &i->value, sz, NULL);
+ if (r < 0)
+ return r;
+ if (!dns_svc_param_is_valid(i))
+ return -EBADMSG;
+
+ LIST_INSERT_AFTER(params, rr->svcb.params, last, i);
+ last = TAKE_PTR(i);
+ }
+
+ break;
+
case DNS_TYPE_CAA:
r = dns_packet_read_uint8(p, &rr->caa.flags, NULL);
if (r < 0)
@@ -2140,6 +2271,30 @@ int dns_packet_read_rr(
break;
+ case DNS_TYPE_NAPTR:
+ r = dns_packet_read_uint16(p, &rr->naptr.order, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_uint16(p, &rr->naptr.preference, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_string(p, &rr->naptr.flags, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_string(p, &rr->naptr.services, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_string(p, &rr->naptr.regexp, NULL);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_read_name(p, &rr->naptr.replacement, /* allow_compressed= */ false, NULL);
+ break;
+
case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */
case DNS_TYPE_OPENPGPKEY:
default:
@@ -2197,7 +2352,7 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
return false;
/* RFC 6975 DAU, DHU or N3U fields found. */
- if (IN_SET(option_code, 5, 6, 7))
+ if (IN_SET(option_code, DNS_EDNS_OPT_DAU, DNS_EDNS_OPT_DHU, DNS_EDNS_OPT_N3U))
found_dau_dhu_n3u = true;
p += option_length + 4U;
@@ -2567,7 +2722,7 @@ int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp) {
static void dns_packet_hash_func(const DnsPacket *s, struct siphash *state) {
assert(s);
- siphash24_compress(&s->size, sizeof(s->size), state);
+ siphash24_compress_typesafe(s->size, state);
siphash24_compress(DNS_PACKET_DATA((DnsPacket*) s), s->size, state);
}
@@ -2587,6 +2742,85 @@ bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b) {
return dns_packet_compare_func(a, b) == 0;
}
+int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) {
+ const uint8_t *d;
+ size_t l;
+ int r;
+
+ assert(p);
+
+ if (!p->opt)
+ return -ENOENT;
+
+ d = p->opt->opt.data;
+ l = p->opt->opt.data_size;
+
+ while (l > 0) {
+ uint16_t code, length;
+
+ if (l < 4U)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "EDNS0 variable part has invalid size.");
+
+ code = unaligned_read_be16(d);
+ length = unaligned_read_be16(d + 2);
+
+ if (l < 4U + length)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "Truncated option in EDNS0 variable part.");
+
+ if (code == DNS_EDNS_OPT_EXT_ERROR) {
+ _cleanup_free_ char *msg = NULL;
+
+ if (length < 2U)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "EDNS0 truncated EDE info code.");
+
+ r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid EDE text in opt.");
+
+ if (ret_ede_msg) {
+ if (!utf8_is_valid(msg)) {
+ _cleanup_free_ char *msg_escaped = NULL;
+
+ msg_escaped = cescape(msg);
+ if (!msg_escaped)
+ return log_oom_debug();
+
+ *ret_ede_msg = TAKE_PTR(msg_escaped);
+ } else
+ *ret_ede_msg = TAKE_PTR(msg);
+ }
+
+ if (ret_ede_rcode)
+ *ret_ede_rcode = unaligned_read_be16(d + 4);
+
+ return 0;
+ }
+
+ d += 4U + length;
+ l -= 4U + length;
+ }
+
+ return -ENOENT;
+}
+
+bool dns_ede_rcode_is_dnssec(int ede_rcode) {
+ return IN_SET(ede_rcode,
+ DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG,
+ DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST,
+ DNS_EDE_RCODE_DNSSEC_INDETERMINATE,
+ DNS_EDE_RCODE_DNSSEC_BOGUS,
+ DNS_EDE_RCODE_SIG_EXPIRED,
+ DNS_EDE_RCODE_SIG_NOT_YET_VALID,
+ DNS_EDE_RCODE_DNSKEY_MISSING,
+ DNS_EDE_RCODE_RRSIG_MISSING,
+ DNS_EDE_RCODE_NO_ZONE_KEY_BIT,
+ DNS_EDE_RCODE_NSEC_MISSING
+ );
+}
+
int dns_packet_has_nsid_request(DnsPacket *p) {
bool has_nsid = false;
const uint8_t *d;
@@ -2614,7 +2848,7 @@ int dns_packet_has_nsid_request(DnsPacket *p) {
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Truncated option in EDNS0 variable part.");
- if (code == 3) {
+ if (code == DNS_EDNS_OPT_NSID) {
if (has_nsid)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Duplicate NSID option in EDNS0 variable part.");
@@ -2659,6 +2893,7 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_NXRRSET] = "NXRRSET",
[DNS_RCODE_NOTAUTH] = "NOTAUTH",
[DNS_RCODE_NOTZONE] = "NOTZONE",
+ [DNS_RCODE_DSOTYPENI] = "DSOTYPENI",
[DNS_RCODE_BADVERS] = "BADVERS",
[DNS_RCODE_BADKEY] = "BADKEY",
[DNS_RCODE_BADTIME] = "BADTIME",
@@ -2678,6 +2913,69 @@ const char *format_dns_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_ede_rcode_table[_DNS_EDE_RCODE_MAX_DEFINED] = {
+ [DNS_EDE_RCODE_OTHER] = "Other",
+ [DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG] = "Unsupported DNSKEY Algorithm",
+ [DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST] = "Unsupported DS Digest Type",
+ [DNS_EDE_RCODE_STALE_ANSWER] = "Stale Answer",
+ [DNS_EDE_RCODE_FORGED_ANSWER] = "Forged Answer",
+ [DNS_EDE_RCODE_DNSSEC_INDETERMINATE] = "DNSSEC Indeterminate",
+ [DNS_EDE_RCODE_DNSSEC_BOGUS] = "DNSSEC Bogus",
+ [DNS_EDE_RCODE_SIG_EXPIRED] = "Signature Expired",
+ [DNS_EDE_RCODE_SIG_NOT_YET_VALID] = "Signature Not Yet Valid",
+ [DNS_EDE_RCODE_DNSKEY_MISSING] = "DNSKEY Missing",
+ [DNS_EDE_RCODE_RRSIG_MISSING] = "RRSIG Missing",
+ [DNS_EDE_RCODE_NO_ZONE_KEY_BIT] = "No Zone Key Bit Set",
+ [DNS_EDE_RCODE_NSEC_MISSING] = "NSEC Missing",
+ [DNS_EDE_RCODE_CACHED_ERROR] = "Cached Error",
+ [DNS_EDE_RCODE_NOT_READY] = "Not Ready",
+ [DNS_EDE_RCODE_BLOCKED] = "Blocked",
+ [DNS_EDE_RCODE_CENSORED] = "Censored",
+ [DNS_EDE_RCODE_FILTERED] = "Filtered",
+ [DNS_EDE_RCODE_PROHIBITIED] = "Prohibited",
+ [DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER] = "Stale NXDOMAIN Answer",
+ [DNS_EDE_RCODE_NOT_AUTHORITATIVE] = "Not Authoritative",
+ [DNS_EDE_RCODE_NOT_SUPPORTED] = "Not Supported",
+ [DNS_EDE_RCODE_UNREACH_AUTHORITY] = "No Reachable Authority",
+ [DNS_EDE_RCODE_NET_ERROR] = "Network Error",
+ [DNS_EDE_RCODE_INVALID_DATA] = "Invalid Data",
+ [DNS_EDE_RCODE_SIG_NEVER] = "Signature Never Valid",
+ [DNS_EDE_RCODE_TOO_EARLY] = "Too Early",
+ [DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER] = "Unsupported NSEC3 Iterations",
+ [DNS_EDE_RCODE_TRANSPORT_POLICY] = "Impossible Transport Policy",
+ [DNS_EDE_RCODE_SYNTHESIZED] = "Synthesized",
+};
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_ede_rcode, int);
+
+const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) {
+ const char *p = dns_ede_rcode_to_string(i);
+ if (p)
+ return p;
+
+ 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",
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index a6af44c..393b7b2 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -253,32 +253,100 @@ int dns_packet_extract(DnsPacket *p);
bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b);
+int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg);
+bool dns_ede_rcode_is_dnssec(int ede_rcode);
int dns_packet_has_nsid_request(DnsPacket *p);
/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */
enum {
- DNS_RCODE_SUCCESS = 0,
- DNS_RCODE_FORMERR = 1,
- DNS_RCODE_SERVFAIL = 2,
- DNS_RCODE_NXDOMAIN = 3,
- DNS_RCODE_NOTIMP = 4,
- DNS_RCODE_REFUSED = 5,
- DNS_RCODE_YXDOMAIN = 6,
- DNS_RCODE_YXRRSET = 7,
- DNS_RCODE_NXRRSET = 8,
- DNS_RCODE_NOTAUTH = 9,
- DNS_RCODE_NOTZONE = 10,
- DNS_RCODE_BADVERS = 16,
- DNS_RCODE_BADSIG = 16, /* duplicate value! */
- DNS_RCODE_BADKEY = 17,
- DNS_RCODE_BADTIME = 18,
- DNS_RCODE_BADMODE = 19,
- DNS_RCODE_BADNAME = 20,
- DNS_RCODE_BADALG = 21,
- DNS_RCODE_BADTRUNC = 22,
- DNS_RCODE_BADCOOKIE = 23,
+ DNS_RCODE_SUCCESS = 0,
+ DNS_RCODE_FORMERR = 1,
+ DNS_RCODE_SERVFAIL = 2,
+ DNS_RCODE_NXDOMAIN = 3,
+ DNS_RCODE_NOTIMP = 4,
+ DNS_RCODE_REFUSED = 5,
+ DNS_RCODE_YXDOMAIN = 6,
+ DNS_RCODE_YXRRSET = 7,
+ DNS_RCODE_NXRRSET = 8,
+ DNS_RCODE_NOTAUTH = 9,
+ DNS_RCODE_NOTZONE = 10,
+ DNS_RCODE_DSOTYPENI = 11,
+ /* 12-15 are unassigned. */
+ DNS_RCODE_BADVERS = 16,
+ DNS_RCODE_BADSIG = 16, /* duplicate value! */
+ DNS_RCODE_BADKEY = 17,
+ DNS_RCODE_BADTIME = 18,
+ DNS_RCODE_BADMODE = 19,
+ DNS_RCODE_BADNAME = 20,
+ DNS_RCODE_BADALG = 21,
+ DNS_RCODE_BADTRUNC = 22,
+ DNS_RCODE_BADCOOKIE = 23,
+ /* 24-3840 are unassigned. */
+ /* 3841-4095 are for private use. */
+ /* 4096-65534 are unassigned. */
_DNS_RCODE_MAX_DEFINED,
- _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */
+ _DNS_RCODE_MAX = 65535, /* reserved */
+ _DNS_RCODE_INVALID = -EINVAL,
+};
+
+/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 */
+enum {
+ DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */
+ DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */
+ DNS_EDNS_OPT_UL = 2,
+ DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */
+ /* DNS_EDNS_OPT_RESERVED = 4 */
+ DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */
+ DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */
+ DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */
+ DNS_EDNS_OPT_CLIENT_SUBNET = 8, /* RFC 7871 */
+ DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */
+ DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */
+ DNS_EDNS_OPT_TCP_KEEPALIVE = 11, /* RFC 7828 */
+ DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */
+ DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */
+ DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */
+ DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */
+ DNS_EDNS_OPT_CLIENT_TAG = 16,
+ DNS_EDNS_OPT_SERVER_TAG = 17,
+ _DNS_EDNS_OPT_MAX_DEFINED,
+ _DNS_EDNS_OPT_INVALID = -EINVAL,
+};
+
+/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes */
+enum {
+ DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */
+ DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */
+ DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */
+ DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */
+ DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */
+ DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */
+ DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */
+ DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */
+ DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */
+ DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */
+ DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */
+ DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */
+ DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */
+ DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */
+ DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */
+ DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */
+ DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */
+ DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */
+ DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */
+ DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */
+ DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */
+ DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */
+ DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */
+ DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */
+ DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */
+ DNS_EDE_RCODE_SIG_NEVER = 25,
+ DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */
+ DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */
+ DNS_EDE_RCODE_TRANSPORT_POLICY = 28,
+ DNS_EDE_RCODE_SYNTHESIZED = 29,
+ _DNS_EDE_RCODE_MAX_DEFINED,
+ _DNS_EDE_RCODE_INVALID = -EINVAL,
};
const char* dns_rcode_to_string(int i) _const_;
@@ -286,9 +354,32 @@ int dns_rcode_from_string(const char *s) _pure_;
const char *format_dns_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
#define FORMAT_DNS_RCODE(i) format_dns_rcode(i, (char [DECIMAL_STR_MAX(int)]) {})
+const char* dns_ede_rcode_to_string(int i) _const_;
+const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
+#define FORMAT_DNS_EDE_RCODE(i) format_dns_ede_rcode(i, (char [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 } })
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 16334c6..14adb90 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -393,6 +393,8 @@ static void dns_query_reset_answer(DnsQuery *q) {
q->answer = dns_answer_unref(q->answer);
q->answer_rcode = 0;
+ q->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
+ q->answer_ede_msg = mfree(q->answer_ede_msg);
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_errno = 0;
q->answer_query_flags = 0;
@@ -539,6 +541,7 @@ int dns_query_new(
.question_bypass = dns_packet_ref(question_bypass),
.ifindex = ifindex,
.flags = flags,
+ .answer_ede_rcode = _DNS_EDE_RCODE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
.answer_protocol = _DNS_PROTOCOL_INVALID,
.answer_family = AF_UNSPEC,
@@ -611,7 +614,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
q->state = state;
- (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer);
+ (void) manager_monitor_send(q->manager, q);
dns_query_abandon(q);
if (q->complete)
@@ -694,6 +697,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC;
*state = DNS_TRANSACTION_RCODE_FAILURE;
+ log_debug("Found synthetic NXDOMAIN response.");
+
return 0;
}
if (r <= 0)
@@ -709,6 +714,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
*state = DNS_TRANSACTION_SUCCESS;
+ log_debug("Found synthetic success response.");
+
return 1;
}
@@ -763,7 +770,7 @@ int dns_query_go(DnsQuery *q) {
LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
DnsScopeMatch match;
- match = dns_scope_good_domain(s, q);
+ match = dns_scope_good_domain(s, q, q->flags);
assert(match >= 0);
if (match > found) { /* Does this match better? If so, remember how well it matched, and the first one
* that matches this well */
@@ -790,7 +797,7 @@ int dns_query_go(DnsQuery *q) {
LIST_FOREACH(scopes, s, first->scopes_next) {
DnsScopeMatch match;
- match = dns_scope_good_domain(s, q);
+ match = dns_scope_good_domain(s, q, q->flags);
assert(match >= 0);
if (match < found)
continue;
@@ -923,6 +930,10 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
DNS_ANSWER_REPLACE(q->answer, dns_answer_ref(t->answer));
q->answer_rcode = t->answer_rcode;
+ q->answer_ede_rcode = t->answer_ede_rcode;
+ r = free_and_strdup_warn(&q->answer_ede_msg, t->answer_ede_msg);
+ if (r < 0)
+ goto fail;
q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_query_flags = t->answer_query_flags | dns_transaction_source_to_query_flags(t->answer_source);
q->answer_errno = t->answer_errno;
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index 2723299..29d7288 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -73,6 +73,8 @@ struct DnsQuery {
/* Discovered data */
DnsAnswer *answer;
int answer_rcode;
+ int answer_ede_rcode;
+ char *answer_ede_msg;
DnssecResult answer_dnssec_result;
uint64_t answer_query_flags;
DnsProtocol answer_protocol;
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index b280a5a..204d4a6 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -15,6 +15,7 @@
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
+#include "unaligned.h"
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) {
DnsResourceKey *k;
@@ -195,7 +196,7 @@ bool dns_resource_key_is_dnssd_two_label_ptr(const DnsResourceKey *key) {
if (dns_name_parent(&name) <= 0)
return false;
- return dns_name_equal(name, "_tcp.local") || dns_name_equal(name, "_udp.local");
+ return dns_name_equal(name, "_tcp.local") > 0 || dns_name_equal(name, "_udp.local") > 0;
}
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
@@ -310,8 +311,8 @@ static void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash *
assert(k);
dns_name_hash_func(dns_resource_key_name(k), state);
- siphash24_compress(&k->class, sizeof(k->class), state);
- siphash24_compress(&k->type, sizeof(k->type), state);
+ siphash24_compress_typesafe(k->class, state);
+ siphash24_compress_typesafe(k->type, state);
}
static int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y) {
@@ -486,11 +487,24 @@ static DnsResourceRecord* dns_resource_record_free(DnsResourceRecord *rr) {
free(rr->tlsa.data);
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ free(rr->svcb.target_name);
+ dns_svc_param_free_all(rr->svcb.params);
+ break;
+
case DNS_TYPE_CAA:
free(rr->caa.tag);
free(rr->caa.value);
break;
+ case DNS_TYPE_NAPTR:
+ free(rr->naptr.flags);
+ free(rr->naptr.services);
+ free(rr->naptr.regexp);
+ free(rr->naptr.replacement);
+ break;
+
case DNS_TYPE_OPENPGPKEY:
default:
if (!rr->unparsable)
@@ -665,19 +679,24 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou
case DNS_TYPE_RRSIG:
/* do the fast comparisons first */
- return a->rrsig.type_covered == b->rrsig.type_covered &&
- a->rrsig.algorithm == b->rrsig.algorithm &&
- a->rrsig.labels == b->rrsig.labels &&
- a->rrsig.original_ttl == b->rrsig.original_ttl &&
- a->rrsig.expiration == b->rrsig.expiration &&
- a->rrsig.inception == b->rrsig.inception &&
- a->rrsig.key_tag == b->rrsig.key_tag &&
- FIELD_EQUAL(a->rrsig, b->rrsig, signature) &&
- dns_name_equal(a->rrsig.signer, b->rrsig.signer);
+ if (!(a->rrsig.type_covered == b->rrsig.type_covered &&
+ a->rrsig.algorithm == b->rrsig.algorithm &&
+ a->rrsig.labels == b->rrsig.labels &&
+ a->rrsig.original_ttl == b->rrsig.original_ttl &&
+ a->rrsig.expiration == b->rrsig.expiration &&
+ a->rrsig.inception == b->rrsig.inception &&
+ a->rrsig.key_tag == b->rrsig.key_tag &&
+ FIELD_EQUAL(a->rrsig, b->rrsig, signature)))
+ return false;
+
+ return dns_name_equal(a->rrsig.signer, b->rrsig.signer);
case DNS_TYPE_NSEC:
- return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) &&
- bitmap_equal(a->nsec.types, b->nsec.types);
+ r = dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name);
+ if (r <= 0)
+ return r;
+
+ return bitmap_equal(a->nsec.types, b->nsec.types);
case DNS_TYPE_NSEC3:
return a->nsec3.algorithm == b->nsec3.algorithm &&
@@ -693,11 +712,31 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou
a->tlsa.matching_type == b->tlsa.matching_type &&
FIELD_EQUAL(a->tlsa, b->tlsa, data);
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+
+ if (!(a->svcb.priority == b->svcb.priority &&
+ dns_svc_params_equal(a->svcb.params, b->svcb.params)))
+ return false;
+
+ return dns_name_equal(a->svcb.target_name, b->svcb.target_name);
+
case DNS_TYPE_CAA:
return a->caa.flags == b->caa.flags &&
streq(a->caa.tag, b->caa.tag) &&
FIELD_EQUAL(a->caa, b->caa, value);
+ case DNS_TYPE_NAPTR:
+ r = dns_name_equal(a->naptr.replacement, b->naptr.replacement);
+ if (r <= 0)
+ return r;
+
+ return a->naptr.order == b->naptr.order &&
+ a->naptr.preference == b->naptr.preference &&
+ streq(a->naptr.flags, b->naptr.flags) &&
+ streq(a->naptr.services, b->naptr.services) &&
+ streq(a->naptr.regexp, b->naptr.regexp);
+
case DNS_TYPE_OPENPGPKEY:
default:
return FIELD_EQUAL(a->generic, b->generic, data);
@@ -831,6 +870,107 @@ static char *format_txt(DnsTxtItem *first) {
return s;
}
+static char *format_svc_param_value(DnsSvcParam *i) {
+ _cleanup_free_ char *value = NULL;
+
+ assert(i);
+
+ switch (i->key) {
+ case DNS_SVC_PARAM_KEY_ALPN: {
+ size_t offset = 0;
+ _cleanup_strv_free_ char **values_strv = NULL;
+ while (offset < i->length) {
+ size_t sz = (uint8_t) i->value[offset++];
+
+ char *alpn = cescape_length((char *)&i->value[offset], sz);
+ if (!alpn)
+ return NULL;
+
+ if (strv_push(&values_strv, alpn) < 0)
+ return NULL;
+
+ offset += sz;
+ }
+ value = strv_join(values_strv, ",");
+ if (!value)
+ return NULL;
+ break;
+
+ }
+ case DNS_SVC_PARAM_KEY_PORT: {
+ uint16_t port = unaligned_read_be16(i->value);
+ if (asprintf(&value, "%" PRIu16, port) < 0)
+ return NULL;
+ return TAKE_PTR(value);
+ }
+ case DNS_SVC_PARAM_KEY_IPV4HINT: {
+ const struct in_addr *addrs = i->value_in_addr;
+ _cleanup_strv_free_ char **values_strv = NULL;
+ for (size_t n = 0; n < i->length / sizeof (struct in_addr); n++) {
+ char *addr;
+ if (in_addr_to_string(AF_INET, (const union in_addr_union*) &addrs[n], &addr) < 0)
+ return NULL;
+ if (strv_push(&values_strv, addr) < 0)
+ return NULL;
+ }
+ return strv_join(values_strv, ",");
+ }
+ case DNS_SVC_PARAM_KEY_IPV6HINT: {
+ const struct in6_addr *addrs = i->value_in6_addr;
+ _cleanup_strv_free_ char **values_strv = NULL;
+ for (size_t n = 0; n < i->length / sizeof (struct in6_addr); n++) {
+ char *addr;
+ if (in_addr_to_string(AF_INET6, (const union in_addr_union*) &addrs[n], &addr) < 0)
+ return NULL;
+ if (strv_push(&values_strv, addr) < 0)
+ return NULL;
+ }
+ return strv_join(values_strv, ",");
+ }
+ default: {
+ value = decescape((char *)&i->value, " ,", i->length);
+ if (!value)
+ return NULL;
+ break;
+ }
+ }
+
+ char *qvalue;
+ if (asprintf(&qvalue, "\"%s\"", value) < 0)
+ return NULL;
+ return qvalue;
+}
+
+static char *format_svc_param(DnsSvcParam *i) {
+ const char *key = FORMAT_DNS_SVC_PARAM_KEY(i->key);
+ _cleanup_free_ char *value = NULL;
+
+ assert(i);
+
+ if (i->length == 0)
+ return strdup(key);
+
+ value = format_svc_param_value(i);
+ if (!value)
+ return NULL;
+
+ return strjoin(key, "=", value);
+}
+
+static char *format_svc_params(DnsSvcParam *first) {
+ _cleanup_strv_free_ char **params = NULL;
+
+ LIST_FOREACH(params, i, first) {
+ char *param = format_svc_param(i);
+ if (!param)
+ return NULL;
+ if (strv_push(&params, param) < 0)
+ return NULL;
+ }
+
+ return strv_join(params, " ");
+}
+
const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
_cleanup_free_ char *s = NULL, *t = NULL;
char k[DNS_RESOURCE_KEY_STRING_MAX];
@@ -1141,6 +1281,19 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ t = format_svc_params(rr->svcb.params);
+ if (!t)
+ return NULL;
+ r = asprintf(&s, "%s %d %s %s", k, rr->svcb.priority,
+ isempty(rr->svcb.target_name) ? "." : rr->svcb.target_name,
+ t);
+ if (r < 0)
+ return NULL;
+
+ break;
+
case DNS_TYPE_OPENPGPKEY:
r = asprintf(&s, "%s", k);
if (r < 0)
@@ -1153,6 +1306,31 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
return NULL;
break;
+ case DNS_TYPE_NAPTR: {
+ _cleanup_free_ char *tt = NULL, *ttt = NULL;
+
+ t = octescape(rr->naptr.flags, SIZE_MAX);
+ if (!t)
+ return NULL;
+
+ tt = octescape(rr->naptr.services, SIZE_MAX);
+ if (!tt)
+ return NULL;
+
+ ttt = octescape(rr->naptr.regexp, SIZE_MAX);
+ if (!ttt)
+ return NULL;
+
+ if (asprintf(&s, "%" PRIu16 " %" PRIu16 " \"%s\" \"%s\" \"%s\" %s.",
+ rr->naptr.order,
+ rr->naptr.preference,
+ t,
+ tt,
+ ttt,
+ rr->naptr.replacement) < 0)
+ return NULL;
+ break;
+ }
default:
/* Format as documented in RFC 3597, Section 5 */
if (rr->generic.data_size == 0)
@@ -1344,9 +1522,9 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash *
switch (rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) {
case DNS_TYPE_SRV:
- siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state);
- siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state);
- siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state);
+ siphash24_compress_typesafe(rr->srv.priority, state);
+ siphash24_compress_typesafe(rr->srv.weight, state);
+ siphash24_compress_typesafe(rr->srv.port, state);
dns_name_hash_func(rr->srv.name, state);
break;
@@ -1375,59 +1553,59 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash *
}
case DNS_TYPE_A:
- siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state);
+ siphash24_compress_typesafe(rr->a.in_addr, state);
break;
case DNS_TYPE_AAAA:
- siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state);
+ siphash24_compress_typesafe(rr->aaaa.in6_addr, state);
break;
case DNS_TYPE_SOA:
dns_name_hash_func(rr->soa.mname, state);
dns_name_hash_func(rr->soa.rname, state);
- siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state);
- siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state);
- siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state);
- siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state);
- siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state);
+ siphash24_compress_typesafe(rr->soa.serial, state);
+ siphash24_compress_typesafe(rr->soa.refresh, state);
+ siphash24_compress_typesafe(rr->soa.retry, state);
+ siphash24_compress_typesafe(rr->soa.expire, state);
+ siphash24_compress_typesafe(rr->soa.minimum, state);
break;
case DNS_TYPE_MX:
- siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state);
+ siphash24_compress_typesafe(rr->mx.priority, state);
dns_name_hash_func(rr->mx.exchange, state);
break;
case DNS_TYPE_LOC:
- siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state);
- siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state);
- siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state);
- siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state);
- siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state);
- siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state);
- siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state);
+ siphash24_compress_typesafe(rr->loc.version, state);
+ siphash24_compress_typesafe(rr->loc.size, state);
+ siphash24_compress_typesafe(rr->loc.horiz_pre, state);
+ siphash24_compress_typesafe(rr->loc.vert_pre, state);
+ siphash24_compress_typesafe(rr->loc.latitude, state);
+ siphash24_compress_typesafe(rr->loc.longitude, state);
+ siphash24_compress_typesafe(rr->loc.altitude, state);
break;
case DNS_TYPE_SSHFP:
- siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state);
- siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state);
+ siphash24_compress_typesafe(rr->sshfp.algorithm, state);
+ siphash24_compress_typesafe(rr->sshfp.fptype, state);
siphash24_compress_safe(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state);
break;
case DNS_TYPE_DNSKEY:
- siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state);
- siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state);
- siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state);
+ siphash24_compress_typesafe(rr->dnskey.flags, state);
+ siphash24_compress_typesafe(rr->dnskey.protocol, state);
+ siphash24_compress_typesafe(rr->dnskey.algorithm, state);
siphash24_compress_safe(rr->dnskey.key, rr->dnskey.key_size, state);
break;
case DNS_TYPE_RRSIG:
- siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state);
- siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state);
- siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state);
- siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state);
- siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state);
- siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state);
- siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state);
+ siphash24_compress_typesafe(rr->rrsig.type_covered, state);
+ siphash24_compress_typesafe(rr->rrsig.algorithm, state);
+ siphash24_compress_typesafe(rr->rrsig.labels, state);
+ siphash24_compress_typesafe(rr->rrsig.original_ttl, state);
+ siphash24_compress_typesafe(rr->rrsig.expiration, state);
+ siphash24_compress_typesafe(rr->rrsig.inception, state);
+ siphash24_compress_typesafe(rr->rrsig.key_tag, state);
dns_name_hash_func(rr->rrsig.signer, state);
siphash24_compress_safe(rr->rrsig.signature, rr->rrsig.signature_size, state);
break;
@@ -1440,34 +1618,53 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash *
break;
case DNS_TYPE_DS:
- siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state);
- siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state);
- siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state);
+ siphash24_compress_typesafe(rr->ds.key_tag, state);
+ siphash24_compress_typesafe(rr->ds.algorithm, state);
+ siphash24_compress_typesafe(rr->ds.digest_type, state);
siphash24_compress_safe(rr->ds.digest, rr->ds.digest_size, state);
break;
case DNS_TYPE_NSEC3:
- siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state);
- siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state);
- siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state);
+ siphash24_compress_typesafe(rr->nsec3.algorithm, state);
+ siphash24_compress_typesafe(rr->nsec3.flags, state);
+ siphash24_compress_typesafe(rr->nsec3.iterations, state);
siphash24_compress_safe(rr->nsec3.salt, rr->nsec3.salt_size, state);
siphash24_compress_safe(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state);
/* FIXME: We leave the bitmaps out */
break;
case DNS_TYPE_TLSA:
- siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state);
- siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state);
- siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state);
+ siphash24_compress_typesafe(rr->tlsa.cert_usage, state);
+ siphash24_compress_typesafe(rr->tlsa.selector, state);
+ siphash24_compress_typesafe(rr->tlsa.matching_type, state);
siphash24_compress_safe(rr->tlsa.data, rr->tlsa.data_size, state);
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ dns_name_hash_func(rr->svcb.target_name, state);
+ siphash24_compress_typesafe(rr->svcb.priority, state);
+ LIST_FOREACH(params, j, rr->svcb.params) {
+ siphash24_compress_typesafe(j->key, state);
+ siphash24_compress_safe(j->value, j->length, state);
+ }
+ break;
+
case DNS_TYPE_CAA:
- siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state);
+ siphash24_compress_typesafe(rr->caa.flags, state);
string_hash_func(rr->caa.tag, state);
siphash24_compress_safe(rr->caa.value, rr->caa.value_size, state);
break;
+ case DNS_TYPE_NAPTR:
+ siphash24_compress_typesafe(rr->naptr.order, state);
+ siphash24_compress_typesafe(rr->naptr.preference, state);
+ string_hash_func(rr->naptr.flags, state);
+ string_hash_func(rr->naptr.services, state);
+ string_hash_func(rr->naptr.regexp, state);
+ dns_name_hash_func(rr->naptr.replacement, state);
+ break;
+
case DNS_TYPE_OPENPGPKEY:
default:
siphash24_compress_safe(rr->generic.data, rr->generic.data_size, state);
@@ -1675,6 +1872,34 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) {
copy->caa.value_size = rr->caa.value_size;
break;
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS:
+ copy->svcb.priority = rr->svcb.priority;
+ copy->svcb.target_name = strdup(rr->svcb.target_name);
+ if (!copy->svcb.target_name)
+ return NULL;
+ copy->svcb.params = dns_svc_params_copy(rr->svcb.params);
+ if (rr->svcb.params && !copy->svcb.params)
+ return NULL;
+ break;
+
+ case DNS_TYPE_NAPTR:
+ copy->naptr.order = rr->naptr.order;
+ copy->naptr.preference = rr->naptr.preference;
+ copy->naptr.flags = strdup(rr->naptr.flags);
+ if (!copy->naptr.flags)
+ return NULL;
+ copy->naptr.services = strdup(rr->naptr.services);
+ if (!copy->naptr.services)
+ return NULL;
+ copy->naptr.regexp = strdup(rr->naptr.regexp);
+ if (!copy->naptr.regexp)
+ return NULL;
+ copy->naptr.replacement = strdup(rr->naptr.replacement);
+ if (!copy->naptr.replacement)
+ return NULL;
+ break;
+
case DNS_TYPE_OPT:
default:
copy->generic.data = memdup(rr->generic.data, rr->generic.data_size);
@@ -1789,6 +2014,13 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *first) {
return NULL;
}
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *first) {
+ LIST_FOREACH(params, i, first)
+ free(i);
+
+ return NULL;
+}
+
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
DnsTxtItem *bb = b;
@@ -1825,6 +2057,45 @@ DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) {
return copy;
}
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b) {
+ DnsSvcParam *bb = b;
+
+ if (a == b)
+ return true;
+
+ LIST_FOREACH(params, aa, a) {
+ if (!bb)
+ return false;
+
+ if (aa->key != bb->key)
+ return false;
+
+ if (memcmp_nn(aa->value, aa->length, bb->value, bb->length) != 0)
+ return false;
+
+ bb = bb->params_next;
+ }
+
+ return !bb;
+}
+
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first) {
+ DnsSvcParam *copy = NULL, *end = NULL;
+
+ LIST_FOREACH(params, i, first) {
+ DnsSvcParam *j;
+
+ j = memdup(i, offsetof(DnsSvcParam, value) + i->length);
+ if (!j)
+ return dns_svc_param_free_all(copy);
+
+ LIST_INSERT_AFTER(params, copy, end, j);
+ end = j;
+ }
+
+ return copy;
+}
+
int dns_txt_item_new_empty(DnsTxtItem **ret) {
DnsTxtItem *i;
@@ -1947,10 +2218,33 @@ static int txt_to_json(DnsTxtItem *items, JsonVariant **ret) {
r = json_variant_new_array(ret, elements, n);
finalize:
- for (size_t i = 0; i < n; i++)
- json_variant_unref(elements[i]);
+ json_variant_unref_many(elements, n);
+ return r;
+}
+
+static int svc_params_to_json(DnsSvcParam *params, JsonVariant **ret) {
+ JsonVariant **elements = NULL;
+ size_t n = 0;
+ int r;
+
+ assert(ret);
+
+ LIST_FOREACH(params, i, params) {
+ if (!GREEDY_REALLOC(elements, n + 1)) {
+ r = -ENOMEM;
+ goto finalize;
+ }
- free(elements);
+ r = json_variant_new_base64(elements + n, i->value, i->length);
+ if (r < 0)
+ goto finalize;
+
+ n++;
+ }
+
+ r = json_variant_new_array(ret, elements, n);
+finalize:
+ json_variant_unref_many(elements, n);
return r;
}
@@ -2129,6 +2423,21 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) {
JSON_BUILD_PAIR("matchingType", JSON_BUILD_UNSIGNED(rr->tlsa.matching_type)),
JSON_BUILD_PAIR("data", JSON_BUILD_HEX(rr->tlsa.data, rr->tlsa.data_size))));
+ case DNS_TYPE_SVCB:
+ case DNS_TYPE_HTTPS: {
+ _cleanup_(json_variant_unrefp) JsonVariant *p = NULL;
+ r = svc_params_to_json(rr->svcb.params, &p);
+ if (r < 0)
+ return r;
+
+ return json_build(ret,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)),
+ JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->svcb.priority)),
+ JSON_BUILD_PAIR("target", JSON_BUILD_STRING(rr->svcb.target_name)),
+ JSON_BUILD_PAIR("params", JSON_BUILD_VARIANT(p))));
+ }
+
case DNS_TYPE_CAA:
return json_build(ret,
JSON_BUILD_OBJECT(
@@ -2137,6 +2446,18 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) {
JSON_BUILD_PAIR("tag", JSON_BUILD_STRING(rr->caa.tag)),
JSON_BUILD_PAIR("value", JSON_BUILD_OCTESCAPE(rr->caa.value, rr->caa.value_size))));
+ case DNS_TYPE_NAPTR:
+ return json_build(ret,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)),
+ JSON_BUILD_PAIR("order", JSON_BUILD_UNSIGNED(rr->naptr.order)),
+ JSON_BUILD_PAIR("preference", JSON_BUILD_UNSIGNED(rr->naptr.preference)),
+ /* NB: we name this flags field here naptrFlags, because there's already another "flags" field (for example in CAA) which has a different type */
+ JSON_BUILD_PAIR("naptrFlags", JSON_BUILD_STRING(rr->naptr.flags)),
+ JSON_BUILD_PAIR("services", JSON_BUILD_STRING(rr->naptr.services)),
+ JSON_BUILD_PAIR("regexp", JSON_BUILD_STRING(rr->naptr.regexp)),
+ JSON_BUILD_PAIR("replacement", JSON_BUILD_STRING(rr->naptr.replacement))));
+
default:
/* Can't provide broken-down format */
*ret = NULL;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 1a12933..156fa01 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -16,6 +16,7 @@
typedef struct DnsResourceKey DnsResourceKey;
typedef struct DnsResourceRecord DnsResourceRecord;
typedef struct DnsTxtItem DnsTxtItem;
+typedef struct DnsSvcParam DnsSvcParam;
/* DNSKEY RR flags */
#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0)
@@ -90,6 +91,17 @@ struct DnsTxtItem {
uint8_t data[];
};
+struct DnsSvcParam {
+ uint16_t key;
+ size_t length;
+ LIST_FIELDS(DnsSvcParam, params);
+ union {
+ DECLARE_FLEX_ARRAY(uint8_t, value);
+ DECLARE_FLEX_ARRAY(struct in_addr, value_in_addr);
+ DECLARE_FLEX_ARRAY(struct in6_addr, value_in6_addr);
+ };
+};
+
struct DnsResourceRecord {
unsigned n_ref;
uint32_t ttl;
@@ -243,6 +255,13 @@ struct DnsResourceRecord {
uint8_t matching_type;
} tlsa;
+ /* https://tools.ietf.org/html/rfc9460 */
+ struct {
+ uint16_t priority;
+ char *target_name;
+ DnsSvcParam *params;
+ } svcb, https;
+
/* https://tools.ietf.org/html/rfc6844 */
struct {
char *tag;
@@ -251,6 +270,16 @@ struct DnsResourceRecord {
uint8_t flags;
} caa;
+
+ /* https://datatracker.ietf.org/doc/html/rfc2915 */
+ struct {
+ uint16_t order;
+ uint16_t preference;
+ char *flags;
+ char *services;
+ char *regexp;
+ char *replacement;
+ } naptr;
};
/* Note: fields should be ordered to minimize alignment gaps. Use pahole! */
@@ -369,6 +398,10 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i);
int dns_txt_item_new_empty(DnsTxtItem **ret);
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *i);
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b);
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first);
+
int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size);
int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index af8e9cd..17bc823 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -12,6 +12,7 @@
#include "random-util.h"
#include "resolved-dnssd.h"
#include "resolved-dns-scope.h"
+#include "resolved-dns-synthesize.h"
#include "resolved-dns-zone.h"
#include "resolved-llmnr.h"
#include "resolved-mdns.h"
@@ -41,6 +42,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
.protocol = protocol,
.family = family,
.resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC,
+ .mdns_goodbye_event_source = NULL,
};
if (protocol == DNS_PROTOCOL_DNS) {
@@ -115,6 +117,8 @@ DnsScope* dns_scope_free(DnsScope *s) {
sd_event_source_disable_unref(s->announce_event_source);
+ sd_event_source_disable_unref(s->mdns_goodbye_event_source);
+
dns_cache_flush(&s->cache);
dns_zone_flush(&s->zone);
@@ -616,12 +620,13 @@ static bool dns_refuse_special_use_domain(const char *domain, DnsQuestion *quest
DnsScopeMatch dns_scope_good_domain(
DnsScope *s,
- DnsQuery *q) {
+ DnsQuery *q,
+ uint64_t query_flags) {
DnsQuestion *question;
const char *domain;
uint64_t flags;
- int ifindex;
+ int ifindex, r;
/* This returns the following return values:
*
@@ -680,6 +685,15 @@ DnsScopeMatch dns_scope_good_domain(
is_dns_proxy_stub_hostname(domain))
return DNS_SCOPE_NO;
+ /* Don't look up the local host name via the network, unless user turned of local synthesis of it */
+ if (manager_is_own_hostname(s->manager, domain) && shall_synthesize_own_hostname_rrs())
+ return DNS_SCOPE_NO;
+
+ /* Never send SOA or NS or DNSSEC request to LLMNR, where they make little sense. */
+ r = dns_question_types_suitable_for_protocol(question, s->protocol);
+ if (r <= 0)
+ return DNS_SCOPE_NO;
+
switch (s->protocol) {
case DNS_PROTOCOL_DNS: {
@@ -735,7 +749,8 @@ DnsScopeMatch dns_scope_good_domain(
/* If ResolveUnicastSingleLabel=yes and the query is single-label, then bump match result
to prevent LLMNR monopoly among candidates. */
- if (s->manager->resolve_unicast_single_label && dns_name_is_single_label(domain))
+ if ((s->manager->resolve_unicast_single_label || (query_flags & SD_RESOLVED_RELAX_SINGLE_LABEL)) &&
+ dns_name_is_single_label(domain))
return DNS_SCOPE_YES_BASE + 1;
/* Let's return the number of labels in the best matching result */
@@ -1588,7 +1603,7 @@ int dns_scope_add_dnssd_services(DnsScope *scope) {
assert(scope);
- if (hashmap_size(scope->manager->dnssd_services) == 0)
+ if (hashmap_isempty(scope->manager->dnssd_services))
return 0;
scope->announced = false;
@@ -1600,6 +1615,12 @@ int dns_scope_add_dnssd_services(DnsScope *scope) {
if (r < 0)
log_warning_errno(r, "Failed to add PTR record to MDNS zone: %m");
+ if (service->sub_ptr_rr) {
+ r = dns_zone_put(&scope->zone, scope, service->sub_ptr_rr, false);
+ if (r < 0)
+ log_warning_errno(r, "Failed to add selective PTR record to MDNS zone: %m");
+ }
+
r = dns_zone_put(&scope->zone, scope, service->srv_rr, true);
if (r < 0)
log_warning_errno(r, "Failed to add SRV record to MDNS zone: %m");
@@ -1632,6 +1653,7 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) {
HASHMAP_FOREACH(service, scope->manager->dnssd_services) {
dns_zone_remove_rr(&scope->zone, service->ptr_rr);
+ dns_zone_remove_rr(&scope->zone, service->sub_ptr_rr);
dns_zone_remove_rr(&scope->zone, service->srv_rr);
LIST_FOREACH(items, txt_data, service->txt_data_items)
dns_zone_remove_rr(&scope->zone, txt_data->rr);
@@ -1712,3 +1734,65 @@ int dns_scope_dump_cache_to_json(DnsScope *scope, JsonVariant **ret) {
JSON_BUILD_PAIR_CONDITION(scope->link, "ifname", JSON_BUILD_STRING(scope->link ? scope->link->ifname : NULL)),
JSON_BUILD_PAIR_VARIANT("cache", cache)));
}
+
+int dns_type_suitable_for_protocol(uint16_t type, DnsProtocol protocol) {
+
+ /* Tests whether it makes sense to route queries for the specified DNS RR types to the specified
+ * protocol. For classic DNS pretty much all RR types are suitable, but for LLMNR/mDNS let's
+ * allowlist only a few that make sense. We use this when routing queries so that we can more quickly
+ * return errors for queries that will almost certainly fail/time-out otherwise. For example, this
+ * ensures that SOA, NS, or DS/DNSKEY queries are never routed to mDNS/LLMNR where they simply make
+ * no sense. */
+
+ if (dns_type_is_obsolete(type))
+ return false;
+
+ if (!dns_type_is_valid_query(type))
+ return false;
+
+ switch (protocol) {
+
+ case DNS_PROTOCOL_DNS:
+ return true;
+
+ case DNS_PROTOCOL_LLMNR:
+ return IN_SET(type,
+ DNS_TYPE_ANY,
+ DNS_TYPE_A,
+ DNS_TYPE_AAAA,
+ DNS_TYPE_CNAME,
+ DNS_TYPE_PTR,
+ DNS_TYPE_TXT);
+
+ case DNS_PROTOCOL_MDNS:
+ return IN_SET(type,
+ DNS_TYPE_ANY,
+ DNS_TYPE_A,
+ DNS_TYPE_AAAA,
+ DNS_TYPE_CNAME,
+ DNS_TYPE_PTR,
+ DNS_TYPE_TXT,
+ DNS_TYPE_SRV,
+ DNS_TYPE_NSEC,
+ DNS_TYPE_HINFO);
+
+ default:
+ return -EPROTONOSUPPORT;
+ }
+}
+
+int dns_question_types_suitable_for_protocol(DnsQuestion *q, DnsProtocol protocol) {
+ DnsResourceKey *key;
+ int r;
+
+ /* Tests whether the types in the specified question make any sense to be routed to the specified
+ * protocol, i.e. if dns_type_suitable_for_protocol() is true for any of the contained RR types */
+
+ DNS_QUESTION_FOREACH(key, q) {
+ r = dns_type_suitable_for_protocol(key->type, protocol);
+ if (r != 0)
+ return r;
+ }
+
+ return false;
+}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index b1d1206..23f147b 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -46,6 +46,8 @@ struct DnsScope {
sd_event_source *announce_event_source;
+ sd_event_source *mdns_goodbye_event_source;
+
RateLimit ratelimit;
usec_t resend_timeout;
@@ -77,7 +79,7 @@ int dns_scope_emit_udp(DnsScope *s, int fd, int af, DnsPacket *p);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port, union sockaddr_union *ret_socket_address);
int dns_scope_socket_udp(DnsScope *s, DnsServer *server);
-DnsScopeMatch dns_scope_good_domain(DnsScope *s, DnsQuery *q);
+DnsScopeMatch dns_scope_good_domain(DnsScope *s, DnsQuery *q, uint64_t query_flags);
bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key);
DnsServer *dns_scope_get_dns_server(DnsScope *s);
@@ -113,3 +115,6 @@ int dns_scope_remove_dnssd_services(DnsScope *scope);
bool dns_scope_is_default_route(DnsScope *scope);
int dns_scope_dump_cache_to_json(DnsScope *scope, JsonVariant **ret);
+
+int dns_type_suitable_for_protocol(uint16_t type, DnsProtocol protocol);
+int dns_question_types_suitable_for_protocol(DnsQuestion *q, DnsProtocol protocol);
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index b7db839..340f11f 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -28,7 +28,8 @@ int dns_server_new(
const union in_addr_union *in_addr,
uint16_t port,
int ifindex,
- const char *server_name) {
+ const char *server_name,
+ ResolveConfigSource config_source) {
_cleanup_free_ char *name = NULL;
DnsServer *s;
@@ -67,6 +68,7 @@ int dns_server_new(
.port = port,
.ifindex = ifindex,
.server_name = TAKE_PTR(name),
+ .config_source = config_source,
};
dns_server_reset_features(s);
@@ -751,10 +753,10 @@ size_t dns_server_get_mtu(DnsServer *s) {
static void dns_server_hash_func(const DnsServer *s, struct siphash *state) {
assert(s);
- siphash24_compress(&s->family, sizeof(s->family), state);
- siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state);
- siphash24_compress(&s->port, sizeof(s->port), state);
- siphash24_compress(&s->ifindex, sizeof(s->ifindex), state);
+ siphash24_compress_typesafe(s->family, state);
+ in_addr_hash_func(&s->address, s->family, state);
+ siphash24_compress_typesafe(s->port, state);
+ siphash24_compress_typesafe(s->ifindex, state);
siphash24_compress_string(s->server_name, state);
}
@@ -794,6 +796,17 @@ void dns_server_unlink_all(DnsServer *first) {
dns_server_unlink_all(next);
}
+void dns_server_unlink_on_reload(DnsServer *server) {
+ while (server) {
+ DnsServer *next = server->servers_next;
+
+ if (server->config_source == RESOLVE_CONFIG_SOURCE_FILE)
+ dns_server_unlink(server);
+
+ server = next;
+ }
+}
+
bool dns_server_unlink_marked(DnsServer *server) {
bool changed = false;
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index ed6560f..ef76bbc 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -24,6 +24,8 @@ typedef enum DnsServerType {
_DNS_SERVER_TYPE_INVALID = -EINVAL,
} DnsServerType;
+#include "resolved-conf.h"
+
const char* dns_server_type_to_string(DnsServerType i) _const_;
DnsServerType dns_server_type_from_string(const char *s) _pure_;
@@ -100,6 +102,9 @@ struct DnsServer {
/* If linked is set, then this server appears in the servers linked list */
bool linked:1;
LIST_FIELDS(DnsServer, servers);
+
+ /* Servers registered via D-Bus are not removed on reload */
+ ResolveConfigSource config_source;
};
int dns_server_new(
@@ -111,7 +116,8 @@ int dns_server_new(
const union in_addr_union *address,
uint16_t port,
int ifindex,
- const char *server_string);
+ const char *server_string,
+ ResolveConfigSource config_source);
DnsServer* dns_server_ref(DnsServer *s);
DnsServer* dns_server_unref(DnsServer *s);
@@ -145,6 +151,7 @@ void dns_server_warn_downgrade(DnsServer *server);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, uint16_t port, int ifindex, const char *name);
void dns_server_unlink_all(DnsServer *first);
+void dns_server_unlink_on_reload(DnsServer *server);
bool dns_server_unlink_marked(DnsServer *first);
void dns_server_mark_all(DnsServer *first);
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
index 056ba77..1a43d0b 100644
--- a/src/resolve/resolved-dns-stream.c
+++ b/src/resolve/resolved-dns-stream.c
@@ -195,7 +195,7 @@ static int dns_stream_identify(DnsStream *s) {
/* Make sure all packets for this connection are sent on the same interface */
r = socket_set_unicast_if(s->fd, s->local.sa.sa_family, s->ifindex);
if (r < 0)
- log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF/IPV6_UNICAST_IF: %m");
+ log_debug_errno(r, "Failed to invoke IP_UNICAST_IF/IPV6_UNICAST_IF: %m");
}
s->identified = true;
@@ -454,7 +454,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use
if (progressed && s->timeout_event_source) {
r = sd_event_source_set_time_relative(s->timeout_event_source, DNS_STREAM_ESTABLISHED_TIMEOUT_USEC);
if (r < 0)
- log_warning_errno(errno, "Couldn't restart TCP connection timeout, ignoring: %m");
+ log_warning_errno(r, "Couldn't restart TCP connection timeout, ignoring: %m");
}
return 0;
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
index 10b35da..23d4db9 100644
--- a/src/resolve/resolved-dns-stub.c
+++ b/src/resolve/resolved-dns-stub.c
@@ -27,10 +27,10 @@ static int manager_dns_stub_fd(Manager *m, int family, const union in_addr_union
static void dns_stub_listener_extra_hash_func(const DnsStubListenerExtra *a, struct siphash *state) {
assert(a);
- siphash24_compress(&a->mode, sizeof(a->mode), state);
- siphash24_compress(&a->family, sizeof(a->family), state);
- siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
- siphash24_compress(&a->port, sizeof(a->port), state);
+ siphash24_compress_typesafe(a->mode, state);
+ siphash24_compress_typesafe(a->family, state);
+ in_addr_hash_func(&a->address, a->family, state);
+ siphash24_compress_typesafe(a->port, state);
}
static int dns_stub_listener_extra_compare_func(const DnsStubListenerExtra *a, const DnsStubListenerExtra *b) {
@@ -94,11 +94,11 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) {
static void stub_packet_hash_func(const DnsPacket *p, struct siphash *state) {
assert(p);
- siphash24_compress(&p->protocol, sizeof(p->protocol), state);
- siphash24_compress(&p->family, sizeof(p->family), state);
- siphash24_compress(&p->sender, sizeof(p->sender), state);
- siphash24_compress(&p->ipproto, sizeof(p->ipproto), state);
- siphash24_compress(&p->sender_port, sizeof(p->sender_port), state);
+ siphash24_compress_typesafe(p->protocol, state);
+ siphash24_compress_typesafe(p->family, state);
+ siphash24_compress_typesafe(p->sender, state);
+ siphash24_compress_typesafe(p->ipproto, state);
+ siphash24_compress_typesafe(p->sender_port, state);
siphash24_compress(DNS_PACKET_HEADER(p), sizeof(DnsPacketHeader), state);
/* We don't bother hashing the full packet here, just the header */
@@ -937,7 +937,7 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
return;
}
- if (dns_type_is_zone_transer(dns_question_first_key(p->question)->type)) {
+ if (dns_type_is_zone_transfer(dns_question_first_key(p->question)->type)) {
log_debug("Got request for zone transfer, refusing.");
dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false);
return;
@@ -966,8 +966,8 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
log_debug("Got request to DNS proxy address 127.0.0.54, enabling bypass logic.");
bypass = true;
protocol_flags = SD_RESOLVED_DNS|SD_RESOLVED_NO_ZONE; /* Turn off mDNS/LLMNR for proxy stub. */
- } else if ((DNS_PACKET_DO(p) && DNS_PACKET_CD(p))) {
- log_debug("Got request with DNSSEC checking disabled, enabling bypass logic.");
+ } else if (DNS_PACKET_DO(p)) {
+ log_debug("Got request with DNSSEC enabled, enabling bypass logic.");
bypass = true;
}
@@ -978,7 +978,8 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
SD_RESOLVED_NO_SEARCH|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_REQUIRE_PRIMARY|
- SD_RESOLVED_CLAMP_TTL);
+ SD_RESOLVED_CLAMP_TTL|
+ SD_RESOLVED_RELAX_SINGLE_LABEL);
else
r = dns_query_new(m, &q, p->question, p->question, NULL, 0,
protocol_flags|
diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c
index 6144dc0..cccea54 100644
--- a/src/resolve/resolved-dns-synthesize.c
+++ b/src/resolve/resolved-dns-synthesize.c
@@ -439,6 +439,20 @@ static int synthesize_gateway_ptr(
return answer_add_addresses_ptr(answer, "_gateway", addresses, n, af, address);
}
+bool shall_synthesize_own_hostname_rrs(void) {
+ static int cached = -1;
+ int r;
+
+ if (cached >= 0)
+ return cached;
+
+ r = secure_getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME");
+ if (r < 0 && r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME: %m");
+
+ return (cached = (r != 0));
+}
+
int dns_synthesize_answer(
Manager *m,
DnsQuestion *q,
@@ -479,8 +493,9 @@ int dns_synthesize_answer(
} else if (manager_is_own_hostname(m, name)) {
- if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0)
+ if (!shall_synthesize_own_hostname_rrs())
continue;
+
r = synthesize_system_hostname_rr(m, key, ifindex, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize system hostname RRs: %m");
@@ -530,7 +545,7 @@ int dns_synthesize_answer(
} else if (dns_name_address(name, &af, &address) > 0) {
int v, w, u;
- if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0)
+ if (!shall_synthesize_own_hostname_rrs())
continue;
v = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer);
diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h
index bf271e8..ca39e68 100644
--- a/src/resolve/resolved-dns-synthesize.h
+++ b/src/resolve/resolved-dns-synthesize.h
@@ -9,3 +9,5 @@ int dns_synthesize_family(uint64_t flags);
DnsProtocol dns_synthesize_protocol(uint64_t flags);
int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret);
+
+bool shall_synthesize_own_hostname_rrs(void);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index ad8b88e..92ac075 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -28,6 +28,8 @@ static void dns_transaction_reset_answer(DnsTransaction *t) {
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
+ t->answer_ede_rcode = _DNS_EDE_RCODE_INVALID;
+ t->answer_ede_msg = mfree(t->answer_ede_msg);
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_query_flags = 0;
@@ -74,8 +76,12 @@ static void dns_transaction_close_connection(
* and the reply we might still get from the server will be eaten up instead of resulting in an ICMP
* port unreachable error message. */
- /* Skip the graveyard stuff when we're shutting down, since that requires running event loop */
- if (!t->scope->manager->event || sd_event_get_state(t->scope->manager->event) == SD_EVENT_FINISHED)
+ /* Skip the graveyard stuff when we're shutting down, since that requires running event loop.
+ * Note that this is also called from dns_transaction_free(). In that case, scope may be NULL. */
+ if (!t->scope ||
+ !t->scope->manager ||
+ !t->scope->manager->event ||
+ sd_event_get_state(t->scope->manager->event) == SD_EVENT_FINISHED)
use_graveyard = false;
if (use_graveyard && t->dns_udp_fd >= 0 && t->sent && !t->received) {
@@ -283,6 +289,7 @@ int dns_transaction_new(
.dns_udp_fd = -EBADF,
.answer_source = _DNS_TRANSACTION_SOURCE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
+ .answer_ede_rcode = _DNS_EDE_RCODE_INVALID,
.answer_nsec_ttl = UINT32_MAX,
.key = dns_resource_key_ref(key),
.query_flags = query_flags,
@@ -496,7 +503,7 @@ static int dns_transaction_pick_server(DnsTransaction *t) {
dns_server_unref(t->server);
t->server = dns_server_ref(server);
- t->n_picked_servers ++;
+ t->n_picked_servers++;
log_debug("Using DNS server %s for transaction %u.", strna(dns_server_string_full(t->server)), t->id);
@@ -895,8 +902,25 @@ static int dns_transaction_dnssec_ready(DnsTransaction *t) {
/* We handle DNSSEC failures different from other errors, as we care about the DNSSEC
* validation result */
- log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result));
- t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */
+ log_debug("Auxiliary DNSSEC RR query failed validation: %s%s%s%s%s%s",
+ dnssec_result_to_string(dt->answer_dnssec_result),
+ dt->answer_ede_rcode >= 0 ? " (" : "",
+ dt->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(dt->answer_ede_rcode) : "",
+ (dt->answer_ede_rcode >= 0 && !isempty(dt->answer_ede_msg)) ? ": " : "",
+ dt->answer_ede_rcode >= 0 ? strempty(dt->answer_ede_msg) : "",
+ dt->answer_ede_rcode >= 0 ? ")" : "");
+
+ /* Copy error code over */
+ t->answer_dnssec_result = dt->answer_dnssec_result;
+ t->answer_ede_rcode = dt->answer_ede_rcode;
+ r = free_and_strdup(&t->answer_ede_msg, dt->answer_ede_msg);
+ if (r < 0)
+ log_oom_debug();
+
+ /* The answer would normally be replaced by the validated subset, but at this point
+ * we aren't going to bother validating the rest, so just drop it. */
+ t->answer = dns_answer_unref(t->answer);
+
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return 0;
@@ -1135,13 +1159,130 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
}
}
+ if (DNS_PACKET_TC(p)) {
+
+ /* Truncated packets for mDNS are not allowed. Give up immediately. */
+ if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
+
+ /* Response was truncated, let's try again with good old TCP */
+ log_debug("Reply truncated, retrying via TCP.");
+ retry_with_tcp = true;
+
+ } else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
+ DNS_PACKET_IS_FRAGMENTED(p)) {
+
+ /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
+ if (t->server)
+ dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
+
+ if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
+ /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
+ * issues. (We don't do that on the lowest feature level however, since crappy DNS
+ * servers often do not implement TCP, hence falling back to TCP on fragmentation is
+ * counter-productive there.) */
+
+ log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)",
+ p->fragsize, p->size);
+ retry_with_tcp = true;
+ }
+ }
+
+ if (retry_with_tcp) {
+ r = dns_transaction_emit_tcp(t);
+ if (r == -ESRCH) {
+ /* No servers found? Damn! */
+ dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
+ return;
+ }
+ if (r == -EOPNOTSUPP) {
+ /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
+ dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
+ return;
+ }
+ if (r < 0) {
+ /* On LLMNR, if we cannot connect to the host,
+ * we immediately give up */
+ if (t->scope->protocol != DNS_PROTOCOL_DNS)
+ goto fail;
+
+ /* On DNS, couldn't send? Try immediately again, with a new server */
+ if (dns_transaction_limited_retry(t))
+ return;
+
+ /* No new server to try, give up */
+ dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
+ }
+
+ return;
+ }
+
+ /* After the superficial checks, actually parse the message. */
+ r = dns_packet_extract(p);
+ if (r < 0) {
+ if (t->server) {
+ dns_server_packet_invalid(t->server, t->current_feature_level);
+
+ r = dns_transaction_maybe_restart(t);
+ if (r < 0)
+ goto fail;
+ if (r > 0) /* Transaction got restarted... */
+ return;
+ }
+
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
+
switch (t->scope->protocol) {
- case DNS_PROTOCOL_DNS:
+ case DNS_PROTOCOL_DNS: {
assert(t->server);
+ (void) dns_packet_ede_rcode(p, &t->answer_ede_rcode, &t->answer_ede_msg);
+
if (!t->bypass &&
IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
+ /* If the server has replied with detailed error data, using a degraded feature set
+ * will likely not help anyone. Examine the detailed error to determine the best
+ * course of action. */
+ if (t->answer_ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) {
+ /* These codes are related to DNSSEC configuration errors. If accurate,
+ * this is the domain operator's problem, and retrying won't help. */
+ if (dns_ede_rcode_is_dnssec(t->answer_ede_rcode)) {
+ log_debug("Server returned error: %s (%s%s%s). Lookup failed.",
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+
+ t->answer_dnssec_result = DNSSEC_UPSTREAM_FAILURE;
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
+ /* These codes probably indicate a transient error. Let's try again. */
+ if (IN_SET(t->answer_ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) {
+ log_debug("Server returned error: %s (%s%s%s), retrying transaction.",
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+ dns_transaction_retry(t, false);
+ return;
+ }
+
+ /* OK, the query failed, but we still shouldn't degrade the feature set for
+ * this server. */
+ log_debug("Server returned error: %s (%s%s%s)",
+ FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode),
+ isempty(t->answer_ede_msg) ? "" : ": ",
+ strempty(t->answer_ede_msg));
+ break;
+ }
/* Request failed, immediately try again with reduced features */
@@ -1198,7 +1339,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) {
/* This server refused our request? If so, try again, use a different server */
- log_debug("Server returned REFUSED, switching servers, and retrying.");
+ if (t->answer_ede_rcode >= 0)
+ log_debug("Server returned REFUSED (%s), switching servers, and retrying.",
+ FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode));
+ else
+ log_debug("Server returned REFUSED, switching servers, and retrying.");
if (dns_transaction_limited_retry(t))
return;
@@ -1210,6 +1355,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
dns_server_packet_truncated(t->server, t->current_feature_level);
break;
+ }
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
@@ -1220,83 +1366,6 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
assert_not_reached();
}
- if (DNS_PACKET_TC(p)) {
-
- /* Truncated packets for mDNS are not allowed. Give up immediately. */
- if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
- dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
- return;
- }
-
- /* Response was truncated, let's try again with good old TCP */
- log_debug("Reply truncated, retrying via TCP.");
- retry_with_tcp = true;
-
- } else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
- DNS_PACKET_IS_FRAGMENTED(p)) {
-
- /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
- if (t->server)
- dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
-
- if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
- /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
- * issues. (We don't do that on the lowest feature level however, since crappy DNS
- * servers often do not implement TCP, hence falling back to TCP on fragmentation is
- * counter-productive there.) */
-
- log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)",
- p->fragsize, p->size);
- retry_with_tcp = true;
- }
- }
-
- if (retry_with_tcp) {
- r = dns_transaction_emit_tcp(t);
- if (r == -ESRCH) {
- /* No servers found? Damn! */
- dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
- return;
- }
- if (r == -EOPNOTSUPP) {
- /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
- dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
- return;
- }
- if (r < 0) {
- /* On LLMNR, if we cannot connect to the host,
- * we immediately give up */
- if (t->scope->protocol != DNS_PROTOCOL_DNS)
- goto fail;
-
- /* On DNS, couldn't send? Try immediately again, with a new server */
- if (dns_transaction_limited_retry(t))
- return;
-
- /* No new server to try, give up */
- dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
- }
-
- return;
- }
-
- /* After the superficial checks, actually parse the message. */
- r = dns_packet_extract(p);
- if (r < 0) {
- if (t->server) {
- dns_server_packet_invalid(t->server, t->current_feature_level);
-
- r = dns_transaction_maybe_restart(t);
- if (r < 0)
- goto fail;
- if (r > 0) /* Transaction got restarted... */
- return;
- }
-
- dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
- return;
- }
-
if (t->server) {
/* Report that we successfully received a valid packet with a good rcode after we initially got a bad
* rcode and subsequently downgraded the protocol */
@@ -1770,8 +1839,12 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
t->answer_source = DNS_TRANSACTION_CACHE;
if (t->answer_rcode == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
- else
+ else {
+ if (t->received)
+ (void) dns_packet_ede_rcode(t->received, &t->answer_ede_rcode, &t->answer_ede_msg);
+
dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
+ }
return 0;
}
}
@@ -2268,9 +2341,9 @@ static int dns_transaction_request_dnssec_rr_full(DnsTransaction *t, DnsResource
r = dns_transaction_go(aux);
if (r < 0)
return r;
- if (ret)
- *ret = aux;
}
+ if (ret)
+ *ret = aux;
return 1;
}
@@ -2467,7 +2540,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
if (r < 0)
return r;
- /* If we are requesting a DNSKEY, we can anticiapte that we will want the matching DS
+ /* If we are requesting a DNSKEY, we can anticipate that we will want the matching DS
* in the near future. Let's request it in advance so we don't have to wait in the
* common case. */
if (aux) {
@@ -2545,6 +2618,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
return r;
if (r == 0)
continue;
+
+ /* If we were looking for the DS RR, don't request it again. */
+ if (dns_transaction_key(t)->type == DNS_TYPE_DS)
+ continue;
}
r = dnssec_has_rrsig(t->answer, rr->key);
@@ -2618,6 +2695,21 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
if (r < 0)
return r;
+ if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && dns_name_is_root(name)) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
+ /* We made it all the way to the root zone. If we are in allow-downgrade
+ * mode, we need to make at least one request that we can be certain should
+ * have been signed, to test for servers that are not dnssec aware. */
+ soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name);
+ if (!soa)
+ return -ENOMEM;
+
+ log_debug("Requesting root zone SOA to probe dnssec support.");
+ r = dns_transaction_request_dnssec_rr(t, soa);
+ if (r < 0)
+ return r;
+ }
+
break;
}
@@ -2676,7 +2768,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
* zones return NXDOMAIN even though they have further children. */
if (chased_insecure || was_signed)
- /* In this case we already reqeusted what we need above. */
+ /* In this case we already requested what we need above. */
name = NULL;
else if (dns_transaction_key(t)->type == DNS_TYPE_DS)
/* If the DS response is empty, we'll walk up the dns labels requesting DS until we
@@ -2844,7 +2936,12 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
if (r == 0)
continue;
- return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED);
+ if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED))
+ return false;
+
+ /* We expect this to be signed when the DS record exists, and don't expect it to be
+ * signed when the DS record is proven not to exist. */
+ return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL);
}
return true;
@@ -3523,14 +3620,17 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
bool have_nsec = false;
r = dnssec_validate_records(t, phase, &have_nsec, &nvalidations, &validated);
- if (r <= 0)
+ if (r <= 0) {
+ DNS_ANSWER_REPLACE(t->answer, TAKE_PTR(validated));
return r;
+ }
if (nvalidations > DNSSEC_VALIDATION_MAX) {
/* This reply requires an onerous number of signature validations to verify. Let's
* not waste our time trying, as this shouldn't happen for well-behaved domains
* anyway. */
t->answer_dnssec_result = DNSSEC_TOO_MANY_VALIDATIONS;
+ DNS_ANSWER_REPLACE(t->answer, TAKE_PTR(validated));
return 0;
}
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index 6be7c5f..30d2167 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -61,6 +61,8 @@ struct DnsTransaction {
DnsAnswer *answer;
int answer_rcode;
+ int answer_ede_rcode;
+ char *answer_ede_msg;
DnssecResult answer_dnssec_result;
DnsTransactionSource answer_source;
uint32_t answer_nsec_ttl;
diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c
index 8aea5e1..9df93f1 100644
--- a/src/resolve/resolved-dns-trust-anchor.c
+++ b/src/resolve/resolved-dns-trust-anchor.c
@@ -186,7 +186,7 @@ static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) {
* trust anchor defined at all. This enables easy overriding
* of negative trust anchors. */
- if (set_size(d->negative_by_name) > 0)
+ if (!set_isempty(d->negative_by_name))
return 0;
r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops);
@@ -233,7 +233,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
return -EINVAL;
}
- r = extract_many_words(&p, NULL, 0, &class, &type, NULL);
+ r = extract_many_words(&p, NULL, 0, &class, &type);
if (r < 0)
return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line);
if (r != 2) {
@@ -253,7 +253,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
int a, dt;
size_t l;
- r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, NULL);
+ r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type);
if (r < 0) {
log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line);
return -EINVAL;
@@ -284,7 +284,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
return -EINVAL;
}
- r = unhexmem(p, strlen(p), &dd, &l);
+ r = unhexmem(p, &dd, &l);
if (r < 0) {
log_warning("Failed to parse DS digest %s on line %s:%u", p, path, line);
return -EINVAL;
@@ -307,7 +307,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
size_t l;
int a;
- r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, NULL);
+ r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm);
if (r < 0)
return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line);
if (r != 3) {
@@ -343,7 +343,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
return -EINVAL;
}
- r = unbase64mem(p, strlen(p), &k, &l);
+ r = unbase64mem(p, &k, &l);
if (r < 0)
return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", p, path, line);
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
index f533f97..d4ede46 100644
--- a/src/resolve/resolved-dns-zone.c
+++ b/src/resolve/resolved-dns-zone.c
@@ -70,8 +70,8 @@ void dns_zone_flush(DnsZone *z) {
while ((i = hashmap_first(z->by_key)))
dns_zone_item_remove_and_free(z, i);
- assert(hashmap_size(z->by_key) == 0);
- assert(hashmap_size(z->by_name) == 0);
+ assert(hashmap_isempty(z->by_key));
+ assert(hashmap_isempty(z->by_name));
z->by_key = hashmap_free(z->by_key);
z->by_name = hashmap_free(z->by_name);
diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c
index 0f0d478..4857a92 100644
--- a/src/resolve/resolved-dnssd-bus.c
+++ b/src/resolve/resolved-dnssd-bus.c
@@ -20,10 +20,14 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_
m = s->manager;
- r = bus_verify_polkit_async(message, CAP_SYS_ADMIN,
- "org.freedesktop.resolve1.unregister-service",
- NULL, false, s->originator,
- &m->polkit_registry, error);
+ r = bus_verify_polkit_async_full(
+ message,
+ "org.freedesktop.resolve1.unregister-service",
+ /* details= */ NULL,
+ /* good_user= */ s->originator,
+ /* flags= */ 0,
+ &m->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -36,6 +40,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_
log_warning_errno(r, "Failed to send goodbye messages in IPv4 scope: %m");
dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->ptr_rr);
+ dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->sub_ptr_rr);
dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->srv_rr);
LIST_FOREACH(items, txt_data, s->txt_data_items)
dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, txt_data->rr);
@@ -47,6 +52,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_
log_warning_errno(r, "Failed to send goodbye messages in IPv6 scope: %m");
dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->ptr_rr);
+ dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->sub_ptr_rr);
dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->srv_rr);
LIST_FOREACH(items, txt_data, s->txt_data_items)
dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, txt_data->rr);
@@ -101,7 +107,7 @@ static int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata,
HASHMAP_FOREACH(service, m->dnssd_services) {
char *p;
- r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->name, &p);
+ r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->id, &p);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-dnssd-gperf.gperf b/src/resolve/resolved-dnssd-gperf.gperf
index f10eae3..e78573b 100644
--- a/src/resolve/resolved-dnssd-gperf.gperf
+++ b/src/resolve/resolved-dnssd-gperf.gperf
@@ -16,10 +16,11 @@ struct ConfigPerfItem;
%struct-type
%includes
%%
-Service.Name, config_parse_dnssd_service_name, 0, 0
-Service.Type, config_parse_dnssd_service_type, 0, 0
-Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port)
-Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority)
-Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight)
-Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0
-Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0
+Service.Name, config_parse_dnssd_service_name, 0, 0
+Service.Type, config_parse_dnssd_service_type, 0, 0
+Service.SubType, config_parse_dnssd_service_subtype, 0, 0
+Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port)
+Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority)
+Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight)
+Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0
+Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0
diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c
index 994771e..5f66e3c 100644
--- a/src/resolve/resolved-dnssd.c
+++ b/src/resolve/resolved-dnssd.c
@@ -3,10 +3,11 @@
#include "conf-files.h"
#include "conf-parser.h"
#include "constants.h"
-#include "resolved-dnssd.h"
+#include "path-util.h"
+#include "resolved-conf.h"
#include "resolved-dns-rr.h"
+#include "resolved-dnssd.h"
#include "resolved-manager.h"
-#include "resolved-conf.h"
#include "specifier.h"
#include "strv.h"
@@ -40,55 +41,81 @@ DnssdService *dnssd_service_free(DnssdService *service) {
return NULL;
if (service->manager)
- hashmap_remove(service->manager->dnssd_services, service->name);
+ hashmap_remove(service->manager->dnssd_services, service->id);
dns_resource_record_unref(service->ptr_rr);
+ dns_resource_record_unref(service->sub_ptr_rr);
dns_resource_record_unref(service->srv_rr);
dnssd_txtdata_free_all(service->txt_data_items);
- free(service->filename);
- free(service->name);
+ free(service->path);
+ free(service->id);
free(service->type);
+ free(service->subtype);
free(service->name_template);
return mfree(service);
}
-static int dnssd_service_load(Manager *manager, const char *filename) {
+void dnssd_service_clear_on_reload(Hashmap *services) {
+ DnssdService *service;
+
+ HASHMAP_FOREACH(service, services)
+ if (service->config_source == RESOLVE_CONFIG_SOURCE_FILE) {
+ hashmap_remove(services, service->id);
+ dnssd_service_free(service);
+ }
+}
+
+static int dnssd_id_from_path(const char *path, char **ret_id) {
+ int r;
+
+ assert(path);
+ assert(ret_id);
+
+ _cleanup_free_ char *fn = NULL;
+ r = path_extract_filename(path, &fn);
+ if (r < 0)
+ return r;
+
+ char *d = endswith(fn, ".dnssd");
+ if (!d)
+ return -EINVAL;
+
+ *d = '\0';
+
+ *ret_id = TAKE_PTR(fn);
+ return 0;
+}
+
+static int dnssd_service_load(Manager *manager, const char *path) {
_cleanup_(dnssd_service_freep) DnssdService *service = NULL;
_cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
- char *d;
- const char *dropin_dirname;
+ _cleanup_free_ char *dropin_dirname = NULL;
int r;
assert(manager);
- assert(filename);
+ assert(path);
service = new0(DnssdService, 1);
if (!service)
return log_oom();
- service->filename = strdup(filename);
- if (!service->filename)
+ service->path = strdup(path);
+ if (!service->path)
return log_oom();
- service->name = strdup(basename(filename));
- if (!service->name)
- return log_oom();
-
- d = endswith(service->name, ".dnssd");
- if (!d)
- return -EINVAL;
-
- assert(streq(d, ".dnssd"));
-
- *d = '\0';
+ r = dnssd_id_from_path(path, &service->id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract DNS-SD service id from filename: %m");
- dropin_dirname = strjoina(service->name, ".dnssd.d");
+ dropin_dirname = strjoin(service->id, ".dnssd.d");
+ if (!dropin_dirname)
+ return log_oom();
r = config_parse_many(
- STRV_MAKE_CONST(filename), DNSSD_SERVICE_DIRS, dropin_dirname, /* root = */ NULL,
+ STRV_MAKE_CONST(path), DNSSD_SERVICE_DIRS, dropin_dirname, /* root = */ NULL,
"Service\0",
config_item_perf_lookup, resolved_dnssd_gperf_lookup,
CONFIG_PARSE_WARN,
@@ -101,12 +128,12 @@ static int dnssd_service_load(Manager *manager, const char *filename) {
if (!service->name_template)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s doesn't define service instance name",
- service->name);
+ service->id);
if (!service->type)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s doesn't define service type",
- service->name);
+ service->id);
if (!service->txt_data_items) {
txt_data = new0(DnssdTxtData, 1);
@@ -121,7 +148,7 @@ static int dnssd_service_load(Manager *manager, const char *filename) {
TAKE_PTR(txt_data);
}
- r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->name, service);
+ r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->id, service);
if (r < 0)
return r;
@@ -138,16 +165,10 @@ static int dnssd_service_load(Manager *manager, const char *filename) {
static int specifier_dnssd_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
const Manager *m = ASSERT_PTR(userdata);
- char *n;
assert(m->llmnr_hostname);
- n = strdup(m->llmnr_hostname);
- if (!n)
- return -ENOMEM;
-
- *ret = n;
- return 0;
+ return strdup_to(ret, m->llmnr_hostname);
}
int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret) {
@@ -208,7 +229,7 @@ int dnssd_load(Manager *manager) {
}
int dnssd_update_rrs(DnssdService *s) {
- _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL;
+ _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL, *sub_name = NULL, *selective_name = NULL;
int r;
assert(s);
@@ -216,6 +237,7 @@ int dnssd_update_rrs(DnssdService *s) {
assert(s->manager);
s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
+ s->sub_ptr_rr = dns_resource_record_unref(s->sub_ptr_rr);
s->srv_rr = dns_resource_record_unref(s->srv_rr);
LIST_FOREACH(items, txt_data, s->txt_data_items)
txt_data->rr = dns_resource_record_unref(txt_data->rr);
@@ -230,6 +252,14 @@ int dnssd_update_rrs(DnssdService *s) {
r = dns_name_concat(n, service_name, 0, &full_name);
if (r < 0)
return r;
+ if (s->subtype) {
+ r = dns_name_concat("_sub", service_name, 0, &sub_name);
+ if (r < 0)
+ return r;
+ r = dns_name_concat(s->subtype, sub_name, 0, &selective_name);
+ if (r < 0)
+ return r;
+ }
LIST_FOREACH(items, txt_data, s->txt_data_items) {
txt_data->rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT,
@@ -253,6 +283,17 @@ int dnssd_update_rrs(DnssdService *s) {
if (!s->ptr_rr->ptr.name)
goto oom;
+ if (selective_name) {
+ s->sub_ptr_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, selective_name);
+ if (!s->sub_ptr_rr)
+ goto oom;
+
+ s->sub_ptr_rr->ttl = MDNS_DEFAULT_TTL;
+ s->sub_ptr_rr->ptr.name = strdup(full_name);
+ if (!s->sub_ptr_rr->ptr.name)
+ goto oom;
+ }
+
s->srv_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SRV,
full_name);
if (!s->srv_rr)
@@ -272,6 +313,7 @@ oom:
LIST_FOREACH(items, txt_data, s->txt_data_items)
txt_data->rr = dns_resource_record_unref(txt_data->rr);
s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
+ s->sub_ptr_rr = dns_resource_record_unref(s->sub_ptr_rr);
s->srv_rr = dns_resource_record_unref(s->srv_rr);
return -ENOMEM;
}
@@ -337,12 +379,12 @@ int dnssd_signal_conflict(Manager *manager, const char *name) {
if (s->withdrawn)
continue;
- if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name)) {
+ if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name) > 0) {
_cleanup_free_ char *path = NULL;
s->withdrawn = true;
- r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->name, &path);
+ r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->id, &path);
if (r < 0)
return log_error_errno(r, "Can't get D-BUS object path: %m");
diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h
index e978a0d..84f7853 100644
--- a/src/resolve/resolved-dnssd.h
+++ b/src/resolve/resolved-dnssd.h
@@ -3,6 +3,7 @@
#pragma once
#include "list.h"
+#include "resolved-conf.h"
typedef struct DnssdService DnssdService;
typedef struct DnssdTxtData DnssdTxtData;
@@ -25,15 +26,17 @@ struct DnssdTxtData {
};
struct DnssdService {
- char *filename;
- char *name;
+ char *path;
+ char *id;
char *name_template;
char *type;
+ char *subtype;
uint16_t port;
uint16_t priority;
uint16_t weight;
DnsResourceRecord *ptr_rr;
+ DnsResourceRecord *sub_ptr_rr;
DnsResourceRecord *srv_rr;
/* Section 6.8 of RFC 6763 allows having service
@@ -42,6 +45,9 @@ struct DnssdService {
Manager *manager;
+ /* Services registered via D-Bus are not removed on reload */
+ ResolveConfigSource config_source;
+
bool withdrawn:1;
uid_t originator;
};
@@ -49,6 +55,7 @@ struct DnssdService {
DnssdService *dnssd_service_free(DnssdService *service);
DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data);
DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data);
+void dnssd_service_clear_on_reload(Hashmap *services);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdTxtData*, dnssd_txtdata_free);
diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c
index fbcee7f..3112ccb 100644
--- a/src/resolve/resolved-dnstls-openssl.c
+++ b/src/resolve/resolved-dnstls-openssl.c
@@ -392,9 +392,6 @@ int dnstls_manager_init(Manager *manager) {
assert(manager);
- ERR_load_crypto_strings();
- SSL_load_error_strings();
-
manager->dnstls_data.ctx = SSL_CTX_new(TLS_client_method());
if (!manager->dnstls_data.ctx)
return -ENOMEM;
diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c
index 6af160a..7b05386 100644
--- a/src/resolve/resolved-etc-hosts.c
+++ b/src/resolve/resolved-etc-hosts.c
@@ -342,7 +342,7 @@ static int manager_etc_hosts_read(Manager *m) {
m->etc_hosts_last = ts;
- if (m->etc_hosts_stat.st_mode != 0) {
+ if (stat_is_set(&m->etc_hosts_stat)) {
if (stat("/etc/hosts", &st) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to stat /etc/hosts: %m");
@@ -491,7 +491,7 @@ static int etc_hosts_lookup_by_name(
const char *name,
DnsAnswer **answer) {
- bool found_a = false, found_aaaa = false;
+ bool question_for_a = false, question_for_aaaa = false;
const struct in_addr_data *a;
EtcHostsItemByName *item;
DnsResourceKey *t;
@@ -513,6 +513,7 @@ static int etc_hosts_lookup_by_name(
return 0;
}
+ /* Determine whether we are looking for A and/or AAAA RRs */
DNS_QUESTION_FOREACH(t, q) {
if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY))
continue;
@@ -526,20 +527,20 @@ static int etc_hosts_lookup_by_name(
continue;
if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY))
- found_a = true;
+ question_for_a = true;
if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY))
- found_aaaa = true;
+ question_for_aaaa = true;
- if (found_a && found_aaaa)
- break;
+ if (question_for_a && question_for_aaaa)
+ break; /* We are looking for both, no need to continue loop */
}
SET_FOREACH(a, item ? item->addresses : NULL) {
EtcHostsItemByAddress *item_by_addr;
const char *canonical_name;
- if ((!found_a && a->family == AF_INET) ||
- (!found_aaaa && a->family == AF_INET6))
+ if ((!question_for_a && a->family == AF_INET) ||
+ (!question_for_aaaa && a->family == AF_INET6))
continue;
item_by_addr = hashmap_get(hosts->by_address, a);
@@ -559,7 +560,7 @@ static int etc_hosts_lookup_by_name(
return r;
}
- return found_a || found_aaaa;
+ return true; /* We consider ourselves authoritative for the whole name, all RR types, not just A/AAAA */
}
int manager_etc_hosts_lookup(Manager *m, DnsQuestion *q, DnsAnswer **answer) {
diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c
index 4f8f591..656bdd9 100644
--- a/src/resolve/resolved-link-bus.c
+++ b/src/resolve/resolved-link-bus.c
@@ -236,10 +236,11 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi
if (r < 0)
return r;
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-dns-servers",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-dns-servers",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry, error);
if (r < 0)
goto finalize;
if (r == 0) {
@@ -273,7 +274,7 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi
if (s)
dns_server_move_back_and_unmark(s);
else {
- r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i]->family, &dns[i]->address, dns[i]->port, 0, dns[i]->server_name);
+ r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i]->family, &dns[i]->address, dns[i]->port, 0, dns[i]->server_name, RESOLVE_CONFIG_SOURCE_DBUS);
if (r < 0) {
dns_server_unlink_all(l->dns_servers);
goto finalize;
@@ -368,10 +369,12 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
if (r < 0)
return r;
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-domains",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-domains",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -446,10 +449,12 @@ int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-default-route",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-default-route",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -493,10 +498,12 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr);
}
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-llmnr",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-llmnr",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -541,10 +548,12 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns);
}
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-mdns",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-mdns",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -589,10 +598,12 @@ int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSOverTLS setting: %s", dns_over_tls);
}
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-dns-over-tls",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-dns-over-tls",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -637,10 +648,12 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec);
}
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-dnssec",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-dnssec",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -698,10 +711,12 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v
return -ENOMEM;
}
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.set-dnssec-negative-trust-anchors",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.set-dnssec-negative-trust-anchors",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
@@ -734,10 +749,12 @@ int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error
if (r < 0)
return r;
- r = bus_verify_polkit_async(message, CAP_NET_ADMIN,
- "org.freedesktop.resolve1.revert",
- NULL, true, UID_INVALID,
- &l->manager->polkit_registry, error);
+ r = bus_verify_polkit_async(
+ message,
+ "org.freedesktop.resolve1.revert",
+ (const char**) STRV_MAKE("interface", l->ifname),
+ &l->manager->polkit_registry,
+ error);
if (r < 0)
return r;
if (r == 0)
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index dd5dadd..bb43a73 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -273,7 +273,7 @@ static int link_update_dns_server_one(Link *l, const char *str) {
return 0;
}
- return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, port, 0, name);
+ return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, port, 0, name, RESOLVE_CONFIG_SOURCE_NETWORKD);
}
static int link_update_dns_servers(Link *l) {
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index b52619e..99787f7 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -11,6 +11,7 @@
#include "af-list.h"
#include "alloc-util.h"
#include "bus-polkit.h"
+#include "daemon-util.h"
#include "dirent-util.h"
#include "dns-domain.h"
#include "event-util.h"
@@ -388,7 +389,7 @@ static char* fallback_hostname(void) {
static int make_fallback_hostnames(char **full_hostname, char **llmnr_hostname, char **mdns_hostname) {
_cleanup_free_ char *h = NULL, *n = NULL, *m = NULL;
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
const char *p;
int r;
@@ -565,6 +566,73 @@ static int manager_memory_pressure_listen(Manager *m) {
return 0;
}
+static void manager_set_defaults(Manager *m) {
+ assert(m);
+
+ m->llmnr_support = DEFAULT_LLMNR_MODE;
+ m->mdns_support = DEFAULT_MDNS_MODE;
+ m->dnssec_mode = DEFAULT_DNSSEC_MODE;
+ m->dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE;
+ m->enable_cache = DNS_CACHE_MODE_YES;
+ m->dns_stub_listener_mode = DNS_STUB_LISTENER_YES;
+ m->read_etc_hosts = true;
+ m->resolve_unicast_single_label = false;
+ m->cache_from_localhost = false;
+ m->stale_retention_usec = 0;
+}
+
+static int manager_dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ (void) notify_reloading();
+
+ manager_set_defaults(m);
+
+ dns_server_unlink_on_reload(m->dns_servers);
+ dns_server_unlink_on_reload(m->fallback_dns_servers);
+ m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners);
+ dnssd_service_clear_on_reload(m->dnssd_services);
+ m->unicast_scope = dns_scope_free(m->unicast_scope);
+
+ dns_trust_anchor_flush(&m->trust_anchor);
+
+ r = dns_trust_anchor_load(&m->trust_anchor);
+ if (r < 0)
+ return r;
+
+ r = manager_parse_config_file(m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse config file on reload: %m");
+ else
+ log_info("Config file reloaded.");
+
+ r = dnssd_load(m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to load DNS-SD configuration files: %m");
+
+ /* The default scope configuration is influenced by the manager's configuration (modes, etc.), so
+ * recreate it on reload. */
+ r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC);
+ if (r < 0)
+ return r;
+
+ /* The configuration has changed, so reload the per-interface configuration too in order to take
+ * into account any changes (e.g.: enable/disable DNSSEC). */
+ r = on_network_event(/* sd_event_source= */ NULL, -EBADF, /* revents= */ 0, m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to update network information: %m");
+
+ /* We have new configuration, which means potentially new servers, so close all connections and drop
+ * all caches, so that we can start fresh. */
+ (void) dns_stream_disconnect_all(m);
+ manager_flush_caches(m, LOG_INFO);
+ manager_verify_all(m);
+
+ (void) sd_notify(/* unset= */ false, NOTIFY_READY);
+ return 0;
+}
+
int manager_new(Manager **ret) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@@ -584,21 +652,16 @@ int manager_new(Manager **ret) {
.mdns_ipv6_fd = -EBADF,
.hostname_fd = -EBADF,
- .llmnr_support = DEFAULT_LLMNR_MODE,
- .mdns_support = DEFAULT_MDNS_MODE,
- .dnssec_mode = DEFAULT_DNSSEC_MODE,
- .dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE,
- .enable_cache = DNS_CACHE_MODE_YES,
- .dns_stub_listener_mode = DNS_STUB_LISTENER_YES,
.read_resolv_conf = true,
.need_builtin_fallbacks = true,
.etc_hosts_last = USEC_INFINITY,
- .read_etc_hosts = true,
.sigrtmin18_info.memory_pressure_handler = manager_memory_pressure,
.sigrtmin18_info.memory_pressure_userdata = m,
};
+ manager_set_defaults(m);
+
r = dns_trust_anchor_load(&m->trust_anchor);
if (r < 0)
return r;
@@ -619,6 +682,7 @@ int manager_new(Manager **ret) {
(void) sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
(void) sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
+ (void) sd_event_add_signal(m->event, NULL, SIGHUP | SD_EVENT_SIGNAL_PROCMASK, manager_dispatch_reload_signal, m);
(void) sd_event_set_watchdog(m->event, true);
@@ -731,7 +795,7 @@ Manager *manager_free(Manager *m) {
ordered_set_free(m->dns_extra_stub_listeners);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
@@ -894,7 +958,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
return 1;
}
-static int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
+int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
usec_t end;
int r;
@@ -1098,17 +1162,7 @@ static int dns_question_to_json(DnsQuestion *q, JsonVariant **ret) {
return 0;
}
-int manager_monitor_send(
- Manager *m,
- int state,
- int rcode,
- int error,
- DnsQuestion *question_idna,
- DnsQuestion *question_utf8,
- DnsPacket *question_bypass,
- DnsQuestion *collected_questions,
- DnsAnswer *answer) {
-
+int manager_monitor_send(Manager *m, DnsQuery *q) {
_cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL;
_cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL;
Varlink *connection;
@@ -1121,14 +1175,14 @@ int manager_monitor_send(
return 0;
/* Merge all questions into one */
- r = dns_question_merge(question_idna, question_utf8, &merged);
+ r = dns_question_merge(q->question_idna, q->question_utf8, &merged);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m");
- if (question_bypass) {
+ if (q->question_bypass) {
_cleanup_(dns_question_unrefp) DnsQuestion *merged2 = NULL;
- r = dns_question_merge(merged, question_bypass->question, &merged2);
+ r = dns_question_merge(merged, q->question_bypass->question, &merged2);
if (r < 0)
return log_error_errno(r, "Failed to merge UTF8/IDNA questions and DNS packet question: %m");
@@ -1142,11 +1196,11 @@ int manager_monitor_send(
return log_error_errno(r, "Failed to convert question to JSON: %m");
/* Generate a JSON array of the questions preceding the current one in the CNAME chain */
- r = dns_question_to_json(collected_questions, &jcollected_questions);
+ r = dns_question_to_json(q->collected_questions, &jcollected_questions);
if (r < 0)
return log_error_errno(r, "Failed to convert question to JSON: %m");
- DNS_ANSWER_FOREACH_ITEM(rri, answer) {
+ DNS_ANSWER_FOREACH_ITEM(rri, q->answer) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = dns_resource_record_to_json(rri->rr, &v);
@@ -1169,12 +1223,28 @@ int manager_monitor_send(
SET_FOREACH(connection, m->varlink_subscription) {
r = varlink_notifyb(connection,
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))),
- JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)),
- JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)),
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED,
+ "result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE,
+ "rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO,
+ "errno", JSON_BUILD_INTEGER(q->answer_errno)),
+ JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_RCODE_FAILURE) &&
+ q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(IN_SET(q->state,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_RCODE_FAILURE) &&
+ q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)),
JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)),
- JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
- JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer))));
+ JSON_BUILD_PAIR_CONDITION(jcollected_questions,
+ "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)),
+ JSON_BUILD_PAIR_CONDITION(janswer,
+ "answer", JSON_BUILD_VARIANT(janswer))));
if (r < 0)
log_debug_errno(r, "Failed to send monitor event, ignoring: %m");
}
@@ -1279,7 +1349,7 @@ void manager_refresh_rrs(Manager *m) {
if (m->mdns_support == RESOLVE_SUPPORT_YES)
HASHMAP_FOREACH(s, m->dnssd_services)
if (dnssd_update_rrs(s) < 0)
- log_warning("Failed to refresh DNS-SD service '%s'", s->name);
+ log_warning("Failed to refresh DNS-SD service '%s'", s->id);
HASHMAP_FOREACH(l, m->links)
link_add_rrs(l, false);
@@ -1708,7 +1778,7 @@ bool manager_next_dnssd_names(Manager *m) {
r = manager_next_random_name(s->name_template, &new_name);
if (r < 0) {
- log_warning_errno(r, "Failed to get new name for service '%s': %m", s->name);
+ log_warning_errno(r, "Failed to get new name for service '%s': %m", s->id);
continue;
}
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index 5cd5e83..bd0e053 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -176,8 +176,9 @@ int manager_start(Manager *m);
uint32_t manager_find_mtu(Manager *m);
-int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsPacket *question_bypass, DnsQuestion *collected_questions, DnsAnswer *answer);
+int manager_monitor_send(Manager *m, DnsQuery *q);
+int sendmsg_loop(int fd, struct msghdr *mh, int flags);
int manager_write(Manager *m, int fd, DnsPacket *p);
int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);
diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c
index 3e6e83f..8f93af3 100644
--- a/src/resolve/resolved-mdns.c
+++ b/src/resolve/resolved-mdns.c
@@ -349,6 +349,33 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
return 0;
}
+static int mdns_goodbye_callback(sd_event_source *s, uint64_t usec, void *userdata) {
+ DnsScope *scope = userdata;
+ int r;
+
+ assert(s);
+ assert(scope);
+
+ scope->mdns_goodbye_event_source = sd_event_source_disable_unref(scope->mdns_goodbye_event_source);
+
+ dns_cache_prune(&scope->cache);
+
+ if (dns_cache_expiry_in_one_second(&scope->cache, usec)) {
+ r = sd_event_add_time_relative(
+ scope->manager->event,
+ &scope->mdns_goodbye_event_source,
+ CLOCK_BOOTTIME,
+ USEC_PER_SEC,
+ 0,
+ mdns_goodbye_callback,
+ scope);
+ if (r < 0)
+ return log_error_errno(r, "mDNS: Failed to re-schedule goodbye callback: %m");
+ }
+
+ return 0;
+}
+
static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
Manager *m = userdata;
@@ -407,6 +434,22 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
log_debug("Got a goodbye packet");
/* See the section 10.1 of RFC6762 */
rr->ttl = 1;
+
+ /* Look at the cache 1 second later and remove stale entries.
+ * This is particularly useful to keep service browsers updated on service removal,
+ * as there are no other reliable triggers to propagate that info. */
+ if (!scope->mdns_goodbye_event_source) {
+ r = sd_event_add_time_relative(
+ scope->manager->event,
+ &scope->mdns_goodbye_event_source,
+ CLOCK_BOOTTIME,
+ USEC_PER_SEC,
+ 0,
+ mdns_goodbye_callback,
+ scope);
+ if (r < 0)
+ return r;
+ }
}
}
diff --git a/src/resolve/resolved-util.c b/src/resolve/resolved-util.c
index 00abada..adcd35d 100644
--- a/src/resolve/resolved-util.c
+++ b/src/resolve/resolved-util.c
@@ -14,7 +14,7 @@ int resolve_system_hostname(char **full_hostname, char **first_label) {
#elif HAVE_LIBIDN
int k;
#endif
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
const char *p, *decoded;
int r;
diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c
index 3e178a6..25f85d8 100644
--- a/src/resolve/resolved-varlink.c
+++ b/src/resolve/resolved-varlink.c
@@ -15,8 +15,19 @@ typedef struct LookupParameters {
union in_addr_union address;
size_t address_size;
char *name;
+ uint16_t class;
+ uint16_t type;
} LookupParameters;
+typedef struct LookupParametersResolveService {
+ const char *name;
+ const char *type;
+ const char *domain;
+ int family;
+ int ifindex;
+ uint64_t flags;
+} LookupParametersResolveService;
+
static void lookup_parameters_destroy(LookupParameters *p) {
assert(p);
free(p->name);
@@ -49,7 +60,11 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_DNSSEC_FAILED:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result)))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL);
@@ -74,7 +89,11 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_RCODE_FAILURE:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
@@ -145,6 +164,7 @@ static bool validate_and_mangle_flags(
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
SD_RESOLVED_NO_STALE|
+ SD_RESOLVED_RELAX_SINGLE_LABEL|
ok))
return false;
@@ -160,45 +180,23 @@ static bool validate_and_mangle_flags(
return true;
}
-static void vl_method_resolve_hostname_complete(DnsQuery *query) {
- _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
- _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
- _cleanup_(dns_query_freep) DnsQuery *q = query;
- _cleanup_free_ char *normalized = NULL;
+static int find_addr_records(
+ JsonVariant **array,
+ DnsQuestion *question,
+ DnsQuery *q,
+ DnsResourceRecord **canonical,
+ const char *search_domain) {
DnsResourceRecord *rr;
- DnsQuestion *question;
int ifindex, r;
- assert(q);
-
- if (q->state != DNS_TRANSACTION_SUCCESS) {
- r = reply_query_state(q);
- goto finish;
- }
-
- r = dns_query_process_cname_many(q);
- if (r == -ELOOP) {
- r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL);
- goto finish;
- }
- if (r < 0)
- goto finish;
- if (r == DNS_QUERY_CNAME) {
- /* This was a cname, and the query was restarted. */
- TAKE_PTR(q);
- return;
- }
-
- question = dns_query_question_for_protocol(q, q->answer_protocol);
-
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
_cleanup_(json_variant_unrefp) JsonVariant *entry = NULL;
int family;
const void *p;
- r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+ r = dns_question_matches_rr(question, rr, search_domain);
if (r < 0)
- goto finish;
+ return r;
if (r == 0)
continue;
@@ -209,8 +207,7 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) {
family = AF_INET6;
p = &rr->aaaa.in6_addr;
} else {
- r = -EAFNOSUPPORT;
- goto finish;
+ return -EAFNOSUPPORT;
}
r = json_build(&entry,
@@ -219,16 +216,53 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) {
JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)),
JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family)))));
if (r < 0)
- goto finish;
-
- if (!canonical)
- canonical = dns_resource_record_ref(rr);
+ return r;
- r = json_variant_append_array(&array, entry);
+ r = json_variant_append_array(array, entry);
if (r < 0)
- goto finish;
+ return r;
+
+ if (canonical && !*canonical)
+ *canonical = dns_resource_record_ref(rr);
}
+ return 0;
+}
+
+static void vl_method_resolve_hostname_complete(DnsQuery *query) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+ _cleanup_(dns_query_freep) DnsQuery *q = query;
+ _cleanup_free_ char *normalized = NULL;
+ DnsQuestion *question;
+ int r;
+
+ assert(q);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS) {
+ r = reply_query_state(q);
+ goto finish;
+ }
+
+ r = dns_query_process_cname_many(q);
+ if (r == -ELOOP) {
+ r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL);
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+ if (r == DNS_QUERY_CNAME) {
+ /* This was a cname, and the query was restarted. */
+ TAKE_PTR(q);
+ return;
+ }
+
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
+
+ r = find_addr_records(&array, question, q, &canonical, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+ if (r < 0)
+ goto finish;
+
if (json_variant_is_blank_object(array)) {
r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
goto finish;
@@ -530,6 +564,670 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var
return 1;
}
+static int append_txt(JsonVariant **txt, DnsResourceRecord *rr) {
+ int r;
+
+ assert(txt);
+ assert(rr);
+ assert(rr->key);
+
+ if (rr->key->type != DNS_TYPE_TXT)
+ return 0;
+
+ LIST_FOREACH(items, i, rr->txt.items) {
+ _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL;
+
+ if (i->length <= 0)
+ continue;
+
+ r = json_variant_new_octescape(&entry, i->data, i->length);
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(txt, entry);
+ if (r < 0)
+ return r;
+ }
+
+ return 1;
+}
+
+static int append_srv(
+ DnsQuery *q,
+ DnsResourceRecord *rr,
+ JsonVariant **array) {
+
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *normalized = NULL;
+ int r;
+
+ assert(q);
+ assert(rr);
+ assert(rr->key);
+ assert(array);
+
+ if (rr->key->type != DNS_TYPE_SRV)
+ return 0;
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ /* First, let's see if we could find an appropriate A or AAAA
+ * record for the SRV record */
+ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+ DnsResourceRecord *zz;
+ DnsQuestion *question;
+
+ if (aux->state != DNS_TRANSACTION_SUCCESS)
+ continue;
+ if (aux->auxiliary_result != 0)
+ continue;
+
+ question = dns_query_question_for_protocol(aux, aux->answer_protocol);
+
+ r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ DNS_ANSWER_FOREACH(zz, aux->answer) {
+ r = dns_question_matches_rr(question, zz, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ canonical = dns_resource_record_ref(zz);
+ break;
+ }
+
+ if (canonical)
+ break;
+ }
+
+ /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */
+ if (!canonical)
+ return 0;
+ }
+
+ r = dns_name_normalize(rr->srv.name, 0, &normalized);
+ if (r < 0)
+ return r;
+
+ r = json_build(&v,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->srv.priority)),
+ JSON_BUILD_PAIR("weight", JSON_BUILD_UNSIGNED(rr->srv.weight)),
+ JSON_BUILD_PAIR("port", JSON_BUILD_UNSIGNED(rr->srv.port)),
+ JSON_BUILD_PAIR("hostname", JSON_BUILD_STRING(normalized))));
+ if (r < 0)
+ return r;
+
+ if (canonical) {
+ normalized = mfree(normalized);
+
+ r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized);
+ if (r < 0)
+ return r;
+
+ r = json_variant_set_field_string(&v, "canonicalName", normalized);
+ if (r < 0)
+ return r;
+ }
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ _cleanup_(json_variant_unrefp) JsonVariant *addresses = NULL;
+
+ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+ DnsQuestion *question;
+
+ if (aux->state != DNS_TRANSACTION_SUCCESS)
+ continue;
+ if (aux->auxiliary_result != 0)
+ continue;
+
+ question = dns_query_question_for_protocol(aux, aux->answer_protocol);
+
+ r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = find_addr_records(&addresses, question, aux, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ r = json_variant_set_field(&v, "addresses", addresses);
+ if (r < 0)
+ return r;
+ }
+
+ r = json_variant_append_array(array, v);
+ if (r < 0)
+ return r;
+
+ return 1; /* added */
+}
+
+static Varlink *get_vl_link_aux_query(DnsQuery *aux) {
+ assert(aux);
+
+ /* Find the main query */
+ while (aux->auxiliary_for)
+ aux = aux->auxiliary_for;
+
+ return aux->varlink_request;
+}
+
+static void resolve_service_all_complete(DnsQuery *query) {
+ _cleanup_(dns_query_freep) DnsQuery *q = query;
+ _cleanup_(json_variant_unrefp) JsonVariant *srv = NULL, *txt = NULL;
+ _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ DnsQuestion *question;
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(q);
+
+ if (q->block_all_complete > 0) {
+ TAKE_PTR(q);
+ return;
+ }
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ DnsQuery *bad = NULL;
+ bool have_success = false;
+
+ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+ switch (aux->state) {
+
+ case DNS_TRANSACTION_PENDING:
+ /* If an auxiliary query is still pending, let's wait */
+ TAKE_PTR(q);
+ return;
+
+ case DNS_TRANSACTION_SUCCESS:
+ if (aux->auxiliary_result == 0)
+ have_success = true;
+ else
+ bad = aux;
+ break;
+
+ default:
+ bad = aux;
+ break;
+ }
+ }
+ if (!have_success) {
+ /* We can only return one error, hence pick the last error we encountered */
+
+ assert(bad);
+ if (bad->state == DNS_TRANSACTION_SUCCESS) {
+ assert(bad->auxiliary_result != 0);
+
+ if (bad->auxiliary_result == -ELOOP) {
+ r = varlink_error(query->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL);
+ goto finish;
+ }
+
+ assert(bad->auxiliary_result < 0);
+ r = bad->auxiliary_result;
+ goto finish;
+ }
+
+ bad->varlink_request = get_vl_link_aux_query(bad);
+ r = reply_query_state(bad);
+ bad->varlink_request = NULL;
+ goto finish;
+ }
+ }
+
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
+
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ r = append_srv(q, rr, &srv);
+ if (r < 0)
+ goto finish;
+ if (r == 0) /* not an SRV record */
+ continue;
+
+ if (!canonical)
+ canonical = dns_resource_record_ref(rr);
+ }
+
+ if (json_variant_is_blank_object(srv)) {
+ r = varlink_error(query->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
+ goto finish;
+ }
+
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ if (rr->key->type != DNS_TYPE_TXT)
+ continue;
+
+ r = append_txt(&txt, rr);
+ if (r < 0)
+ goto finish;
+ }
+
+ assert(canonical);
+ r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain);
+ if (r < 0)
+ goto finish;
+
+ r = varlink_replyb(query->varlink_request, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("services", JSON_BUILD_VARIANT(srv)),
+ JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_object(txt), "txt", JSON_BUILD_VARIANT(txt)),
+ JSON_BUILD_PAIR("canonical", JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)),
+ JSON_BUILD_PAIR("type", JSON_BUILD_STRING(type)),
+ JSON_BUILD_PAIR("domain", JSON_BUILD_STRING(domain)))),
+ JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(dns_query_reply_flags_make(query)))));
+
+finish:
+ if (r < 0) {
+ log_error_errno(r, "Failed to resolve service: %m");
+ r = varlink_error_errno(q->varlink_request, r);
+ }
+}
+
+static void resolve_service_hostname_complete(DnsQuery *q) {
+ int r;
+
+ assert(q);
+ assert(q->auxiliary_for);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS) {
+ resolve_service_all_complete(q->auxiliary_for);
+ return;
+ }
+
+ r = dns_query_process_cname_many(q);
+ if (r == DNS_QUERY_CNAME) /* This was a cname, and the query was restarted. */
+ return;
+
+ /* This auxiliary lookup is finished or failed, let's see if all are finished now. */
+ q->auxiliary_result = r < 0 ? r : 0;
+ resolve_service_all_complete(q->auxiliary_for);
+}
+
+static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) {
+ _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+ _cleanup_(dns_query_freep) DnsQuery *aux = NULL;
+ int r;
+
+ assert(q);
+ assert(rr);
+ assert(rr->key);
+ assert(rr->key->type == DNS_TYPE_SRV);
+
+ /* OK, we found an SRV record for the service. Let's resolve
+ * the hostname included in it */
+
+ r = dns_question_new_address(&question, q->request_family, rr->srv.name, false);
+ if (r < 0)
+ return r;
+
+ r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
+ if (r < 0)
+ return r;
+
+ aux->request_family = q->request_family;
+ aux->complete = resolve_service_hostname_complete;
+
+ r = dns_query_make_auxiliary(aux, q);
+ if (r == -EAGAIN)
+ /* Too many auxiliary lookups? If so, don't complain,
+ * let's just not add this one, we already have more
+ * than enough */
+ return 0;
+ if (r < 0)
+ return r;
+
+ /* Note that auxiliary queries do not track the original
+ * client, only the primary request does that. */
+
+ r = dns_query_go(aux);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(aux);
+ return 1;
+}
+
+static void vl_method_resolve_service_complete(DnsQuery *query) {
+ _cleanup_(dns_query_freep) DnsQuery *q = query;
+ bool has_root_domain = false;
+ DnsResourceRecord *rr;
+ DnsQuestion *question;
+ unsigned found = 0;
+ int ifindex, r;
+
+ assert(q);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS) {
+ r = reply_query_state(q);
+ goto finish;
+ }
+
+ r = dns_query_process_cname_many(q);
+ if (r == -ELOOP) {
+ r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL);
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+ if (r == DNS_QUERY_CNAME) {
+ /* This was a cname, and the query was restarted. */
+ TAKE_PTR(q);
+ return;
+ }
+
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ if (rr->key->type != DNS_TYPE_SRV)
+ continue;
+
+ if (dns_name_is_root(rr->srv.name)) {
+ has_root_domain = true;
+ continue;
+ }
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ q->block_all_complete++;
+ r = resolve_service_hostname(q, rr, ifindex);
+ q->block_all_complete--;
+
+ if (r < 0)
+ goto finish;
+ }
+
+ found++;
+ }
+
+ if (has_root_domain && found <= 0) {
+ /* If there's exactly one SRV RR and it uses the root domain as hostname, then the service is
+ * explicitly not offered on the domain. Report this as a recognizable error. See RFC 2782,
+ * Section "Usage Rules". */
+ r = varlink_error(q->varlink_request, "io.systemd.Resolve.ServiceNotProvided", NULL);
+ goto finish;
+ }
+
+ if (found <= 0) {
+ r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
+ goto finish;
+ }
+
+ /* Maybe we are already finished? check now... */
+ resolve_service_all_complete(TAKE_PTR(q));
+ return;
+
+finish:
+ if (r < 0) {
+ log_error_errno(r, "Failed to send address reply: %m");
+ r = varlink_error_errno(q->varlink_request, r);
+ }
+}
+
+static int vl_method_resolve_service(Varlink* link, JsonVariant* parameters, VarlinkMethodFlags flags, void* userdata) {
+ static const JsonDispatch dispatch_table[] = {
+ { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, name), 0 },
+ { "type", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, type), 0 },
+ { "domain", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, domain), JSON_MANDATORY },
+ { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, ifindex), 0 },
+ { "family", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, family), 0 },
+ { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParametersResolveService, flags), 0 },
+ {}
+ };
+
+ _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
+ LookupParametersResolveService p = {
+ .family = AF_UNSPEC,
+ };
+
+ _cleanup_(dns_query_freep) DnsQuery *q = NULL;
+ Manager *m;
+ int r;
+
+ assert(link);
+
+ m = varlink_server_get_userdata(varlink_get_server(link));
+ assert(m);
+
+ if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY))
+ return -EINVAL;
+
+ r = varlink_dispatch(link, parameters, dispatch_table, &p);
+ if (r != 0)
+ return r;
+
+ if (p.ifindex < 0)
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex"));
+
+ if (!IN_SET(p.family, AF_INET, AF_INET6, AF_UNSPEC))
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family"));
+
+ if (isempty(p.name))
+ p.name = NULL;
+ else if (!dns_service_name_is_valid(p.name))
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name"));
+
+ if (isempty(p.type))
+ p.type = NULL;
+ else if (!dns_srv_type_is_valid(p.type))
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type"));
+
+ r = dns_name_is_valid(p.domain);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("domain"));
+
+ if (p.name && !p.type) /* Service name cannot be specified without service type. */
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type"));
+
+ if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS))
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
+
+ r = dns_question_new_service(&question_utf8, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), false);
+ if (r < 0)
+ return r;
+
+ r = dns_question_new_service(&question_idna, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), true);
+ if (r < 0)
+ return r;
+
+ r = dns_query_new(m, &q, question_utf8, question_idna, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH);
+ if (r < 0)
+ return r;
+
+ q->varlink_request = varlink_ref(link);
+ q->request_family = p.family;
+ q->complete = vl_method_resolve_service_complete;
+
+ varlink_set_userdata(link, q);
+
+ r = dns_query_go(q);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(q);
+ return 1;
+}
+
+static void vl_method_resolve_record_complete(DnsQuery *query) {
+ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+ _cleanup_(dns_query_freep) DnsQuery *q = query;
+ DnsQuestion *question;
+ int r;
+
+ assert(q);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS) {
+ r = reply_query_state(q);
+ goto finish;
+ }
+
+ r = dns_query_process_cname_many(q);
+ if (r == -ELOOP) {
+ r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL);
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+ if (r == DNS_QUERY_CNAME) {
+ /* This was a cname, and the query was restarted. */
+ TAKE_PTR(q);
+ return;
+ }
+
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
+
+ unsigned added = 0;
+ int ifindex;
+ DnsResourceRecord *rr;
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ r = dns_resource_record_to_json(rr, &v);
+ if (r < 0)
+ goto finish;
+
+ r = dns_resource_record_to_wire_format(rr, /* canonical= */ false); /* don't use DNSSEC canonical format, since it removes casing, but we want that for DNS_SD compat */
+ if (r < 0)
+ goto finish;
+
+ r = json_variant_append_arrayb(
+ &array,
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)),
+ JSON_BUILD_PAIR_CONDITION(v, "rr", JSON_BUILD_VARIANT(v)),
+ JSON_BUILD_PAIR("raw", JSON_BUILD_BASE64(rr->wire_format, rr->wire_format_size))));
+ if (r < 0)
+ goto finish;
+
+ added++;
+ }
+
+ if (added <= 0) {
+ r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
+ goto finish;
+ }
+
+ r = varlink_replyb(q->varlink_request,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("rrs", JSON_BUILD_VARIANT(array)),
+ JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q)))));
+finish:
+ if (r < 0) {
+ log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to send record reply: %m");
+ varlink_error_errno(q->varlink_request, r);
+ }
+}
+
+static int vl_method_resolve_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ static const JsonDispatch dispatch_table[] = {
+ { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 },
+ { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(LookupParameters, name), JSON_MANDATORY },
+ { "class", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, class), 0 },
+ { "type", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, type), JSON_MANDATORY },
+ { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 },
+ {}
+ };
+
+ _cleanup_(lookup_parameters_destroy) LookupParameters p = {
+ .class = DNS_CLASS_IN,
+ .type = _DNS_TYPE_INVALID,
+ };
+ _cleanup_(dns_query_freep) DnsQuery *q = NULL;
+ Manager *m;
+ int r;
+
+ assert(link);
+
+ m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link)));
+
+ if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY))
+ return -EINVAL;
+
+ r = varlink_dispatch(link, parameters, dispatch_table, &p);
+ if (r != 0)
+ return r;
+
+ if (p.ifindex < 0)
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex"));
+
+ r = dns_name_is_valid(p.name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name"));
+
+ if (!dns_type_is_valid_query(p.type))
+ return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeInvalidForQuery", NULL);
+ if (dns_type_is_zone_transfer(p.type))
+ return varlink_error(link, "io.systemd.Resolve.ZoneTransfersNotPermitted", NULL);
+ if (dns_type_is_obsolete(p.type))
+ return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeObsolete", NULL);
+
+ if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH))
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
+
+ _cleanup_(dns_question_unrefp) DnsQuestion *question = dns_question_new(1);
+ if (!question)
+ return -ENOMEM;
+
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ key = dns_resource_key_new(p.class, p.type, p.name);
+ if (!key)
+ return -ENOMEM;
+
+ r = dns_question_add(question, key, /* flags= */ 0);
+ if (r < 0)
+ return r;
+
+ r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL);
+ if (r < 0)
+ return r;
+
+ q->varlink_request = varlink_ref(link);
+ varlink_set_userdata(link, q);
+ q->complete = vl_method_resolve_record_complete;
+
+ r = dns_query_go(q);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(q);
+ return 1;
+}
+
static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
Manager *m;
int r;
@@ -540,7 +1238,7 @@ static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *paramet
/* if the client didn't set the more flag, it is using us incorrectly */
if (!FLAGS_SET(flags, VARLINK_METHOD_MORE))
- return varlink_error_invalid_parameter(link, NULL);
+ return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL);
if (json_variant_elements(parameters) > 0)
return varlink_error_invalid_parameter(link, parameters);
@@ -753,8 +1451,10 @@ static int varlink_main_server_init(Manager *m) {
r = varlink_server_bind_method_many(
s,
- "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname,
- "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address);
+ "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname,
+ "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address,
+ "io.systemd.Resolve.ResolveService", vl_method_resolve_service,
+ "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");
diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c
index 1625c51..664e7dd 100644
--- a/src/resolve/resolved.c
+++ b/src/resolve/resolved.c
@@ -67,7 +67,7 @@ static int run(int argc, char *argv[]) {
return log_error_errno(r, "Failed to drop privileges: %m");
}
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, SIGRTMIN+1, SIGRTMIN+18, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, SIGRTMIN+1, SIGRTMIN+18) >= 0);
r = manager_new(&m);
if (r < 0)
diff --git a/src/resolve/test-resolved-dummy-server.c b/src/resolve/test-resolved-dummy-server.c
new file mode 100644
index 0000000..58257d7
--- /dev/null
+++ b/src/resolve/test-resolved-dummy-server.c
@@ -0,0 +1,450 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-daemon.h"
+
+#include "fd-util.h"
+#include "iovec-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "resolved-dns-packet.h"
+#include "resolved-manager.h"
+#include "socket-netlink.h"
+#include "socket-util.h"
+
+/* Taken from resolved-dns-stub.c */
+#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
+
+/* This is more or less verbatim manager_recv() from resolved-manager.c, sans the manager stuff */
+static int server_recv(int fd, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
+ + CMSG_SPACE(int) /* ttl/hoplimit */
+ + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */) control;
+ union sockaddr_union sa;
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t ms, l;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ ms = next_datagram_size_fd(fd);
+ if (ms < 0)
+ return ms;
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, ms, DNS_PACKET_SIZE_MAX);
+ if (r < 0)
+ return r;
+
+ iov = IOVEC_MAKE(DNS_PACKET_DATA(p), p->allocated);
+
+ l = recvmsg_safe(fd, &mh, 0);
+ if (ERRNO_IS_NEG_TRANSIENT(l))
+ return 0;
+ if (l <= 0)
+ return l;
+
+ assert(!(mh.msg_flags & MSG_TRUNC));
+
+ p->size = (size_t) l;
+
+ p->family = sa.sa.sa_family;
+ p->ipproto = IPPROTO_UDP;
+ if (p->family == AF_INET) {
+ p->sender.in = sa.in.sin_addr;
+ p->sender_port = be16toh(sa.in.sin_port);
+ } else if (p->family == AF_INET6) {
+ p->sender.in6 = sa.in6.sin6_addr;
+ p->sender_port = be16toh(sa.in6.sin6_port);
+ p->ifindex = sa.in6.sin6_scope_id;
+ } else
+ return -EAFNOSUPPORT;
+
+ p->timestamp = now(CLOCK_BOOTTIME);
+
+ CMSG_FOREACH(cmsg, &mh) {
+
+ if (cmsg->cmsg_level == IPPROTO_IPV6) {
+ assert(p->family == AF_INET6);
+
+ switch (cmsg->cmsg_type) {
+
+ case IPV6_PKTINFO: {
+ struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
+
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi6_ifindex;
+
+ p->destination.in6 = i->ipi6_addr;
+ break;
+ }
+
+ case IPV6_HOPLIMIT:
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case IPV6_RECVFRAGSIZE:
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+ }
+ } else if (cmsg->cmsg_level == IPPROTO_IP) {
+ assert(p->family == AF_INET);
+
+ switch (cmsg->cmsg_type) {
+
+ case IP_PKTINFO: {
+ struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
+
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi_ifindex;
+
+ p->destination.in = i->ipi_addr;
+ break;
+ }
+
+ case IP_TTL:
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case IP_RECVFRAGSIZE:
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+ }
+ }
+ }
+
+ /* The Linux kernel sets the interface index to the loopback
+ * device if the packet came from the local host since it
+ * avoids the routing table in such a case. Let's unset the
+ * interface index in such a case. */
+ if (p->ifindex == LOOPBACK_IFINDEX)
+ p->ifindex = 0;
+
+ log_debug("Received DNS UDP packet of size %zu, ifindex=%i, ttl=%u, fragsize=%zu, sender=%s, destination=%s",
+ p->size, p->ifindex, p->ttl, p->fragsize,
+ IN_ADDR_TO_STRING(p->family, &p->sender),
+ IN_ADDR_TO_STRING(p->family, &p->destination));
+
+ *ret = TAKE_PTR(p);
+ return 1;
+}
+
+/* Same as above, see manager_ipv4_send() in resolved-manager.c */
+static int server_ipv4_send(
+ int fd,
+ const struct in_addr *destination,
+ uint16_t port,
+ const struct in_addr *source,
+ DnsPacket *packet) {
+
+ union sockaddr_union sa;
+ struct iovec iov;
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa.in),
+ };
+
+ assert(fd >= 0);
+ assert(destination);
+ assert(port > 0);
+ assert(packet);
+
+ iov = IOVEC_MAKE(DNS_PACKET_DATA(packet), packet->size);
+
+ sa = (union sockaddr_union) {
+ .in.sin_family = AF_INET,
+ .in.sin_addr = *destination,
+ .in.sin_port = htobe16(port),
+ };
+
+ return sendmsg_loop(fd, &mh, 0);
+}
+
+static int make_reply_packet(DnsPacket *packet, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ int r;
+
+ assert(packet);
+ assert(ret);
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_PAYLOAD_SIZE_MAX(packet));
+ if (r < 0)
+ return r;
+
+ r = dns_packet_append_question(p, packet->question);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->id = DNS_PACKET_ID(packet);
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(packet->question));
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+static int reply_append_edns(DnsPacket *packet, DnsPacket *reply, const char *extra_text, size_t rcode, uint16_t ede_code) {
+ size_t saved_size;
+ int r;
+
+ assert(packet);
+ assert(reply);
+
+ /* Append EDNS0 stuff (inspired by dns_packet_append_opt() from resolved-dns-packet.c).
+ *
+ * Relevant headers from RFC 6891:
+ *
+ * +------------+--------------+------------------------------+
+ * | Field Name | Field Type | Description |
+ * +------------+--------------+------------------------------+
+ * | NAME | domain name | MUST be 0 (root domain) |
+ * | TYPE | u_int16_t | OPT (41) |
+ * | CLASS | u_int16_t | requestor's UDP payload size |
+ * | TTL | u_int32_t | extended RCODE and flags |
+ * | RDLEN | u_int16_t | length of all RDATA |
+ * | RDATA | octet stream | {attribute,value} pairs |
+ * +------------+--------------+------------------------------+
+ *
+ * +0 (MSB) +1 (LSB)
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | |
+ * / OPTION-DATA /
+ * / /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * And from RFC 8914:
+ *
+ * 1 1 1 1 1 1
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 0: | OPTION-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 2: | OPTION-LENGTH |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 4: | INFO-CODE |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * 6: / EXTRA-TEXT ... /
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ */
+
+ saved_size = reply->size;
+
+ /* empty name */
+ r = dns_packet_append_uint8(reply, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* type */
+ r = dns_packet_append_uint16(reply, DNS_TYPE_OPT, NULL);
+ if (r < 0)
+ return r;
+
+ /* class: maximum udp packet that can be received */
+ r = dns_packet_append_uint16(reply, ADVERTISE_DATAGRAM_SIZE_MAX, NULL);
+ if (r < 0)
+ return r;
+
+ /* extended RCODE and VERSION */
+ r = dns_packet_append_uint16(reply, ((uint16_t) rcode & 0x0FF0) << 4, NULL);
+ if (r < 0)
+ return r;
+
+ /* flags: DNSSEC OK (DO), see RFC3225 */
+ r = dns_packet_append_uint16(reply, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* RDATA */
+
+ size_t extra_text_len = isempty(extra_text) ? 0 : strlen(extra_text);
+ /* RDLENGTH (OPTION CODE + OPTION LENGTH + INFO-CODE + EXTRA-TEXT) */
+ r = dns_packet_append_uint16(reply, 2 + 2 + 2 + extra_text_len, NULL);
+ if (r < 0)
+ return 0;
+
+ /* OPTION-CODE: 15 for EDE */
+ r = dns_packet_append_uint16(reply, 15, NULL);
+ if (r < 0)
+ return r;
+
+ /* OPTION-LENGTH: INFO-CODE + EXTRA-TEXT */
+ r = dns_packet_append_uint16(reply, 2 + extra_text_len, NULL);
+ if (r < 0)
+ return r;
+
+ /* INFO-CODE: EDE code */
+ r = dns_packet_append_uint16(reply, ede_code, NULL);
+ if (r < 0)
+ return r;
+
+ /* EXTRA-TEXT */
+ if (extra_text_len > 0) {
+ /* From RFC 8914:
+ * EDE text may be null terminated but MUST NOT be assumed to be; the length MUST be derived
+ * from the OPTION-LENGTH field
+ *
+ * Let's exercise our code on the receiving side and not NUL-terminate the EXTRA-TEXT field
+ */
+ r = dns_packet_append_blob(reply, extra_text, extra_text_len, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ DNS_PACKET_HEADER(reply)->arcount = htobe16(DNS_PACKET_ARCOUNT(reply) + 1);
+ reply->opt_start = saved_size;
+ reply->opt_size = reply->size - saved_size;
+
+ /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
+ DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
+ return 0;
+}
+
+static void server_fail(DnsPacket *packet, DnsPacket *reply, int rcode) {
+ assert(reply);
+
+ /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */
+ DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode));
+}
+
+static int server_handle_edns_bogus_dnssec(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+
+ return reply_append_edns(packet, reply, NULL, DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_DNSSEC_BOGUS);
+}
+
+static int server_handle_edns_extra_text(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+
+ return reply_append_edns(packet, reply, "Nothing to see here!", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_CENSORED);
+}
+
+static int server_handle_edns_invalid_code(DnsPacket *packet, DnsPacket *reply, const char *extra_text) {
+ assert(packet);
+ assert(reply);
+ assert_cc(_DNS_EDE_RCODE_MAX_DEFINED < UINT16_MAX);
+
+ return reply_append_edns(packet, reply, extra_text, DNS_RCODE_SERVFAIL, _DNS_EDE_RCODE_MAX_DEFINED + 1);
+}
+
+static int server_handle_edns_code_zero(DnsPacket *packet, DnsPacket *reply) {
+ assert(packet);
+ assert(reply);
+ assert_cc(DNS_EDE_RCODE_OTHER == 0);
+
+ return reply_append_edns(packet, reply, "\xF0\x9F\x90\xB1", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_OTHER);
+}
+
+static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+ const char *name;
+ int r;
+
+ assert(fd >= 0);
+
+ r = server_recv(fd, &packet);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to receive packet, ignoring: %m");
+ return 0;
+ }
+
+ r = dns_packet_validate_query(packet);
+ if (r < 0) {
+ log_debug_errno(r, "Invalid DNS UDP packet, ignoring.");
+ return 0;
+ }
+
+ r = dns_packet_extract(packet);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract DNS packet, ignoring: %m");
+ return 0;
+ }
+
+ name = dns_question_first_name(packet->question);
+ log_info("Processing question for name '%s'", name);
+
+ (void) dns_question_dump(packet->question, stdout);
+
+ r = make_reply_packet(packet, &reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to make reply packet, ignoring: %m");
+ return 0;
+ }
+
+ if (streq_ptr(name, "edns-bogus-dnssec.forwarded.test"))
+ r = server_handle_edns_bogus_dnssec(packet, reply);
+ else if (streq_ptr(name, "edns-extra-text.forwarded.test"))
+ r = server_handle_edns_extra_text(packet, reply);
+ else if (streq_ptr(name, "edns-invalid-code.forwarded.test"))
+ r = server_handle_edns_invalid_code(packet, reply, NULL);
+ else if (streq_ptr(name, "edns-invalid-code-with-extra-text.forwarded.test"))
+ r = server_handle_edns_invalid_code(packet, reply, "Hello [#]$%~ World");
+ else if (streq_ptr(name, "edns-code-zero.forwarded.test"))
+ r = server_handle_edns_code_zero(packet, reply);
+ else
+ r = log_debug_errno(SYNTHETIC_ERRNO(EFAULT), "Unhandled name '%s', ignoring.", name);
+ if (r < 0)
+ server_fail(packet, reply, DNS_RCODE_NXDOMAIN);
+
+ r = server_ipv4_send(fd, &packet->sender.in, packet->sender_port, &packet->destination.in, reply);
+ if (r < 0)
+ log_debug_errno(r, "Failed to send reply, ignoring: %m");
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ log_setup();
+
+ if (argc != 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program takes one argument in format ip_address:port");
+
+ fd = make_socket_fd(LOG_DEBUG, argv[1], SOCK_DGRAM, SOCK_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to listen on address '%s': %m", argv[1]);
+
+ r = sd_event_default(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event: %m");
+
+ r = sd_event_add_io(event, NULL, fd, EPOLLIN, on_dns_packet, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add IO event source: %m");
+
+ r = sd_event_set_signal_exit(event, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
+
+ (void) sd_notify(/* unset_environment=false */ false, "READY=1");
+
+ r = sd_event_loop(event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run event loop: %m");
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/resolve/test-resolved-packet.c b/src/resolve/test-resolved-packet.c
index dd8c969..8a65ea0 100644
--- a/src/resolve/test-resolved-packet.c
+++ b/src/resolve/test-resolved-packet.c
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "hexdecoct.h"
#include "log.h"
#include "resolved-dns-packet.h"
#include "tests.h"
@@ -23,4 +24,190 @@ TEST(dns_packet_new) {
assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, DNS_PACKET_SIZE_MAX + 1, DNS_PACKET_SIZE_MAX) == -EFBIG);
}
+TEST(naptr) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+
+ static const char twilio_reply[] =
+ "Sq+BgAABAAkAAAABBnR3aWxpbwNjb20AACMAAcAMACMAAQAABwgAMgAUAAoBUwdTSVArRDJUAARf"
+ "c2lwBF90Y3AEcHN0bgdpZTEtdG54BnR3aWxpbwNjb20AwAwAIwABAAAHCAAyAAoACgFTB1NJUCtE"
+ "MlUABF9zaXAEX3VkcARwc3RuB3VzMi10bngGdHdpbGlvA2NvbQDADAAjAAEAAAcIADQAFAAKAVMI"
+ "U0lQUytEMlQABV9zaXBzBF90Y3AEcHN0bgd1czEtdG54BnR3aWxpbwNjb20AwAwAIwABAAAHCAAy"
+ "AAoACgFTB1NJUCtEMlUABF9zaXAEX3VkcARwc3RuB2llMS10bngGdHdpbGlvA2NvbQDADAAjAAEA"
+ "AAcIADIAFAAKAVMHU0lQK0QyVAAEX3NpcARfdGNwBHBzdG4HdXMyLXRueAZ0d2lsaW8DY29tAMAM"
+ "ACMAAQAABwgANAAUAAoBUwhTSVBTK0QyVAAFX3NpcHMEX3RjcARwc3RuB3VzMi10bngGdHdpbGlv"
+ "A2NvbQDADAAjAAEAAAcIADQAFAAKAVMIU0lQUytEMlQABV9zaXBzBF90Y3AEcHN0bgdpZTEtdG54"
+ "BnR3aWxpbwNjb20AwAwAIwABAAAHCAAyAAoACgFTB1NJUCtEMlUABF9zaXAEX3VkcARwc3RuB3Vz"
+ "MS10bngGdHdpbGlvA2NvbQDADAAjAAEAAAcIADIAFAAKAVMHU0lQK0QyVAAEX3NpcARfdGNwBHBz"
+ "dG4HdXMxLXRueAZ0d2lsaW8DY29tAAAAKQIAAAAAAAAA";
+
+ static const char twilio_reply_string[] =
+ "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.ie1-tnx.twilio.com.\n"
+ "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.us2-tnx.twilio.com.\n"
+ "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.us1-tnx.twilio.com.\n"
+ "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.ie1-tnx.twilio.com.\n"
+ "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.us2-tnx.twilio.com.\n"
+ "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.us2-tnx.twilio.com.\n"
+ "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.ie1-tnx.twilio.com.\n"
+ "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.us1-tnx.twilio.com.\n"
+ "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.us1-tnx.twilio.com.\n";
+
+ static const char twilio_reply_json[] =
+ "[\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 20,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIP+D2T\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sip._tcp.pstn.ie1-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 10,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIP+D2U\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sip._udp.pstn.us2-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 20,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIPS+D2T\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sips._tcp.pstn.us1-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 10,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIP+D2U\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sip._udp.pstn.ie1-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 20,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIP+D2T\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sip._tcp.pstn.us2-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 20,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIPS+D2T\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sips._tcp.pstn.us2-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 20,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIPS+D2T\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sips._tcp.pstn.ie1-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 10,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIP+D2U\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sip._udp.pstn.us1-tnx.twilio.com\"\n"
+ " },\n"
+ " {\n"
+ " \"key\" : {\n"
+ " \"class\" : 1,\n"
+ " \"type\" : 35,\n"
+ " \"name\" : \"twilio.com\"\n"
+ " },\n"
+ " \"order\" : 20,\n"
+ " \"preference\" : 10,\n"
+ " \"naptrFlags\" : \"S\",\n"
+ " \"services\" : \"SIP+D2T\",\n"
+ " \"regexp\" : \"\",\n"
+ " \"replacement\" : \"_sip._tcp.pstn.us1-tnx.twilio.com\"\n"
+ " }\n"
+ "]\n";
+
+ _cleanup_free_ void *buf = NULL;
+ size_t sz = 0;
+
+ assert_se(unbase64mem(twilio_reply, &buf, &sz) >= 0);
+
+ assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, sz, DNS_PACKET_SIZE_MAX) == 0);
+ assert_se(p->allocated >= sz);
+
+ memcpy(DNS_PACKET_DATA(p), buf, sz);
+ p->size = sz;
+
+ assert_se(dns_packet_extract(p) >= 0);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *a = NULL;
+ _cleanup_free_ char *joined = NULL;
+ DnsResourceRecord *rr;
+ DNS_ANSWER_FOREACH(rr, p->answer) {
+ const char *s;
+
+ s = ASSERT_PTR(dns_resource_record_to_string(rr));
+ printf("%s\n", s);
+
+ assert_se(strextend(&joined, s, "\n"));
+
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ assert_se(dns_resource_record_to_json(rr, &v) >= 0);
+
+ assert_se(json_variant_append_array(&a, v) >= 0);
+ }
+
+ assert(streq(joined, twilio_reply_string));
+
+ _cleanup_(json_variant_unrefp) JsonVariant *parsed = NULL;
+ assert_se(json_parse(twilio_reply_json, /* flags= */ 0, &parsed, /* ret_line= */ NULL, /* ret_column= */ NULL) >= 0);
+
+ assert_se(json_variant_equal(parsed, a));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/resolve/test-resolved-stream.c b/src/resolve/test-resolved-stream.c
index 847de04..4f27eac 100644
--- a/src/resolve/test-resolved-stream.c
+++ b/src/resolve/test-resolved-stream.c
@@ -329,7 +329,7 @@ static void test_dns_stream(bool tls) {
log_info("test-resolved-stream: Finished %s test", tls ? "TLS" : "TCP");
}
-static void try_isolate_network(void) {
+static int try_isolate_network(void) {
_cleanup_close_ int socket_fd = -EBADF;
int r;
@@ -356,20 +356,25 @@ static void try_isolate_network(void) {
_exit(EXIT_SUCCESS);
}
if (r == -EPROTO) /* EPROTO means nonzero exit code of child, i.e. the tests in the child failed */
- return;
+ return 0;
assert_se(r > 0);
/* Now that we know that the unshare() is safe, let's actually do it */
assert_se(unshare(CLONE_NEWUSER | CLONE_NEWNET) >= 0);
- /* Bring up the loopback interfaceon the newly created network namespace */
+ /* Bring up the loopback interface on the newly created network namespace */
struct ifreq req = { .ifr_ifindex = 1 };
assert_se((socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0);
assert_se(ioctl(socket_fd, SIOCGIFNAME, &req) >= 0);
assert_se(ioctl(socket_fd, SIOCGIFFLAGS, &req) >= 0);
assert_se(FLAGS_SET(req.ifr_flags, IFF_LOOPBACK));
req.ifr_flags |= IFF_UP;
- assert_se(ioctl(socket_fd, SIOCSIFFLAGS, &req) >= 0);
+ /* Do not assert on this, fails in the Ubuntu 24.04 CI environment */
+ r = RET_NERRNO(ioctl(socket_fd, SIOCSIFFLAGS, &req));
+ if (r < 0)
+ return r;
+
+ return 0;
}
int main(int argc, char **argv) {
@@ -378,10 +383,14 @@ int main(int argc, char **argv) {
.in.sin_port = htobe16(random_u64_range(UINT16_MAX - 1024) + 1024),
.in.sin_addr.s_addr = htobe32(INADDR_LOOPBACK)
};
+ int r;
test_setup_logging(LOG_DEBUG);
- try_isolate_network();
+ r = try_isolate_network();
+ if (ERRNO_IS_NEG_PRIVILEGE(r))
+ return log_tests_skipped("lacking privileges");
+ assert_se(r >= 0);
test_dns_stream(false);
#if ENABLE_DNS_OVER_TLS