From: Muhammad Nuzaihan Bin Kamal Luddin Date: Tue, 11 Feb 2025 09:29:39 +0000 (+0800) Subject: resolve: add an option to explicitly disable query AAAA, SRV, MX, etc... (#34165) X-Git-Tag: v258-rc1~1362 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=81ae2237c1792943a1ec712ae2e630bcc592175b;p=thirdparty%2Fsystemd.git resolve: add an option to explicitly disable query AAAA, SRV, MX, etc... (#34165) Based on this patch i had submitted to RedHat (https://issues.redhat.com/browse/RHEL-56280), i am submitting this patch to this upstream systemd. There is no way to explicitly enable/disable IPv6 AAAA queries. Problem was that i am using RHEL9 and some applications does not use a newer glibc that supports `no-aaaa` option in `/etc/resolv.conf`. So some applications will still resolve IPv6 AAAA even with `no-aaaa` option and it is inconsistent across the system where some work and some don't. So this systemd-resolved patch catch-all queries and disable IPv6 AAAA queries for all applications in the OS by having an option `RefuseRecordTypes=AAAA` to disable IPv6 AAAA queries. Although https://github.com/systemd/systemd/pull/28136 tries to fix this automatically but it still does not work with `net.ipv6.conf.all.disable_ipv6 = 1`. Also tried with explicitly removing the conditional and force set `family = AF_INET` and still resolves AAAA records. The issue is that i want to explicitly disable IPv6 AAAA queries instead of systemd-resolved to figure out itself which address family it is using, which always have problems. --- diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 8a314a4ba45..9adc0143c7c 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -317,6 +317,21 @@ + + RefuseRecordTypes= + Takes a list of DNS record types separated by space. + Specified record types in each query will be refused. This option can be specified multiple times. + If an empty string is specified, then all previous assignments are cleared. + Examples: + RefuseRecordTypes=AAAA SRV TXT + + + Note that to refuse AAAA record (unlike SRV, TXT, etc.) completely, please also disable IPv6 stack in kernel + with "sysctl -w net.ipv6.conf.all.disable_ipv6=1". + + + + DNSStubListenerExtra= Takes an IPv4 or IPv6 address to listen on. The address may be optionally diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 2fc702200c2..b81b9358d54 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -29,6 +29,24 @@ BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_resolve_support, resolve_support, ResolveSupport); +static int dns_query_new_for_bus( + Manager *m, + DnsQuery **ret, + DnsQuestion *question_utf8, + DnsQuestion *question_idna, + DnsPacket *question_bypass, + int ifindex, + uint64_t flags, + sd_bus_error *error) { + + int r; + + r = dns_query_new(m, ret, question_utf8, question_idna, question_bypass, ifindex, flags); + if (r == -ENOANO) + return sd_bus_error_set(error, BUS_ERROR_DNS_REFUSED, "DNS query type refused."); + return r; +} + static int query_on_bus_track(sd_bus_track *t, void *userdata) { DnsQuery *q = ASSERT_PTR(userdata); @@ -525,7 +543,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, bus_client_log(message, "hostname resolution"); - r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, ifindex, flags); + r = dns_query_new_for_bus(m, &q, question_utf8, question_idna ?: question_utf8, NULL, ifindex, flags, error); if (r < 0) return r; @@ -666,7 +684,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s bus_client_log(message, "address resolution"); - r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new_for_bus(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH, error); if (r < 0) return r; @@ -845,7 +863,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd /* Setting SD_RESOLVED_CLAMP_TTL: let's request that the TTL is fixed up for locally cached entries, * after all we return it in the wire format blob. */ - r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL); + r = dns_query_new_for_bus(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL, error); if (r < 0) return r; @@ -1209,6 +1227,8 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin return r; r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + if (r == -ENOANO) + return reply_method_errorf(q, BUS_ERROR_DNS_REFUSED, "DNS query type refused."); if (r < 0) return r; @@ -1372,7 +1392,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s bus_client_log(message, "service resolution"); - r = dns_query_new(m, &q, question_utf8, question_idna, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new_for_bus(m, &q, question_utf8, question_idna, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH, error); if (r < 0) return r; diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 57200307795..393e8a898ff 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -413,3 +413,43 @@ int manager_parse_config_file(Manager *m) { #endif return 0; } + +int config_parse_record_types( + 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) { + + Set **types = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + *types = set_free(*types); + return 1; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) + return 1; + + r = dns_type_from_string(word); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid DNS record type, ignoring: %s", word); + continue; + } + + r = set_ensure_put(types, NULL, INT_TO_PTR(r)); + if (r < 0) + return log_oom(); + } +} diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index ce280cb0a32..069ae7d78fa 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -24,3 +24,4 @@ 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_dns_stub_listener_extra); +CONFIG_PARSER_PROTOTYPE(config_parse_record_types); diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 700c40a8aeb..d112c61c3ce 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -136,7 +136,7 @@ static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { dns_search_domain_unref(c->search_domain); c->search_domain = dns_search_domain_ref(next); - return 1; + return 0; } static int dns_query_candidate_add_transaction( @@ -508,6 +508,44 @@ DnsQuery *dns_query_free(DnsQuery *q) { return mfree(q); } +static int validate_and_mangle_question(DnsQuestion **question, Set *types) { + int r; + + if (set_isempty(types)) + return 0; /* No filtering configured. Let's shortcut. */ + + bool has_good = false, has_bad = false; + DnsResourceKey *key; + DNS_QUESTION_FOREACH(key, *question) + if (set_contains(types, INT_TO_PTR(key->type))) + has_bad = true; + else + has_good = true; + + if (has_bad && !has_good) + return -ENOANO; /* All bad, refuse.*/ + if (!has_bad) { + assert(has_good); /* The question should have at least one key. */ + return 0; /* All good. Not necessary to filter. */ + } + + /* Mangle the question suppressing bad entries, leaving good entries */ + _cleanup_(dns_question_unrefp) DnsQuestion *new_question = dns_question_new(dns_question_size(*question)); + if (!new_question) + return -ENOMEM; + + DnsQuestionItem *item; + DNS_QUESTION_FOREACH_ITEM(item, *question) { + if (set_contains(types, INT_TO_PTR(item->key->type))) + continue; + r = dns_question_add_raw(new_question, item->key, item->flags); + if (r < 0) + return r; + } + + return free_and_replace_full(*question, new_question, dns_question_unref); +} + int dns_query_new( Manager *m, DnsQuery **ret, @@ -524,6 +562,15 @@ int dns_query_new( assert(m); + /* Check for records that is refused and refuse query for the records if matched in configuration */ + r = validate_and_mangle_question(&question_utf8, m->refuse_record_types); + if (r < 0) + return r; + + r = validate_and_mangle_question(&question_idna, m->refuse_record_types); + if (r < 0) + return r; + if (question_bypass) { /* It's either a "bypass" query, or a regular one, but can't be both. */ if (question_utf8 || question_idna) diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 40c51fe8ddb..7acfffbb66d 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -996,6 +996,8 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea (DNS_PACKET_CD(p) ? SD_RESOLVED_NO_VALIDATE | SD_RESOLVED_NO_CACHE : 0)| (DNS_PACKET_DO(p) ? SD_RESOLVED_REQUIRE_PRIMARY : 0)| SD_RESOLVED_CLAMP_TTL); + if (r == -ENOANO) /* Refuse query if there is -ENOANO */ + return (void) dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false); if (r < 0) { log_error_errno(r, "Failed to generate query object: %m"); dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index d311754e767..0124ef4208f 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -34,3 +34,4 @@ Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, Resolve.DNSStubListenerExtra, config_parse_dns_stub_listener_extra, 0, offsetof(Manager, dns_extra_stub_listeners) Resolve.CacheFromLocalhost, config_parse_bool, 0, offsetof(Manager, cache_from_localhost) Resolve.StaleRetentionSec, config_parse_sec, 0, offsetof(Manager, stale_retention_usec) +Resolve.RefuseRecordTypes, config_parse_record_types, 0, offsetof(Manager, refuse_record_types) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index e3749786918..f86cc4267d2 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -843,6 +843,7 @@ Manager *manager_free(Manager *m) { dnstls_manager_free(m); #endif + set_free(m->refuse_record_types); hashmap_free(m->links); hashmap_free(m->dns_transactions); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 3481a8836bf..7aac4afe101 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -137,6 +137,9 @@ struct Manager { struct stat etc_hosts_stat; bool read_etc_hosts; + /* List of refused DNS Record Types*/ + Set *refuse_record_types; + OrderedSet *dns_extra_stub_listeners; /* Local DNS stub on 127.0.0.53:53 */ diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index e5fdc34659d..d04bcc332ee 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -37,6 +37,24 @@ static void lookup_parameters_destroy(LookupParameters *p) { free(p->name); } +static int dns_query_new_for_varlink( + Manager *m, + DnsQuery **ret, + DnsQuestion *question_utf8, + DnsQuestion *question_idna, + DnsPacket *question_bypass, + int ifindex, + uint64_t flags, + sd_varlink *link) { + + int r; + + r = dns_query_new(m, ret, question_utf8, question_idna, question_bypass, ifindex, flags); + if (r == -ENOANO) + return sd_varlink_error(link, "io.systemd.Resolve.QueryRefused", NULL); + return r; +} + static int reply_query_state(DnsQuery *q) { assert(q); @@ -384,7 +402,7 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet if (r < 0 && r != -EALREADY) return r; - r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags); + r = dns_query_new_for_varlink(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags, link); if (r < 0) return r; @@ -539,7 +557,7 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete if (r < 0) return r; - r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new_for_varlink(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH, link); if (r < 0) return r; @@ -874,7 +892,7 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin if (r < 0) return r; - r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new_for_varlink(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH, q->varlink_request); if (r < 0) return r; @@ -1037,7 +1055,7 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete if (r < 0) return r; - r = dns_query_new(m, &q, question_utf8, question_idna, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new_for_varlink(m, &q, question_utf8, question_idna, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH, link); if (r < 0) return r; @@ -1182,7 +1200,7 @@ static int vl_method_resolve_record(sd_varlink *link, sd_json_variant *parameter 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); + r = dns_query_new_for_varlink(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL, link); if (r < 0) return r; diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 0031b156b1f..47c9d84c001 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -35,3 +35,4 @@ #ReadEtcHosts=yes #ResolveUnicastSingleLabel=no #StaleRetentionSec=0 +#RefuseRecordTypes= diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index 48e5e12e87d..dc282d038b7 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -165,6 +165,7 @@ static SD_VARLINK_DEFINE_ERROR(QueryTimedOut); static SD_VARLINK_DEFINE_ERROR(MaxAttemptsReached); static SD_VARLINK_DEFINE_ERROR(InvalidReply); static SD_VARLINK_DEFINE_ERROR(QueryAborted); +static SD_VARLINK_DEFINE_ERROR(QueryRefused); static SD_VARLINK_DEFINE_ERROR( DNSSECValidationFailed, SD_VARLINK_DEFINE_FIELD(result, SD_VARLINK_STRING, 0), @@ -218,6 +219,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_error_MaxAttemptsReached, &vl_error_InvalidReply, &vl_error_QueryAborted, + &vl_error_QueryRefused, &vl_error_DNSSECValidationFailed, &vl_error_NoTrustAnchor, &vl_error_ResourceRecordTypeUnsupported, diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index 431b37a5e52..facc39b47de 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -1084,6 +1084,59 @@ testcase_13_varlink_subscribe_dns_configuration() { <(jq -cr --seq '.configuration[] | select(.ifname == "dns0" and .servers != null and .searchDomains != null) | {"dns0":{servers: [.servers[] | .address], domains: [.searchDomains[] | .name]}}' "$tmpfile") } +# Test RefuseRecordTypes +testcase_14_refuse_record_types() { + # shellcheck disable=SC2317 + cleanup() { + rm -f /run/systemd/resolved.conf.d/refuserecords.conf + if [[ -e /etc/resolv.conf.bak ]]; then + rm -f /etc/resolv.conf + mv /etc/resolv.conf.bak /etc/resolv.conf + fi + restart_resolved + } + trap cleanup RETURN ERR + + mkdir -p /run/systemd/resolved.conf.d + { + echo "[Resolve]" + echo "RefuseRecordTypes=AAAA SRV TXT" + } >/run/systemd/resolved.conf.d/refuserecords.conf + if [[ -e /etc/resolv.conf ]]; then + mv /etc/resolv.conf /etc/resolv.conf.bak + fi + ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf + systemctl reload systemd-resolved.service + # disable_ipv6 is necessary do refuse AAAA + disable_ipv6 + run dig localhost -t AAAA + grep -qF "status: REFUSED" "$RUN_OUT" + + run dig localhost -t SRV + grep -qF "status: REFUSED" "$RUN_OUT" + + run dig localhost -t TXT + grep -qF "status: REFUSED" "$RUN_OUT" + + run dig localhost -t A + grep -qF "status: NOERROR" "$RUN_OUT" + + run resolvectl query localhost5 + grep -qF "127.128.0.5" "$RUN_OUT" + + (! run resolvectl query localhost5 --type=SRV) + grep -qF "DNS query type refused." "$RUN_OUT" + + (! run resolvectl query localhost5 --type=TXT) + grep -qF "DNS query type refused." "$RUN_OUT" + + (! run resolvectl query localhost5 --type=AAAA) + grep -qF "DNS query type refused." "$RUN_OUT" + + run resolvectl query localhost5 --type=A + grep -qF "127.128.0.5" "$RUN_OUT" +} + # PRE-SETUP systemctl unmask systemd-resolved.service systemctl enable --now systemd-resolved.service