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;