MEDIUM: dns: add a "resolve-net" option which allow to prefer an ip in a network

This options prioritize th choice of an ip address matching a network. This is
useful with clouds to prefer a local ip. In some cases, a cloud high
avalailibility service can be announced with many ip addresses on many
differents datacenters. The latency between datacenter is not negligible, so
this patch permitsto prefers a local datacenter. If none address matchs the
configured network, another address is selected.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 7f369da..f55f68d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10595,6 +10595,18 @@
 
   Example: server s1 app1.domain.com:80 resolvers mydns resolve-prefer ipv6
 
+resolve-net <network>[,<network[,...]]
+  This options prioritize th choice of an ip address matching a network. This is
+  useful with clouds to prefer a local ip. In some cases, a cloud high
+  avalailibility service can be announced with many ip addresses on many
+  differents datacenters. The latency between datacenter is not negligible, so
+  this patch permitsto prefers a local datacenter. If none address matchs the
+  configured network, another address is selected.
+
+  Supported in default-server: Yes
+
+  Example: server s1 app1.domain.com:80 resolvers mydns resolve-net 10.0.0.0/8
+
 resolvers <id>
   Points to an existing "resolvers" section to resolve current server's
   hostname.
diff --git a/include/types/dns.h b/include/types/dns.h
index cf784cd..757eaaf 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -55,6 +55,11 @@
 #define DNS_FLAG_TRUNCATED	0x0200	/* mask for truncated flag */
 #define DNS_FLAG_REPLYCODE	0x000F	/* mask for reply code */
 
+/* max number of network preference entries are avalaible from the
+ * configuration file.
+ */
+#define SRV_MAX_PREF_NET 5
+
 /* DNS request or response header structure */
 struct dns_header {
 	unsigned short	id:16;		/* identifier */
@@ -142,6 +147,18 @@
 
 struct dns_options {
 	int family_prio;	/* which IP family should the resolver use when both are returned */
+	struct {
+		int family;
+		union {
+			struct in_addr in4;
+			struct in6_addr in6;
+		} addr;
+		union {
+			struct in_addr in4;
+			struct in6_addr in6;
+		} mask;
+	} pref_net[SRV_MAX_PREF_NET];
+	int pref_net_nb;               /* The number of registered prefered networks. */
 };
 
 /*
diff --git a/include/types/server.h b/include/types/server.h
index c04af9c..952671c 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -92,7 +92,6 @@
 #define SRV_STATE_FILE_NB_FIELDS_VERSION_1 18
 #define SRV_STATE_LINE_MAXLEN 512
 
-
 /* server flags */
 #define SRV_F_BACKUP       0x0001        /* this server is a backup server */
 #define SRV_F_MAPPORTS     0x0002        /* this server uses mapped ports */
diff --git a/src/dns.c b/src/dns.c
index f43a675..c854744 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -592,6 +592,7 @@
  * For both cases above, dns_validate_dns_response is required
  * returns one of the DNS_UPD_* code
  */
+#define DNS_MAX_IP_REC 20
 int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
                              struct dns_resolution *resol, void *currentip,
                              short currentip_sin_family,
@@ -602,6 +603,14 @@
 	int dn_name_len;
 	int i, ancount, cnamelen, type, data_len, currentip_found;
 	unsigned char *reader, *cname, *ptr, *newip4, *newip6;
+	struct {
+		unsigned char *ip;
+		unsigned char type;
+	} rec[DNS_MAX_IP_REC];
+	int currentip_sel;
+	int j;
+	int rec_nb = 0;
+	int score, max_score;
 
 	family_priority = resol->opts->family_prio;
 	dn_name = resol->hostname_dn;
@@ -685,20 +694,12 @@
 		/* analyzing record content */
 		switch (type) {
 			case DNS_RTYPE_A:
-				/* check if current reccord's IP is the same as server one's */
-				if ((currentip_sin_family == AF_INET)
-						&& (*(uint32_t *)reader == *(uint32_t *)currentip)) {
-					currentip_found = 1;
-					newip4 = reader;
-					/* we can stop now if server's family preference is IPv4
-					 * and its current IP is found in the response list */
-					if (family_priority == AF_INET)
-						return DNS_UPD_NO; /* DNS_UPD matrix #1 */
+				/* Store IPv4, only if some room is avalaible. */
+				if (rec_nb < DNS_MAX_IP_REC) {
+					rec[rec_nb].ip = reader;
+					rec[rec_nb].type = AF_INET;
+					rec_nb++;
 				}
-				else if (!newip4) {
-					newip4 = reader;
-				}
-
 				/* move forward data_len for analyzing next record in the response */
 				reader += data_len;
 				break;
@@ -711,19 +712,12 @@
 				break;
 
 			case DNS_RTYPE_AAAA:
-				/* check if current record's IP is the same as server's one */
-				if ((currentip_sin_family == AF_INET6) && (memcmp(reader, currentip, 16) == 0)) {
-					currentip_found = 1;
-					newip6 = reader;
-					/* we can stop now if server's preference is IPv6 or is not
-					 * set (which implies we prioritize IPv6 over IPv4 */
-					if (family_priority == AF_INET6)
-						return DNS_UPD_NO;
+				/* Store IPv6, only if some room is avalaible. */
+				if (rec_nb < DNS_MAX_IP_REC) {
+					rec[rec_nb].ip = reader;
+					rec[rec_nb].type = AF_INET6;
+					rec_nb++;
 				}
-				else if (!newip6) {
-					newip6 = reader;
-				}
-
 				/* move forward data_len for analyzing next record in the response */
 				reader += data_len;
 				break;
@@ -735,6 +729,75 @@
 		} /* switch (record type) */
 	} /* for i 0 to ancount */
 
+	/* Select an IP regarding configuration preference.
+	 * Top priority is the prefered network ip version,
+	 * second priority is the prefered network.
+	 * the last priority is the currently used IP,
+	 *
+	 * For these three priorities, a score is calculated. The
+	 * weight are:
+	 *  4 - prefered netwok ip version.
+	 *  2 - prefered network.
+	 *  1 - current ip.
+	 * The result with the biggest score is returned.
+	 */
+	max_score = -1;
+	for (i = 0; i < rec_nb; i++) {
+
+		score = 0;
+
+		/* Check for prefered ip protocol. */
+		if (rec[i].type == family_priority)
+			score += 4;
+
+		/* Check for prefered network. */
+		for (j = 0; j < resol->opts->pref_net_nb; j++) {
+
+			/* Compare only the same adresses class. */
+			if (resol->opts->pref_net[j].family != rec[i].type)
+				continue;
+
+			if ((rec[i].type == AF_INET &&
+			     in_net_ipv4((struct in_addr *)rec[i].ip,
+			                 &resol->opts->pref_net[j].mask.in4,
+			                 &resol->opts->pref_net[j].addr.in4)) ||
+			    (rec[i].type == AF_INET6 &&
+			     in_net_ipv6((struct in6_addr *)rec[i].ip,
+			                 &resol->opts->pref_net[j].mask.in6,
+			                 &resol->opts->pref_net[j].addr.in6))) {
+				score += 2;
+				break;
+			}
+		}
+
+		/* Check for current ip matching. */
+		if (rec[i].type == currentip_sin_family &&
+		    ((currentip_sin_family == AF_INET &&
+		      *(uint32_t *)rec[i].ip == *(uint32_t *)currentip) ||
+		     (currentip_sin_family == AF_INET6 &&
+		      memcmp(rec[i].ip, currentip, 16) == 0))) {
+			score += 1;
+			currentip_sel = 1;
+		} else
+			currentip_sel = 0;
+
+		/* Keep the address if the score is better than the previous
+		 * score. The maximum score is 7, if this value is reached,
+		 * we break the parsing. Implicitly, this score is reached
+		 * the ip selected is the current ip.
+		 */
+		if (score > max_score) {
+			if (rec[i].type == AF_INET)
+				newip4 = rec[i].ip;
+			else
+				newip6 = rec[i].ip;
+			currentip_found = currentip_sel;
+			if (score == 7)
+				return DNS_UPD_NO;
+			max_score = score;
+		}
+	}
+
 	/* only CNAMEs in the response, no IP found */
 	if (cname && !newip4 && !newip6) {
 		return DNS_UPD_CNAME;
diff --git a/src/server.c b/src/server.c
index 84dad38..30722fc 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1020,6 +1020,10 @@
 			newsrv->dns_opts.family_prio = curproxy->defsrv.dns_opts.family_prio;
 			if (newsrv->dns_opts.family_prio == AF_UNSPEC)
 				newsrv->dns_opts.family_prio = AF_INET6;
+			memcpy(newsrv->dns_opts.pref_net,
+			       curproxy->defsrv.dns_opts.pref_net,
+			       sizeof(newsrv->dns_opts.pref_net));
+			newsrv->dns_opts.pref_net_nb = curproxy->defsrv.dns_opts.pref_net_nb;
 
 			cur_arg = 3;
 		} else {
@@ -1090,6 +1094,62 @@
 				}
 				cur_arg += 2;
 			}
+			else if (!strcmp(args[cur_arg], "resolve-net")) {
+				char *p, *e;
+				unsigned char mask;
+				struct dns_options *opt;
+
+				if (!args[cur_arg + 1] || args[cur_arg + 1][0] == '\0') {
+					Alert("parsing [%s:%d]: '%s' expects a list of networks.\n",
+					      file, linenum, args[cur_arg]);
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto out;
+				}
+
+				opt = &newsrv->dns_opts;
+
+				/* Split arguments by comma, and convert it from ipv4 or ipv6
+				 * string network in in_addr or in6_addr.
+				 */
+				p = args[cur_arg + 1];
+				e = p;
+				while (*p != '\0') {
+					/* If no room avalaible, return error. */
+					if (opt->pref_net_nb > SRV_MAX_PREF_NET) {
+						Alert("parsing [%s:%d]: '%s' exceed %d networks.\n",
+						      file, linenum, args[cur_arg], SRV_MAX_PREF_NET);
+						err_code |= ERR_ALERT | ERR_FATAL;
+						goto out;
+					}
+					/* look for end or comma. */
+					while (*e != ',' && *e != '\0')
+						e++;
+					if (*e == ',') {
+						*e = '\0';
+						e++;
+					}
+					if (str2net(p, 0, &opt->pref_net[opt->pref_net_nb].addr.in4,
+					                  &opt->pref_net[opt->pref_net_nb].mask.in4)) {
+						/* Try to convert input string from ipv4 or ipv6 network. */
+						opt->pref_net[opt->pref_net_nb].family = AF_INET;
+					} else if (str62net(p, &opt->pref_net[opt->pref_net_nb].addr.in6,
+					                     &mask)) {
+						/* Try to convert input string from ipv6 network. */
+						len2mask6(mask, &opt->pref_net[opt->pref_net_nb].mask.in6);
+						opt->pref_net[opt->pref_net_nb].family = AF_INET6;
+					} else {
+						/* All network conversions fail, retrun error. */
+						Alert("parsing [%s:%d]: '%s': invalid network '%s'.\n",
+						      file, linenum, args[cur_arg], p);
+						err_code |= ERR_ALERT | ERR_FATAL;
+						goto out;
+					}
+					opt->pref_net_nb++;
+					p = e;
+				}
+
+				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",