diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml
index 8a314a4ba4..9adc0143c7 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 2fc702200c..b81b9358d5 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 5720030779..393e8a898f 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 ce280cb0a3..069ae7d78f 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 700c40a8ae..d112c61c3c 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 40c51fe8dd..7acfffbb66 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 d311754e76..0124ef4208 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 e374978691..f86cc4267d 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 3481a8836b..7aac4afe10 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 e5fdc34659..d04bcc332e 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 0031b156b1..47c9d84c00 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 48e5e12e87..dc282d038b 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 431b37a5e5..facc39b47d 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