mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-22 14:21:25 +02:00
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.
This commit is contained in:
parent
ada348459f
commit
ac88cfe452
@ -10595,6 +10595,18 @@ resolve-prefer <family>
|
|||||||
|
|
||||||
Example: server s1 app1.domain.com:80 resolvers mydns resolve-prefer ipv6
|
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>
|
resolvers <id>
|
||||||
Points to an existing "resolvers" section to resolve current server's
|
Points to an existing "resolvers" section to resolve current server's
|
||||||
hostname.
|
hostname.
|
||||||
|
@ -55,6 +55,11 @@
|
|||||||
#define DNS_FLAG_TRUNCATED 0x0200 /* mask for truncated flag */
|
#define DNS_FLAG_TRUNCATED 0x0200 /* mask for truncated flag */
|
||||||
#define DNS_FLAG_REPLYCODE 0x000F /* mask for reply code */
|
#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 */
|
/* DNS request or response header structure */
|
||||||
struct dns_header {
|
struct dns_header {
|
||||||
unsigned short id:16; /* identifier */
|
unsigned short id:16; /* identifier */
|
||||||
@ -142,6 +147,18 @@ struct dns_nameserver {
|
|||||||
|
|
||||||
struct dns_options {
|
struct dns_options {
|
||||||
int family_prio; /* which IP family should the resolver use when both are returned */
|
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. */
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -92,7 +92,6 @@ enum srv_admin {
|
|||||||
#define SRV_STATE_FILE_NB_FIELDS_VERSION_1 18
|
#define SRV_STATE_FILE_NB_FIELDS_VERSION_1 18
|
||||||
#define SRV_STATE_LINE_MAXLEN 512
|
#define SRV_STATE_LINE_MAXLEN 512
|
||||||
|
|
||||||
|
|
||||||
/* server flags */
|
/* server flags */
|
||||||
#define SRV_F_BACKUP 0x0001 /* this server is a backup server */
|
#define SRV_F_BACKUP 0x0001 /* this server is a backup server */
|
||||||
#define SRV_F_MAPPORTS 0x0002 /* this server uses mapped ports */
|
#define SRV_F_MAPPORTS 0x0002 /* this server uses mapped ports */
|
||||||
|
113
src/dns.c
113
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
|
* For both cases above, dns_validate_dns_response is required
|
||||||
* returns one of the DNS_UPD_* code
|
* 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,
|
int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
|
||||||
struct dns_resolution *resol, void *currentip,
|
struct dns_resolution *resol, void *currentip,
|
||||||
short currentip_sin_family,
|
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 dn_name_len;
|
||||||
int i, ancount, cnamelen, type, data_len, currentip_found;
|
int i, ancount, cnamelen, type, data_len, currentip_found;
|
||||||
unsigned char *reader, *cname, *ptr, *newip4, *newip6;
|
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;
|
family_priority = resol->opts->family_prio;
|
||||||
dn_name = resol->hostname_dn;
|
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 */
|
/* analyzing record content */
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DNS_RTYPE_A:
|
case DNS_RTYPE_A:
|
||||||
/* check if current reccord's IP is the same as server one's */
|
/* Store IPv4, only if some room is avalaible. */
|
||||||
if ((currentip_sin_family == AF_INET)
|
if (rec_nb < DNS_MAX_IP_REC) {
|
||||||
&& (*(uint32_t *)reader == *(uint32_t *)currentip)) {
|
rec[rec_nb].ip = reader;
|
||||||
currentip_found = 1;
|
rec[rec_nb].type = AF_INET;
|
||||||
newip4 = reader;
|
rec_nb++;
|
||||||
/* 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 */
|
|
||||||
}
|
}
|
||||||
else if (!newip4) {
|
|
||||||
newip4 = reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* move forward data_len for analyzing next record in the response */
|
/* move forward data_len for analyzing next record in the response */
|
||||||
reader += data_len;
|
reader += data_len;
|
||||||
break;
|
break;
|
||||||
@ -711,19 +712,12 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case DNS_RTYPE_AAAA:
|
case DNS_RTYPE_AAAA:
|
||||||
/* check if current record's IP is the same as server's one */
|
/* Store IPv6, only if some room is avalaible. */
|
||||||
if ((currentip_sin_family == AF_INET6) && (memcmp(reader, currentip, 16) == 0)) {
|
if (rec_nb < DNS_MAX_IP_REC) {
|
||||||
currentip_found = 1;
|
rec[rec_nb].ip = reader;
|
||||||
newip6 = reader;
|
rec[rec_nb].type = AF_INET6;
|
||||||
/* we can stop now if server's preference is IPv6 or is not
|
rec_nb++;
|
||||||
* set (which implies we prioritize IPv6 over IPv4 */
|
|
||||||
if (family_priority == AF_INET6)
|
|
||||||
return DNS_UPD_NO;
|
|
||||||
}
|
}
|
||||||
else if (!newip6) {
|
|
||||||
newip6 = reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* move forward data_len for analyzing next record in the response */
|
/* move forward data_len for analyzing next record in the response */
|
||||||
reader += data_len;
|
reader += data_len;
|
||||||
break;
|
break;
|
||||||
@ -735,6 +729,75 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
|
|||||||
} /* switch (record type) */
|
} /* switch (record type) */
|
||||||
} /* for i 0 to ancount */
|
} /* 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 */
|
/* only CNAMEs in the response, no IP found */
|
||||||
if (cname && !newip4 && !newip6) {
|
if (cname && !newip4 && !newip6) {
|
||||||
return DNS_UPD_CNAME;
|
return DNS_UPD_CNAME;
|
||||||
|
60
src/server.c
60
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;
|
newsrv->dns_opts.family_prio = curproxy->defsrv.dns_opts.family_prio;
|
||||||
if (newsrv->dns_opts.family_prio == AF_UNSPEC)
|
if (newsrv->dns_opts.family_prio == AF_UNSPEC)
|
||||||
newsrv->dns_opts.family_prio = AF_INET6;
|
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;
|
cur_arg = 3;
|
||||||
} else {
|
} else {
|
||||||
@ -1090,6 +1094,62 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
|
|||||||
}
|
}
|
||||||
cur_arg += 2;
|
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")) {
|
else if (!strcmp(args[cur_arg], "rise")) {
|
||||||
if (!*args[cur_arg + 1]) {
|
if (!*args[cur_arg + 1]) {
|
||||||
Alert("parsing [%s:%d]: '%s' expects an integer argument.\n",
|
Alert("parsing [%s:%d]: '%s' expects an integer argument.\n",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user