]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: dns: implement a LRU cache for DNS resolutions
authorBaptiste Assmann <bedis9@gmail.com>
Thu, 4 May 2017 07:05:00 +0000 (09:05 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 2 Jun 2017 09:40:01 +0000 (11:40 +0200)
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.

include/proto/dns.h
include/types/dns.h
src/dns.c
src/server.c

index 14a6af1253b2f08ca4623317e952741bcea2ca03..00a6f4a10b3c0a5fcc90f939ff00c3df852c3a3d 100644 (file)
@@ -48,5 +48,7 @@ int dns_check_resolution_queue(struct dns_resolvers *resolvers);
 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
index 54bbd0220e9c24ffe90b8a85111313a6a37eb507..de9c71331373b756ba59dcf892227d3c043d3802 100644 (file)
@@ -237,6 +237,7 @@ struct dns_resolution {
        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 */
index bcb78bf415f542a28a41917b83fe6f2080e27cb3..45444fdb3e44593572ae75744b3d3dd6c9b1d2b6 100644 (file)
--- 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 @@ struct dns_resolution *resolution = NULL;
 
 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 @@ void dns_resolve_recv(struct dgram_conn *dgram)
        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 @@ void dns_resolve_recv(struct dgram_conn *dgram)
                        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 @@ int dns_init_resolvers(int close_socket)
        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 @@ struct task *dns_process_resolve(struct task *t)
        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)
 {
index c6e42be6797689734229b25a228f1ae177ea33d1..9d0714a8cb6a9c190eba819c3afcdb4687b442a0 100644 (file)
@@ -1682,6 +1682,7 @@ static int srv_alloc_dns_resolution(struct server *srv, const char *hostname)
 
        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;