MINOR: dns: implement a LRU cache for DNS resolutions
Introduction of a DNS response LRU cache in HAProxy.
When a positive response is received from a DNS server, HAProxy stores
it in the struct resolution and then also populates a LRU cache with the
response.
For now, the key in the cache is a XXHASH64 of the hostname in the
domain name format concatened to the query type in string format.
diff --git a/include/proto/dns.h b/include/proto/dns.h
index 14a6af1..00a6f4a 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -48,5 +48,7 @@
unsigned short dns_response_get_query_id(unsigned char *resp);
struct dns_resolution *dns_alloc_resolution(void);
void dns_free_resolution(struct dns_resolution *resolution);
+struct chunk *dns_cache_key(int query_type, char *hostname_dn, int hostname_dn_len, struct chunk *buf);
+struct lru64 *dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int valid_period, void *cache_domain);
#endif // _PROTO_DNS_H
diff --git a/include/types/dns.h b/include/types/dns.h
index 54bbd02..de9c713 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -237,6 +237,7 @@
int try; /* current resolution try */
int try_cname; /* number of CNAME requests sent */
int nb_responses; /* count number of responses received */
+ unsigned long long revision; /* updated for each update */
struct dns_response_packet response; /* structure hosting the DNS response */
struct dns_query_item response_query_records[DNS_MAX_QUERY_RECORDS]; /* <response> query records */
struct dns_answer_item response_answer_records[DNS_MAX_ANSWER_RECORDS]; /* <response> answer records */
diff --git a/src/dns.c b/src/dns.c
index bcb78bf..45444fd 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -22,6 +22,9 @@
#include <common/time.h>
#include <common/ticks.h>
+#include <import/lru.h>
+#include <import/xxhash.h>
+
#include <types/applet.h>
#include <types/cli.h>
#include <types/global.h>
@@ -45,6 +48,9 @@
static int64_t dns_query_id_seed; /* random seed */
+static struct lru64_head *dns_lru_tree;
+static int dns_cache_size = 1024; /* arbitrary DNS cache size */
+
/* proto_udp callback functions for a DNS resolution */
struct dgram_data_cb resolve_dgram_cb = {
.recv = dns_resolve_recv,
@@ -130,6 +136,7 @@
int fd, buflen, ret;
unsigned short query_id;
struct eb32_node *eb;
+ struct lru64 *lru = NULL;
fd = dgram->t.sock.fd;
@@ -245,6 +252,27 @@
continue;
}
+ /* no errors, we can save the response in the cache */
+ if (dns_lru_tree) {
+ unsigned long long seed = 1;
+ struct chunk *buf = get_trash_chunk();
+ struct chunk *tmp = NULL;
+
+ chunk_reset(buf);
+ tmp = dns_cache_key(resolution->query_type, resolution->hostname_dn,
+ resolution->hostname_dn_len, buf);
+ if (!tmp) {
+ nameserver->counters.other += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_ERROR);
+ continue;
+ }
+
+ lru = lru64_get(XXH64(buf->str, buf->len, seed),
+ dns_lru_tree, nameserver->resolvers, 1);
+
+ lru64_commit(lru, resolution, nameserver->resolvers, 1, NULL);
+ }
+
nameserver->counters.valid += 1;
resolution->requester_cb(resolution, nameserver);
}
@@ -936,6 +964,9 @@
struct task *t;
int fd;
+ /* initialize our DNS resolution cache */
+ dns_lru_tree = lru64_new(dns_cache_size);
+
/* give a first random value to our dns query_id seed */
dns_query_id_seed = random();
@@ -1277,6 +1308,110 @@
return t;
}
+/*
+ * build a dns cache key composed as follow:
+ * <query type>#<hostname in domain name format>
+ * and store it into <str>.
+ * It's up to the caller to allocate <buf> and to reset it.
+ * The function returns NULL in case of error (IE <buf> too small) or a pointer
+ * to buf if successful
+ */
+struct chunk *
+dns_cache_key(int query_type, char *hostname_dn, int hostname_dn_len, struct chunk *buf)
+{
+ int len, size;
+ char *str;
+
+ str = buf->str;
+ len = buf->len;
+ size = buf->size;
+
+ switch (query_type) {
+ case DNS_RTYPE_A:
+ if (len + 1 > size)
+ return NULL;
+ memcpy(&str[len], "A", 1);
+ len += 1;
+ break;
+ case DNS_RTYPE_AAAA:
+ if (len + 4 > size)
+ return NULL;
+ memcpy(&str[len], "AAAA", 4);
+ len += 4;
+ break;
+ default:
+ return NULL;
+ }
+
+ if (len + 1 > size)
+ return NULL;
+ memcpy(&str[len], "#", 1);
+ len += 1;
+
+ if (len + hostname_dn_len + 1 > size) // +1 for trailing zero
+ return NULL;
+ memcpy(&str[len], hostname_dn, hostname_dn_len);
+ len += hostname_dn_len;
+ str[len] = '\0';
+
+ return buf;
+}
+
+/*
+ * returns a pointer to a cache entry which may still be considered as up to date
+ * by the caller.
+ * returns NULL if no entry can be found or if the data found is outdated.
+ */
+struct lru64 *
+dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int valid_period, void *cache_domain) {
+ struct lru64 *elem = NULL;
+ struct dns_resolution *resolution = NULL;
+ struct dns_resolvers *resolvers = NULL;
+ int inter = 0;
+ struct chunk *buf = get_trash_chunk();
+ struct chunk *tmp = NULL;
+
+ if (!dns_lru_tree)
+ return NULL;
+
+ chunk_reset(buf);
+ tmp = dns_cache_key(query_type, hostname_dn, hostname_dn_len, buf);
+ if (tmp == NULL)
+ return NULL;
+
+ elem = lru64_lookup(XXH64(buf->str, buf->len, 1), dns_lru_tree, cache_domain, 1);
+
+ if (!elem || !elem->data)
+ return NULL;
+
+ resolution = elem->data;
+
+ /* since we can change the fqdn of a server at run time, it may happen that
+ * we got an innacurate elem.
+ * This is because resolution->hostname_dn points to (owner)->hostname_dn (which
+ * may be changed at run time)
+ */
+ if ((hostname_dn_len == resolution->hostname_dn_len) &&
+ (memcmp(hostname_dn, resolution->hostname_dn, hostname_dn_len) != 0)) {
+ return NULL;
+ }
+
+ resolvers = ((struct server *)resolution->requester)->resolvers;
+
+ if (!resolvers)
+ return NULL;
+
+ if (resolvers->hold.valid < valid_period)
+ inter = resolvers->hold.valid;
+ else
+ inter = valid_period;
+
+ if (!tick_is_expired(tick_add(resolution->last_resolution, inter), now_ms))
+ return elem;
+
+ return NULL;
+}
+
/* if an arg is found, it sets the resolvers section pointer into cli.p0 */
static int cli_parse_stat_resolvers(char **args, struct appctx *appctx, void *private)
{
diff --git a/src/server.c b/src/server.c
index c6e42be..9d0714a 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1682,6 +1682,7 @@
srv->resolution->hostname_dn = srv->hostname_dn;
srv->resolution->hostname_dn_len = srv->hostname_dn_len;
+ srv->resolution->revision = 1;
srv->resolution->requester = srv;
srv->resolution->requester_cb = snr_resolution_cb;
srv->resolution->requester_error_cb = snr_resolution_error_cb;