mirror of
https://github.com/systemd/systemd.git
synced 2026-06-24 08:47:49 +00:00
resolved: add configurable DNS cache size
Add CacheSize= option to [Resolve] section of resolved.conf to allow configuring the maximum number of entries in the per-scope DNS cache. The default remains 4096 entries. Setting this to 0 disables caching (similar to Cache=no). CacheSize= is only read when Cache=yes or Cache=no-negative. When Cache=no, caching is fully disabled regardless of CacheSize=. Changes: - Add cache_size field to Manager struct - Parse CacheSize= from resolved.conf via gperf - Thread cache_size through dns_cache_put() and helper functions - Replace hard-coded CACHE_MAX with the configurable cache_size - When cache_size is 0 or Cache=no, flush cache and skip caching - Add man page documentation for the new option - Add unit tests for cache size enforcement Co-developed-by: Claude <claude@anthropic.com>
This commit is contained in:
@@ -301,6 +301,32 @@
|
||||
</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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#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"
|
||||
@@ -304,6 +305,28 @@ int manager_parse_config_file(Manager *m) {
|
||||
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,
|
||||
|
||||
@@ -19,4 +19,5 @@ 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_dns_cache_max);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_record_types);
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
#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)
|
||||
|
||||
@@ -184,9 +180,12 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) {
|
||||
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 (;;) {
|
||||
@@ -196,7 +195,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) {
|
||||
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);
|
||||
@@ -753,6 +752,10 @@ int dns_cache_put(
|
||||
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
|
||||
|
||||
@@ -3,11 +3,17 @@
|
||||
|
||||
#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);
|
||||
|
||||
@@ -76,6 +76,7 @@ int dns_scope_new(
|
||||
.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 },
|
||||
|
||||
@@ -6,6 +6,7 @@ _Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"")
|
||||
#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"
|
||||
@@ -35,5 +36,8 @@ Resolve.ReadStaticRecords, config_parse_bool, 0,
|
||||
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)
|
||||
|
||||
@@ -641,6 +641,8 @@ static void manager_set_defaults(Manager *m) {
|
||||
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) {};
|
||||
|
||||
@@ -25,6 +25,7 @@ typedef struct Manager {
|
||||
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;
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#LLMNR={{DEFAULT_LLMNR_MODE_STR}}
|
||||
#Cache=yes
|
||||
#CacheFromLocalhost=no
|
||||
#DNSCacheSize=4096
|
||||
#MulticastDNSCacheSize=4096
|
||||
#LLMNRCacheSize=4096
|
||||
#DNSStubListener=yes
|
||||
#DNSStubListenerExtra=
|
||||
#ReadEtcHosts=yes
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include "tmpfile-util.h"
|
||||
|
||||
static DnsCache new_cache(void) {
|
||||
return (DnsCache) {};
|
||||
return (DnsCache) {
|
||||
.cache_max = DEFAULT_CACHE_MAX,
|
||||
};
|
||||
}
|
||||
|
||||
typedef struct PutArgs {
|
||||
@@ -511,6 +513,79 @@ TEST(dns_a_to_cname_success_escaped_name_returns_error) {
|
||||
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()
|
||||
* ================================================================ */
|
||||
|
||||
Reference in New Issue
Block a user