]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolve: add an option to explicitly disable query AAAA, SRV, MX, etc... (#34165)
authorMuhammad Nuzaihan Bin Kamal Luddin <zaihan@unrealasia.net>
Tue, 11 Feb 2025 09:29:39 +0000 (17:29 +0800)
committerGitHub <noreply@github.com>
Tue, 11 Feb 2025 09:29:39 +0000 (18:29 +0900)
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.

13 files changed:
man/resolved.conf.xml
src/resolve/resolved-bus.c
src/resolve/resolved-conf.c
src/resolve/resolved-conf.h
src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-stub.c
src/resolve/resolved-gperf.gperf
src/resolve/resolved-manager.c
src/resolve/resolved-manager.h
src/resolve/resolved-varlink.c
src/resolve/resolved.conf.in
src/shared/varlink-io.systemd.Resolve.c
test/units/TEST-75-RESOLVED.sh

index 8a314a4ba45827b80fee19d115b51e82d87bdac2..9adc0143c7c05540936a54400503f841689afc48 100644 (file)
         <xi:include href="version-info.xml" xpointer="v232"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>RefuseRecordTypes=</varname></term>
+        <listitem><para>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.</para>
+        <para>Examples:
+        <programlisting>RefuseRecordTypes=AAAA SRV TXT</programlisting>
+        </para>
+
+        <para>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".</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>DNSStubListenerExtra=</varname></term>
         <listitem><para>Takes an IPv4 or IPv6 address to listen on. The address may be optionally
index 2fc702200c20a04a4feaaa088c428e0133364f70..b81b9358d54b018ac0804c8bfcd2d5d2962045cd 100644 (file)
 
 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;
 
index 572003077959b8cd51f584bc50e8377b6142bb65..393e8a898ff3c1bd37e878ff8be6c517e1263b94 100644 (file)
@@ -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();
+        }
+}
index ce280cb0a32c710976368b284248d492c21bee3a..069ae7d78fa798d4a15552359e4b1938c690679e 100644 (file)
@@ -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);
index 700c40a8aeb42e4f35a7e7feafde46570675c4a7..d112c61c3ce384803d28bbe3dd20a6b4a84361d9 100644 (file)
@@ -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)
index 40c51fe8ddbc4dfdcb2b1b0a9c00d7d053c458d8..7acfffbb66dcd8f43e7e344b948e0995724a7d53 100644 (file)
@@ -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);
index d311754e767e337d81eff55d42779a97a0c66d33..0124ef4208fab155d6e9a23c99c10ab5c2d0a8a8 100644 (file)
@@ -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)
index e37497869181dd8a8970f00b6d0df9c68d583c61..f86cc4267d2895c7e9fa6d0ecfa072715b140e33 100644 (file)
@@ -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);
 
index 3481a8836bfdc97c37922291291da4acc1451aa1..7aac4afe101d3faa74b60142beb37c90cd457122 100644 (file)
@@ -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 */
index e5fdc34659d0a75e2eb68815fe13ed261c268001..d04bcc332eeb4ae8062402c7e5d15bf22c922df8 100644 (file)
@@ -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;
 
index 0031b156b1f512d1743296f64b47f9c79b2df453..47c9d84c001bddacc92c3edd7c0e1010904eefff 100644 (file)
@@ -35,3 +35,4 @@
 #ReadEtcHosts=yes
 #ResolveUnicastSingleLabel=no
 #StaleRetentionSec=0
+#RefuseRecordTypes=
index 48e5e12e87d730b538ec07c26d246ca514dc17d7..dc282d038b7236779e90e4636cf1521ca5a0a915 100644 (file)
@@ -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,
index 431b37a5e527359bc2b0ed799345bad95135776a..facc39b47de05a65227e0c2cfaf133ad4600ac10 100755 (executable)
@@ -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