<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
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);
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;
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;
/* 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;
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;
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;
#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();
+ }
+}
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);
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(
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,
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)
(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);
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)
dnstls_manager_free(m);
#endif
+ set_free(m->refuse_record_types);
hashmap_free(m->links);
hashmap_free(m->dns_transactions);
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 */
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);
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;
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;
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;
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;
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;
#ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no
#StaleRetentionSec=0
+#RefuseRecordTypes=
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),
&vl_error_MaxAttemptsReached,
&vl_error_InvalidReply,
&vl_error_QueryAborted,
+ &vl_error_QueryRefused,
&vl_error_DNSSECValidationFailed,
&vl_error_NoTrustAnchor,
&vl_error_ResourceRecordTypeUnsupported,
<(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