diff --git a/doc/configuration.txt b/doc/configuration.txt index 7f369da34..f55f68dd6 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -10595,6 +10595,18 @@ resolve-prefer Example: server s1 app1.domain.com:80 resolvers mydns resolve-prefer ipv6 +resolve-net [, 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 cf784cd1d..757eaaf28 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_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. */ }; /* diff --git a/include/types/server.h b/include/types/server.h index c04af9c3e..952671c8a 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -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 */ diff --git a/src/dns.c b/src/dns.c index f43a67583..c854744df 100644 --- 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; diff --git a/src/server.c b/src/server.c index 84dad38e1..30722fcd2 100644 --- a/src/server.c +++ b/src/server.c @@ -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",