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.
This commit is contained in:
Muhammad Nuzaihan Bin Kamal Luddin
2025-02-11 17:29:39 +08:00
committed by GitHub
parent 1e4d0b00e5
commit 81ae2237c1
13 changed files with 214 additions and 10 deletions

View File

@@ -317,6 +317,21 @@
<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

View File

@@ -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;

View 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();
}
}

View 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);

View 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)

View 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);

View 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)

View 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);

View 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 */

View 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;

View File

@@ -35,3 +35,4 @@
#ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no
#StaleRetentionSec=0
#RefuseRecordTypes=

View 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,

View File

@@ -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