MAJOR/REORG: dns: DNS resolution task and requester queues
This patch is a major upgrade of the internal run-time DNS resolver in
HAProxy and it brings the following 2 main changes:
1. DNS resolution task
Up to now, DNS resolution was triggered by the health check task.
From now, DNS resolution task is autonomous. It is started by HAProxy
right after the scheduler is available and it is woken either when a
network IO occurs for one of its nameserver or when a timeout is
matched.
From now, this means we can enable DNS resolution for a server without
enabling health checking.
2. Introduction of a dns_requester structure
Up to now, DNS resolution was purposely made for resolving server
hostnames.
The idea, is to ensure that any HAProxy internal object should be able
to trigger a DNS resolution. For this purpose, 2 things has to be done:
- clean up the DNS code from the server structure (this was already
quite clean actually) and clean up the server's callbacks from
manipulating too much DNS resolution
- create an agnostic structure which allows linking a DNS resolution
and a requester of any type (using obj_type enum)
3. Manage requesters through queues
Up to now, there was an uniq relationship between a resolution and it's
owner (aka the requester now). It's a shame, because in some cases,
multiple objects may share the same hostname and may benefit from a
resolution being performed by a third party.
This patch introduces the notion of queues, which are basically lists of
either currently running resolution or waiting ones.
The resolutions are now available as a pool, which belongs to the resolvers.
The pool has has a default size of 64 resolutions per resolvers and is
allocated at configuration parsing.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 78dfe23..b002f40 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11597,9 +11597,6 @@
Whether run time server name resolution has been enable or not, HAProxy will
carry on doing the first resolution when parsing the configuration.
-Bear in mind that DNS resolution is triggered by health checks. This makes
-health checks mandatory to allow DNS resolution.
-
5.3.1. Global overview
----------------------
@@ -11703,6 +11700,11 @@
resolution is triggered after <period> modulo the <inter> parameter of
the healch check.
+resolution_pool_size <nb>
+ Defines the number of resolutions available in the pool for this resolvers.
+ If not defines, it defaults to 64. If your configuration requires more than
+ <nb>, then HAProxy will return an error when parsing the configuration.
+
resolve_retries <nb>
Defines the number <nb> of queries to send to resolve a server name before
giving up.
diff --git a/include/proto/checks.h b/include/proto/checks.h
index eb26c9a..853daad 100644
--- a/include/proto/checks.h
+++ b/include/proto/checks.h
@@ -28,7 +28,6 @@
const char *get_check_status_description(short check_status);
const char *get_check_status_info(short check_status);
void __health_adjust(struct server *s, short status);
-int trigger_resolution(struct server *s);
extern struct data_cb check_conn_cb;
diff --git a/include/proto/dns.h b/include/proto/dns.h
index 00a6f4a..6675d50 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -44,11 +44,24 @@
void dns_print_current_resolutions(struct dns_resolvers *resolvers);
void dns_update_resolvers_timeout(struct dns_resolvers *resolvers);
void dns_reset_resolution(struct dns_resolution *resolution);
+void dns_resolution_free(struct dns_resolvers *resolvers, struct dns_resolution *resolution);
+void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dns_resolution *resolution);
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);
+int dns_link_resolution(void *requester, int requester_type, struct dns_resolution *resolution);
+struct dns_resolution *dns_resolution_list_get(struct dns_resolvers *resolvers, char *hostname_dn, int query_type);
+int dns_trigger_resolution(struct dns_resolution *resolution);
+int dns_alloc_resolution_pool(struct dns_resolvers *resolvers);
+
+void dump_dns_config(void);
+
+/*
+ * erases all information of a dns_requester structure
+ */
+#define dns_clear_requester(requester) memset(requester, '\0', sizeof(*requester));
#endif // _PROTO_DNS_H
diff --git a/include/proto/server.h b/include/proto/server.h
index 53df241..43e4e42 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -53,8 +53,8 @@
/* functions related to server name resolution */
int snr_update_srv_status(struct server *s);
-int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver);
-int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code);
+int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
+int snr_resolution_error_cb(struct dns_requester *requester, int error_code);
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family);
/* increase the number of cumulated connections on the designated server */
diff --git a/include/types/dns.h b/include/types/dns.h
index de9c713..7a19aa3 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -82,6 +82,9 @@
/* DNS header size */
#define DNS_HEADER_SIZE sizeof(struct dns_header)
+/* DNS resolution pool size, per resolvers section */
+#define DNS_DEFAULT_RESOLUTION_POOL_SIZE 64
+
/* DNS request or response header structure */
struct dns_header {
uint16_t id;
@@ -157,7 +160,12 @@
int other; /* other dns response errors */
} hold;
struct task *t; /* timeout management */
- struct list curr_resolution; /* current running resolutions */
+ int resolution_pool_size; /* size of the resolution pool associated to this resolvers section */
+ struct {
+ struct list pool; /* resolution pool dedicated to this resolvers section */
+ struct list wait; /* resolutions managed to this resolvers section */
+ struct list curr; /* current running resolutions */
+ } resolution;
struct eb_root query_ids; /* tree to quickly lookup/retrieve query ids currently in use */
/* used by each nameserver, but stored in resolvers since there must */
/* be a unique relation between an eb_root and an eb_node (resolution) */
@@ -218,11 +226,15 @@
*/
struct dns_resolution {
struct list list; /* resolution list */
- void *requester; /* owner of this name resolution */
+ struct {
+ struct list wait; /* list of standby requesters for this resolution */
+ struct list curr; /* list of requesters currently active on this resolution */
+ } requester;
int (*requester_cb)(struct dns_resolution *, struct dns_nameserver *);
/* requester callback for valid response */
int (*requester_error_cb)(struct dns_resolution *, int);
/* requester callback, for error management */
+ int uuid; /* unique id (used for debugging purpose) */
char *hostname_dn; /* server hostname in domain name label format */
int hostname_dn_len; /* server domain name label len */
unsigned int last_resolution; /* time of the lastest valid resolution */
@@ -244,6 +256,19 @@
struct chunk response_buffer; /* buffer used as a data store for <response> above TODO: optimize the size (might be smaller) */
};
+/*
+ * structure used to describe the owner of a DNS resolution.
+ */
+struct dns_requester {
+ struct list list; /* requester list */
+ enum obj_type *requester; /* pointer to the requester */
+ int prefered_query_type; /* prefered query type */
+ int (*requester_cb)(struct dns_requester *, struct dns_nameserver *);
+ /* requester callback for valid response */
+ int (*requester_error_cb)(struct dns_requester *, int);
+ /* requester callback, for error management */
+};
+
/* last resolution status code */
enum {
RSLV_STATUS_NONE = 0, /* no resolution occured yet */
diff --git a/include/types/server.h b/include/types/server.h
index 7c6df5a..8fb6f2e 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -254,6 +254,7 @@
struct check check; /* health-check specific configuration */
struct check agent; /* agent specific configuration */
+ struct dns_requester *dns_requester; /* used to link a server to its DNS resolution */
char *resolvers_id; /* resolvers section used by this server */
struct dns_resolvers *resolvers; /* pointer to the resolvers structure used by this server */
char *hostname; /* server hostname */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 7c720fc..261a0eb 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -59,6 +59,7 @@
#include <proto/channel.h>
#include <proto/checks.h>
#include <proto/compression.h>
+#include <proto/dns.h>
#include <proto/stats.h>
#include <proto/filters.h>
#include <proto/frontend.h>
@@ -2164,8 +2165,12 @@
curr_resolvers->hold.valid = 10000;
curr_resolvers->timeout.retry = 1000;
curr_resolvers->resolve_retries = 3;
+ /* default resolution pool size */
+ curr_resolvers->resolution_pool_size = DNS_DEFAULT_RESOLUTION_POOL_SIZE;
LIST_INIT(&curr_resolvers->nameserver_list);
- LIST_INIT(&curr_resolvers->curr_resolution);
+ LIST_INIT(&curr_resolvers->resolution.curr);
+ LIST_INIT(&curr_resolvers->resolution.wait);
+ LIST_INIT(&curr_resolvers->resolution.pool);
}
else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition */
struct sockaddr_storage *sk;
@@ -2277,6 +2282,15 @@
}
}
+ else if (strcmp(args[0], "resolution_pool_size") == 0) {
+ if (!*args[1]) {
+ Alert("parsing [%s:%d] : '%s' expects <nb> as argument.\n",
+ file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curr_resolvers->resolution_pool_size = atoi(args[1]);
+ }
else if (strcmp(args[0], "resolve_retries") == 0) {
if (!*args[1]) {
Alert("parsing [%s:%d] : '%s' expects <nb> as argument.\n",
@@ -7365,6 +7379,7 @@
unsigned int next_pxid = 1;
struct bind_conf *bind_conf;
char *err;
+ struct dns_resolvers *curr_resolvers;
bind_conf = NULL;
/*
@@ -7385,6 +7400,15 @@
pool2_capture = create_pool("capture", global.tune.cookie_len, MEM_F_SHARED);
+ /* allocate pool of resolution per resolvers */
+ list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
+ if (dns_alloc_resolution_pool(curr_resolvers) != 0) {
+ /* error message is already displayed by dns_alloc_resolution_pool() */
+ err_code |= ERR_ALERT | ERR_ABORT;
+ goto out;
+ }
+ }
+
/* Post initialisation of the users and groups lists. */
err_code = userlist_postinit();
if (err_code != ERR_NONE)
@@ -8514,8 +8538,15 @@
newsrv->id, newsrv->resolvers_id);
cfgerr++;
} else {
- if (newsrv->resolution)
+ if (newsrv->hostname_dn) {
newsrv->resolvers = curr_resolvers;
+ if (dns_link_resolution(newsrv, OBJ_TYPE_SERVER, NULL) != 0) {
+ Alert("config : %s '%s', server '%s': unable to set DNS resolution\n",
+ proxy_type_str(curproxy), curproxy->id,
+ newsrv->id);
+ cfgerr++;
+ }
+ }
}
}
else {
diff --git a/src/checks.c b/src/checks.c
index b061647..1af862e 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -695,7 +695,7 @@
* Let's trigger a DNS resolution if none are currently running.
*/
if ((check->server->resolution) && (check->server->resolution->step == RSLV_STEP_NONE))
- trigger_resolution(check->server);
+ dns_trigger_resolution(check->server->resolution);
}
else if ((conn->flags & (CO_FL_CONNECTED|CO_FL_WAIT_L6_CONN)) == CO_FL_WAIT_L6_CONN) {
@@ -2207,98 +2207,11 @@
static struct task *process_chk(struct task *t)
{
struct check *check = t->context;
- struct server *s = check->server;
- struct dns_resolution *resolution = s->resolution;
- struct dns_resolvers *resolvers = s->resolvers;
-
- /* trigger name resolution */
- if ((s->check.state & CHK_ST_ENABLED) && (resolution)) {
- /* check if a no resolution is running for this server */
- if (resolution->step == RSLV_STEP_NONE) {
- /*
- * if there has not been any name resolution for a longer period than
- * hold.valid, let's trigger a new one.
- */
- if (!resolution->last_resolution || tick_is_expired(tick_add(resolution->last_resolution, resolvers->hold.valid), now_ms)) {
- trigger_resolution(s);
- }
- }
- }
if (check->type == PR_O2_EXT_CHK)
return process_chk_proc(t);
return process_chk_conn(t);
-}
-
-/*
- * Initiates a new name resolution:
- * - generates a query id
- * - configure the resolution structure
- * - startup the resolvers task if required
- *
- * returns:
- * - 0 in case of error or if resolution already running
- * - 1 if everything started properly
- */
-int trigger_resolution(struct server *s)
-{
- struct dns_resolution *resolution = NULL;
- struct dns_resolvers *resolvers = NULL;
- int query_id;
- int i;
-
- resolution = s->resolution;
- resolvers = s->resolvers;
-
- /*
- * check if a resolution has already been started for this server
- * return directly to avoid resolution pill up
- */
- if (resolution->step != RSLV_STEP_NONE)
- return 0;
-
- /* generates a query id */
- i = 0;
- do {
- query_id = dns_rnd16();
- /* we do try only 100 times to find a free query id */
- if (i++ > 100) {
- chunk_printf(&trash, "could not generate a query id for %s/%s, in resolvers %s",
- s->proxy->id, s->id, resolvers->id);
-
- send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.str);
- return 0;
- }
- } while (eb32_lookup(&resolvers->query_ids, query_id));
-
- LIST_ADDQ(&resolvers->curr_resolution, &resolution->list);
-
- /* now update resolution parameters */
- resolution->query_id = query_id;
- resolution->qid.key = query_id;
- resolution->step = RSLV_STEP_RUNNING;
- if (s->dns_opts.family_prio == AF_INET) {
- resolution->query_type = DNS_RTYPE_A;
- } else {
- resolution->query_type = DNS_RTYPE_AAAA;
- }
- resolution->try = resolvers->resolve_retries;
- resolution->try_cname = 0;
- resolution->nb_responses = 0;
- eb32_insert(&resolvers->query_ids, &resolution->qid);
-
- dns_send_query(resolution);
- resolution->try -= 1;
-
- /* update wakeup date if this resolution is the only one in the FIFO list */
- if (dns_check_resolution_queue(resolvers) == 1) {
- /* update task timeout */
- dns_update_resolvers_timeout(resolvers);
- task_queue(resolvers->t);
- }
-
- return 1;
}
static int start_check_task(struct check *check, int mininter,
diff --git a/src/dns.c b/src/dns.c
index cab1ad3..cb7de6a 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -57,6 +57,9 @@
.send = dns_resolve_send,
};
+/* local function prototypes */
+static int dns_run_resolution(struct dns_requester *requester);
+
#if DEBUG
/*
* go through the resolutions associated to a resolvers section and print the ID and hostname in
@@ -65,13 +68,202 @@
*/
void dns_print_current_resolutions(struct dns_resolvers *resolvers)
{
- list_for_each_entry(resolution, &resolvers->curr_resolution, list) {
+ list_for_each_entry(resolution, &resolvers->resolution.curr, list) {
printf(" resolution %d for %s\n", resolution->query_id, resolution->hostname_dn);
}
}
#endif
+void dump_dns_config()
+{
+ struct dns_resolvers *curr_resolvers = NULL;
+ struct dns_nameserver *curr_nameserver = NULL;
+ struct dns_resolution *curr_resolution = NULL;
+ struct dns_requester *curr_requester = NULL;
+
+ printf("===============\n");
+ list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
+ printf("Resolvers: %s\n", curr_resolvers->id);
+
+ printf(" nameservers:\n");
+ list_for_each_entry(curr_nameserver, &curr_resolvers->nameserver_list, list) {
+ printf(" %s\n", curr_nameserver->id);
+ }
+
+/*
+ printf(" resolution.pool list:\n");
+ list_for_each_entry(curr_resolution, &curr_resolvers->resolution.pool, list) {
+ printf(" %p\n", curr_resolution);
+ }
+*/
+
+ printf(" resolution.wait list:\n");
+ list_for_each_entry(curr_resolution, &curr_resolvers->resolution.wait, list) {
+ printf(" %p %s\n", curr_resolution, curr_resolution->hostname_dn);
+ printf(" requester.wait list:\n");
+ list_for_each_entry(curr_requester, &curr_resolution->requester.wait, list) {
+ printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type);
+ }
+ printf(" requester.curr list:\n");
+ list_for_each_entry(curr_requester, &curr_resolution->requester.curr, list) {
+ printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type);
+ }
+ }
+ printf(" resolution.curr list:\n");
+ list_for_each_entry(curr_resolution, &curr_resolvers->resolution.curr, list) {
+ printf(" %p %s\n", curr_resolution, curr_resolution->hostname_dn);
+ printf(" requester.wait list:\n");
+ list_for_each_entry(curr_requester, &curr_resolution->requester.wait, list) {
+ printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type);
+ }
+ printf(" requester.curr list:\n");
+ list_for_each_entry(curr_requester, &curr_resolution->requester.curr, list) {
+ printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type);
+ }
+ }
+ }
+
+ printf("===============\n");
+}
+
+/*
+ * Initiates a new name resolution:
+ * - generates a query id
+ * - configure the resolution structure
+ * - startup the resolvers task if required
+ *
+ * returns:
+ * - 0 if everything started properly
+ * - -1 in case of error or if resolution already running
+ */
+int dns_trigger_resolution(struct dns_resolution *resolution)
+{
+ struct dns_requester *requester = NULL, *tmprequester;
+ struct dns_resolvers *resolvers = NULL;
+ int inter;
+
+ /* process the element of the wait queue */
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.wait, list) {
+ inter = 0;
+
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ inter = objt_server(requester->requester)->check.inter;
+ resolvers = objt_server(requester->requester)->resolvers;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ return -1;
+ }
+
+ /* if data is fresh enough, let's use it */
+ if (!tick_is_expired(tick_add(resolution->last_resolution, inter), now_ms)) {
+ /* we only use cache if the response there is valid.
+ * If not valid, we run the resolution and move the requester to
+ * the run queue. */
+ if (resolution->status != RSLV_STATUS_VALID) {
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.curr, &requester->list);
+ dns_run_resolution(requester);
+ continue;
+ }
+
+ requester->requester_cb(requester, NULL);
+ }
+ else {
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.curr, &requester->list);
+ dns_run_resolution(requester);
+ }
+ }
+
+ if (resolvers)
+ dns_update_resolvers_timeout(resolvers);
+
+ return 0;
+}
+
/*
+ * Prepare and send a DNS resolution.
+ *
+ * Return code:
+ * - 0 if no error occured
+ * - -1 in case of error
+ */
+static int
+dns_run_resolution(struct dns_requester *requester)
+{
+ struct dns_resolution *resolution;
+ struct dns_resolvers *resolvers;
+ int query_id, query_type, i;
+ struct proxy *proxy;
+
+ resolution = NULL;
+ resolvers = NULL;
+ proxy = NULL;
+ query_type = -1;
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ resolution = objt_server(requester->requester)->resolution;
+ resolvers = objt_server(requester->requester)->resolvers;
+ proxy = objt_server(requester->requester)->proxy;
+ query_type = requester->prefered_query_type;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ return -1;
+ }
+
+ /*
+ * check if a resolution has already been started for this server
+ * return directly to avoid resolution pill up.
+ */
+ if (resolution->step != RSLV_STEP_NONE)
+ return 0;
+
+ /* generates a query id */
+ i = 0;
+ do {
+ query_id = dns_rnd16();
+ /* we do try only 100 times to find a free query id */
+ if (i++ > 100) {
+ chunk_printf(&trash, "could not generate a query id for %s, in resolvers %s",
+ resolution->hostname_dn, resolvers->id);
+
+ if (proxy)
+ send_log(proxy, LOG_NOTICE, "%s.\n", trash.str);
+ return -1;
+ }
+ } while (eb32_lookup(&resolvers->query_ids, query_id));
+
+ /* move the resolution into the run queue */
+ LIST_DEL(&resolution->list);
+ LIST_ADDQ(&resolvers->resolution.curr, &resolution->list);
+
+ /* now update resolution parameters */
+ resolution->query_id = query_id;
+ resolution->qid.key = query_id;
+ resolution->step = RSLV_STEP_RUNNING;
+ resolution->query_type = query_type;
+ resolution->try = resolvers->resolve_retries;
+ resolution->try_cname = 0;
+ resolution->nb_responses = 0;
+ eb32_insert(&resolvers->query_ids, &resolution->qid);
+
+ dns_send_query(resolution);
+ resolution->try -= 1;
+
+ /* update wakeup date if this resolution is the only one in the FIFO list */
+ if (dns_check_resolution_queue(resolvers) == 1) {
+ /* update task timeout */
+ dns_update_resolvers_timeout(resolvers);
+ task_queue(resolvers->t);
+ }
+
+ return 0;
+}
+
+/*
* check if there is more than 1 resolution in the resolver's resolution list
* return value:
* 0: empty list
@@ -81,22 +273,22 @@
int dns_check_resolution_queue(struct dns_resolvers *resolvers)
{
- if (LIST_ISEMPTY(&resolvers->curr_resolution))
+ if (LIST_ISEMPTY(&resolvers->resolution.curr))
return 0;
- if ((resolvers->curr_resolution.n) && (resolvers->curr_resolution.n == resolvers->curr_resolution.p))
+ if ((resolvers->resolution.curr.n) && (resolvers->resolution.curr.n == resolvers->resolution.curr.p))
return 1;
- if (! ((resolvers->curr_resolution.n == resolvers->curr_resolution.p)
- && (&resolvers->curr_resolution != resolvers->curr_resolution.n)))
+ if (! ((resolvers->resolution.curr.n == resolvers->resolution.curr.p)
+ && (&resolvers->resolution.curr != resolvers->resolution.curr.n)))
return 2;
return 0;
}
/*
- * reset all parameters of a DNS resolution to 0 (or equivalent)
- * and clean it up from all associated lists (resolution->qid and resolution->list)
+ * reset some resolution parameters to initial values and also delete the
+ * query ID from the resolver's tree.
*/
void dns_reset_resolution(struct dns_resolution *resolution)
{
@@ -112,9 +304,6 @@
eb32_delete(&resolution->qid);
resolution->query_id = 0;
resolution->qid.key = 0;
-
- /* the second resolution in the queue becomes the first one */
- LIST_DEL(&resolution->list);
}
/*
@@ -127,16 +316,17 @@
*/
void dns_resolve_recv(struct dgram_conn *dgram)
{
- struct dns_nameserver *nameserver;
+ struct dns_nameserver *nameserver, *tmpnameserver;
struct dns_resolvers *resolvers;
- struct dns_resolution *resolution;
+ struct dns_resolution *resolution = NULL;
struct dns_query_item *query;
unsigned char buf[DNS_MAX_UDP_MESSAGE + 1];
unsigned char *bufend;
- int fd, buflen, ret;
+ int fd, buflen, dns_resp, need_resend;
unsigned short query_id;
struct eb32_node *eb;
struct lru64 *lru = NULL;
+ struct dns_requester *requester = NULL, *tmprequester = NULL;
fd = dgram->t.sock.fd;
@@ -195,51 +385,178 @@
/* number of responses received */
resolution->nb_responses += 1;
- ret = dns_validate_dns_response(buf, bufend, resolution);
+ dns_resp = dns_validate_dns_response(buf, bufend, resolution);
- /* treat only errors */
- switch (ret) {
- case DNS_RESP_QUERY_COUNT_ERROR:
- case DNS_RESP_INVALID:
- nameserver->counters.invalid += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_INVALID);
- continue;
+ /* treat errors first
+ * need_resend flag could be set to 0 by default before the 'switch' and then
+ * set to 1 only where needed, but I think it's better this way to make people
+ * aware they have to think twice how to set this flag when updating this portion
+ * of the code
+ */
+ switch (dns_resp) {
+ case DNS_RESP_VALID:
+ need_resend = 0;
+ break;
- case DNS_RESP_INTERNAL:
- case DNS_RESP_ERROR:
- nameserver->counters.other += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_ERROR);
- continue;
+ case DNS_RESP_INVALID:
+ case DNS_RESP_QUERY_COUNT_ERROR:
+ case DNS_RESP_WRONG_NAME:
+ if (resolution->status != RSLV_STATUS_INVALID) {
+ resolution->status = RSLV_STATUS_INVALID;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.invalid += 1;
+ need_resend = 0;
+ break;
- case DNS_RESP_ANCOUNT_ZERO:
- nameserver->counters.any_err += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_ANCOUNT_ZERO);
- continue;
+ case DNS_RESP_ANCOUNT_ZERO:
+ if (resolution->status != RSLV_STATUS_OTHER) {
+ resolution->status = RSLV_STATUS_OTHER;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.any_err += 1;
+ need_resend = 1;
+ break;
- case DNS_RESP_NX_DOMAIN:
- nameserver->counters.nx += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_NX_DOMAIN);
- continue;
+ case DNS_RESP_NX_DOMAIN:
+ if (resolution->status != RSLV_STATUS_NX) {
+ resolution->status = RSLV_STATUS_NX;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.nx += 1;
+ need_resend = 0;
+ break;
- case DNS_RESP_REFUSED:
- nameserver->counters.refused += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_REFUSED);
- continue;
+ case DNS_RESP_REFUSED:
+ if (resolution->status != RSLV_STATUS_REFUSED) {
+ resolution->status = RSLV_STATUS_REFUSED;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.refused += 1;
+ need_resend = 0;
+ break;
- case DNS_RESP_CNAME_ERROR:
- nameserver->counters.cname_error += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_CNAME_ERROR);
- continue;
+ case DNS_RESP_CNAME_ERROR:
+ if (resolution->status != RSLV_STATUS_OTHER) {
+ resolution->status = RSLV_STATUS_OTHER;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.cname_error += 1;
+ need_resend = 1;
+ break;
- case DNS_RESP_TRUNCATED:
- nameserver->counters.truncated += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_TRUNCATED);
- continue;
+ case DNS_RESP_TRUNCATED:
+ if (resolution->status != RSLV_STATUS_OTHER) {
+ resolution->status = RSLV_STATUS_OTHER;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.truncated += 1;
+ need_resend = 1;
+ break;
- case DNS_RESP_NO_EXPECTED_RECORD:
- nameserver->counters.other += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_NO_EXPECTED_RECORD);
- continue;
+ case DNS_RESP_NO_EXPECTED_RECORD:
+ if (resolution->status != RSLV_STATUS_OTHER) {
+ resolution->status = RSLV_STATUS_OTHER;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.other += 1;
+ need_resend = 1;
+ break;
+
+ case DNS_RESP_ERROR:
+ case DNS_RESP_INTERNAL:
+ if (resolution->status != RSLV_STATUS_OTHER) {
+ resolution->status = RSLV_STATUS_OTHER;
+ resolution->last_status_change = now_ms;
+ }
+ nameserver->counters.other += 1;
+ need_resend = 1;
+ break;
+ }
+
+ /* some error codes trigger a re-send of the query, but switching the
+ * query type.
+ * This is the case for the following error codes:
+ * DNS_RESP_ANCOUNT_ZERO
+ * DNS_RESP_TRUNCATED
+ * DNS_RESP_ERROR
+ * DNS_RESP_INTERNAL
+ * DNS_RESP_NO_EXPECTED_RECORD
+ * DNS_RESP_CNAME_ERROR
+ */
+ if (need_resend) {
+ int family_prio;
+ int res_preferred_afinet, res_preferred_afinet6;
+
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ family_prio = objt_server(requester->requester)->dns_opts.family_prio;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ family_prio = AF_INET6;
+ }
+ res_preferred_afinet = family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
+ res_preferred_afinet6 = family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
+ if ((res_preferred_afinet || res_preferred_afinet6)
+ || (resolution->try > 0)) {
+ /* let's change the query type */
+ if (res_preferred_afinet6) {
+ /* fallback from AAAA to A */
+ resolution->query_type = DNS_RTYPE_A;
+ }
+ else if (res_preferred_afinet) {
+ /* fallback from A to AAAA */
+ resolution->query_type = DNS_RTYPE_AAAA;
+ }
+ else {
+ resolution->try -= 1;
+ if (family_prio == AF_INET) {
+ resolution->query_type = DNS_RTYPE_A;
+ } else {
+ resolution->query_type = DNS_RTYPE_AAAA;
+ }
+ }
+
+ dns_send_query(resolution);
+ /*
+ * move the resolution to the last element of the FIFO queue
+ * and update timeout wakeup based on the new first entry
+ */
+ if (dns_check_resolution_queue(resolvers) > 1) {
+ /* second resolution becomes first one */
+ LIST_DEL(&resolution->list);
+ /* ex first resolution goes to the end of the queue */
+ LIST_ADDQ(&resolvers->resolution.curr, &resolution->list);
+ }
+
+ dns_update_resolvers_timeout(resolvers);
+ goto next_packet;
+ }
+
+ /* if we're there, this means that we already ran out of chances to re-send
+ * the query */
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ requester->requester_error_cb(requester, dns_resp);
+ }
+ goto next_packet;
+ }
+
+ /* now processing those error codes only:
+ * DNS_RESP_NX_DOMAIN
+ * DNS_RESP_REFUSED
+ */
+ if (dns_resp != DNS_RESP_VALID) {
+ /* now parse list of requesters currently waiting for this resolution */
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ requester->requester_error_cb(requester, dns_resp);
+
+ /* we can move the requester the wait queue */
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.wait, &requester->list);
+ }
+ goto next_packet;
}
/* Now let's check the query's dname corresponds to the one we sent.
@@ -248,8 +565,14 @@
query = LIST_NEXT(&resolution->response.query_list, struct dns_query_item *, list);
if (query && memcmp(query->name, resolution->hostname_dn, resolution->hostname_dn_len) != 0) {
nameserver->counters.other += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_WRONG_NAME);
- continue;
+ /* now parse list of requesters currently waiting for this resolution */
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ requester->requester_error_cb(requester, DNS_RESP_WRONG_NAME);
+ /* we can move the requester the wait queue */
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.wait, &requester->list);
+ }
+ goto next_packet;
}
/* no errors, we can save the response in the cache */
@@ -260,11 +583,17 @@
chunk_reset(buf);
tmp = dns_cache_key(resolution->query_type, resolution->hostname_dn,
- resolution->hostname_dn_len, buf);
+ resolution->hostname_dn_len, buf);
if (!tmp) {
nameserver->counters.other += 1;
- resolution->requester_error_cb(resolution, DNS_RESP_ERROR);
- continue;
+ /* now parse list of requesters currently waiting for this resolution */
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ requester->requester_error_cb(requester, DNS_RESP_ERROR);
+ /* we can move the requester the wait queue */
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.wait, &requester->list);
+ }
+ goto next_packet;
}
lru = lru64_get(XXH64(buf->str, buf->len, seed),
@@ -273,9 +602,40 @@
lru64_commit(lru, resolution, nameserver->resolvers, 1, NULL);
}
+ if (resolution->status != RSLV_STATUS_VALID) {
+ resolution->status = RSLV_STATUS_VALID;
+ resolution->last_status_change = now_ms;
+ }
+
nameserver->counters.valid += 1;
- resolution->requester_cb(resolution, nameserver);
- }
+ /* now parse list of requesters currently waiting for this resolution */
+ tmpnameserver = nameserver;
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ requester->requester_cb(requester, tmpnameserver);
+ /* we can move the requester the wait queue */
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.wait, &requester->list);
+ /* first response is managed by the server, others are from the cache */
+ tmpnameserver = NULL;
+ }
+
+ next_packet:
+ /* resolution may be NULL when we receive an ICMP unreachable packet */
+ if (resolution && LIST_ISEMPTY(&resolution->requester.curr)) {
+ /* move the resolution into the wait queue */
+ LIST_DEL(&resolution->list);
+ LIST_ADDQ(&resolvers->resolution.wait, &resolution->list);
+ /* update last resolution date and time */
+ resolution->last_resolution = now_ms;
+ /* reset current status flag */
+ resolution->step = RSLV_STEP_NONE;
+ /* reset values */
+ dns_reset_resolution(resolution);
+ }
+
+ } // end of while "packets" loop
+
+ dns_update_resolvers_timeout(nameserver->resolvers);
}
/*
@@ -303,7 +663,7 @@
return;
resolvers = nameserver->resolvers;
- resolution = LIST_NEXT(&resolvers->curr_resolution, struct dns_resolution *, list);
+ resolution = LIST_NEXT(&resolvers->resolution.curr, struct dns_resolution *, list);
dns_send_query(resolution);
dns_update_resolvers_timeout(resolvers);
@@ -320,9 +680,23 @@
{
struct dns_resolvers *resolvers = NULL;
struct dns_nameserver *nameserver;
+ struct dns_requester *requester = NULL;
int ret, bufsize, fd;
+ /* nothing to do */
+ if (LIST_ISEMPTY(&resolution->requester.curr))
+ return 0;
+
- resolvers = ((struct server *)resolution->requester)->resolvers;
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ resolvers = objt_server(requester->requester)->resolvers;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ return 0;
+ }
if (!resolvers)
return 0;
@@ -363,15 +737,67 @@
void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
{
struct dns_resolution *resolution;
+ struct dns_requester *requester;
- if (LIST_ISEMPTY(&resolvers->curr_resolution)) {
- /* no more resolution pending, so no wakeup anymore */
+ if ((LIST_ISEMPTY(&resolvers->resolution.curr)) && (LIST_ISEMPTY(&resolvers->resolution.wait))) {
resolvers->t->expire = TICK_ETERNITY;
}
- else {
- resolution = LIST_NEXT(&resolvers->curr_resolution, struct dns_resolution *, list);
- resolvers->t->expire = tick_add(resolution->last_sent_packet, resolvers->timeout.retry);
+ else if (!LIST_ISEMPTY(&resolvers->resolution.curr)) {
+ resolution = LIST_NEXT(&resolvers->resolution.curr, struct dns_resolution *, list);
+ if (!resolvers->t->expire || tick_is_le(resolvers->t->expire, tick_add(resolution->last_sent_packet, resolvers->timeout.retry))) {
+ resolvers->t->expire = tick_add(resolution->last_sent_packet, resolvers->timeout.retry);
+ }
}
+ else if (!LIST_ISEMPTY(&resolvers->resolution.wait)) {
+ int valid_period, inter, need_wakeup;
+ struct dns_resolution *res_back;
+ need_wakeup = 0;
+ list_for_each_entry_safe(resolution, res_back, &resolvers->resolution.wait, list) {
+ valid_period = 0;
+ inter = 0;
+
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ valid_period = objt_server(requester->requester)->check.inter;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ continue;
+ }
+
+ 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)) {
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ dns_trigger_resolution(objt_server(requester->requester)->resolution);
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ ;;
+ }
+ }
+ else {
+ need_wakeup = 1;
+ }
+ }
+ /* in such case, we wake up in 1s */
+ if (need_wakeup) {
+ int r = 1000;
+
+ resolution = LIST_NEXT(&resolvers->resolution.wait, struct dns_resolution *, list);
+ if (tick_is_le(resolvers->t->expire, tick_add(now_ms, r)))
+ resolvers->t->expire = tick_add(now_ms, r);
+ resolvers->t->expire = tick_add(now_ms, 1000);
+ }
+ }
+
+ task_queue(resolvers->t);
}
/*
@@ -979,6 +1405,7 @@
{
struct dns_resolvers *curr_resolvers;
struct dns_nameserver *curnameserver;
+ struct dns_resolution *resolution, *res_back;
struct dgram_conn *dgram;
struct task *t;
int fd;
@@ -1000,12 +1427,17 @@
/* update task's parameters */
t->process = dns_process_resolve;
t->context = curr_resolvers;
- t->expire = TICK_ETERNITY;
+ t->expire = 0;
- curr_resolvers->t = t;
+ /* no need to keep the new task if one is already affected to our resolvers
+ * section */
+ if (!curr_resolvers->t)
+ curr_resolvers->t = t;
+ else
+ task_free(t);
list_for_each_entry(curnameserver, &curr_resolvers->nameserver_list, list) {
- dgram = NULL;
+ dgram = NULL;
if (close_socket == 1) {
if (curnameserver->dgram) {
@@ -1058,7 +1490,19 @@
/* update nameserver's datagram property */
curnameserver->dgram = dgram;
+ continue;
+ }
+
+ if (close_socket == 0)
continue;
+
+ /* now, we can trigger DNS resolution */
+ list_for_each_entry_safe(resolution, res_back, &curr_resolvers->resolution.wait, list) {
+ /* if there is no requester in the wait queue, no need to trigger the resolution */
+ if (LIST_ISEMPTY(&resolution->requester.wait))
+ continue;
+
+ dns_trigger_resolution(resolution);
}
/* task can be queued */
@@ -1069,6 +1513,37 @@
}
/*
+ * Allocate a pool of resolution to a resolvers section.
+ * Each resolution is associated with a UUID.
+ *
+ * Return code:
+ * - 0 if everything went smoothly
+ * - -1 if an error occured
+ */
+int dns_alloc_resolution_pool(struct dns_resolvers *resolvers)
+{
+ int i;
+ struct dns_resolution *resolution;
+
+ /* return if a pool has already been set for this resolvers */
+ if (!LIST_ISEMPTY(&resolvers->resolution.pool)) {
+ return 0;
+ }
+
+ for (i = 0; i < resolvers->resolution_pool_size; i++) {
+ resolution = dns_alloc_resolution();
+ if (!resolution) {
+ Alert("Starting [%s] resolvers: can't allocate memory for DNS resolution pool.\n", resolvers->id);
+ return -1;
+ }
+ resolution->uuid = i;
+ LIST_ADDQ(&resolvers->resolution.pool, &resolution->list);
+ }
+
+ return 0;
+}
+
+/*
* Forge a DNS query. It needs the following information from the caller:
* - <query_id>: the DNS query id corresponding to this query
* - <query_type>: DNS_RTYPE_* request DNS record type (A, AAAA, ANY, etc...)
@@ -1268,35 +1743,75 @@
int res_preferred_afinet, res_preferred_afinet6;
struct dns_options *dns_opts = NULL;
- /* timeout occurs inevitably for the first element of the FIFO queue */
- if (LIST_ISEMPTY(&resolvers->curr_resolution)) {
+ /* if both there is no resolution in the run queue, we can re-schedule a wake up */
+ if (LIST_ISEMPTY(&resolvers->resolution.curr)) {
/* no first entry, so wake up was useless */
- t->expire = TICK_ETERNITY;
+ dns_update_resolvers_timeout(resolvers);
return t;
}
/* look for the first resolution which is not expired */
- list_for_each_entry_safe(resolution, res_back, &resolvers->curr_resolution, list) {
+ list_for_each_entry_safe(resolution, res_back, &resolvers->resolution.curr, list) {
+ struct dns_requester *requester = NULL;
+
/* when we find the first resolution in the future, then we can stop here */
if (tick_is_le(now_ms, resolution->last_sent_packet))
goto out;
+ if (LIST_ISEMPTY(&resolution->requester.curr))
+ goto out;
+
/*
* if current resolution has been tried too many times and finishes in timeout
* we update its status and remove it from the list
*/
if (resolution->try <= 0) {
+ struct dns_requester *tmprequester;
/* clean up resolution information and remove from the list */
dns_reset_resolution(resolution);
- /* notify the result to the requester */
- resolution->requester_error_cb(resolution, DNS_RESP_TIMEOUT);
+ LIST_DEL(&resolution->list);
+ LIST_ADDQ(&resolvers->resolution.wait, &resolution->list);
+
+ if (resolution->status != RSLV_STATUS_TIMEOUT) {
+ resolution->status = RSLV_STATUS_TIMEOUT;
+ resolution->last_status_change = now_ms;
+ }
+
+ /* notify the result to the requesters */
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ requester->requester_error_cb(requester, DNS_RESP_TIMEOUT);
+ LIST_DEL(&requester->list);
+ LIST_ADDQ(&resolution->requester.wait, &requester->list);
+ }
goto out;
}
resolution->try -= 1;
- dns_opts = &((struct server *)resolution->requester)->dns_opts;
+ /* running queue is empty, nothing to do but wait */
+ if (LIST_ISEMPTY(&resolution->requester.curr))
+ goto out;
+
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ dns_opts = &(objt_server(requester->requester)->dns_opts);
+ break;
+
+ case OBJ_TYPE_NONE:
+ default:
+ /* clean up resolution information and remove from the list */
+ dns_reset_resolution(resolution);
+
+ LIST_DEL(&resolution->list);
+ LIST_ADDQ(&resolvers->resolution.wait, &resolution->list);
+
+ /* notify the result to the requester */
+ requester->requester_error_cb(requester, DNS_RESP_INTERNAL);
+ goto out;
+ }
res_preferred_afinet = dns_opts->family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
res_preferred_afinet6 = dns_opts->family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
@@ -1318,7 +1833,7 @@
if (dns_check_resolution_queue(resolvers) > 1) {
/* move the rsolution to the end of the list */
LIST_DEL(&resolution->list);
- LIST_ADDQ(&resolvers->curr_resolution, &resolution->list);
+ LIST_ADDQ(&resolvers->resolution.curr, &resolution->list);
}
}
@@ -1386,6 +1901,7 @@
struct lru64 *elem = NULL;
struct dns_resolution *resolution = NULL;
struct dns_resolvers *resolvers = NULL;
+ struct dns_requester *requester = NULL;
int inter = 0;
struct chunk *buf = get_trash_chunk();
struct chunk *tmp = NULL;
@@ -1415,7 +1931,16 @@
return NULL;
}
- resolvers = ((struct server *)resolution->requester)->resolvers;
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ resolvers = objt_server(requester->requester)->resolvers;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ return NULL;
+ }
if (!resolvers)
return NULL;
@@ -1452,6 +1977,187 @@
return 0;
}
+/*
+ * if <resolution> is provided, then the function skips the memory allocation part.
+ * It does the linking only.
+ *
+ * if <resolution> is NULL, the function links a dns resolution to a requester:
+ * - it allocates memory for the struct requester used to link
+ * the resolution to the requester
+ * - it configures the resolution if this is the first requester to be linked to it
+ * - it updates the requester with a pointer to the resolution
+ *
+ * Return code:
+ * - 0 if everything happened smoothly
+ * - -1 if an error occured. Of course, no resolution is linked to the requester
+ */
+int dns_link_resolution(void *requester, int requester_type, struct dns_resolution *resolution)
+{
+ struct dns_resolution *tmpresolution = NULL;
+ struct dns_requester *tmprequester = NULL;
+ struct dns_resolvers *resolvers = NULL;
+ char *hostname_dn = NULL;
+ int new_resolution;
+
+ if (!resolution) {
+ tmprequester = calloc(1, sizeof(*tmprequester));
+ if (!tmprequester)
+ return -1;
+
+ switch (requester_type) {
+ case OBJ_TYPE_SERVER:
+ tmprequester->requester = &((struct server *)requester)->obj_type;
+ hostname_dn = objt_server(tmprequester->requester)->hostname_dn;
+ resolvers = objt_server(tmprequester->requester)->resolvers;
+ switch (objt_server(tmprequester->requester)->dns_opts.family_prio) {
+ case AF_INET:
+ tmprequester->prefered_query_type = DNS_RTYPE_A;
+ break;
+ default:
+ tmprequester->prefered_query_type = DNS_RTYPE_AAAA;
+ }
+
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ free(tmprequester);
+ return -1;
+ }
+
+ /* get a resolution from the resolvers' wait queue or pool */
+ tmpresolution = dns_resolution_list_get(resolvers, hostname_dn, tmprequester->prefered_query_type);
+ if (!tmpresolution) {
+ free(tmprequester);
+ return -1;
+ }
+ }
+ else {
+ tmpresolution = resolution;
+
+ switch (requester_type) {
+ case OBJ_TYPE_SERVER:
+ tmprequester = ((struct server *)requester)->dns_requester;
+ resolvers = ((struct server *)requester)->resolvers;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ return -1;
+ }
+ }
+
+ /* flag this resolution as NEW if applicable (not already linked to any requester).
+ * this is required to decide which parameters we have to update on the resolution.
+ * If new, it means we pulled up the resolution from the resolvers' pool.
+ */
+ if (LIST_ISEMPTY(&tmpresolution->requester.wait)) {
+ new_resolution = 1;
+ }
+ else
+ new_resolution = 0;
+
+ /* those parameters are related to the requester type */
+ switch (obj_type(tmprequester->requester)) {
+ case OBJ_TYPE_SERVER:
+ /* some parameters should be set only if the resolution is brand new */
+ if (new_resolution) {
+ tmpresolution->query_type = tmprequester->prefered_query_type;
+ tmpresolution->hostname_dn = objt_server(tmprequester->requester)->hostname_dn;
+ tmpresolution->hostname_dn_len = objt_server(tmprequester->requester)->hostname_dn_len;
+ }
+
+ /* update requester as well, only if we just allocated it */
+ objt_server(tmprequester->requester)->resolution = tmpresolution;
+ if (!resolution) {
+ tmprequester->requester_cb = snr_resolution_cb;
+ tmprequester->requester_error_cb = snr_resolution_error_cb;
+ objt_server(tmprequester->requester)->dns_requester = tmprequester;
+ }
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ free(tmprequester);
+ return -1;
+ }
+
+ /* update some parameters only if this is a brand new resolution */
+ if (new_resolution) {
+ /* move the resolution to the requesters' wait queue */
+ LIST_DEL(&tmpresolution->list);
+ LIST_ADDQ(&resolvers->resolution.wait, &tmpresolution->list);
+
+ tmpresolution->status = RSLV_STATUS_NONE;
+ tmpresolution->step = RSLV_STEP_NONE;
+ tmpresolution->revision = 1;
+ }
+
+ /* add the requester to the resolution's wait queue */
+ if (resolution)
+ LIST_DEL(&tmprequester->list);
+ LIST_ADDQ(&tmpresolution->requester.wait, &tmprequester->list);
+
+ return 0;
+}
+
+/*
+ * pick up an available resolution from the different resolution list associated to a resolvers section,
+ * in this order:
+ * 1. check in resolution.curr for the same hostname and query_type
+ * 2. check in resolution.wait for the same hostname and query_type
+ * 3. take an available resolution from resolution.pool
+ *
+ * return an available resolution, NULL if none found.
+ */
+struct dns_resolution *dns_resolution_list_get(struct dns_resolvers *resolvers, char *hostname_dn, int query_type)
+{
+ struct dns_resolution *resolution, *tmpresolution;
+ struct dns_requester *requester;
+
+ /* search for same hostname and query type in resolution.curr */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.curr, list) {
+ requester = NULL;
+
+ if (!LIST_ISEMPTY(&resolution->requester.wait))
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+ else if (!LIST_ISEMPTY(&resolution->requester.curr))
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+
+ if (!requester)
+ continue;
+
+ if ((query_type == requester->prefered_query_type) &&
+ (strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
+ return resolution;
+ }
+ }
+
+ /* search for same hostname and query type in resolution.wait */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.wait, list) {
+ requester = NULL;
+
+ if (!LIST_ISEMPTY(&resolution->requester.wait))
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+ else if (!LIST_ISEMPTY(&resolution->requester.curr))
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+
+ if (!requester)
+ continue;
+
+ if ((query_type == requester->prefered_query_type) &&
+ (strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
+ return resolution;
+ }
+ }
+
+ /* take the first one (hopefully) from the pool */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.pool, list) {
+ if (LIST_ISEMPTY(&resolution->requester.wait)) {
+ return resolution;
+ }
+ }
+
+ return NULL;
+}
+
/* This function allocates memory for a DNS resolution structure.
* It's up to the caller to set the parameters
* Returns a pointer to the structure resolution or NULL if memory could
@@ -1472,6 +2178,8 @@
}
chunk_init(&resolution->response_buffer, buffer, global.tune.bufsize);
+ LIST_INIT(&resolution->requester.wait);
+ LIST_INIT(&resolution->requester.curr);
return resolution;
}
@@ -1485,6 +2193,93 @@
return;
}
+/* this function free a resolution from its requester(s) and move it back to the pool */
+void dns_resolution_free(struct dns_resolvers *resolvers, struct dns_resolution *resolution)
+{
+ struct dns_requester *requester, *tmprequester;
+
+ /* clean up configuration */
+ dns_reset_resolution(resolution);
+ resolution->hostname_dn = NULL;
+ resolution->hostname_dn_len = 0;
+
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.wait, list) {
+ LIST_DEL(&requester->list);
+ }
+ list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) {
+ LIST_DEL(&requester->list);
+ }
+
+ LIST_DEL(&resolution->list);
+ LIST_ADDQ(&resolvers->resolution.pool, &resolution->list);
+
+ return;
+}
+
+/*
+ * this function remove a requester from a resolution
+ * and takes care of all the consequences.
+ * It also cleans up some parameters from the requester
+ */
+void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dns_resolution *resolution)
+{
+ char *hostname_dn;
+ struct dns_requester *tmprequester;
+
+ /* resolution is still used by other requesters, we need to move
+ * some pointers to an other requester if needed
+ */
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ hostname_dn = objt_server(requester->requester)->hostname_dn;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ hostname_dn = NULL;
+ break;
+ }
+
+ if (resolution->hostname_dn != hostname_dn)
+ return;
+
+ /* First, we need to find this other requester */
+ tmprequester = NULL;
+ list_for_each_entry(tmprequester, &resolution->requester.wait, list) {
+ if (tmprequester != requester)
+ break;
+ }
+ if (!tmprequester) {
+ /* if we can't find it in wait queue, let's get one in run queue */
+ list_for_each_entry(tmprequester, &resolution->requester.curr, list) {
+ if (tmprequester != requester)
+ break;
+ }
+ }
+
+ /* move hostname_dn related pointers to the next requester */
+ switch (obj_type(tmprequester->requester)) {
+ case OBJ_TYPE_SERVER:
+ resolution->hostname_dn = objt_server(tmprequester->requester)->hostname_dn;
+ resolution->hostname_dn_len = objt_server(tmprequester->requester)->hostname_dn_len;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ ;;
+ }
+
+
+ /* clean up the requester */
+ LIST_DEL(&requester->list);
+ switch (obj_type(requester->requester)) {
+ case OBJ_TYPE_SERVER:
+ objt_server(requester->requester)->resolution = NULL;
+ break;
+ case OBJ_TYPE_NONE:
+ default:
+ ;;
+ }
+}
+
/* This function dumps counters from all resolvers section and associated name
* servers. It returns 0 if the output buffer is full and it needs to be called
* again, otherwise non-zero. It may limit itself to the resolver pointed to by
diff --git a/src/server.c b/src/server.c
index 9d0714a..74450a1 100644
--- a/src/server.c
+++ b/src/server.c
@@ -47,7 +47,6 @@
static void srv_update_state(struct server *srv, int version, char **params);
static int srv_apply_lastaddr(struct server *srv, int *err_code);
static int srv_set_fqdn(struct server *srv, const char *fqdn);
-static void srv_free_dns_resolution(struct server *srv);
/* List head of all known server keywords */
static struct srv_kw_list srv_keywords = {
@@ -1652,60 +1651,29 @@
#endif
/*
- * Allocate <srv> server dns resolution.
+ * Prepare <srv> for hostname resolution.
* May be safely called with a default server as <src> argument (without hostname).
* Returns -1 in case of any allocation failure, 0 if not.
*/
-static int srv_alloc_dns_resolution(struct server *srv, const char *hostname)
+static int srv_prepare_for_resolution(struct server *srv, const char *hostname)
{
- struct dns_resolution *dst_dns_rslt;
-
if (!hostname)
return 0;
free(srv->hostname);
srv->hostname = strdup(hostname);
- dst_dns_rslt = dns_alloc_resolution();
srv->hostname_dn_len = dns_str_to_dn_label_len(hostname);
srv->hostname_dn = calloc(srv->hostname_dn_len + 1, sizeof(char));
- if (!srv->hostname || !dst_dns_rslt || !srv->hostname_dn)
+ if (!srv->hostname || !srv->hostname_dn)
goto err;
- srv->resolution = dst_dns_rslt;
-
if (!dns_str_to_dn_label(srv->hostname,
srv->hostname_dn,
srv->hostname_dn_len + 1))
goto err;
- 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;
- srv->resolution->status = RSLV_STATUS_NONE;
- srv->resolution->step = RSLV_STEP_NONE;
- /* a first resolution has been done by the configuration parser */
- srv->resolution->last_resolution = 0;
-
- if (srv->resolvers_id) {
- struct dns_resolvers *curr_resolvers;
- int found = 0;
-
- list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
- if (!strcmp(curr_resolvers->id, srv->resolvers_id)) {
- found = 1;
- break;
- }
- }
- if (!found)
- goto err;
- srv->resolvers = curr_resolvers;
- }
-
return 0;
err:
@@ -1713,17 +1681,44 @@
srv->hostname = NULL;
free(srv->hostname_dn);
srv->hostname_dn = NULL;
- dns_free_resolution(dst_dns_rslt);
return -1;
}
-static void srv_free_dns_resolution(struct server *srv)
+/*
+ * Free the link between a server and its resolution.
+ * It also performs the following tasks:
+ * - check if resolution can be moved back in the resolvers' pool
+ * (and do it)
+ * - move resolution's hostname_dn and hostname_dn_len to the next requester
+ * available (when applied)
+ */
+static void srv_free_from_resolution(struct server *srv)
{
- if (!srv->resolution)
+ struct dns_requester *requester;
+ int count;
+
+ /* check if we can move the resolution back to the pool.
+ * if <count> is greater than 1, then we can't */
+ count = 0;
+ list_for_each_entry(requester, &srv->resolution->requester.wait, list) {
+ ++count;
+ if (count > 1)
+ break;
+ }
+ list_for_each_entry(requester, &srv->resolution->requester.curr, list) {
+ ++count;
+ if (count > 1)
+ break;
+ }
+ if (count <= 1) {
+ /* move the resolution back to the pool */
+ dns_resolution_free(srv->resolvers, srv->resolution);
return;
+ }
- dns_free_resolution(srv->resolution);
- srv->resolution = NULL;
+ dns_rm_requester_from_resolution(srv->dns_requester, srv->resolution);
+
+ return;
}
/*
@@ -2114,7 +2109,7 @@
goto err;
srv_settings_cpy(newsrv, srv, 1);
- srv_alloc_dns_resolution(newsrv, srv->hostname);
+ srv_prepare_for_resolution(newsrv, srv->hostname);
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
if (newsrv->sni_expr) {
newsrv->ssl_ctx.sni = srv_sni_sample_parse_expr(newsrv, px, NULL, 0, NULL);
@@ -2290,7 +2285,7 @@
/* save hostname and create associated name resolution */
if (fqdn) {
- if (srv_alloc_dns_resolution(newsrv, fqdn) == -1) {
+ if (srv_prepare_for_resolution(newsrv, fqdn) == -1) {
Alert("parsing [%s:%d] : Can't create DNS resolution for server '%s'\n",
file, linenum, newsrv->id);
err_code |= ERR_ALERT | ERR_FATAL;
@@ -3749,8 +3744,8 @@
switch (resolution->status) {
case RSLV_STATUS_NONE:
- /* status when HAProxy has just (re)started */
- trigger_resolution(s);
+ /* status when HAProxy has just (re)started.
+ * Nothing to do, since the task is already automatically started */
break;
case RSLV_STATUS_VALID:
@@ -3825,23 +3820,27 @@
* 0 on error
* 1 when no error or safe ignore
*/
-int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver)
+int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver)
{
- struct server *s;
+ struct server *s = NULL;
+ struct dns_resolution *resolution = NULL;
void *serverip, *firstip;
short server_sin_family, firstip_sin_family;
int ret;
struct chunk *chk = get_trash_chunk();
+ s = objt_server(requester->requester);
+ if (!s)
+ return 1;
+
+ resolution = s->resolution;
+
/* initializing variables */
firstip = NULL; /* pointer to the first valid response found */
/* it will be used as the new IP if a change is required */
firstip_sin_family = AF_UNSPEC;
serverip = NULL; /* current server IP address */
- /* shortcut to the server whose name is being resolved */
- s = resolution->requester;
-
/* initializing server IP pointer */
server_sin_family = s->addr.ss_family;
switch (server_sin_family) {
@@ -3866,20 +3865,12 @@
switch (ret) {
case DNS_UPD_NO:
- if (resolution->status != RSLV_STATUS_VALID) {
- resolution->status = RSLV_STATUS_VALID;
- resolution->last_status_change = now_ms;
- }
- goto stop_resolution;
+ goto update_status;
case DNS_UPD_SRVIP_NOT_FOUND:
goto save_ip;
case DNS_UPD_CNAME:
- if (resolution->status != RSLV_STATUS_VALID) {
- resolution->status = RSLV_STATUS_VALID;
- resolution->last_status_change = now_ms;
- }
goto invalid;
case DNS_UPD_NO_IP_FOUND:
@@ -3887,18 +3878,18 @@
resolution->status = RSLV_STATUS_OTHER;
resolution->last_status_change = now_ms;
}
- goto stop_resolution;
+ goto update_status;
case DNS_UPD_NAME_ERROR:
/* if this is not the last expected response, we ignore it */
- if (resolution->nb_responses < nameserver->resolvers->count_nameservers)
+ if (nameserver && (resolution->nb_responses < nameserver->resolvers->count_nameservers))
return 0;
/* update resolution status to OTHER error type */
if (resolution->status != RSLV_STATUS_OTHER) {
resolution->status = RSLV_STATUS_OTHER;
resolution->last_status_change = now_ms;
}
- goto stop_resolution;
+ goto update_status;
default:
goto invalid;
@@ -3906,33 +3897,25 @@
}
save_ip:
- nameserver->counters.update += 1;
- if (resolution->status != RSLV_STATUS_VALID) {
- resolution->status = RSLV_STATUS_VALID;
- resolution->last_status_change = now_ms;
- }
+ if (nameserver)
+ nameserver->counters.update += 1;
/* save the first ip we found */
- chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id);
+ if (nameserver)
+ chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id);
+ else
+ chunk_printf(chk, "DNS cache");
update_server_addr(s, firstip, firstip_sin_family, (char *)chk->str);
- stop_resolution:
- /* update last resolution date and time */
- resolution->last_resolution = now_ms;
- /* reset current status flag */
- resolution->step = RSLV_STEP_NONE;
- /* reset values */
- dns_reset_resolution(resolution);
-
- dns_update_resolvers_timeout(nameserver->resolvers);
-
+ update_status:
snr_update_srv_status(s);
- return 0;
+ return 1;
invalid:
- nameserver->counters.invalid += 1;
+ if (nameserver)
+ nameserver->counters.invalid += 1;
if (resolution->nb_responses >= nameserver->resolvers->count_nameservers)
- goto stop_resolution;
+ goto update_status;
snr_update_srv_status(s);
return 0;
@@ -3944,14 +3927,17 @@
* 0 on error
* 1 when no error or safe ignore
*/
-int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code)
+int snr_resolution_error_cb(struct dns_requester *requester, int error_code)
{
- struct server *s;
- struct dns_resolvers *resolvers;
- int res_preferred_afinet, res_preferred_afinet6;
+ struct server *s = NULL;
+ struct dns_resolution *resolution = NULL;
+ struct dns_resolvers *resolvers = NULL;
- /* shortcut to the server whose name is being resolved */
- s = resolution->requester;
+ s = objt_server(requester->requester);
+ if (!s)
+ return 1;
+
+ resolution = s->resolution;
resolvers = s->resolvers;
/* can be ignored if this is not the last response */
@@ -3959,99 +3945,6 @@
return 1;
}
- switch (error_code) {
- case DNS_RESP_INVALID:
- case DNS_RESP_WRONG_NAME:
- if (resolution->status != RSLV_STATUS_INVALID) {
- resolution->status = RSLV_STATUS_INVALID;
- resolution->last_status_change = now_ms;
- }
- break;
-
- case DNS_RESP_ANCOUNT_ZERO:
- case DNS_RESP_TRUNCATED:
- case DNS_RESP_ERROR:
- case DNS_RESP_NO_EXPECTED_RECORD:
- case DNS_RESP_CNAME_ERROR:
- res_preferred_afinet = s->dns_opts.family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
- res_preferred_afinet6 = s->dns_opts.family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
-
- if ((res_preferred_afinet || res_preferred_afinet6)
- || (resolution->try > 0)) {
- /* let's change the query type */
- if (res_preferred_afinet6) {
- /* fallback from AAAA to A */
- resolution->query_type = DNS_RTYPE_A;
- }
- else if (res_preferred_afinet) {
- /* fallback from A to AAAA */
- resolution->query_type = DNS_RTYPE_AAAA;
- }
- else {
- resolution->try -= 1;
- if (s->dns_opts.family_prio == AF_INET) {
- resolution->query_type = DNS_RTYPE_A;
- } else {
- resolution->query_type = DNS_RTYPE_AAAA;
- }
- }
-
- dns_send_query(resolution);
-
- /*
- * move the resolution to the last element of the FIFO queue
- * and update timeout wakeup based on the new first entry
- */
- if (dns_check_resolution_queue(resolvers) > 1) {
- /* second resolution becomes first one */
- LIST_DEL(&resolution->list);
- /* ex first resolution goes to the end of the queue */
- LIST_ADDQ(&resolvers->curr_resolution, &resolution->list);
- }
- dns_update_resolvers_timeout(resolvers);
- goto leave;
- }
- else {
- if (resolution->status != RSLV_STATUS_OTHER) {
- resolution->status = RSLV_STATUS_OTHER;
- resolution->last_status_change = now_ms;
- }
- }
- break;
-
- case DNS_RESP_NX_DOMAIN:
- if (resolution->status != RSLV_STATUS_NX) {
- resolution->status = RSLV_STATUS_NX;
- resolution->last_status_change = now_ms;
- }
- break;
-
- case DNS_RESP_REFUSED:
- if (resolution->status != RSLV_STATUS_REFUSED) {
- resolution->status = RSLV_STATUS_REFUSED;
- resolution->last_status_change = now_ms;
- }
- break;
-
- case DNS_RESP_TIMEOUT:
- if (resolution->status != RSLV_STATUS_TIMEOUT) {
- resolution->status = RSLV_STATUS_TIMEOUT;
- resolution->last_status_change = now_ms;
- }
- break;
- }
-
- /* update last resolution date and time */
- resolution->last_resolution = now_ms;
- /* reset current status flag */
- resolution->step = RSLV_STEP_NONE;
- /* reset values */
- dns_reset_resolution(resolution);
-
- LIST_DEL(&resolution->list);
- dns_update_resolvers_timeout(resolvers);
-
- leave:
snr_update_srv_status(s);
return 1;
}
@@ -4080,9 +3973,9 @@
* one used for the server found in the backend
* * the server found in the backend is not our current server
*/
- if ((tmpsrv->resolution == NULL) ||
- (srv->resolution->hostname_dn_len != tmpsrv->resolution->hostname_dn_len) ||
- (strcmp(srv->resolution->hostname_dn, tmpsrv->resolution->hostname_dn) != 0) ||
+ if ((tmpsrv->hostname_dn == NULL) ||
+ (srv->hostname_dn_len != tmpsrv->hostname_dn_len) ||
+ (strcmp(srv->hostname_dn, tmpsrv->hostname_dn) != 0) ||
(srv->puid == tmpsrv->puid))
continue;
@@ -4121,9 +4014,55 @@
*/
int srv_set_fqdn(struct server *srv, const char *hostname)
{
- srv_free_dns_resolution(srv);
+ struct dns_resolution *resolution;
+ int hostname_dn_len;
+
+ /* run time DNS resolution was not active for this server
+ * and we can't enable it at run time for now.
+ */
+ if (!srv->dns_requester)
+ return -1;
+
+ chunk_reset(&trash);
+
+ /* check if hostname is really a hostname and if we have enough
+ * room to save it in its domain name format
+ */
+ hostname_dn_len = dns_str_to_dn_label_len(hostname);
+ if (hostname_dn_len == -1 || hostname_dn_len + 1 > trash.size)
+ return -1;
+
+ if (!dns_str_to_dn_label(hostname,
+ trash.str,
+ hostname_dn_len + 1))
+ return -1;
+
- return srv_alloc_dns_resolution(srv, hostname);
+ /* get a resolution from the curr or wait queues, or a brand new one from the pool */
+ resolution = dns_resolution_list_get(srv->resolvers, trash.str, srv->dns_requester->prefered_query_type);
+ if (!resolution)
+ return -1;
+
+ /* in this case, the new hostanme is the same than the old one */
+ if (srv->resolution == resolution)
+ return 0;
+
+ /* first, we need to unlink our server from its current resolution */
+ srv_free_from_resolution(srv);
+
+ /* now we update server's parameters */
+ free(srv->hostname);
+ free(srv->hostname_dn);
+ srv->hostname = strdup(hostname);
+ srv->hostname_dn = strdup(trash.str);
+ srv->hostname_dn_len = hostname_dn_len;
+ if (!srv->hostname || !srv->hostname_dn)
+ return -1;
+
+ /* then we can link srv to its new resolution */
+ dns_link_resolution(srv, OBJ_TYPE_SERVER, resolution);
+
+ return 0;
}
/* Sets the server's address (srv->addr) from srv->lastaddr which was filled