From fa4a6630950138cfcb8904855c7517d9fdd472f7 Mon Sep 17 00:00:00 2001 From: Baptiste Assmann Date: Thu, 4 May 2017 09:05:00 +0200 Subject: [PATCH] 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. --- include/proto/dns.h | 2 + include/types/dns.h | 1 + src/dns.c | 135 ++++++++++++++++++++++++++++++++++++++++++++ src/server.c | 1 + 4 files changed, 139 insertions(+) diff --git a/include/proto/dns.h b/include/proto/dns.h index 14a6af125..00a6f4a10 100644 --- a/include/proto/dns.h +++ b/include/proto/dns.h @@ -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 diff --git a/include/types/dns.h b/include/types/dns.h index 54bbd0220..de9c71331 100644 --- a/include/types/dns.h +++ b/include/types/dns.h @@ -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]; /* query records */ struct dns_answer_item response_answer_records[DNS_MAX_ANSWER_RECORDS]; /* answer records */ diff --git a/src/dns.c b/src/dns.c index bcb78bf41..45444fdb3 100644 --- a/src/dns.c +++ b/src/dns.c @@ -22,6 +22,9 @@ #include #include +#include +#include + #include #include #include @@ -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: + * # + * and store it into . + * It's up to the caller to allocate and to reset it. + * The function returns NULL in case of error (IE 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 c6e42be67..9d0714a8c 100644 --- a/src/server.c +++ b/src/server.c @@ -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;