]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: dns: add a "resolve-net" option which allow to prefer an ip in a network
authorThierry Fournier <tfournier@arpalert.org>
Wed, 17 Feb 2016 21:05:30 +0000 (22:05 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 19 Feb 2016 13:37:49 +0000 (14:37 +0100)
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.

doc/configuration.txt
include/types/dns.h
include/types/server.h
src/dns.c
src/server.c

index 7f369da343a75a51fd4baaa600234eb113fd90d6..f55f68dd68ec9c886d07460571970a227dd428d1 100644 (file)
@@ -10595,6 +10595,18 @@ resolve-prefer <family>
 
   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.
index cf784cd1d6b8528fdad16f186311edb9576d7039..757eaaf28d9a90b0d8cd9a3fb7bcc1d3bf87ed06 100644 (file)
 #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_nameserver {
 
 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. */
 };
 
 /*
index c04af9c3e10ad414f6f8b2624baa5b93a8a1abf0..952671c8acf155088ff2880c52d85646b4c941fa 100644 (file)
@@ -92,7 +92,6 @@ enum srv_admin {
 #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 */
index f43a675832f406da564805dc6ac1fe914f68efa8..c854744df08fae606e1b3de74d78a01eea7bbdda 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -592,6 +592,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *
  * 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 dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
        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 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
                /* 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 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
                                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 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
                } /* 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;
index 84dad38e182b84484b6f95a4c6c98bb32dfbba8b..30722fcd20bf6884aedee4282e6a2bdd9610fcf9 100644 (file)
@@ -1020,6 +1020,10 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
                        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 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
                                }
                                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",