Skip to content

Commit

Permalink
Merge pull request zephyrproject-rtos#12 from converge-io/lucas/dns-c…
Browse files Browse the repository at this point in the history
…ache

net: dns: Add DNS cache for improved performance
  • Loading branch information
ldenefle authored Aug 2, 2024
2 parents 6f84f6a + d7e7f6e commit bb47630
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 10 deletions.
1 change: 1 addition & 0 deletions subsys/net/lib/dns/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ zephyr_library_sources(dns_pack.c)

zephyr_library_sources_ifdef(CONFIG_DNS_RESOLVER resolve.c)
zephyr_library_sources_ifdef(CONFIG_DNS_SD dns_sd.c)
zephyr_library_sources_ifdef(CONFIG_DNS_RESOLVER_CACHE dns_cache.c)

if(CONFIG_MDNS_RESPONDER)
zephyr_library_sources(mdns_responder.c)
Expand Down
28 changes: 28 additions & 0 deletions subsys/net/lib/dns/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ config DNS_RESOLVER_MAX_SERVERS
DNS server is enough. Each connection to DNS server will use one
network context.

config DNS_RESOLVER_MAX_QUERY_LEN
int "Max length of a DNS query"
range 1 255
default 255
help
Max length of a DNS query that should be looked up including the
trailing 0. So e.g. "example.com" would have a query len of 12.

menuconfig DNS_SERVER_IP_ADDRESSES
bool "Set DNS server IP addresses"
help
Expand Down Expand Up @@ -121,6 +129,26 @@ module-str = Log level for DNS resolver
module-help = Enables DNS resolver code to output debug messages.
source "subsys/net/Kconfig.template.log_config.net"

menuconfig DNS_RESOLVER_CACHE
bool "DNS resolver cache"
help
This option enables the dns resolver cache. DNS queries
will be cached based on TTL and delivered from cache
whenever possible. This reduces network usage.

if DNS_RESOLVER_CACHE

config DNS_RESOLVER_CACHE_MAX_ENTRIES
int "Number of cache entries supported by the dns cache"
default 6
help
This defines how many entries the DNS cache can hold. If
not enough entries for caching are available the oldest
entry gets replaced. Adjusting this value will affect
RAM usage.

endif # DNS_RESOLVER_CACHE

endif # DNS_RESOLVER

config MDNS_RESPONDER
Expand Down
162 changes: 162 additions & 0 deletions subsys/net/lib/dns/dns_cache.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2024 Endress+Hauser AG
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/net/dns_resolve.h>
#include "dns_cache.h"

LOG_MODULE_REGISTER(net_dns_cache, CONFIG_DNS_RESOLVER_LOG_LEVEL);

static void dns_cache_clean(struct dns_cache const *cache);

int dns_cache_flush(struct dns_cache *cache)
{
k_mutex_lock(cache->lock, K_FOREVER);
for (size_t i = 0; i < cache->size; i++) {
cache->entries[i].in_use = false;
}
k_mutex_unlock(cache->lock);

return 0;
}

int dns_cache_add(struct dns_cache *cache, char const *query, struct dns_addrinfo const *addrinfo,
uint32_t ttl)
{
k_timepoint_t closest_to_expiry = sys_timepoint_calc(K_FOREVER);
size_t index_to_replace = 0;
bool found_empty = false;

if (cache == NULL || query == NULL || addrinfo == NULL || ttl == 0) {
return -EINVAL;
}

if (strlen(query) >= CONFIG_DNS_RESOLVER_MAX_QUERY_LEN) {
NET_WARN("Query string to big to be processed %u >= "
"CONFIG_DNS_RESOLVER_MAX_QUERY_LEN",
strlen(query));
return -EINVAL;
}

k_mutex_lock(cache->lock, K_FOREVER);

NET_DBG("Add \"%s\" with TTL %" PRIu32, query, ttl);

dns_cache_clean(cache);

for (size_t i = 0; i < cache->size; i++) {
if (!cache->entries[i].in_use) {
index_to_replace = i;
found_empty = true;
break;
} else if (sys_timepoint_cmp(closest_to_expiry, cache->entries[i].expiry) > 0) {
index_to_replace = i;
closest_to_expiry = cache->entries[i].expiry;
}
}

if (!found_empty) {
NET_DBG("Overwrite \"%s\"", cache->entries[index_to_replace].query);
}

strncpy(cache->entries[index_to_replace].query, query,
CONFIG_DNS_RESOLVER_MAX_QUERY_LEN - 1);
cache->entries[index_to_replace].data = *addrinfo;
cache->entries[index_to_replace].expiry = sys_timepoint_calc(K_SECONDS(ttl));
cache->entries[index_to_replace].in_use = true;

k_mutex_unlock(cache->lock);

return 0;
}

int dns_cache_remove(struct dns_cache *cache, char const *query)
{
NET_DBG("Remove all entries with query \"%s\"", query);
if (strlen(query) >= CONFIG_DNS_RESOLVER_MAX_QUERY_LEN) {
NET_WARN("Query string to big to be processed %u >= "
"CONFIG_DNS_RESOLVER_MAX_QUERY_LEN",
strlen(query));
return -EINVAL;
}

k_mutex_lock(cache->lock, K_FOREVER);

dns_cache_clean(cache);

for (size_t i = 0; i < cache->size; i++) {
if (cache->entries[i].in_use && strcmp(cache->entries[i].query, query) == 0) {
cache->entries[i].in_use = false;
}
}

k_mutex_unlock(cache->lock);

return 0;
}

int dns_cache_find(struct dns_cache const *cache, const char *query, struct dns_addrinfo *addrinfo,
size_t addrinfo_array_len)
{
size_t found = 0;

NET_DBG("Find \"%s\"", query);
if (cache == NULL || query == NULL || addrinfo == NULL || addrinfo_array_len <= 0) {
return -EINVAL;
}
if (strlen(query) >= CONFIG_DNS_RESOLVER_MAX_QUERY_LEN) {
NET_WARN("Query string to big to be processed %u >= "
"CONFIG_DNS_RESOLVER_MAX_QUERY_LEN",
strlen(query));
return -EINVAL;
}

k_mutex_lock(cache->lock, K_FOREVER);

dns_cache_clean(cache);

for (size_t i = 0; i < cache->size; i++) {
if (!cache->entries[i].in_use) {
continue;
}
if (strcmp(cache->entries[i].query, query) != 0) {
continue;
}
if (found >= addrinfo_array_len) {
NET_WARN("Found \"%s\" but not enough space in provided buffer.", query);
found++;
} else {
addrinfo[found] = cache->entries[i].data;
found++;
NET_DBG("Found \"%s\"", query);
}
}

k_mutex_unlock(cache->lock);

if (found > addrinfo_array_len) {
return -ENOSR;
}

if (found == 0) {
NET_DBG("Could not find \"%s\"", query);
}
return found;
}

/* Needs to be called when lock is already acquired */
static void dns_cache_clean(struct dns_cache const *cache)
{
for (size_t i = 0; i < cache->size; i++) {
if (!cache->entries[i].in_use) {
continue;
}

if (sys_timepoint_expired(cache->entries[i].expiry)) {
NET_DBG("Remove \"%s\"", cache->entries[i].query);
cache->entries[i].in_use = false;
}
}
}
100 changes: 100 additions & 0 deletions subsys/net/lib/dns/dns_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/** @file
* @brief DNS cache
*
* An cache holding dns records for faster dns resolving.
*/

/*
* Copyright (c) 2024 Endress+Hauser AG
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_NET_DNS_CACHE_H_
#define ZEPHYR_INCLUDE_NET_DNS_CACHE_H_

#include <stdint.h>
#include <zephyr/net/dns_resolve.h>
#include <zephyr/kernel.h>
#include <zephyr/sys_clock.h>

struct dns_cache_entry {
char query[CONFIG_DNS_RESOLVER_MAX_QUERY_LEN];
struct dns_addrinfo data;
k_timepoint_t expiry;
bool in_use;
};

struct dns_cache {
size_t size;
struct dns_cache_entry *entries;
struct k_mutex *lock;
};

/**
* @brief Statically define and initialize a DNS queue.
*
* The cache can be accessed outside the module where it is defined using:
*
* @code extern struct dns_cache <name>; @endcode
*
* @param name Name of the cache.
*/
#define DNS_CACHE_DEFINE(name, cache_size) \
static K_MUTEX_DEFINE(name##_mutex); \
static struct dns_cache_entry name##_entries[cache_size]; \
static struct dns_cache name = { \
.entries = name##_entries, .size = cache_size, .lock = &name##_mutex};

/**
* @brief Flushes the dns cache removing all its entries.
*
* @param cache Cache to be flushed
* @retval 0 on success
* @retval On error, a negative value is returned.
*/
int dns_cache_flush(struct dns_cache *cache);

/**
* @brief Adds a new entry to the dns cache removing the one closest to expiry
* if no free space is available.
*
* @param cache Cache where the entry should be added.
* @param query Query which should be persisted in the cache.
* @param addrinfo Addrinfo resulting from the query which will be returned
* upon cache hit.
* @param ttl Time to live for the entry in seconds. This usually represents
* the TTL of the RR.
* @retval 0 on success
* @retval On error, a negative value is returned.
*/
int dns_cache_add(struct dns_cache *cache, char const *query, struct dns_addrinfo const *addrinfo,
uint32_t ttl);

/**
* @brief Removes all entries with the given query
*
* @param cache Cache where the entries should be removed.
* @param query Query which should be searched for.
* @retval 0 on success
* @retval On error, a negative value is returned.
*/
int dns_cache_remove(struct dns_cache *cache, char const *query);

/**
* @brief Tries to find the specified query entry within the cache.
*
* @param cache Cache where the entry should be searched.
* @param query Query which should be searched for.
* @param addrinfo dns_addrinfo array which will be written if the query was found.
* @param addrinfo_array_len Array size of the dns_addrinfo array
* @retval on success the amount of dns_addrinfo written into the addrinfo array will be returned.
* A cache miss will therefore return a 0.
* @retval On error a negative value is returned.
* -ENOSR means there was not enough space in the addrinfo array to accommodate all cache hits the
* array will however be filled with valid data.
*/
int dns_cache_find(struct dns_cache const *cache, const char *query, struct dns_addrinfo *addrinfo,
size_t addrinfo_array_len);

#endif /* ZEPHYR_INCLUDE_NET_DNS_CACHE_H_ */
Loading

0 comments on commit bb47630

Please sign in to comment.