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.
This commit is contained in:
Baptiste Assmann 2017-05-04 09:05:00 +02:00 committed by Willy Tarreau
parent 729c901c3f
commit fa4a663095
4 changed files with 139 additions and 0 deletions

View 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

View 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 */

135
src/dns.c
View File

@ -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)
{

View 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;