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