</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>DNSCacheSize=</varname></term>
+ <term><varname>MulticastDNSCacheSize=</varname></term>
+ <term><varname>LLMNRCacheSize=</varname></term>
+ <listitem><para>Takes a non-negative integer. Configures the maximum number of DNS resource record
+ entries that may be stored in the per-scope cache for unicast DNS, Multicast DNS (mDNS), and
+ Link-Local Multicast Name Resolution (LLMNR) respectively. Each defaults to 4096. The maximum
+ allowed value is 16777216 (2^24). Setting any of these to 0 effectively disables caching for the
+ respective protocol. These settings are only effective when <varname>Cache=</varname> is set to
+ <literal>yes</literal> or <literal>no-negative</literal>. If <varname>Cache=no</varname>, caching
+ is fully disabled regardless of these values.</para>
+
+ <para>Note that Multicast DNS relies heavily on caching for request suppression and efficient
+ operation. It is recommended to keep <varname>MulticastDNSCacheSize=</varname> at a reasonably high
+ value even when reducing <varname>DNSCacheSize=</varname>.</para>
+
+ <para>Note that <command>systemd-resolved</command> automatically flushes all caches on system
+ memory pressure, thus in most cases manual cache size configuration should not be necessary.</para>
+
+ <para>Note that caching is turned off by default for host-local DNS servers.
+ See <varname>CacheFromLocalhost=</varname> for details.</para>
+
+ <xi:include href="version-info.xml" xpointer="v261"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>DNSStubListener=</varname></term>
<listitem><para>Takes a boolean argument or one of <literal>udp</literal> and
#include "ordered-set.h"
#include "proc-cmdline.h"
#include "resolved-conf.h"
+#include "resolved-dns-cache.h"
#include "resolved-dns-search-domain.h"
#include "resolved-dns-server.h"
#include "resolved-dns-stub.h"
return 0;
}
+int config_parse_dns_cache_max(
+ 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) {
+
+ Manager *m = ASSERT_PTR(userdata);
+
+ assert(ltype >= 0 && ltype < _DNS_PROTOCOL_MAX);
+
+ return config_parse_unsigned_bounded(
+ unit, filename, line, section, section_line, lvalue, rvalue,
+ 0, CACHE_MAX_UPPER_LIMIT, true,
+ &m->cache_max[ltype]);
+}
+
int config_parse_record_types(
const char *unit,
const char *filename,
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_dns_cache_max);
CONFIG_PARSER_PROTOTYPE(config_parse_record_types);
#include "string-util.h"
#include "time-util.h"
-/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to
- * leave DNS caches unbounded, but that's crazy. */
-#define CACHE_MAX 4096
-
/* We never keep any item longer than 2h in our cache unless StaleRetentionSec is greater than zero. */
#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR)
if (add <= 0)
return;
+ if (c->cache_max == 0)
+ return;
+
/* Makes space for n new entries. Note that we actually allow
- * the cache to grow beyond CACHE_MAX, but only when we shall
- * add more RRs to the cache than CACHE_MAX at once. In that
+ * the cache to grow beyond cache_max, but only when we shall
+ * add more RRs to the cache than cache_max at once. In that
* case the cache will be emptied completely otherwise. */
for (;;) {
if (prioq_isempty(c->by_expiry))
break;
- if (prioq_size(c->by_expiry) + add < CACHE_MAX)
+ if (prioq_size(c->by_expiry) + add < c->cache_max)
break;
i = prioq_peek(c->by_expiry);
assert(c);
assert(owner_address);
+ /* Check cache mode here too, since the mDNS caller doesn't guard against Cache=no. */
+ if (cache_mode == DNS_CACHE_MODE_NO || c->cache_max == 0)
+ return 0;
+
dns_cache_remove_previous(c, key, answer);
/* We only care for positive replies and NXDOMAINs, on all other replies we will simply flush the respective
#include "resolved-forward.h"
+/* Never cache more than 4K entries by default. RFC 1536, Section 5 suggests to
+ * leave DNS caches unbounded, but that's crazy. */
+#define DEFAULT_CACHE_MAX 4096U
+#define CACHE_MAX_UPPER_LIMIT (1U << 24)
+
typedef struct DnsCache {
Hashmap *by_key;
Prioq *by_expiry;
unsigned n_hit;
unsigned n_miss;
+ unsigned cache_max;
} DnsCache;
void dns_cache_flush(DnsCache *c);
.protocol = protocol,
.family = family,
.resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC,
+ .cache.cache_max = m->cache_max[protocol],
/* Enforce ratelimiting for the multicast protocols */
.ratelimit = { MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST },
#endif
#include <stddef.h>
#include "conf-parser.h"
+#include "dns-packet.h"
#include "resolved-conf.h"
#include "resolved-dns-server.h"
#include "resolved-manager.h"
Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label)
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.DNSCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_DNS, 0
+Resolve.MulticastDNSCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_MDNS, 0
+Resolve.LLMNRCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_LLMNR, 0
Resolve.StaleRetentionSec, config_parse_sec, 0, offsetof(Manager, stale_retention_usec)
Resolve.RefuseRecordTypes, config_parse_record_types, 0, offsetof(Manager, refuse_record_types)
m->read_static_records = true;
m->resolve_unicast_single_label = false;
m->cache_from_localhost = false;
+ for (DnsProtocol p = 0; p < _DNS_PROTOCOL_MAX; p++)
+ m->cache_max[p] = DEFAULT_CACHE_MAX;
m->stale_retention_usec = 0;
m->refuse_record_types = set_free(m->refuse_record_types);
m->resolv_conf_stat = (struct stat) {};
DnssecMode dnssec_mode;
DnsOverTlsMode dns_over_tls_mode;
DnsCacheMode enable_cache;
+ unsigned cache_max[_DNS_PROTOCOL_MAX];
bool cache_from_localhost;
DnsStubListenerMode dns_stub_listener_mode;
usec_t stale_retention_usec;
#LLMNR={{DEFAULT_LLMNR_MODE_STR}}
#Cache=yes
#CacheFromLocalhost=no
+#DNSCacheSize=4096
+#MulticastDNSCacheSize=4096
+#LLMNRCacheSize=4096
#DNSStubListener=yes
#DNSStubListenerExtra=
#ReadEtcHosts=yes
#include "tmpfile-util.h"
static DnsCache new_cache(void) {
- return (DnsCache) {};
+ return (DnsCache) {
+ .cache_max = DEFAULT_CACHE_MAX,
+ };
}
typedef struct PutArgs {
ASSERT_TRUE(dns_cache_is_empty(&cache));
}
+TEST(dns_cache_size_honored) {
+ _cleanup_(dns_cache_unrefp) DnsCache cache = new_cache();
+ _cleanup_(put_args_unrefp) PutArgs put_args = mk_put_args();
+
+ cache.cache_max = 4;
+
+ put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "one.example.com");
+ ASSERT_NOT_NULL(put_args.key);
+ put_args.rcode = DNS_RCODE_SUCCESS;
+ answer_add_a(&put_args, put_args.key, 0xc0a80101, 3600, DNS_ANSWER_CACHEABLE);
+ ASSERT_OK(cache_put(&cache, &put_args));
+
+ dns_resource_key_unref(put_args.key);
+ dns_answer_unref(put_args.answer);
+ put_args.answer = dns_answer_new(1);
+ ASSERT_NOT_NULL(put_args.answer);
+
+ put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "two.example.com");
+ ASSERT_NOT_NULL(put_args.key);
+ answer_add_a(&put_args, put_args.key, 0xc0a80102, 3600, DNS_ANSWER_CACHEABLE);
+ ASSERT_OK(cache_put(&cache, &put_args));
+
+ dns_resource_key_unref(put_args.key);
+ dns_answer_unref(put_args.answer);
+ put_args.answer = dns_answer_new(1);
+ ASSERT_NOT_NULL(put_args.answer);
+
+ put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "three.example.com");
+ ASSERT_NOT_NULL(put_args.key);
+ answer_add_a(&put_args, put_args.key, 0xc0a80103, 3600, DNS_ANSWER_CACHEABLE);
+ ASSERT_OK(cache_put(&cache, &put_args));
+
+ dns_resource_key_unref(put_args.key);
+ dns_answer_unref(put_args.answer);
+ put_args.answer = dns_answer_new(1);
+ ASSERT_NOT_NULL(put_args.answer);
+
+ put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "four.example.com");
+ ASSERT_NOT_NULL(put_args.key);
+ answer_add_a(&put_args, put_args.key, 0xc0a80104, 3600, DNS_ANSWER_CACHEABLE);
+ ASSERT_OK(cache_put(&cache, &put_args));
+
+ dns_resource_key_unref(put_args.key);
+ dns_answer_unref(put_args.answer);
+ put_args.answer = dns_answer_new(1);
+ ASSERT_NOT_NULL(put_args.answer);
+
+ put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "five.example.com");
+ ASSERT_NOT_NULL(put_args.key);
+ answer_add_a(&put_args, put_args.key, 0xc0a80105, 3600, DNS_ANSWER_CACHEABLE);
+ ASSERT_OK(cache_put(&cache, &put_args));
+
+ /* Each dns_cache_put() call reserves space for both the answer RR and the key (cache_keys=2),
+ * so eviction triggers when prioq_size + 2 >= cache_max (i.e. at the 3rd entry with cache_max=4).
+ * After 5 inserts, only the last 2 entries remain. */
+ ASSERT_EQ(dns_cache_size(&cache), 2u);
+}
+
+TEST(dns_cache_size_zero_evicts_all) {
+ _cleanup_(dns_cache_unrefp) DnsCache cache = new_cache();
+ _cleanup_(put_args_unrefp) PutArgs put_args = mk_put_args();
+
+ cache.cache_max = 0;
+
+ put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com");
+ ASSERT_NOT_NULL(put_args.key);
+ put_args.rcode = DNS_RCODE_SUCCESS;
+ answer_add_a(&put_args, put_args.key, 0xc0a8017f, 3600, DNS_ANSWER_CACHEABLE);
+ ASSERT_OK(cache_put(&cache, &put_args));
+
+ ASSERT_TRUE(dns_cache_is_empty(&cache));
+}
+
/* ================================================================
* dns_cache_lookup()
* ================================================================ */