MAJOR: server: add DNS-based server name resolution

Relies on the DNS protocol freshly implemented in HAProxy.
It performs a server IP addr resolution based on a server hostname.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e48cf93..129cf17 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -8103,6 +8103,47 @@
 				free(newsrv->trackit);
 				newsrv->trackit = NULL;
 			}
+
+			/*
+			 * resolve server's resolvers name and update the resolvers pointer
+			 * accordingly
+			 */
+			if (newsrv->resolvers_id) {
+				struct dns_resolvers *curr_resolvers;
+				int found;
+
+				found = 0;
+				list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
+					if (!strcmp(curr_resolvers->id, newsrv->resolvers_id)) {
+						found = 1;
+						break;
+					}
+				}
+
+				if (!found) {
+					Alert("config : %s '%s', server '%s': unable to find required resolvers '%s'\n",
+					proxy_type_str(curproxy), curproxy->id,
+					newsrv->id, newsrv->resolvers_id);
+					cfgerr++;
+				} else {
+					free(newsrv->resolvers_id);
+					newsrv->resolvers_id = NULL;
+					if (newsrv->resolution)
+						newsrv->resolution->resolvers = curr_resolvers;
+				}
+			}
+			else {
+				/* if no resolvers section associated to this server
+				 * we can clean up the associated resolution structure
+				 */
+				if (newsrv->resolution) {
+					free(newsrv->resolution->hostname_dn);
+					newsrv->resolution->hostname_dn = NULL;
+					free(newsrv->resolution);
+					newsrv->resolution = NULL;
+				}
+			}
+
 		next_srv:
 			newsrv = newsrv->next;
 		}
diff --git a/src/checks.c b/src/checks.c
index 8d8c31c..2179d4f 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -38,6 +38,7 @@
 
 #include <types/global.h>
 #include <types/mailers.h>
+#include <types/dns.h>
 
 #ifdef USE_OPENSSL
 #include <types/ssl_sock.h>
@@ -59,6 +60,9 @@
 #include <proto/server.h>
 #include <proto/stream_interface.h>
 #include <proto/task.h>
+#include <proto/log.h>
+#include <proto/dns.h>
+#include <proto/proto_udp.h>
 
 static int httpchk_expect(struct server *s, int done);
 static int tcpcheck_get_step_id(struct check *);
@@ -680,6 +684,14 @@
 			set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
 		else if (expired)
 			set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
+
+		/*
+		 * might be due to a server IP change.
+		 * 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);
+
 	}
 	else if ((conn->flags & (CO_FL_CONNECTED|CO_FL_WAIT_L6_CONN)) == CO_FL_WAIT_L6_CONN) {
 		/* L6 not established (yet) */
@@ -2132,10 +2144,93 @@
 static struct task *process_chk(struct task *t)
 {
 	struct check *check = t->context;
+	struct server *s = check->server;
+	struct dns_resolution *resolution = s->resolution;
+
+	/* 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 (tick_is_expired(tick_add(resolution->last_resolution, 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;
+	struct dns_resolvers *resolvers;
+	int query_id;
+	int i;
+
+	resolution = s->resolution;
+	resolvers = resolution->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;
+	resolution->query_type = DNS_RTYPE_ANY;
+	resolution->try = 0;
+	resolution->try_cname = 0;
+	resolution->nb_responses = 0;
+	resolution->resolver_family_priority = s->resolver_family_priority;
+	eb32_insert(&resolvers->query_ids, &resolution->qid);
+
+	dns_send_query(resolution);
+
+	/* 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/server.c b/src/server.c
index 2bde246..ee52903 100644
--- a/src/server.c
+++ b/src/server.c
@@ -20,6 +20,7 @@
 #include <common/time.h>
 
 #include <types/global.h>
+#include <types/dns.h>
 
 #include <proto/checks.h>
 #include <proto/port_range.h>
@@ -29,6 +30,7 @@
 #include <proto/server.h>
 #include <proto/stream.h>
 #include <proto/task.h>
+#include <proto/dns.h>
 
 
 /* List head of all known server keywords */
@@ -865,6 +867,7 @@
 			struct sockaddr_storage *sk;
 			int port1, port2;
 			struct protocol *proto;
+			struct dns_resolution *curr_resolution;
 
 			if ((newsrv = (struct server *)calloc(1, sizeof(struct server))) == NULL) {
 				Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
@@ -928,6 +931,53 @@
 				realport = port1;
 			}
 
+			/* save hostname and create associated name resolution */
+			switch (sk->ss_family) {
+			case AF_INET: {
+				/* remove the port if any */
+				char *c;
+				if ((c = rindex(args[2], ':')) != NULL) {
+					newsrv->hostname = my_strndup(args[2], c - args[2]);
+				}
+				else {
+					newsrv->hostname = strdup(args[2]);
+				}
+			}
+				break;
+			case AF_INET6:
+				newsrv->hostname = strdup(args[2]);
+				break;
+			default:
+				goto skip_name_resolution;
+			}
+
+			/* no name resolution if an IP has been provided */
+			if (inet_pton(sk->ss_family, newsrv->hostname, trash.str) == 1)
+				goto skip_name_resolution;
+
+			if ((curr_resolution = calloc(1, sizeof(struct dns_resolution))) == NULL)
+				goto skip_name_resolution;
+
+			curr_resolution->hostname_dn_len = dns_str_to_dn_label_len(newsrv->hostname);
+			if ((curr_resolution->hostname_dn = calloc(curr_resolution->hostname_dn_len + 1, sizeof(char))) == NULL)
+				goto skip_name_resolution;
+			if ((dns_str_to_dn_label(newsrv->hostname, curr_resolution->hostname_dn, curr_resolution->hostname_dn_len + 1)) == NULL) {
+				Alert("parsing [%s:%d] : Invalid hostname '%s'\n",
+				      file, linenum, args[2]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+
+			curr_resolution->requester = newsrv;
+			curr_resolution->requester_cb = snr_resolution_cb;
+			curr_resolution->requester_error_cb = snr_resolution_error_cb;
+			curr_resolution->status = RSLV_STATUS_NONE;
+			curr_resolution->step = RSLV_STEP_NONE;
+			/* a first resolution has been done by the configuration parser */
+			curr_resolution->last_resolution = now_ms;
+			newsrv->resolution = curr_resolution;
+
+ skip_name_resolution:
 			newsrv->addr = *sk;
 			newsrv->xprt  = newsrv->check.xprt = newsrv->agent.xprt = &raw_sock;
 
@@ -975,11 +1025,15 @@
 			newsrv->agent.fall	= curproxy->defsrv.agent.fall;
 			newsrv->agent.health	= newsrv->agent.rise;	/* up, but will fall down at first failure */
 			newsrv->agent.server	= newsrv;
+			newsrv->resolver_family_priority = curproxy->defsrv.resolver_family_priority;
+			if (newsrv->resolver_family_priority == AF_UNSPEC)
+				newsrv->resolver_family_priority = AF_INET6;
 
 			cur_arg = 3;
 		} else {
 			newsrv = &curproxy->defsrv;
 			cur_arg = 1;
+			newsrv->resolver_family_priority = AF_INET6;
 		}
 
 		while (*args[cur_arg]) {
@@ -1019,6 +1073,23 @@
 				newsrv->rdr_len = strlen(args[cur_arg + 1]);
 				cur_arg += 2;
 			}
+			else if (!strcmp(args[cur_arg], "resolvers")) {
+				newsrv->resolvers_id = strdup(args[cur_arg + 1]);
+				cur_arg += 2;
+			}
+			else if (!strcmp(args[cur_arg], "resolve-prefer")) {
+				if (!strcmp(args[cur_arg + 1], "ipv4"))
+					newsrv->resolver_family_priority = AF_INET;
+				else if (!strcmp(args[cur_arg + 1], "ipv6"))
+					newsrv->resolver_family_priority = AF_INET6;
+				else {
+					Alert("parsing [%s:%d]: '%s' expects either ipv4 or ipv6 as argument.\n",
+						file, linenum, args[cur_arg]);
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto out;
+				}
+				cur_arg += 2;
+			}
 			else if (!strcmp(args[cur_arg], "rise")) {
 				if (!*args[cur_arg + 1]) {
 					Alert("parsing [%s:%d]: '%s' expects an integer argument.\n",
@@ -1677,6 +1748,9 @@
 				goto out;
 			}
 
+			if (newsrv->resolution)
+				newsrv->resolution->resolver_family_priority = newsrv->resolver_family_priority;
+
 			newsrv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
 		}
 
@@ -1786,6 +1860,238 @@
 }
 
 /*
+ * update server status based on result of name resolution
+ * returns:
+ *  0 if server status is updated
+ *  1 if server status has not changed
+ */
+int snr_update_srv_status(struct server *s)
+{
+	struct dns_resolution *resolution = s->resolution;
+
+	switch (resolution->status) {
+		case RSLV_STATUS_NONE:
+			/* status when HAProxy has just (re)started */
+			trigger_resolution(s);
+			break;
+
+		default:
+			break;
+	}
+
+	return 1;
+}
+
+/*
+ * Server Name Resolution valid response callback
+ * It expects:
+ *  - <nameserver>: the name server which answered the valid response
+ *  - <response>: buffer containing a valid DNS response
+ *  - <response_len>: size of <response>
+ * It performs the following actions:
+ *  - ignore response if current ip found and server family not met
+ *  - update with first new ip found if family is met and current IP is not found
+ * returns:
+ *  0 on error
+ *  1 when no error or safe ignore
+ */
+int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver, unsigned char *response, int response_len)
+{
+	struct server *s;
+	void *serverip, *firstip;
+	short server_sin_family, firstip_sin_family;
+	unsigned char *response_end;
+	int ret;
+	struct chunk *chk = get_trash_chunk();
+
+	/* initializing variables */
+	response_end = response + response_len;	/* pointer to mark the end of the response */
+	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 = (struct server *)resolution->requester;
+
+	/* initializing server IP pointer */
+	server_sin_family = s->addr.ss_family;
+	switch (server_sin_family) {
+		case AF_INET:
+			serverip = &((struct sockaddr_in *)&s->addr)->sin_addr.s_addr;
+			break;
+
+		case AF_INET6:
+			serverip = &((struct sockaddr_in6 *)&s->addr)->sin6_addr.s6_addr;
+			break;
+
+		default:
+			goto invalid;
+	}
+
+	ret = dns_get_ip_from_response(response, response_end, resolution->hostname_dn, resolution->hostname_dn_len,
+			serverip, server_sin_family, resolution->resolver_family_priority, &firstip,
+			&firstip_sin_family);
+
+	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;
+
+		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;
+
+		default:
+			goto invalid;
+
+	}
+
+ save_ip:
+	nameserver->counters.update += 1;
+	if (resolution->status != RSLV_STATUS_VALID) {
+		resolution->status = RSLV_STATUS_VALID;
+		resolution->last_status_change = now_ms;
+	}
+
+	/* save the first ip we found */
+	chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id);
+	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);
+
+	LIST_DEL(&resolution->list);
+	dns_update_resolvers_timeout(nameserver->resolvers);
+
+	snr_update_srv_status(s);
+	return 0;
+
+ invalid:
+	nameserver->counters.invalid += 1;
+	if (resolution->nb_responses >= nameserver->resolvers->count_nameservers)
+		goto stop_resolution;
+
+	snr_update_srv_status(s);
+	return 0;
+}
+
+/*
+ * Server Name Resolution error management callback
+ * returns:
+ *  0 on error
+ *  1 when no error or safe ignore
+ */
+int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code)
+{
+	struct server *s;
+	struct dns_resolvers *resolvers;
+
+	/* shortcut to the server whose name is being resolved */
+	s = (struct server *)resolution->requester;
+	resolvers = resolution->resolvers;
+
+	/* can be ignored if this is not the last response */
+	if ((error_code != DNS_RESP_TIMEOUT) && (resolution->nb_responses < resolvers->count_nameservers)) {
+		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_ERROR:
+			if (resolution->query_type == DNS_RTYPE_ANY) {
+				/* let's change the query type */
+				if (resolution->resolver_family_priority == AF_INET6)
+					resolution->query_type = DNS_RTYPE_AAAA;
+				else
+					resolution->query_type = DNS_RTYPE_A;
+
+				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(&resolvers->curr_resolution);
+					/* 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_CNAME_ERROR:
+			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;
+}
+
+/*
  * Local variables:
  *  c-indent-level: 8
  *  c-basic-offset: 8
diff --git a/src/standard.c b/src/standard.c
index 709db8b..4e458c2 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -25,6 +25,7 @@
 #include <common/config.h>
 #include <common/standard.h>
 #include <types/global.h>
+#include <proto/dns.h>
 #include <eb32tree.h>
 
 /* enough to store NB_ITOA_STR integers of :
@@ -608,6 +609,9 @@
 	if (!resolve)
 		return NULL;
 
+	if (!dns_hostname_validation(str, NULL))
+		return NULL;
+
 #ifdef USE_GETADDRINFO
 	if (global.tune.options & GTUNE_USE_GAI) {
 		struct addrinfo hints, *result;