diff options
Diffstat (limited to 'src/resolve')
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(¶ms, 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 |