Hi guys,
Following Baptiste's work on DNS, the attached patchset adds support for DNS
obsolescence, and SRV support.
DNS obsolescence means we cache DNS answers, and only consider the entries
are gone if we don't see for X seconds, X being defined in the config file
with the "hold obsolete" entry in the resolvers section, ie :
esolvers dns
nameserver pouet 8.8.8.8:53
hold valid 10s
hold obsolete 5s
This is done as we may get incomplete DNS answers for each request, so we
can't assume an entry is gone just because it was not in a DNS answer.
This also adds support for SRV records. To use them, simply use a SRV label
instead of a hostname on the server line, ie :
server s1 _http._tcp.example.com resolvers dns check
server s2 _http._tcp.example.com resolvers dns check
When this is done, haproxy will first resolve _http._tcp.example.com, and then
give the hostname (as well as port and weight) to each available server, that
will then do a regular DNS resolution to get the IP.
The SRV label is resolved periodically, any server that disappeares will be
removed, and any new server will be added, assuming there're free servers in
the haproxy config.
Any testing would be greatly appreciated.
Regards,
Olivier
From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Thu, 6 Jul 2017 18:46:47 +0200
Subject: [PATCH 1/4] MINOR: dns: Cache previous DNS answers.
As DNS servers may not return all IPs in one answer, we want to cache the
previous entries. Those entries are removed when considered obsolete, which
happens when the IP hasn't been returned by the DNS server for a time
defined in the "hold obsolete" parameter of the resolver section. The default
is 30s.
---
doc/configuration.txt | 7 +-
include/proto/server.h | 2 +-
include/types/dns.h | 9 +-
src/cfgparse.c | 5 +-
src/dns.c | 247 ++++++++++++++++++++++++++++---------------------
src/server.c | 28 ++++--
6 files changed, 175 insertions(+), 123 deletions(-)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index bfeb3ce0..f4674387 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11693,6 +11693,10 @@ For example, with 2 name servers configured in a resolvers section:
- first response is truncated and second one is a NX Domain, then HAProxy
stops resolution.
+As a DNS server may not answer all the IPs in one DNS request, haproxy keeps
+a cache of previous answers, an answer will be considered obsolete after
+"hold obsolete" seconds without the IP returned.
+
resolvers <resolvers id>
Creates a new name server list labelled <resolvers id>
@@ -11709,7 +11713,7 @@ hold <status> <period>
Defines <period> during which the last name resolution should be kept based
on last resolution <status>
<status> : last name resolution status. Acceptable values are "nx",
- "other", "refused", "timeout", "valid".
+ "other", "refused", "timeout", "valid", "obsolete".
<period> : interval between two successive name resolution when the last
answer was in <status>. It follows the HAProxy time format.
<period> is in milliseconds by default.
@@ -11756,6 +11760,7 @@ timeout <event> <time>
hold nx 30s
hold timeout 30s
hold valid 10s
+ hold obsolete 30s
6. HTTP header manipulation
diff --git a/include/proto/server.h b/include/proto/server.h
index 43e4e425..c4f8e1d5 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -52,7 +52,7 @@ int srv_init_addr(void);
struct server *cli_find_server(struct appctx *appctx, char *arg);
/* functions related to server name resolution */
-int snr_update_srv_status(struct server *s);
+int snr_update_srv_status(struct server *s, int has_no_ip);
int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
int snr_resolution_error_cb(struct dns_requester *requester, int error_code);
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family);
diff --git a/include/types/dns.h b/include/types/dns.h
index 7a19aa37..12c11552 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -113,7 +113,7 @@ struct dns_query_item {
/* NOTE: big endian structure */
struct dns_answer_item {
struct list list;
- char *name; /* answer name
+ char name[DNS_MAX_NAME_SIZE]; /* answer name
* For SRV type, name also includes service
* and protocol value */
int16_t type; /* question type */
@@ -124,7 +124,8 @@ struct dns_answer_item {
int16_t port; /* SRV type port */
int16_t data_len; /* number of bytes in target below */
struct sockaddr address; /* IPv4 or IPv6, network format */
- char *target; /* Response data: SRV or CNAME type target */
+ char target[DNS_MAX_NAME_SIZE]; /* Response data: SRV or CNAME type target */
+ time_t last_seen; /* When was the answer was last seen */
};
struct dns_response_packet {
@@ -158,6 +159,7 @@ struct dns_resolvers {
int timeout; /* no answer was delivered */
int refused; /* dns server refused to answer */
int other; /* other dns response errors */
+ int obsolete; /* an answer hasn't been seen */
} hold;
struct task *t; /* timeout management */
int resolution_pool_size; /* size of the resolution pool associated to this resolvers section */
@@ -252,8 +254,6 @@ struct dns_resolution {
unsigned long long revision; /* updated for each update */
struct dns_response_packet response; /* structure hosting the DNS response */
struct dns_query_item response_query_records[DNS_MAX_QUERY_RECORDS]; /* <response> query records */
- struct dns_answer_item response_answer_records[DNS_MAX_ANSWER_RECORDS]; /* <response> answer records */
- struct chunk response_buffer; /* buffer used as a data store for <response> above TODO: optimize the size (might be smaller) */
};
/*
@@ -315,6 +315,7 @@ enum {
DNS_UPD_CNAME, /* CNAME without any IP provided in the response */
DNS_UPD_NAME_ERROR, /* name in the response did not match the query */
DNS_UPD_NO_IP_FOUND, /* no IP could be found in the response */
+ DNS_UPD_OBSOLETE_IP, /* The server IP was obsolete, and no other IP was found */
};
#endif /* _TYPES_DNS_H */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 8b6aec6a..e29ae53d 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2167,6 +2167,7 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
curr_resolvers->hold.other = 30000;
curr_resolvers->hold.refused = 30000;
curr_resolvers->hold.timeout = 30000;
+ curr_resolvers->hold.obsolete = 30000;
/* default hold period for valid is 10s */
curr_resolvers->hold.valid = 10000;
curr_resolvers->timeout.retry = 1000;
@@ -2280,8 +2281,10 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
curr_resolvers->hold.timeout = time;
else if (strcmp(args[1], "valid") == 0)
curr_resolvers->hold.valid = time;
+ else if (strcmp(args[1], "obsolete") == 0)
+ curr_resolvers->hold.obsolete = time;
else {
- Alert("parsing [%s:%d] : '%s' unknown <event>: '%s', expects either 'nx', 'timeout', 'valid', or 'other'.\n",
+ Alert("parsing [%s:%d] : '%s' unknown <event>: '%s', expects either 'nx', 'timeout', 'valid', 'obsolete' or 'other'.\n",
file, linenum, args[0], args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
diff --git a/src/dns.c b/src/dns.c
index 221f8709..0ce63c91 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -51,6 +51,8 @@ static int64_t dns_query_id_seed; /* random seed */
static struct lru64_head *dns_lru_tree;
static int dns_cache_size = 1024; /* arbitrary DNS cache size */
+static struct pool_head *dns_answer_item_pool;
+
/* proto_udp callback functions for a DNS resolution */
struct dgram_data_cb resolve_dgram_cb = {
.recv = dns_resolve_recv,
@@ -169,6 +171,7 @@ int dns_trigger_resolution(struct dns_resolution *resolution)
}
requester->requester_cb(requester, NULL);
+ resolvers = NULL;
}
else {
LIST_DEL(&requester->list);
@@ -306,6 +309,12 @@ void dns_reset_resolution(struct dns_resolution *resolution)
resolution->qid.key = 0;
}
+static inline void free_dns_answer_item(struct dns_answer_item *item)
+{
+ pool_free2(dns_answer_item_pool, item);
+}
+
+
/*
* function called when a network IO is generated on a name server socket for an incoming packet
* It performs the following actions:
@@ -327,6 +336,7 @@ void dns_resolve_recv(struct dgram_conn *dgram)
struct eb32_node *eb;
struct lru64 *lru = NULL;
struct dns_requester *requester = NULL, *tmprequester = NULL;
+ struct dns_answer_item *item1, *item2 = NULL;
fd = dgram->t.sock.fd;
@@ -468,6 +478,15 @@ void dns_resolve_recv(struct dgram_conn *dgram)
break;
}
+ /* Check for any obsolete record */
+ list_for_each_entry_safe(item1, item2, &resolution->response.answer_list,
+ list) {
+ if (item1->last_seen + nameserver->resolvers->hold.obsolete / 1000 < now.tv_sec) {
+ LIST_DEL(&item1->list);
+ free_dns_answer_item(item1);
+ }
+ }
+
/* some error codes trigger a re-send of the query, but switching the
* query type.
* This is the case for the following error codes:
@@ -885,13 +904,13 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
{
unsigned char *reader;
char *previous_dname, tmpname[DNS_MAX_NAME_SIZE];
- int len, flags, offset, ret;
- int dns_query_record_id, dns_answer_record_id;
+ int len, flags, offset;
+ int dns_query_record_id;
int nb_saved_records;
struct dns_query_item *dns_query;
- struct dns_answer_item *dns_answer_record;
+ struct dns_answer_item *dns_answer_record, *tmp_record;
struct dns_response_packet *dns_p;
- struct chunk *dns_response_buffer;
+ int found = 0;
reader = resp;
len = 0;
@@ -899,9 +918,6 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
/* initialization of response buffer and structure */
dns_p = &resolution->response;
- dns_response_buffer = &resolution->response_buffer;
- memset(dns_p, '\0', sizeof(struct dns_response_packet));
- chunk_reset(dns_response_buffer);
/* query id */
if (reader + 2 >= bufend)
@@ -1011,30 +1027,29 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
}
/* now parsing response records */
- LIST_INIT(&dns_p->answer_list);
nb_saved_records = 0;
- for (dns_answer_record_id = 0; dns_answer_record_id < dns_p->header.ancount; dns_answer_record_id++) {
+ for (int i = 0; i < dns_p->header.ancount; i++) {
if (reader >= bufend)
return DNS_RESP_INVALID;
- /* pull next response record from the list, if still one available, then add it
- * to the record list */
- if (dns_answer_record_id > DNS_MAX_ANSWER_RECORDS)
- return DNS_RESP_INVALID;
- dns_answer_record = &resolution->response_answer_records[dns_answer_record_id];
- LIST_ADDQ(&dns_p->answer_list, &dns_answer_record->list);
+ dns_answer_record = pool_alloc2(dns_answer_item_pool);
+ if (dns_answer_record == NULL)
+ return (DNS_RESP_INVALID);
offset = 0;
len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
- if (len == 0)
+ if (len == 0) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
/* check if the current record dname is valid.
* previous_dname points either to queried dname or last CNAME target
*/
if (memcmp(previous_dname, tmpname, len) != 0) {
- if (dns_answer_record_id == 0) {
+ free_dns_answer_item(dns_answer_record);
+ if (i == 0) {
/* first record, means a mismatch issue between queried dname
* and dname found in the first record */
return DNS_RESP_INVALID;
@@ -1046,43 +1061,50 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
}
- dns_answer_record->name = chunk_newstr(dns_response_buffer);
- if (dns_answer_record->name == NULL)
- return DNS_RESP_INVALID;
-
- ret = chunk_strncat(dns_response_buffer, tmpname, len);
- if (ret == 0)
- return DNS_RESP_INVALID;
+ memcpy(dns_answer_record->name, tmpname, len);
+ dns_answer_record->name[len] = 0;
reader += offset;
- if (reader >= bufend)
+ if (reader >= bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
- if (reader >= bufend)
+ if (reader >= bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
/* 2 bytes for record type (A, AAAA, CNAME, etc...) */
- if (reader + 2 > bufend)
+ if (reader + 2 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->type = reader[0] * 256 + reader[1];
reader += 2;
/* 2 bytes for class (2) */
- if (reader + 2 > bufend)
+ if (reader + 2 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->class = reader[0] * 256 + reader[1];
reader += 2;
/* 4 bytes for ttl (4) */
- if (reader + 4 > bufend)
+ if (reader + 4 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536
+ reader[2] * 256 + reader[3];
reader += 4;
/* now reading data len */
- if (reader + 2 > bufend)
+ if (reader + 2 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->data_len = reader[0] * 256 + reader[1];
/* move forward 2 bytes for data len */
@@ -1092,8 +1114,10 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
switch (dns_answer_record->type) {
case DNS_RTYPE_A:
/* ipv4 is stored on 4 bytes */
- if (dns_answer_record->data_len != 4)
+ if (dns_answer_record->data_len != 4) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->address.sa_family = AF_INET;
memcpy(&(((struct sockaddr_in *)&dns_answer_record->address)->sin_addr),
reader, dns_answer_record->data_len);
@@ -1107,22 +1131,21 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
* + 1 because dns_answer_record_id starts at 0 while number of answers
* is an integer and starts at 1.
*/
- if (dns_answer_record_id + 1 == dns_p->header.ancount)
+ if (i + 1 == dns_p->header.ancount) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_CNAME_ERROR;
+ }
offset = 0;
len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
- if (len == 0)
- return DNS_RESP_INVALID;
-
- dns_answer_record->target = chunk_newstr(dns_response_buffer);
- if (dns_answer_record->target == NULL)
+ if (len == 0) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
- ret = chunk_strncat(dns_response_buffer, tmpname, len);
- if (ret == 0)
- return DNS_RESP_INVALID;
+ memcpy(dns_answer_record->target, tmpname, len);
+ dns_answer_record->target[len] = 0;
previous_dname = dns_answer_record->target;
@@ -1130,8 +1153,10 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
case DNS_RTYPE_AAAA:
/* ipv6 is stored on 16 bytes */
- if (dns_answer_record->data_len != 16)
+ if (dns_answer_record->data_len != 16) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->address.sa_family = AF_INET6;
memcpy(&(((struct sockaddr_in6 *)&dns_answer_record->address)->sin6_addr),
reader, dns_answer_record->data_len);
@@ -1144,12 +1169,39 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
/* move forward dns_answer_record->data_len for analyzing next record in the response */
reader += dns_answer_record->data_len;
+
+ /* Lookup to see if we already had this entry */
+
+ list_for_each_entry(tmp_record, &dns_p->answer_list, list) {
+ if (tmp_record->type != dns_answer_record->type)
+ continue;
+ switch (tmp_record->type) {
+ case DNS_RTYPE_A:
+ if (!memcmp(&((struct sockaddr_in *)&dns_answer_record->address)->sin_addr,
+ &((struct sockaddr_in *)&tmp_record->address)->sin_addr, sizeof(in_addr_t)))
+ found = 1;
+ break;
+ case DNS_RTYPE_AAAA:
+ if (!memcmp(&((struct sockaddr_in6 *)&dns_answer_record->address)->sin6_addr,
+ &((struct sockaddr_in6 *)&tmp_record->address)->sin6_addr, sizeof(struct in6_addr)))
+ found = 1;
+ break;
+ default:
+ break;
+ }
+ if (found == 1)
+ break;
+ }
+ if (found == 1) {
+ tmp_record->last_seen = now.tv_sec;
+ free_dns_answer_item(dns_answer_record);
+ } else {
+ dns_answer_record->last_seen = now.tv_sec;
+ LIST_ADDQ(&dns_p->answer_list, &dns_answer_record->list);
+ }
+
} /* for i 0 to ancount */
- /* let's add a last \0 to close our last string */
- ret = chunk_strncat(dns_response_buffer, "\0", 1);
- if (ret == 0)
- return DNS_RESP_INVALID;
/* save the number of records we really own */
dns_p->header.ancount = nb_saved_records;
@@ -1175,51 +1227,17 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
{
struct dns_answer_item *record;
int family_priority;
- int i, currentip_found;
+ int currentip_found;
unsigned char *newip4, *newip6;
- struct {
- void *ip;
- unsigned char type;
- } rec[DNS_MAX_IP_REC];
int currentip_sel;
int j;
- int rec_nb = 0;
int score, max_score;
family_priority = dns_opts->family_prio;
*newip = newip4 = newip6 = NULL;
currentip_found = 0;
*newip_sin_family = AF_UNSPEC;
-
- /* now parsing response records */
- list_for_each_entry(record, &dns_p->answer_list, list) {
- /* analyzing record content */
- switch (record->type) {
- case DNS_RTYPE_A:
- /* Store IPv4, only if some room is avalaible. */
- if (rec_nb < DNS_MAX_IP_REC) {
- rec[rec_nb].ip = &(((struct sockaddr_in *)&record->address)->sin_addr);
- rec[rec_nb].type = AF_INET;
- rec_nb++;
- }
- break;
-
- /* we're looking for IPs only. CNAME validation is done when
- * parsing the response buffer for the first time */
- case DNS_RTYPE_CNAME:
- break;
-
- case DNS_RTYPE_AAAA:
- /* Store IPv6, only if some room is avalaible. */
- if (rec_nb < DNS_MAX_IP_REC) {
- rec[rec_nb].ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr);
- rec[rec_nb].type = AF_INET6;
- rec_nb++;
- }
- break;
-
- } /* switch (record type) */
- } /* list for each record entries */
+ max_score = -1;
/* Select an IP regarding configuration preference.
* Top priority is the prefered network ip version,
@@ -1234,29 +1252,38 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
* 1 - current ip.
* The result with the biggest score is returned.
*/
- max_score = -1;
- for (i = 0; i < rec_nb; i++) {
- int record_ip_already_affected = 0;
+ list_for_each_entry(record, &dns_p->answer_list, list) {
+ void *ip;
+ unsigned char ip_type;
+
+ if (record->type == DNS_RTYPE_A) {
+ ip = &(((struct sockaddr_in *)&record->address)->sin_addr);
+ ip_type = AF_INET;
+ } else if (record->type == DNS_RTYPE_AAAA) {
+ ip_type = AF_INET6;
+ ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr);
+ } else
+ continue;
score = 0;
/* Check for prefered ip protocol. */
- if (rec.type == family_priority)
+ if (ip_type == family_priority)
score += 8;
/* Check for prefered network. */
for (j = 0; j < dns_opts->pref_net_nb; j++) {
/* Compare only the same adresses class. */
- if (dns_opts->pref_net[j].family != rec.type)
+ if (dns_opts->pref_net[j].family != ip_type)
continue;
- if ((rec.type == AF_INET &&
- in_net_ipv4(rec.ip,
+ if ((ip_type == AF_INET &&
+ in_net_ipv4(ip,
&dns_opts->pref_net[j].mask.in4,
&dns_opts->pref_net[j].addr.in4)) ||
- (rec.type == AF_INET6 &&
- in_net_ipv6(rec.ip,
+ (ip_type == AF_INET6 &&
+ in_net_ipv6(ip,
&dns_opts->pref_net[j].mask.in6,
&dns_opts->pref_net[j].addr.in6))) {
score += 4;
@@ -1268,18 +1295,17 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
* If yes, the score should be incremented by 2.
*/
if (owner) {
- if (snr_check_ip_callback(owner, rec.ip, &rec.type))
- record_ip_already_affected = 1;
+ if (snr_check_ip_callback(owner, ip, &ip_type))
+ {
+ continue;
+ }
}
- if (record_ip_already_affected == 0)
- score += 2;
-
/* Check for current ip matching. */
- if (rec.type == currentip_sin_family &&
+ if (ip_type == currentip_sin_family &&
((currentip_sin_family == AF_INET &&
- memcmp(rec.ip, currentip, 4) == 0) ||
+ memcmp(ip, currentip, 4) == 0) ||
(currentip_sin_family == AF_INET6 &&
- memcmp(rec.ip, currentip, 16) == 0))) {
+ memcmp(ip, currentip, 16) == 0))) {
score += 1;
currentip_sel = 1;
} else
@@ -1292,21 +1318,22 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
* the ip selected is the current ip.
*/
if (score > max_score) {
- if (rec.type == AF_INET)
- newip4 = rec.ip;
+ if (ip_type == AF_INET)
+ newip4 = ip;
else
- newip6 = rec.ip;
+ newip6 = ip;
currentip_found = currentip_sel;
if (score == 15)
return DNS_UPD_NO;
max_score = score;
}
- }
+
+
+ } /* list for each record entries */
/* no IP found in the response */
- if (!newip4 && !newip6) {
+ if (!newip4 && !newip6)
return DNS_UPD_NO_IP_FOUND;
- }
/* case when the caller looks first for an IPv4 address */
if (family_priority == AF_INET) {
@@ -1410,6 +1437,14 @@ int dns_init_resolvers(int close_socket)
/* give a first random value to our dns query_id seed */
dns_query_id_seed = random();
+ /* Initialize the answer items pool */
+ dns_answer_item_pool = create_pool("dns_answer_item",
+ sizeof(struct dns_answer_item), MEM_F_SHARED);
+ if (dns_answer_item_pool == NULL) {
+ Alert("Failed to create the dns answer items pool");
+ return 0;
+ }
+
/* run through the resolvers section list */
list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
/* create the task associated to the resolvers section */
@@ -1442,6 +1477,7 @@ int dns_init_resolvers(int close_socket)
/* allocate memory only if it has not already been allocated
* by a previous call to this function */
+
if (!dgram && (dgram = calloc(1, sizeof(*dgram))) == NULL) {
Alert("Starting [%s/%s] nameserver: out of memory.\n", curr_resolvers->id,
curnameserver->id);
@@ -2081,6 +2117,7 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
tmpresolution->status = RSLV_STATUS_NONE;
tmpresolution->step = RSLV_STEP_NONE;
tmpresolution->revision = 1;
+ LIST_INIT(&tmpresolution->response.answer_list);
}
/* add the requester to the resolution's wait queue */
@@ -2170,7 +2207,6 @@ struct dns_resolution *dns_alloc_resolution(void)
return NULL;
}
- chunk_init(&resolution->response_buffer, buffer, global.tune.bufsize);
LIST_INIT(&resolution->requester.wait);
LIST_INIT(&resolution->requester.curr);
@@ -2180,7 +2216,6 @@ struct dns_resolution *dns_alloc_resolution(void)
/* This function free the memory allocated to a DNS resolution */
void dns_free_resolution(struct dns_resolution *resolution)
{
- chunk_destroy(&resolution->response_buffer);
free(resolution);
return;
diff --git a/src/server.c b/src/server.c
index f457d555..ef62a63f 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3756,7 +3756,7 @@ out:
* 0 if server status is updated
* 1 if server status has not changed
*/
-int snr_update_srv_status(struct server *s)
+int snr_update_srv_status(struct server *s, int has_no_ip)
{
struct dns_resolution *resolution = s->resolution;
struct dns_resolvers *resolvers = s->resolvers;
@@ -3772,6 +3772,13 @@ int snr_update_srv_status(struct server *s)
* resume health checks
* server will be turned back on if health check is safe
*/
+ if (has_no_ip) {
+ if (s->admin & SRV_ADMF_RMAINT)
+ return 1;
+ srv_set_admin_flag(s, SRV_ADMF_RMAINT,
+ "No IP for server ");
+ return (0);
+ }
if (!(s->admin & SRV_ADMF_RMAINT))
return 1;
srv_clr_admin_flag(s, SRV_ADMF_RMAINT);
@@ -3847,6 +3854,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
short server_sin_family, firstip_sin_family;
int ret;
struct chunk *chk = get_trash_chunk();
+ int has_no_ip = 0;
s = objt_server(requester->requester);
if (!s)
@@ -3893,10 +3901,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
goto invalid;
case DNS_UPD_NO_IP_FOUND:
- if (resolution->status != RSLV_STATUS_OTHER) {
- resolution->status = RSLV_STATUS_OTHER;
- resolution->last_status_change = now_ms;
- }
+ has_no_ip = 1;
goto update_status;
case DNS_UPD_NAME_ERROR:
@@ -3927,7 +3932,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
update_server_addr(s, firstip, firstip_sin_family, (char *)chk->str);
update_status:
- snr_update_srv_status(s);
+ snr_update_srv_status(s, has_no_ip);
return 1;
invalid:
@@ -3936,7 +3941,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
if (resolution->nb_responses >= nameserver->resolvers->count_nameservers)
goto update_status;
- snr_update_srv_status(s);
+ snr_update_srv_status(s, has_no_ip);
return 0;
}
@@ -3964,16 +3969,15 @@ int snr_resolution_error_cb(struct dns_requester *requester, int error_code)
return 1;
}
- snr_update_srv_status(s);
+ snr_update_srv_status(s, 0);
return 1;
}
/*
* Function to check if <ip> is already affected to a server in the backend
- * which owns <srv>.
+ * which owns <srv> and is up.
* It returns a pointer to the first server found or NULL if <ip> is not yet
* assigned.
- * NOTE: <ip> and <ip_family> are provided by a 'struct rec' available in dns.c.
*/
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family)
{
@@ -3998,6 +4002,10 @@ struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char
(srv->puid == tmpsrv->puid))
continue;
+ /* If the server has been taken down, don't consider it */
+ if (tmpsrv->admin & SRV_ADMF_RMAINT)
+ continue;
+
/* At this point, we have 2 different servers using the same DNS hostname
* for their respective resolution.
*/
--
2.13.3
From 477e84d747896aaeeacd40525af5458630bb52dc Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 4 Aug 2017 18:31:56 +0200
Subject: [PATCH 2/4] MINOR: obj: Add a new type of object, OBJ_TYPE_SRVRQ.
dns_srvrq will be objects used for dealing with SRV records.
---
include/proto/obj_type.h | 12 ++++++++++++
include/types/obj_type.h | 1 +
2 files changed, 13 insertions(+)
diff --git a/include/proto/obj_type.h b/include/proto/obj_type.h
index b64244fc..60265b5e 100644
--- a/include/proto/obj_type.h
+++ b/include/proto/obj_type.h
@@ -131,6 +131,18 @@ static inline struct connection *objt_conn(enum obj_type *t)
return __objt_conn(t);
}
+static inline struct dns_srvrq *__objt_dns_srvrq(enum obj_type *t)
+{
+ return container_of(t, struct dns_srvrq, obj_type);
+}
+
+static inline struct dns_srvrq *objt_dns_srvrq(enum obj_type *t)
+{
+ if (!t || *t != OBJ_TYPE_SRVRQ)
+ return NULL;
+ return __objt_dns_srvrq(t);
+}
+
static inline void *obj_base_ptr(enum obj_type *t)
{
switch (obj_type(t)) {
diff --git a/include/types/obj_type.h b/include/types/obj_type.h
index 83a3e782..a6310cfc 100644
--- a/include/types/obj_type.h
+++ b/include/types/obj_type.h
@@ -39,6 +39,7 @@ enum obj_type {
OBJ_TYPE_APPLET, /* object is a struct applet */
OBJ_TYPE_APPCTX, /* object is a struct appctx */
OBJ_TYPE_CONN, /* object is a struct connection */
+ OBJ_TYPE_SRVRQ, /* object is a struct dns_srvrq */
OBJ_TYPE_ENTRIES /* last one : number of entries */
} __attribute__((packed)) ;
--
2.13.3
From a2e3bba11a5c1a3c69e9f33c566d7c285c9121f2 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 4 Aug 2017 18:35:36 +0200
Subject: [PATCH 3/4] MINOR: dns: Handle SRV records.
Make it so for each server, instead of specifying a hostname, one can use
a SRV label.
When doing so, haproxy will first resolve the SRV label, then use the
resulting hostnames, as well as port and weight (priority is ignored right
now), to each server using the SRV label.
It is resolved periodically, and any server disappearing from the SRV records
will be removed, and any server appearing will be added, assuming there're
free servers in haproxy.
---
include/proto/dns.h | 1 +
include/proto/server.h | 1 +
include/types/dns.h | 16 +++
include/types/proxy.h | 1 +
include/types/server.h | 1 +
src/cfgparse.c | 22 +++-
src/dns.c | 313 ++++++++++++++++++++++++++++++++++++++++++-------
src/proxy.c | 1 +
src/server.c | 96 ++++++++++++---
9 files changed, 395 insertions(+), 57 deletions(-)
diff --git a/include/proto/dns.h b/include/proto/dns.h
index 6675d50f..a84f07c4 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -27,6 +27,7 @@
char *dns_str_to_dn_label(const char *string, char *dn, int dn_len);
int dns_str_to_dn_label_len(const char *string);
+void dns_dn_label_to_str(char *dn, char *str, int dn_len);
int dns_hostname_validation(const char *string, char **err);
int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostname_dn_len, char *buf, int bufsize);
struct task *dns_process_resolve(struct task *t);
diff --git a/include/proto/server.h b/include/proto/server.h
index c4f8e1d5..d35a9c1c 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -53,6 +53,7 @@ struct server *cli_find_server(struct appctx *appctx, char *arg);
/* functions related to server name resolution */
int snr_update_srv_status(struct server *s, int has_no_ip);
+const char *update_server_fqdn(struct server *server, const char *fqdn, const char *updater);
int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
int snr_resolution_error_cb(struct dns_requester *requester, int error_code);
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family);
diff --git a/include/types/dns.h b/include/types/dns.h
index 12c11552..c371d5f5 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -63,6 +63,7 @@
#define DNS_RTYPE_A 1 /* IPv4 address */
#define DNS_RTYPE_CNAME 5 /* canonical name */
#define DNS_RTYPE_AAAA 28 /* IPv6 address */
+#define DNS_RTYPE_SRV 33 /* SRV record */
#define DNS_RTYPE_ANY 255 /* all records */
/* dns rcode values */
@@ -318,4 +319,19 @@ enum {
DNS_UPD_OBSOLETE_IP, /* The server IP was obsolete, and no other IP was found */
};
+struct dns_srvrq {
+ enum obj_type obj_type; /* object type == OBJ_TYPE_SRVRQ */
+ struct dns_resolvers *resolvers; /* pointer to the resolvers structure used for this server template */
+
+ struct dns_resolution *resolution; /* server name resolution */
+
+ struct proxy *proxy; /* associated proxy */
+ char *name;
+ char *hostname_dn; /* server hostname in Domain Name format */
+ int hostname_dn_len; /* string length of the server hostname in Domain Name format */
+ struct dns_requester *dns_requester; /* used to link to its DNS resolution */
+ int inter; /* time in ms */
+ struct list list; /* Next SRV RQ for the same proxy */
+};
+
#endif /* _TYPES_DNS_H */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 5306a3b6..a4f3b9e5 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -438,6 +438,7 @@ struct proxy {
* name is used
*/
struct list filter_configs; /* list of the filters that are declared on this proxy */
+ struct list srvrq_list; /* List of SRV requests associated with this proxy */
};
struct switching_rule {
diff --git a/include/types/server.h b/include/types/server.h
index 724d4965..77263dbb 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -298,6 +298,7 @@ struct server {
int nb_low;
int nb_high;
} tmpl_info;
+ struct dns_srvrq *srvrq; /* Pointer representing the DNS SRV requeest, if any */
};
/* Descriptor for a "server" keyword. The ->parse() function returns 0 in case of
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e29ae53d..437d9ef2 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -8549,7 +8549,20 @@ out_uri_auth_compat:
newsrv->id, newsrv->resolvers_id);
cfgerr++;
} else {
- if (newsrv->hostname_dn) {
+ if (newsrv->srvrq) {
+ if (!newsrv->srvrq->resolvers) {
+ newsrv->srvrq->resolvers = curr_resolvers;
+ if (dns_link_resolution(newsrv->srvrq,
+ OBJ_TYPE_SRVRQ, NULL) != 0) {
+ Alert("config : %s '%s', server '%s': unable to set DNS resolution\n",
+ proxy_type_str(curproxy), curproxy->id,
+ newsrv->id);
+ cfgerr++;
+ }
+ }
+
+ }
+ if (newsrv->srvrq || newsrv->hostname_dn) {
newsrv->resolvers = curr_resolvers;
if (dns_link_resolution(newsrv, OBJ_TYPE_SERVER, NULL) != 0) {
Alert("config : %s '%s', server '%s': unable to set DNS resolution\n",
@@ -8575,6 +8588,13 @@ out_uri_auth_compat:
next_srv:
newsrv = newsrv->next;
}
+ {
+ struct dns_srvrq *srvrq;
+
+ list_for_each_entry(srvrq, &curproxy->srvrq_list, list) {
+ dns_link_resolution(srvrq, OBJ_TYPE_SRVRQ, NULL);
+ }
+ }
/*
* Try to generate dynamic cookies for servers now.
diff --git a/src/dns.c b/src/dns.c
index 0ce63c91..2931752a 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -153,6 +153,10 @@ int dns_trigger_resolution(struct dns_resolution *resolution)
inter = objt_server(requester->requester)->check.inter;
resolvers = objt_server(requester->requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ inter = objt_dns_srvrq(requester->requester)->inter;
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return -1;
@@ -212,12 +216,26 @@ dns_run_resolution(struct dns_requester *requester)
proxy = objt_server(requester->requester)->proxy;
query_type = requester->prefered_query_type;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolution = objt_dns_srvrq(requester->requester)->resolution;
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ proxy = objt_dns_srvrq(requester->requester)->proxy;
+ query_type = DNS_RTYPE_SRV;
+ break;
case OBJ_TYPE_NONE:
default:
return -1;
}
/*
+ * Avoid sending requests for resolutions that don't yet have
+ * an hostname, ie resolutions linked to servers that do not yet
+ * have an fqdn
+ */
+ if (!resolution->hostname_dn)
+ return 0;
+
+ /*
* check if a resolution has already been started for this server
* return directly to avoid resolution pill up.
*/
@@ -352,6 +370,7 @@ void dns_resolve_recv(struct dgram_conn *dgram)
/* process all pending input messages */
while (1) {
+ int removed_reso = 0;
/* read message received */
memset(buf, '\0', DNS_MAX_UDP_MESSAGE + 1);
if ((buflen = recv(fd, (char*)buf , DNS_MAX_UDP_MESSAGE, 0)) < 0) {
@@ -478,14 +497,104 @@ void dns_resolve_recv(struct dgram_conn *dgram)
break;
}
- /* Check for any obsolete record */
+ /* Check for any obsolete record, also identify any SRV request, and try to find a corresponding server */
list_for_each_entry_safe(item1, item2, &resolution->response.answer_list,
list) {
if (item1->last_seen + nameserver->resolvers->hold.obsolete / 1000 < now.tv_sec) {
LIST_DEL(&item1->list);
+ if (item1->type == DNS_RTYPE_SRV && !LIST_ISEMPTY(&resolution->requester.curr)) {
+ struct dns_srvrq *srvrq;
+
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+
+ srvrq = objt_dns_srvrq(requester->requester);
+ /* We're removing an obsolete entry, remove any associated server */
+ if (srvrq) {
+ struct server *srv;
+
+ for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) {
+ if (srv->srvrq == srvrq &&
+ item1->data_len ==
+ srv->hostname_dn_len &&
+ !memcmp(srv->hostname_dn, item1->target, item1->data_len) &&
+ srv->svc_port == item1->port) {
+ snr_update_srv_status(srv, 1);
+ free(srv->hostname);
+ srv->hostname = NULL;
+ srv->hostname_dn_len = 0;
+ free(srv->hostname_dn);
+ srv->hostname_dn = NULL;
+ dns_resolution_free(srv->resolvers, srv->resolution);
+ srv->resolution = dns_resolution_list_get(srv->resolvers, NULL, srv->dns_requester->prefered_query_type);
+ if (resolution == srv->resolution)
+ removed_reso = 1;
+ }
+ }
+ }
+ }
free_dns_answer_item(item1);
+ continue;
+ }
+ if (item1->type == DNS_RTYPE_SRV) {
+ struct server *srv;
+ struct dns_srvrq *srvrq;
+
+ if (LIST_ISEMPTY(&resolution->requester.curr))
+ continue;
+
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ srvrq = objt_dns_srvrq(requester->requester);
+ if (!srvrq)
+ continue;
+ /* Check if a server already uses that hostname */
+ for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) {
+ if (srv->srvrq == srvrq &&
+ item1->data_len == srv->hostname_dn_len &&
+ !memcmp(srv->hostname_dn, item1->target, item1->data_len) &&
+ srv->svc_port == item1->port) {
+ if (srv->uweight != item1->weight) {
+ char weight[9];
+
+ snprintf(weight, sizeof(weight),
+ "%d", item1->weight);
+ server_parse_weight_change_request(srv, weight);
+
+ }
+
+ break;
+ }
+ }
+ /* If not, try to find a server that is down */
+ if (!srv) {
+ for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) {
+
+ if (srv->srvrq == srvrq &&
+ !srv->hostname_dn)
+ break;
+ }
+ if (srv) {
+ char weight[9];
+
+ char hostname[DNS_MAX_NAME_SIZE];
+
+ if (item1->data_len > DNS_MAX_NAME_SIZE)
+ continue;
+ dns_dn_label_to_str(item1->target, hostname, item1->data_len);
+ update_server_fqdn(srv, hostname, "SRV record");
+ srv->svc_port = item1->port;
+ srv->flags &= ~SRV_F_MAPPORTS;
+ if ((srv->check.state & CHK_ST_CONFIGURED) && !(srv->flags & SRV_F_CHECKPORT))
+ srv->check.port = item1->port;
+ snprintf(weight, sizeof(weight),
+ "%d", item1->weight);
+ server_parse_weight_change_request(srv, weight);
+ }
+
+ }
}
}
+ if (removed_reso)
+ goto next_packet;
/* some error codes trigger a re-send of the query, but switching the
* query type.
@@ -576,6 +685,8 @@ void dns_resolve_recv(struct dgram_conn *dgram)
* We can check only the first query of the list. We send one query at a time
* so we get one query in the response */
query = LIST_NEXT(&resolution->response.query_list, struct dns_query_item *, list);
+ if (!resolution->hostname_dn)
+ abort();
if (query && memcmp(query->name, resolution->hostname_dn, resolution->hostname_dn_len) != 0) {
nameserver->counters.other += 1;
/* now parse list of requesters currently waiting for this resolution */
@@ -706,6 +817,9 @@ int dns_send_query(struct dns_resolution *resolution)
case OBJ_TYPE_SERVER:
resolvers = objt_server(requester->requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return 0;
@@ -775,6 +889,9 @@ void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
case OBJ_TYPE_SERVER:
valid_period = objt_server(requester->requester)->check.inter;
break;
+ case OBJ_TYPE_SRVRQ:
+ valid_period = objt_dns_srvrq(requester->requester)->inter;
+ break;
case OBJ_TYPE_NONE:
default:
continue;
@@ -790,6 +907,9 @@ void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
case OBJ_TYPE_SERVER:
dns_trigger_resolution(objt_server(requester->requester)->resolution);
break;
+ case OBJ_TYPE_SRVRQ:
+ dns_trigger_resolution(objt_dns_srvrq(requester->requester)->resolution);
+ break;
case OBJ_TYPE_NONE:
default:
;;
@@ -1151,6 +1271,42 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
break;
+
+ case DNS_RTYPE_SRV:
+ /*
+ * Answer must contain :
+ * - 2 bytes for the priority
+ * - 2 bytes for the weight
+ * - 2 bytes for the port
+ * - the target hostname
+ */
+ if (dns_answer_record->data_len <= 6) {
+ free_dns_answer_item(dns_answer_record);
+ return DNS_RESP_INVALID;
+ }
+ memcpy(&dns_answer_record->priority,
+ reader + offset, sizeof(uint16_t));
+ dns_answer_record->priority = ntohs(dns_answer_record->priority);
+ reader += sizeof(uint16_t);
+ memcpy(&dns_answer_record->weight,
+ reader, sizeof(uint16_t));
+ dns_answer_record->weight = ntohs(dns_answer_record->weight);
+ reader += sizeof(uint16_t);
+ memcpy(&dns_answer_record->port,
+ reader, sizeof(uint16_t));
+ dns_answer_record->port = ntohs(dns_answer_record->port);
+ reader += sizeof(uint16_t);
+ offset = 0;
+ len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
+ if (len == 0) {
+ free_dns_answer_item(dns_answer_record);
+ return DNS_RESP_INVALID;
+ }
+ reader++;
+ dns_answer_record->data_len = len;
+ memcpy(dns_answer_record->target, tmpname, len);
+ dns_answer_record->target[len] = 0;
+ break;
case DNS_RTYPE_AAAA:
/* ipv6 is stored on 16 bytes */
if (dns_answer_record->data_len != 16) {
@@ -1172,6 +1328,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
/* Lookup to see if we already had this entry */
+ found = 0;
list_for_each_entry(tmp_record, &dns_p->answer_list, list) {
if (tmp_record->type != dns_answer_record->type)
continue;
@@ -1186,6 +1343,15 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
&((struct sockaddr_in6 *)&tmp_record->address)->sin6_addr, sizeof(struct in6_addr)))
found = 1;
break;
+ case DNS_RTYPE_SRV:
+ if (dns_answer_record->data_len == tmp_record->data_len &&
+ !memcmp(dns_answer_record->target,
+ tmp_record->target, dns_answer_record->data_len) &&
+ dns_answer_record->port == tmp_record->port) {
+ tmp_record->weight = dns_answer_record->weight;
+ found = 1;
+ }
+ break;
default:
break;
}
@@ -1635,6 +1801,27 @@ int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostnam
return ptr - buf;
}
+/* Turn a domain name label into a string */
+void dns_dn_label_to_str(char *dn, char *str, int dn_len)
+{
+ int remain_size = 0;
+
+ for (int i = 0; i < dn_len; i++) {
+ if (remain_size == 0) {
+ remain_size = dn;
+ if (i != 0) {
+ str[i - 1] = '.';
+
+ }
+ } else {
+ str[i - 1] = dn;
+ remain_size--;
+ }
+ }
+ str[dn_len - 1] = 0;
+
+}
+
/*
* turn a string into domain name label:
* www.haproxy.org into 3www7haproxy3org
@@ -1827,8 +2014,23 @@ struct task *dns_process_resolve(struct task *t)
switch (obj_type(requester->requester)) {
case OBJ_TYPE_SERVER:
dns_opts = &(objt_server(requester->requester)->dns_opts);
+ res_preferred_afinet = dns_opts->family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
+ res_preferred_afinet6 = dns_opts->family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
+
+ /* let's change the query type if needed */
+ if (res_preferred_afinet6) {
+ /* fallback from AAAA to A */
+ resolution->query_type = DNS_RTYPE_A;
+ }
+ else if (res_preferred_afinet) {
+ /* fallback from A to AAAA */
+ resolution->query_type = DNS_RTYPE_AAAA;
+ }
+
break;
+ case OBJ_TYPE_SRVRQ:
+ break;
case OBJ_TYPE_NONE:
default:
/* clean up resolution information and remove from the list */
@@ -1842,19 +2044,6 @@ struct task *dns_process_resolve(struct task *t)
goto out;
}
- res_preferred_afinet = dns_opts->family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
- res_preferred_afinet6 = dns_opts->family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
-
- /* let's change the query type if needed */
- if (res_preferred_afinet6) {
- /* fallback from AAAA to A */
- resolution->query_type = DNS_RTYPE_A;
- }
- else if (res_preferred_afinet) {
- /* fallback from A to AAAA */
- resolution->query_type = DNS_RTYPE_AAAA;
- }
-
/* resend the DNS query */
dns_send_query(resolution);
@@ -1966,6 +2155,9 @@ dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int val
case OBJ_TYPE_SERVER:
resolvers = objt_server(requester->requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return NULL;
@@ -2028,6 +2220,7 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
char *hostname_dn = NULL;
int new_resolution;
+
if (!resolution) {
tmprequester = calloc(1, sizeof(*tmprequester));
if (!tmprequester)
@@ -2047,6 +2240,11 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
}
break;
+ case OBJ_TYPE_SRVRQ:
+ tmprequester->requester = &((struct dns_srvrq *)requester)->obj_type;
+ hostname_dn = objt_dns_srvrq(requester)->hostname_dn;
+ resolvers = objt_dns_srvrq(requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
free(tmprequester);
@@ -2068,6 +2266,10 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
tmprequester = ((struct server *)requester)->dns_requester;
resolvers = ((struct server *)requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ tmprequester = objt_dns_srvrq(requester)->dns_requester;
+ resolvers = objt_dns_srvrq(requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return -1;
@@ -2102,6 +2304,23 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
objt_server(tmprequester->requester)->dns_requester = tmprequester;
}
break;
+ case OBJ_TYPE_SRVRQ:
+ /* some parameters should be set only if the resolution is brand new */
+ if (new_resolution) {
+ tmpresolution->query_type = DNS_RTYPE_SRV;
+ tmpresolution->hostname_dn = objt_dns_srvrq(tmprequester->requester)->hostname_dn;
+ tmpresolution->hostname_dn_len = objt_dns_srvrq(tmprequester->requester)->hostname_dn_len;
+ }
+
+ /* update requester as well, only if we just allocated it */
+ objt_dns_srvrq(tmprequester->requester)->resolution = tmpresolution;
+ if (!resolution) {
+ tmprequester->requester_cb = snr_resolution_cb;
+ tmprequester->requester_error_cb = snr_resolution_error_cb;
+ objt_dns_srvrq(tmprequester->requester)->dns_requester = tmprequester;
+ }
+ break;
+
case OBJ_TYPE_NONE:
default:
free(tmprequester);
@@ -2142,42 +2361,45 @@ struct dns_resolution *dns_resolution_list_get(struct dns_resolvers *resolvers,
struct dns_resolution *resolution, *tmpresolution;
struct dns_requester *requester;
- /* search for same hostname and query type in resolution.curr */
- list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.curr, list) {
- requester = NULL;
+ if (hostname_dn) {
+ /* search for same hostname and query type in resolution.curr */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.curr, list) {
+ requester = NULL;
- if (!LIST_ISEMPTY(&resolution->requester.wait))
- requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
- else if (!LIST_ISEMPTY(&resolution->requester.curr))
- requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ if (!LIST_ISEMPTY(&resolution->requester.wait))
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+ else if (!LIST_ISEMPTY(&resolution->requester.curr))
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
- if (!requester)
- continue;
+ if (!requester)
+ continue;
- if ((query_type == requester->prefered_query_type) &&
- (strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
- return resolution;
+ if ((query_type == requester->prefered_query_type) &&
+ (resolution->hostname_dn &&
+ strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
+ return resolution;
+ }
}
- }
- /* search for same hostname and query type in resolution.wait */
- list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.wait, list) {
- requester = NULL;
+ /* search for same hostname and query type in resolution.wait */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.wait, list) {
+ requester = NULL;
- if (!LIST_ISEMPTY(&resolution->requester.wait))
- requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
- else if (!LIST_ISEMPTY(&resolution->requester.curr))
- requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ if (!LIST_ISEMPTY(&resolution->requester.wait))
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+ else if (!LIST_ISEMPTY(&resolution->requester.curr))
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
- if (!requester)
- continue;
+ if (!requester)
+ continue;
- if ((query_type == requester->prefered_query_type) &&
- (strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
- return resolution;
+ if ((query_type == requester->prefered_query_type) &&
+ (resolution->hostname_dn &&
+ strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
+ return resolution;
+ }
}
}
-
/* take the first one (hopefully) from the pool */
list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.pool, list) {
if (LIST_ISEMPTY(&resolution->requester.wait)) {
@@ -2261,6 +2483,9 @@ void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dn
case OBJ_TYPE_SERVER:
hostname_dn = objt_server(requester->requester)->hostname_dn;
break;
+ case OBJ_TYPE_SRVRQ:
+ hostname_dn = objt_dns_srvrq(requester->requester)->hostname_dn;
+ break;
case OBJ_TYPE_NONE:
default:
hostname_dn = NULL;
@@ -2290,6 +2515,11 @@ void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dn
resolution->hostname_dn = objt_server(tmprequester->requester)->hostname_dn;
resolution->hostname_dn_len = objt_server(tmprequester->requester)->hostname_dn_len;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolution->hostname_dn = objt_dns_srvrq(tmprequester->requester)->hostname_dn;
+ resolution->hostname_dn_len = objt_dns_srvrq(tmprequester->requester)->hostname_dn_len;
+ break;
+
case OBJ_TYPE_NONE:
default:
;;
@@ -2302,6 +2532,9 @@ void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dn
case OBJ_TYPE_SERVER:
objt_server(requester->requester)->resolution = NULL;
break;
+ case OBJ_TYPE_SRVRQ:
+ objt_dns_srvrq(requester->requester)->resolution = NULL;
+ break;
case OBJ_TYPE_NONE:
default:
;;
diff --git a/src/proxy.c b/src/proxy.c
index 641d4fa1..bd2031ee 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -754,6 +754,7 @@ void init_new_proxy(struct proxy *p)
LIST_INIT(&p->conf.args.list);
LIST_INIT(&p->tcpcheck_rules);
LIST_INIT(&p->filter_configs);
+ LIST_INIT(&p->srvrq_list);
/* Timeouts are defined as -1 */
proxy_reset_timeouts(p);
diff --git a/src/server.c b/src/server.c
index ef62a63f..b341919c 100644
--- a/src/server.c
+++ b/src/server.c
@@ -2285,18 +2285,68 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
/* save hostname and create associated name resolution */
if (fqdn) {
- if (srv_prepare_for_resolution(newsrv, fqdn) == -1) {
- Alert("parsing [%s:%d] : Can't create DNS resolution for server '%s'\n",
- file, linenum, newsrv->id);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
+ if (fqdn[0] == '_') {
+ struct dns_srvrq *srvrq = NULL;
+ int found = 0;
+ /* SRV record */
+ /* Check if a SRV request already exists, and if not, create it */
+ list_for_each_entry(srvrq, &curproxy->srvrq_list, list) {
+ if (!strcmp(srvrq->name, fqdn)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0) {
+ int hostname_dn_len;
+
+ srvrq = calloc(1, sizeof(*srvrq));
+ if (!srvrq) {
+ Alert("Failed to allocate memory");
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ srvrq->obj_type = OBJ_TYPE_SRVRQ;
+ srvrq->proxy = proxy;
+ srvrq->name = strdup(fqdn);
+ srvrq->inter = 2000;
+ hostname_dn_len = dns_str_to_dn_label_len(fqdn);
+ if (hostname_dn_len == -1) {
+ Alert("Failed to parse domaine name '%s'", fqdn);
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ srvrq->hostname_dn = malloc(hostname_dn_len + 1);
+ srvrq->hostname_dn_len = hostname_dn_len;
+ if (!srvrq->hostname_dn) {
+ Alert("Failed to alloc memory");
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (!dns_str_to_dn_label(fqdn,
+ srvrq->hostname_dn,
+ hostname_dn_len + 1)) {
+ Alert("Failed to parse domain name '%s'", fqdn);
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ LIST_ADDQ(&proxy->srvrq_list, &srvrq->list);
+
+ }
+ newsrv->srvrq = srvrq;
+
+
+ } else if (srv_prepare_for_resolution(newsrv, fqdn) == -1) {
+ Alert("parsing [%s:%d] : Can't create DNS resolution for server '%s'\n",
+ file, linenum, newsrv->id);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
}
}
newsrv->addr = *sk;
newsrv->svc_port = port;
- if (!newsrv->hostname && !protocol_by_family(newsrv->addr.ss_family)) {
+ if (!newsrv->srvrq && !newsrv->hostname && !protocol_by_family(newsrv->addr.ss_family)) {
Alert("parsing [%s:%d] : Unknown protocol family %d '%s'\n",
file, linenum, newsrv->addr.ss_family, args[cur_arg]);
err_code |= ERR_ALERT | ERR_FATAL;
@@ -2528,6 +2578,8 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
goto out;
}
newsrv->check.inter = val;
+ if (newsrv->srvrq)
+ newsrv->srvrq->inter = val;
cur_arg += 2;
}
else if (!strcmp(args[cur_arg], "fastinter")) {
@@ -4043,6 +4095,7 @@ int srv_set_fqdn(struct server *srv, const char *hostname)
{
struct dns_resolution *resolution;
int hostname_dn_len;
+ int did_set_reso = 0;
/* run time DNS resolution was not active for this server
* and we can't enable it at run time for now.
@@ -4065,17 +4118,23 @@ int srv_set_fqdn(struct server *srv, const char *hostname)
return -1;
- /* get a resolution from the curr or wait queues, or a brand new one from the pool */
- resolution = dns_resolution_list_get(srv->resolvers, trash.str, srv->dns_requester->prefered_query_type);
- if (!resolution)
- return -1;
+ if (srv->resolution->hostname_dn) {
+ /* get a resolution from the curr or wait queues, or a brand new one from the pool */
+ resolution = dns_resolution_list_get(srv->resolvers, trash.str, srv->dns_requester->prefered_query_type);
+ if (!resolution)
+ return -1;
- /* in this case, the new hostanme is the same than the old one */
- if (srv->resolution == resolution)
- return 0;
+ /* in this case, the new hostanme is the same than the old one */
+ if (srv->resolution == resolution && srv->hostname)
+ return 0;
- /* first, we need to unlink our server from its current resolution */
- srv_free_from_resolution(srv);
+ /* first, we need to unlink our server from its current resolution */
+ srv_free_from_resolution(srv);
+ } else {
+ resolution = srv->resolution;
+ resolution->last_resolution = now_ms;
+ did_set_reso = 1;
+ }
/* now we update server's parameters */
free(srv->hostname);
@@ -4085,6 +4144,11 @@ int srv_set_fqdn(struct server *srv, const char *hostname)
srv->hostname_dn_len = hostname_dn_len;
if (!srv->hostname || !srv->hostname_dn)
return -1;
+ if (did_set_reso) {
+ resolution->query_type = srv->dns_requester->prefered_query_type;
+ resolution->hostname_dn = srv->hostname_dn;
+ resolution->hostname_dn_len = hostname_dn_len;
+ }
/* then we can link srv to its new resolution */
dns_link_resolution(srv, OBJ_TYPE_SERVER, resolution);
@@ -4223,7 +4287,7 @@ const char *update_server_fqdn(struct server *server, const char *fqdn, const ch
msg = get_trash_chunk();
chunk_reset(msg);
- if (!strcmp(fqdn, server->hostname)) {
+ if (server->hostname && !strcmp(fqdn, server->hostname)) {
chunk_appendf(msg, "no need to change the FDQN");
goto out;
}
--
2.13.3
From 899413f3a965feeaa96ffb5b284b504dbdb01fa1 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 4 Aug 2017 18:39:01 +0200
Subject: [PATCH 4/4] MINOR: check: Fix checks when using SRV records.
When started, a server may not yet have an associated protocol, so don't
bother trying to run the checks until it is there.
---
src/checks.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/checks.c b/src/checks.c
index 7938b873..fc92a243 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1557,7 +1557,7 @@ static int connect_conn_chk(struct task *t)
}
ret = SF_ERR_INTERNAL;
- if (proto->connect)
+ if (proto && proto->connect)
ret = proto->connect(conn, check->type, quickack ? 2 : 0);
if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
conn->send_proxy_ofs = 1;
--
2.13.3
Following Baptiste's work on DNS, the attached patchset adds support for DNS
obsolescence, and SRV support.
DNS obsolescence means we cache DNS answers, and only consider the entries
are gone if we don't see for X seconds, X being defined in the config file
with the "hold obsolete" entry in the resolvers section, ie :
esolvers dns
nameserver pouet 8.8.8.8:53
hold valid 10s
hold obsolete 5s
This is done as we may get incomplete DNS answers for each request, so we
can't assume an entry is gone just because it was not in a DNS answer.
This also adds support for SRV records. To use them, simply use a SRV label
instead of a hostname on the server line, ie :
server s1 _http._tcp.example.com resolvers dns check
server s2 _http._tcp.example.com resolvers dns check
When this is done, haproxy will first resolve _http._tcp.example.com, and then
give the hostname (as well as port and weight) to each available server, that
will then do a regular DNS resolution to get the IP.
The SRV label is resolved periodically, any server that disappeares will be
removed, and any new server will be added, assuming there're free servers in
the haproxy config.
Any testing would be greatly appreciated.
Regards,
Olivier
From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Thu, 6 Jul 2017 18:46:47 +0200
Subject: [PATCH 1/4] MINOR: dns: Cache previous DNS answers.
As DNS servers may not return all IPs in one answer, we want to cache the
previous entries. Those entries are removed when considered obsolete, which
happens when the IP hasn't been returned by the DNS server for a time
defined in the "hold obsolete" parameter of the resolver section. The default
is 30s.
---
doc/configuration.txt | 7 +-
include/proto/server.h | 2 +-
include/types/dns.h | 9 +-
src/cfgparse.c | 5 +-
src/dns.c | 247 ++++++++++++++++++++++++++++---------------------
src/server.c | 28 ++++--
6 files changed, 175 insertions(+), 123 deletions(-)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index bfeb3ce0..f4674387 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11693,6 +11693,10 @@ For example, with 2 name servers configured in a resolvers section:
- first response is truncated and second one is a NX Domain, then HAProxy
stops resolution.
+As a DNS server may not answer all the IPs in one DNS request, haproxy keeps
+a cache of previous answers, an answer will be considered obsolete after
+"hold obsolete" seconds without the IP returned.
+
resolvers <resolvers id>
Creates a new name server list labelled <resolvers id>
@@ -11709,7 +11713,7 @@ hold <status> <period>
Defines <period> during which the last name resolution should be kept based
on last resolution <status>
<status> : last name resolution status. Acceptable values are "nx",
- "other", "refused", "timeout", "valid".
+ "other", "refused", "timeout", "valid", "obsolete".
<period> : interval between two successive name resolution when the last
answer was in <status>. It follows the HAProxy time format.
<period> is in milliseconds by default.
@@ -11756,6 +11760,7 @@ timeout <event> <time>
hold nx 30s
hold timeout 30s
hold valid 10s
+ hold obsolete 30s
6. HTTP header manipulation
diff --git a/include/proto/server.h b/include/proto/server.h
index 43e4e425..c4f8e1d5 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -52,7 +52,7 @@ int srv_init_addr(void);
struct server *cli_find_server(struct appctx *appctx, char *arg);
/* functions related to server name resolution */
-int snr_update_srv_status(struct server *s);
+int snr_update_srv_status(struct server *s, int has_no_ip);
int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
int snr_resolution_error_cb(struct dns_requester *requester, int error_code);
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family);
diff --git a/include/types/dns.h b/include/types/dns.h
index 7a19aa37..12c11552 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -113,7 +113,7 @@ struct dns_query_item {
/* NOTE: big endian structure */
struct dns_answer_item {
struct list list;
- char *name; /* answer name
+ char name[DNS_MAX_NAME_SIZE]; /* answer name
* For SRV type, name also includes service
* and protocol value */
int16_t type; /* question type */
@@ -124,7 +124,8 @@ struct dns_answer_item {
int16_t port; /* SRV type port */
int16_t data_len; /* number of bytes in target below */
struct sockaddr address; /* IPv4 or IPv6, network format */
- char *target; /* Response data: SRV or CNAME type target */
+ char target[DNS_MAX_NAME_SIZE]; /* Response data: SRV or CNAME type target */
+ time_t last_seen; /* When was the answer was last seen */
};
struct dns_response_packet {
@@ -158,6 +159,7 @@ struct dns_resolvers {
int timeout; /* no answer was delivered */
int refused; /* dns server refused to answer */
int other; /* other dns response errors */
+ int obsolete; /* an answer hasn't been seen */
} hold;
struct task *t; /* timeout management */
int resolution_pool_size; /* size of the resolution pool associated to this resolvers section */
@@ -252,8 +254,6 @@ struct dns_resolution {
unsigned long long revision; /* updated for each update */
struct dns_response_packet response; /* structure hosting the DNS response */
struct dns_query_item response_query_records[DNS_MAX_QUERY_RECORDS]; /* <response> query records */
- struct dns_answer_item response_answer_records[DNS_MAX_ANSWER_RECORDS]; /* <response> answer records */
- struct chunk response_buffer; /* buffer used as a data store for <response> above TODO: optimize the size (might be smaller) */
};
/*
@@ -315,6 +315,7 @@ enum {
DNS_UPD_CNAME, /* CNAME without any IP provided in the response */
DNS_UPD_NAME_ERROR, /* name in the response did not match the query */
DNS_UPD_NO_IP_FOUND, /* no IP could be found in the response */
+ DNS_UPD_OBSOLETE_IP, /* The server IP was obsolete, and no other IP was found */
};
#endif /* _TYPES_DNS_H */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 8b6aec6a..e29ae53d 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2167,6 +2167,7 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
curr_resolvers->hold.other = 30000;
curr_resolvers->hold.refused = 30000;
curr_resolvers->hold.timeout = 30000;
+ curr_resolvers->hold.obsolete = 30000;
/* default hold period for valid is 10s */
curr_resolvers->hold.valid = 10000;
curr_resolvers->timeout.retry = 1000;
@@ -2280,8 +2281,10 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
curr_resolvers->hold.timeout = time;
else if (strcmp(args[1], "valid") == 0)
curr_resolvers->hold.valid = time;
+ else if (strcmp(args[1], "obsolete") == 0)
+ curr_resolvers->hold.obsolete = time;
else {
- Alert("parsing [%s:%d] : '%s' unknown <event>: '%s', expects either 'nx', 'timeout', 'valid', or 'other'.\n",
+ Alert("parsing [%s:%d] : '%s' unknown <event>: '%s', expects either 'nx', 'timeout', 'valid', 'obsolete' or 'other'.\n",
file, linenum, args[0], args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
diff --git a/src/dns.c b/src/dns.c
index 221f8709..0ce63c91 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -51,6 +51,8 @@ static int64_t dns_query_id_seed; /* random seed */
static struct lru64_head *dns_lru_tree;
static int dns_cache_size = 1024; /* arbitrary DNS cache size */
+static struct pool_head *dns_answer_item_pool;
+
/* proto_udp callback functions for a DNS resolution */
struct dgram_data_cb resolve_dgram_cb = {
.recv = dns_resolve_recv,
@@ -169,6 +171,7 @@ int dns_trigger_resolution(struct dns_resolution *resolution)
}
requester->requester_cb(requester, NULL);
+ resolvers = NULL;
}
else {
LIST_DEL(&requester->list);
@@ -306,6 +309,12 @@ void dns_reset_resolution(struct dns_resolution *resolution)
resolution->qid.key = 0;
}
+static inline void free_dns_answer_item(struct dns_answer_item *item)
+{
+ pool_free2(dns_answer_item_pool, item);
+}
+
+
/*
* function called when a network IO is generated on a name server socket for an incoming packet
* It performs the following actions:
@@ -327,6 +336,7 @@ void dns_resolve_recv(struct dgram_conn *dgram)
struct eb32_node *eb;
struct lru64 *lru = NULL;
struct dns_requester *requester = NULL, *tmprequester = NULL;
+ struct dns_answer_item *item1, *item2 = NULL;
fd = dgram->t.sock.fd;
@@ -468,6 +478,15 @@ void dns_resolve_recv(struct dgram_conn *dgram)
break;
}
+ /* Check for any obsolete record */
+ list_for_each_entry_safe(item1, item2, &resolution->response.answer_list,
+ list) {
+ if (item1->last_seen + nameserver->resolvers->hold.obsolete / 1000 < now.tv_sec) {
+ LIST_DEL(&item1->list);
+ free_dns_answer_item(item1);
+ }
+ }
+
/* some error codes trigger a re-send of the query, but switching the
* query type.
* This is the case for the following error codes:
@@ -885,13 +904,13 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
{
unsigned char *reader;
char *previous_dname, tmpname[DNS_MAX_NAME_SIZE];
- int len, flags, offset, ret;
- int dns_query_record_id, dns_answer_record_id;
+ int len, flags, offset;
+ int dns_query_record_id;
int nb_saved_records;
struct dns_query_item *dns_query;
- struct dns_answer_item *dns_answer_record;
+ struct dns_answer_item *dns_answer_record, *tmp_record;
struct dns_response_packet *dns_p;
- struct chunk *dns_response_buffer;
+ int found = 0;
reader = resp;
len = 0;
@@ -899,9 +918,6 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
/* initialization of response buffer and structure */
dns_p = &resolution->response;
- dns_response_buffer = &resolution->response_buffer;
- memset(dns_p, '\0', sizeof(struct dns_response_packet));
- chunk_reset(dns_response_buffer);
/* query id */
if (reader + 2 >= bufend)
@@ -1011,30 +1027,29 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
}
/* now parsing response records */
- LIST_INIT(&dns_p->answer_list);
nb_saved_records = 0;
- for (dns_answer_record_id = 0; dns_answer_record_id < dns_p->header.ancount; dns_answer_record_id++) {
+ for (int i = 0; i < dns_p->header.ancount; i++) {
if (reader >= bufend)
return DNS_RESP_INVALID;
- /* pull next response record from the list, if still one available, then add it
- * to the record list */
- if (dns_answer_record_id > DNS_MAX_ANSWER_RECORDS)
- return DNS_RESP_INVALID;
- dns_answer_record = &resolution->response_answer_records[dns_answer_record_id];
- LIST_ADDQ(&dns_p->answer_list, &dns_answer_record->list);
+ dns_answer_record = pool_alloc2(dns_answer_item_pool);
+ if (dns_answer_record == NULL)
+ return (DNS_RESP_INVALID);
offset = 0;
len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
- if (len == 0)
+ if (len == 0) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
/* check if the current record dname is valid.
* previous_dname points either to queried dname or last CNAME target
*/
if (memcmp(previous_dname, tmpname, len) != 0) {
- if (dns_answer_record_id == 0) {
+ free_dns_answer_item(dns_answer_record);
+ if (i == 0) {
/* first record, means a mismatch issue between queried dname
* and dname found in the first record */
return DNS_RESP_INVALID;
@@ -1046,43 +1061,50 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
}
- dns_answer_record->name = chunk_newstr(dns_response_buffer);
- if (dns_answer_record->name == NULL)
- return DNS_RESP_INVALID;
-
- ret = chunk_strncat(dns_response_buffer, tmpname, len);
- if (ret == 0)
- return DNS_RESP_INVALID;
+ memcpy(dns_answer_record->name, tmpname, len);
+ dns_answer_record->name[len] = 0;
reader += offset;
- if (reader >= bufend)
+ if (reader >= bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
- if (reader >= bufend)
+ if (reader >= bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
/* 2 bytes for record type (A, AAAA, CNAME, etc...) */
- if (reader + 2 > bufend)
+ if (reader + 2 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->type = reader[0] * 256 + reader[1];
reader += 2;
/* 2 bytes for class (2) */
- if (reader + 2 > bufend)
+ if (reader + 2 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->class = reader[0] * 256 + reader[1];
reader += 2;
/* 4 bytes for ttl (4) */
- if (reader + 4 > bufend)
+ if (reader + 4 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536
+ reader[2] * 256 + reader[3];
reader += 4;
/* now reading data len */
- if (reader + 2 > bufend)
+ if (reader + 2 > bufend) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->data_len = reader[0] * 256 + reader[1];
/* move forward 2 bytes for data len */
@@ -1092,8 +1114,10 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
switch (dns_answer_record->type) {
case DNS_RTYPE_A:
/* ipv4 is stored on 4 bytes */
- if (dns_answer_record->data_len != 4)
+ if (dns_answer_record->data_len != 4) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->address.sa_family = AF_INET;
memcpy(&(((struct sockaddr_in *)&dns_answer_record->address)->sin_addr),
reader, dns_answer_record->data_len);
@@ -1107,22 +1131,21 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
* + 1 because dns_answer_record_id starts at 0 while number of answers
* is an integer and starts at 1.
*/
- if (dns_answer_record_id + 1 == dns_p->header.ancount)
+ if (i + 1 == dns_p->header.ancount) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_CNAME_ERROR;
+ }
offset = 0;
len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
- if (len == 0)
- return DNS_RESP_INVALID;
-
- dns_answer_record->target = chunk_newstr(dns_response_buffer);
- if (dns_answer_record->target == NULL)
+ if (len == 0) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
- ret = chunk_strncat(dns_response_buffer, tmpname, len);
- if (ret == 0)
- return DNS_RESP_INVALID;
+ memcpy(dns_answer_record->target, tmpname, len);
+ dns_answer_record->target[len] = 0;
previous_dname = dns_answer_record->target;
@@ -1130,8 +1153,10 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
case DNS_RTYPE_AAAA:
/* ipv6 is stored on 16 bytes */
- if (dns_answer_record->data_len != 16)
+ if (dns_answer_record->data_len != 16) {
+ free_dns_answer_item(dns_answer_record);
return DNS_RESP_INVALID;
+ }
dns_answer_record->address.sa_family = AF_INET6;
memcpy(&(((struct sockaddr_in6 *)&dns_answer_record->address)->sin6_addr),
reader, dns_answer_record->data_len);
@@ -1144,12 +1169,39 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
/* move forward dns_answer_record->data_len for analyzing next record in the response */
reader += dns_answer_record->data_len;
+
+ /* Lookup to see if we already had this entry */
+
+ list_for_each_entry(tmp_record, &dns_p->answer_list, list) {
+ if (tmp_record->type != dns_answer_record->type)
+ continue;
+ switch (tmp_record->type) {
+ case DNS_RTYPE_A:
+ if (!memcmp(&((struct sockaddr_in *)&dns_answer_record->address)->sin_addr,
+ &((struct sockaddr_in *)&tmp_record->address)->sin_addr, sizeof(in_addr_t)))
+ found = 1;
+ break;
+ case DNS_RTYPE_AAAA:
+ if (!memcmp(&((struct sockaddr_in6 *)&dns_answer_record->address)->sin6_addr,
+ &((struct sockaddr_in6 *)&tmp_record->address)->sin6_addr, sizeof(struct in6_addr)))
+ found = 1;
+ break;
+ default:
+ break;
+ }
+ if (found == 1)
+ break;
+ }
+ if (found == 1) {
+ tmp_record->last_seen = now.tv_sec;
+ free_dns_answer_item(dns_answer_record);
+ } else {
+ dns_answer_record->last_seen = now.tv_sec;
+ LIST_ADDQ(&dns_p->answer_list, &dns_answer_record->list);
+ }
+
} /* for i 0 to ancount */
- /* let's add a last \0 to close our last string */
- ret = chunk_strncat(dns_response_buffer, "\0", 1);
- if (ret == 0)
- return DNS_RESP_INVALID;
/* save the number of records we really own */
dns_p->header.ancount = nb_saved_records;
@@ -1175,51 +1227,17 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
{
struct dns_answer_item *record;
int family_priority;
- int i, currentip_found;
+ int currentip_found;
unsigned char *newip4, *newip6;
- struct {
- void *ip;
- unsigned char type;
- } rec[DNS_MAX_IP_REC];
int currentip_sel;
int j;
- int rec_nb = 0;
int score, max_score;
family_priority = dns_opts->family_prio;
*newip = newip4 = newip6 = NULL;
currentip_found = 0;
*newip_sin_family = AF_UNSPEC;
-
- /* now parsing response records */
- list_for_each_entry(record, &dns_p->answer_list, list) {
- /* analyzing record content */
- switch (record->type) {
- case DNS_RTYPE_A:
- /* Store IPv4, only if some room is avalaible. */
- if (rec_nb < DNS_MAX_IP_REC) {
- rec[rec_nb].ip = &(((struct sockaddr_in *)&record->address)->sin_addr);
- rec[rec_nb].type = AF_INET;
- rec_nb++;
- }
- break;
-
- /* we're looking for IPs only. CNAME validation is done when
- * parsing the response buffer for the first time */
- case DNS_RTYPE_CNAME:
- break;
-
- case DNS_RTYPE_AAAA:
- /* Store IPv6, only if some room is avalaible. */
- if (rec_nb < DNS_MAX_IP_REC) {
- rec[rec_nb].ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr);
- rec[rec_nb].type = AF_INET6;
- rec_nb++;
- }
- break;
-
- } /* switch (record type) */
- } /* list for each record entries */
+ max_score = -1;
/* Select an IP regarding configuration preference.
* Top priority is the prefered network ip version,
@@ -1234,29 +1252,38 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
* 1 - current ip.
* The result with the biggest score is returned.
*/
- max_score = -1;
- for (i = 0; i < rec_nb; i++) {
- int record_ip_already_affected = 0;
+ list_for_each_entry(record, &dns_p->answer_list, list) {
+ void *ip;
+ unsigned char ip_type;
+
+ if (record->type == DNS_RTYPE_A) {
+ ip = &(((struct sockaddr_in *)&record->address)->sin_addr);
+ ip_type = AF_INET;
+ } else if (record->type == DNS_RTYPE_AAAA) {
+ ip_type = AF_INET6;
+ ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr);
+ } else
+ continue;
score = 0;
/* Check for prefered ip protocol. */
- if (rec.type == family_priority)
+ if (ip_type == family_priority)
score += 8;
/* Check for prefered network. */
for (j = 0; j < dns_opts->pref_net_nb; j++) {
/* Compare only the same adresses class. */
- if (dns_opts->pref_net[j].family != rec.type)
+ if (dns_opts->pref_net[j].family != ip_type)
continue;
- if ((rec.type == AF_INET &&
- in_net_ipv4(rec.ip,
+ if ((ip_type == AF_INET &&
+ in_net_ipv4(ip,
&dns_opts->pref_net[j].mask.in4,
&dns_opts->pref_net[j].addr.in4)) ||
- (rec.type == AF_INET6 &&
- in_net_ipv6(rec.ip,
+ (ip_type == AF_INET6 &&
+ in_net_ipv6(ip,
&dns_opts->pref_net[j].mask.in6,
&dns_opts->pref_net[j].addr.in6))) {
score += 4;
@@ -1268,18 +1295,17 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
* If yes, the score should be incremented by 2.
*/
if (owner) {
- if (snr_check_ip_callback(owner, rec.ip, &rec.type))
- record_ip_already_affected = 1;
+ if (snr_check_ip_callback(owner, ip, &ip_type))
+ {
+ continue;
+ }
}
- if (record_ip_already_affected == 0)
- score += 2;
-
/* Check for current ip matching. */
- if (rec.type == currentip_sin_family &&
+ if (ip_type == currentip_sin_family &&
((currentip_sin_family == AF_INET &&
- memcmp(rec.ip, currentip, 4) == 0) ||
+ memcmp(ip, currentip, 4) == 0) ||
(currentip_sin_family == AF_INET6 &&
- memcmp(rec.ip, currentip, 16) == 0))) {
+ memcmp(ip, currentip, 16) == 0))) {
score += 1;
currentip_sel = 1;
} else
@@ -1292,21 +1318,22 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
* the ip selected is the current ip.
*/
if (score > max_score) {
- if (rec.type == AF_INET)
- newip4 = rec.ip;
+ if (ip_type == AF_INET)
+ newip4 = ip;
else
- newip6 = rec.ip;
+ newip6 = ip;
currentip_found = currentip_sel;
if (score == 15)
return DNS_UPD_NO;
max_score = score;
}
- }
+
+
+ } /* list for each record entries */
/* no IP found in the response */
- if (!newip4 && !newip6) {
+ if (!newip4 && !newip6)
return DNS_UPD_NO_IP_FOUND;
- }
/* case when the caller looks first for an IPv4 address */
if (family_priority == AF_INET) {
@@ -1410,6 +1437,14 @@ int dns_init_resolvers(int close_socket)
/* give a first random value to our dns query_id seed */
dns_query_id_seed = random();
+ /* Initialize the answer items pool */
+ dns_answer_item_pool = create_pool("dns_answer_item",
+ sizeof(struct dns_answer_item), MEM_F_SHARED);
+ if (dns_answer_item_pool == NULL) {
+ Alert("Failed to create the dns answer items pool");
+ return 0;
+ }
+
/* run through the resolvers section list */
list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
/* create the task associated to the resolvers section */
@@ -1442,6 +1477,7 @@ int dns_init_resolvers(int close_socket)
/* allocate memory only if it has not already been allocated
* by a previous call to this function */
+
if (!dgram && (dgram = calloc(1, sizeof(*dgram))) == NULL) {
Alert("Starting [%s/%s] nameserver: out of memory.\n", curr_resolvers->id,
curnameserver->id);
@@ -2081,6 +2117,7 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
tmpresolution->status = RSLV_STATUS_NONE;
tmpresolution->step = RSLV_STEP_NONE;
tmpresolution->revision = 1;
+ LIST_INIT(&tmpresolution->response.answer_list);
}
/* add the requester to the resolution's wait queue */
@@ -2170,7 +2207,6 @@ struct dns_resolution *dns_alloc_resolution(void)
return NULL;
}
- chunk_init(&resolution->response_buffer, buffer, global.tune.bufsize);
LIST_INIT(&resolution->requester.wait);
LIST_INIT(&resolution->requester.curr);
@@ -2180,7 +2216,6 @@ struct dns_resolution *dns_alloc_resolution(void)
/* This function free the memory allocated to a DNS resolution */
void dns_free_resolution(struct dns_resolution *resolution)
{
- chunk_destroy(&resolution->response_buffer);
free(resolution);
return;
diff --git a/src/server.c b/src/server.c
index f457d555..ef62a63f 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3756,7 +3756,7 @@ out:
* 0 if server status is updated
* 1 if server status has not changed
*/
-int snr_update_srv_status(struct server *s)
+int snr_update_srv_status(struct server *s, int has_no_ip)
{
struct dns_resolution *resolution = s->resolution;
struct dns_resolvers *resolvers = s->resolvers;
@@ -3772,6 +3772,13 @@ int snr_update_srv_status(struct server *s)
* resume health checks
* server will be turned back on if health check is safe
*/
+ if (has_no_ip) {
+ if (s->admin & SRV_ADMF_RMAINT)
+ return 1;
+ srv_set_admin_flag(s, SRV_ADMF_RMAINT,
+ "No IP for server ");
+ return (0);
+ }
if (!(s->admin & SRV_ADMF_RMAINT))
return 1;
srv_clr_admin_flag(s, SRV_ADMF_RMAINT);
@@ -3847,6 +3854,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
short server_sin_family, firstip_sin_family;
int ret;
struct chunk *chk = get_trash_chunk();
+ int has_no_ip = 0;
s = objt_server(requester->requester);
if (!s)
@@ -3893,10 +3901,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
goto invalid;
case DNS_UPD_NO_IP_FOUND:
- if (resolution->status != RSLV_STATUS_OTHER) {
- resolution->status = RSLV_STATUS_OTHER;
- resolution->last_status_change = now_ms;
- }
+ has_no_ip = 1;
goto update_status;
case DNS_UPD_NAME_ERROR:
@@ -3927,7 +3932,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
update_server_addr(s, firstip, firstip_sin_family, (char *)chk->str);
update_status:
- snr_update_srv_status(s);
+ snr_update_srv_status(s, has_no_ip);
return 1;
invalid:
@@ -3936,7 +3941,7 @@ int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *na
if (resolution->nb_responses >= nameserver->resolvers->count_nameservers)
goto update_status;
- snr_update_srv_status(s);
+ snr_update_srv_status(s, has_no_ip);
return 0;
}
@@ -3964,16 +3969,15 @@ int snr_resolution_error_cb(struct dns_requester *requester, int error_code)
return 1;
}
- snr_update_srv_status(s);
+ snr_update_srv_status(s, 0);
return 1;
}
/*
* Function to check if <ip> is already affected to a server in the backend
- * which owns <srv>.
+ * which owns <srv> and is up.
* It returns a pointer to the first server found or NULL if <ip> is not yet
* assigned.
- * NOTE: <ip> and <ip_family> are provided by a 'struct rec' available in dns.c.
*/
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family)
{
@@ -3998,6 +4002,10 @@ struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char
(srv->puid == tmpsrv->puid))
continue;
+ /* If the server has been taken down, don't consider it */
+ if (tmpsrv->admin & SRV_ADMF_RMAINT)
+ continue;
+
/* At this point, we have 2 different servers using the same DNS hostname
* for their respective resolution.
*/
--
2.13.3
From 477e84d747896aaeeacd40525af5458630bb52dc Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 4 Aug 2017 18:31:56 +0200
Subject: [PATCH 2/4] MINOR: obj: Add a new type of object, OBJ_TYPE_SRVRQ.
dns_srvrq will be objects used for dealing with SRV records.
---
include/proto/obj_type.h | 12 ++++++++++++
include/types/obj_type.h | 1 +
2 files changed, 13 insertions(+)
diff --git a/include/proto/obj_type.h b/include/proto/obj_type.h
index b64244fc..60265b5e 100644
--- a/include/proto/obj_type.h
+++ b/include/proto/obj_type.h
@@ -131,6 +131,18 @@ static inline struct connection *objt_conn(enum obj_type *t)
return __objt_conn(t);
}
+static inline struct dns_srvrq *__objt_dns_srvrq(enum obj_type *t)
+{
+ return container_of(t, struct dns_srvrq, obj_type);
+}
+
+static inline struct dns_srvrq *objt_dns_srvrq(enum obj_type *t)
+{
+ if (!t || *t != OBJ_TYPE_SRVRQ)
+ return NULL;
+ return __objt_dns_srvrq(t);
+}
+
static inline void *obj_base_ptr(enum obj_type *t)
{
switch (obj_type(t)) {
diff --git a/include/types/obj_type.h b/include/types/obj_type.h
index 83a3e782..a6310cfc 100644
--- a/include/types/obj_type.h
+++ b/include/types/obj_type.h
@@ -39,6 +39,7 @@ enum obj_type {
OBJ_TYPE_APPLET, /* object is a struct applet */
OBJ_TYPE_APPCTX, /* object is a struct appctx */
OBJ_TYPE_CONN, /* object is a struct connection */
+ OBJ_TYPE_SRVRQ, /* object is a struct dns_srvrq */
OBJ_TYPE_ENTRIES /* last one : number of entries */
} __attribute__((packed)) ;
--
2.13.3
From a2e3bba11a5c1a3c69e9f33c566d7c285c9121f2 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 4 Aug 2017 18:35:36 +0200
Subject: [PATCH 3/4] MINOR: dns: Handle SRV records.
Make it so for each server, instead of specifying a hostname, one can use
a SRV label.
When doing so, haproxy will first resolve the SRV label, then use the
resulting hostnames, as well as port and weight (priority is ignored right
now), to each server using the SRV label.
It is resolved periodically, and any server disappearing from the SRV records
will be removed, and any server appearing will be added, assuming there're
free servers in haproxy.
---
include/proto/dns.h | 1 +
include/proto/server.h | 1 +
include/types/dns.h | 16 +++
include/types/proxy.h | 1 +
include/types/server.h | 1 +
src/cfgparse.c | 22 +++-
src/dns.c | 313 ++++++++++++++++++++++++++++++++++++++++++-------
src/proxy.c | 1 +
src/server.c | 96 ++++++++++++---
9 files changed, 395 insertions(+), 57 deletions(-)
diff --git a/include/proto/dns.h b/include/proto/dns.h
index 6675d50f..a84f07c4 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -27,6 +27,7 @@
char *dns_str_to_dn_label(const char *string, char *dn, int dn_len);
int dns_str_to_dn_label_len(const char *string);
+void dns_dn_label_to_str(char *dn, char *str, int dn_len);
int dns_hostname_validation(const char *string, char **err);
int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostname_dn_len, char *buf, int bufsize);
struct task *dns_process_resolve(struct task *t);
diff --git a/include/proto/server.h b/include/proto/server.h
index c4f8e1d5..d35a9c1c 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -53,6 +53,7 @@ struct server *cli_find_server(struct appctx *appctx, char *arg);
/* functions related to server name resolution */
int snr_update_srv_status(struct server *s, int has_no_ip);
+const char *update_server_fqdn(struct server *server, const char *fqdn, const char *updater);
int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
int snr_resolution_error_cb(struct dns_requester *requester, int error_code);
struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family);
diff --git a/include/types/dns.h b/include/types/dns.h
index 12c11552..c371d5f5 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -63,6 +63,7 @@
#define DNS_RTYPE_A 1 /* IPv4 address */
#define DNS_RTYPE_CNAME 5 /* canonical name */
#define DNS_RTYPE_AAAA 28 /* IPv6 address */
+#define DNS_RTYPE_SRV 33 /* SRV record */
#define DNS_RTYPE_ANY 255 /* all records */
/* dns rcode values */
@@ -318,4 +319,19 @@ enum {
DNS_UPD_OBSOLETE_IP, /* The server IP was obsolete, and no other IP was found */
};
+struct dns_srvrq {
+ enum obj_type obj_type; /* object type == OBJ_TYPE_SRVRQ */
+ struct dns_resolvers *resolvers; /* pointer to the resolvers structure used for this server template */
+
+ struct dns_resolution *resolution; /* server name resolution */
+
+ struct proxy *proxy; /* associated proxy */
+ char *name;
+ char *hostname_dn; /* server hostname in Domain Name format */
+ int hostname_dn_len; /* string length of the server hostname in Domain Name format */
+ struct dns_requester *dns_requester; /* used to link to its DNS resolution */
+ int inter; /* time in ms */
+ struct list list; /* Next SRV RQ for the same proxy */
+};
+
#endif /* _TYPES_DNS_H */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 5306a3b6..a4f3b9e5 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -438,6 +438,7 @@ struct proxy {
* name is used
*/
struct list filter_configs; /* list of the filters that are declared on this proxy */
+ struct list srvrq_list; /* List of SRV requests associated with this proxy */
};
struct switching_rule {
diff --git a/include/types/server.h b/include/types/server.h
index 724d4965..77263dbb 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -298,6 +298,7 @@ struct server {
int nb_low;
int nb_high;
} tmpl_info;
+ struct dns_srvrq *srvrq; /* Pointer representing the DNS SRV requeest, if any */
};
/* Descriptor for a "server" keyword. The ->parse() function returns 0 in case of
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e29ae53d..437d9ef2 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -8549,7 +8549,20 @@ out_uri_auth_compat:
newsrv->id, newsrv->resolvers_id);
cfgerr++;
} else {
- if (newsrv->hostname_dn) {
+ if (newsrv->srvrq) {
+ if (!newsrv->srvrq->resolvers) {
+ newsrv->srvrq->resolvers = curr_resolvers;
+ if (dns_link_resolution(newsrv->srvrq,
+ OBJ_TYPE_SRVRQ, NULL) != 0) {
+ Alert("config : %s '%s', server '%s': unable to set DNS resolution\n",
+ proxy_type_str(curproxy), curproxy->id,
+ newsrv->id);
+ cfgerr++;
+ }
+ }
+
+ }
+ if (newsrv->srvrq || newsrv->hostname_dn) {
newsrv->resolvers = curr_resolvers;
if (dns_link_resolution(newsrv, OBJ_TYPE_SERVER, NULL) != 0) {
Alert("config : %s '%s', server '%s': unable to set DNS resolution\n",
@@ -8575,6 +8588,13 @@ out_uri_auth_compat:
next_srv:
newsrv = newsrv->next;
}
+ {
+ struct dns_srvrq *srvrq;
+
+ list_for_each_entry(srvrq, &curproxy->srvrq_list, list) {
+ dns_link_resolution(srvrq, OBJ_TYPE_SRVRQ, NULL);
+ }
+ }
/*
* Try to generate dynamic cookies for servers now.
diff --git a/src/dns.c b/src/dns.c
index 0ce63c91..2931752a 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -153,6 +153,10 @@ int dns_trigger_resolution(struct dns_resolution *resolution)
inter = objt_server(requester->requester)->check.inter;
resolvers = objt_server(requester->requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ inter = objt_dns_srvrq(requester->requester)->inter;
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return -1;
@@ -212,12 +216,26 @@ dns_run_resolution(struct dns_requester *requester)
proxy = objt_server(requester->requester)->proxy;
query_type = requester->prefered_query_type;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolution = objt_dns_srvrq(requester->requester)->resolution;
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ proxy = objt_dns_srvrq(requester->requester)->proxy;
+ query_type = DNS_RTYPE_SRV;
+ break;
case OBJ_TYPE_NONE:
default:
return -1;
}
/*
+ * Avoid sending requests for resolutions that don't yet have
+ * an hostname, ie resolutions linked to servers that do not yet
+ * have an fqdn
+ */
+ if (!resolution->hostname_dn)
+ return 0;
+
+ /*
* check if a resolution has already been started for this server
* return directly to avoid resolution pill up.
*/
@@ -352,6 +370,7 @@ void dns_resolve_recv(struct dgram_conn *dgram)
/* process all pending input messages */
while (1) {
+ int removed_reso = 0;
/* read message received */
memset(buf, '\0', DNS_MAX_UDP_MESSAGE + 1);
if ((buflen = recv(fd, (char*)buf , DNS_MAX_UDP_MESSAGE, 0)) < 0) {
@@ -478,14 +497,104 @@ void dns_resolve_recv(struct dgram_conn *dgram)
break;
}
- /* Check for any obsolete record */
+ /* Check for any obsolete record, also identify any SRV request, and try to find a corresponding server */
list_for_each_entry_safe(item1, item2, &resolution->response.answer_list,
list) {
if (item1->last_seen + nameserver->resolvers->hold.obsolete / 1000 < now.tv_sec) {
LIST_DEL(&item1->list);
+ if (item1->type == DNS_RTYPE_SRV && !LIST_ISEMPTY(&resolution->requester.curr)) {
+ struct dns_srvrq *srvrq;
+
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+
+ srvrq = objt_dns_srvrq(requester->requester);
+ /* We're removing an obsolete entry, remove any associated server */
+ if (srvrq) {
+ struct server *srv;
+
+ for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) {
+ if (srv->srvrq == srvrq &&
+ item1->data_len ==
+ srv->hostname_dn_len &&
+ !memcmp(srv->hostname_dn, item1->target, item1->data_len) &&
+ srv->svc_port == item1->port) {
+ snr_update_srv_status(srv, 1);
+ free(srv->hostname);
+ srv->hostname = NULL;
+ srv->hostname_dn_len = 0;
+ free(srv->hostname_dn);
+ srv->hostname_dn = NULL;
+ dns_resolution_free(srv->resolvers, srv->resolution);
+ srv->resolution = dns_resolution_list_get(srv->resolvers, NULL, srv->dns_requester->prefered_query_type);
+ if (resolution == srv->resolution)
+ removed_reso = 1;
+ }
+ }
+ }
+ }
free_dns_answer_item(item1);
+ continue;
+ }
+ if (item1->type == DNS_RTYPE_SRV) {
+ struct server *srv;
+ struct dns_srvrq *srvrq;
+
+ if (LIST_ISEMPTY(&resolution->requester.curr))
+ continue;
+
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ srvrq = objt_dns_srvrq(requester->requester);
+ if (!srvrq)
+ continue;
+ /* Check if a server already uses that hostname */
+ for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) {
+ if (srv->srvrq == srvrq &&
+ item1->data_len == srv->hostname_dn_len &&
+ !memcmp(srv->hostname_dn, item1->target, item1->data_len) &&
+ srv->svc_port == item1->port) {
+ if (srv->uweight != item1->weight) {
+ char weight[9];
+
+ snprintf(weight, sizeof(weight),
+ "%d", item1->weight);
+ server_parse_weight_change_request(srv, weight);
+
+ }
+
+ break;
+ }
+ }
+ /* If not, try to find a server that is down */
+ if (!srv) {
+ for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) {
+
+ if (srv->srvrq == srvrq &&
+ !srv->hostname_dn)
+ break;
+ }
+ if (srv) {
+ char weight[9];
+
+ char hostname[DNS_MAX_NAME_SIZE];
+
+ if (item1->data_len > DNS_MAX_NAME_SIZE)
+ continue;
+ dns_dn_label_to_str(item1->target, hostname, item1->data_len);
+ update_server_fqdn(srv, hostname, "SRV record");
+ srv->svc_port = item1->port;
+ srv->flags &= ~SRV_F_MAPPORTS;
+ if ((srv->check.state & CHK_ST_CONFIGURED) && !(srv->flags & SRV_F_CHECKPORT))
+ srv->check.port = item1->port;
+ snprintf(weight, sizeof(weight),
+ "%d", item1->weight);
+ server_parse_weight_change_request(srv, weight);
+ }
+
+ }
}
}
+ if (removed_reso)
+ goto next_packet;
/* some error codes trigger a re-send of the query, but switching the
* query type.
@@ -576,6 +685,8 @@ void dns_resolve_recv(struct dgram_conn *dgram)
* We can check only the first query of the list. We send one query at a time
* so we get one query in the response */
query = LIST_NEXT(&resolution->response.query_list, struct dns_query_item *, list);
+ if (!resolution->hostname_dn)
+ abort();
if (query && memcmp(query->name, resolution->hostname_dn, resolution->hostname_dn_len) != 0) {
nameserver->counters.other += 1;
/* now parse list of requesters currently waiting for this resolution */
@@ -706,6 +817,9 @@ int dns_send_query(struct dns_resolution *resolution)
case OBJ_TYPE_SERVER:
resolvers = objt_server(requester->requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return 0;
@@ -775,6 +889,9 @@ void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
case OBJ_TYPE_SERVER:
valid_period = objt_server(requester->requester)->check.inter;
break;
+ case OBJ_TYPE_SRVRQ:
+ valid_period = objt_dns_srvrq(requester->requester)->inter;
+ break;
case OBJ_TYPE_NONE:
default:
continue;
@@ -790,6 +907,9 @@ void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
case OBJ_TYPE_SERVER:
dns_trigger_resolution(objt_server(requester->requester)->resolution);
break;
+ case OBJ_TYPE_SRVRQ:
+ dns_trigger_resolution(objt_dns_srvrq(requester->requester)->resolution);
+ break;
case OBJ_TYPE_NONE:
default:
;;
@@ -1151,6 +1271,42 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
break;
+
+ case DNS_RTYPE_SRV:
+ /*
+ * Answer must contain :
+ * - 2 bytes for the priority
+ * - 2 bytes for the weight
+ * - 2 bytes for the port
+ * - the target hostname
+ */
+ if (dns_answer_record->data_len <= 6) {
+ free_dns_answer_item(dns_answer_record);
+ return DNS_RESP_INVALID;
+ }
+ memcpy(&dns_answer_record->priority,
+ reader + offset, sizeof(uint16_t));
+ dns_answer_record->priority = ntohs(dns_answer_record->priority);
+ reader += sizeof(uint16_t);
+ memcpy(&dns_answer_record->weight,
+ reader, sizeof(uint16_t));
+ dns_answer_record->weight = ntohs(dns_answer_record->weight);
+ reader += sizeof(uint16_t);
+ memcpy(&dns_answer_record->port,
+ reader, sizeof(uint16_t));
+ dns_answer_record->port = ntohs(dns_answer_record->port);
+ reader += sizeof(uint16_t);
+ offset = 0;
+ len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
+ if (len == 0) {
+ free_dns_answer_item(dns_answer_record);
+ return DNS_RESP_INVALID;
+ }
+ reader++;
+ dns_answer_record->data_len = len;
+ memcpy(dns_answer_record->target, tmpname, len);
+ dns_answer_record->target[len] = 0;
+ break;
case DNS_RTYPE_AAAA:
/* ipv6 is stored on 16 bytes */
if (dns_answer_record->data_len != 16) {
@@ -1172,6 +1328,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
/* Lookup to see if we already had this entry */
+ found = 0;
list_for_each_entry(tmp_record, &dns_p->answer_list, list) {
if (tmp_record->type != dns_answer_record->type)
continue;
@@ -1186,6 +1343,15 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
&((struct sockaddr_in6 *)&tmp_record->address)->sin6_addr, sizeof(struct in6_addr)))
found = 1;
break;
+ case DNS_RTYPE_SRV:
+ if (dns_answer_record->data_len == tmp_record->data_len &&
+ !memcmp(dns_answer_record->target,
+ tmp_record->target, dns_answer_record->data_len) &&
+ dns_answer_record->port == tmp_record->port) {
+ tmp_record->weight = dns_answer_record->weight;
+ found = 1;
+ }
+ break;
default:
break;
}
@@ -1635,6 +1801,27 @@ int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostnam
return ptr - buf;
}
+/* Turn a domain name label into a string */
+void dns_dn_label_to_str(char *dn, char *str, int dn_len)
+{
+ int remain_size = 0;
+
+ for (int i = 0; i < dn_len; i++) {
+ if (remain_size == 0) {
+ remain_size = dn;
+ if (i != 0) {
+ str[i - 1] = '.';
+
+ }
+ } else {
+ str[i - 1] = dn;
+ remain_size--;
+ }
+ }
+ str[dn_len - 1] = 0;
+
+}
+
/*
* turn a string into domain name label:
* www.haproxy.org into 3www7haproxy3org
@@ -1827,8 +2014,23 @@ struct task *dns_process_resolve(struct task *t)
switch (obj_type(requester->requester)) {
case OBJ_TYPE_SERVER:
dns_opts = &(objt_server(requester->requester)->dns_opts);
+ res_preferred_afinet = dns_opts->family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
+ res_preferred_afinet6 = dns_opts->family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
+
+ /* let's change the query type if needed */
+ if (res_preferred_afinet6) {
+ /* fallback from AAAA to A */
+ resolution->query_type = DNS_RTYPE_A;
+ }
+ else if (res_preferred_afinet) {
+ /* fallback from A to AAAA */
+ resolution->query_type = DNS_RTYPE_AAAA;
+ }
+
break;
+ case OBJ_TYPE_SRVRQ:
+ break;
case OBJ_TYPE_NONE:
default:
/* clean up resolution information and remove from the list */
@@ -1842,19 +2044,6 @@ struct task *dns_process_resolve(struct task *t)
goto out;
}
- res_preferred_afinet = dns_opts->family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A;
- res_preferred_afinet6 = dns_opts->family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA;
-
- /* let's change the query type if needed */
- if (res_preferred_afinet6) {
- /* fallback from AAAA to A */
- resolution->query_type = DNS_RTYPE_A;
- }
- else if (res_preferred_afinet) {
- /* fallback from A to AAAA */
- resolution->query_type = DNS_RTYPE_AAAA;
- }
-
/* resend the DNS query */
dns_send_query(resolution);
@@ -1966,6 +2155,9 @@ dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int val
case OBJ_TYPE_SERVER:
resolvers = objt_server(requester->requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolvers = objt_dns_srvrq(requester->requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return NULL;
@@ -2028,6 +2220,7 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
char *hostname_dn = NULL;
int new_resolution;
+
if (!resolution) {
tmprequester = calloc(1, sizeof(*tmprequester));
if (!tmprequester)
@@ -2047,6 +2240,11 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
}
break;
+ case OBJ_TYPE_SRVRQ:
+ tmprequester->requester = &((struct dns_srvrq *)requester)->obj_type;
+ hostname_dn = objt_dns_srvrq(requester)->hostname_dn;
+ resolvers = objt_dns_srvrq(requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
free(tmprequester);
@@ -2068,6 +2266,10 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
tmprequester = ((struct server *)requester)->dns_requester;
resolvers = ((struct server *)requester)->resolvers;
break;
+ case OBJ_TYPE_SRVRQ:
+ tmprequester = objt_dns_srvrq(requester)->dns_requester;
+ resolvers = objt_dns_srvrq(requester)->resolvers;
+ break;
case OBJ_TYPE_NONE:
default:
return -1;
@@ -2102,6 +2304,23 @@ int dns_link_resolution(void *requester, int requester_type, struct dns_resoluti
objt_server(tmprequester->requester)->dns_requester = tmprequester;
}
break;
+ case OBJ_TYPE_SRVRQ:
+ /* some parameters should be set only if the resolution is brand new */
+ if (new_resolution) {
+ tmpresolution->query_type = DNS_RTYPE_SRV;
+ tmpresolution->hostname_dn = objt_dns_srvrq(tmprequester->requester)->hostname_dn;
+ tmpresolution->hostname_dn_len = objt_dns_srvrq(tmprequester->requester)->hostname_dn_len;
+ }
+
+ /* update requester as well, only if we just allocated it */
+ objt_dns_srvrq(tmprequester->requester)->resolution = tmpresolution;
+ if (!resolution) {
+ tmprequester->requester_cb = snr_resolution_cb;
+ tmprequester->requester_error_cb = snr_resolution_error_cb;
+ objt_dns_srvrq(tmprequester->requester)->dns_requester = tmprequester;
+ }
+ break;
+
case OBJ_TYPE_NONE:
default:
free(tmprequester);
@@ -2142,42 +2361,45 @@ struct dns_resolution *dns_resolution_list_get(struct dns_resolvers *resolvers,
struct dns_resolution *resolution, *tmpresolution;
struct dns_requester *requester;
- /* search for same hostname and query type in resolution.curr */
- list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.curr, list) {
- requester = NULL;
+ if (hostname_dn) {
+ /* search for same hostname and query type in resolution.curr */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.curr, list) {
+ requester = NULL;
- if (!LIST_ISEMPTY(&resolution->requester.wait))
- requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
- else if (!LIST_ISEMPTY(&resolution->requester.curr))
- requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ if (!LIST_ISEMPTY(&resolution->requester.wait))
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+ else if (!LIST_ISEMPTY(&resolution->requester.curr))
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
- if (!requester)
- continue;
+ if (!requester)
+ continue;
- if ((query_type == requester->prefered_query_type) &&
- (strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
- return resolution;
+ if ((query_type == requester->prefered_query_type) &&
+ (resolution->hostname_dn &&
+ strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
+ return resolution;
+ }
}
- }
- /* search for same hostname and query type in resolution.wait */
- list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.wait, list) {
- requester = NULL;
+ /* search for same hostname and query type in resolution.wait */
+ list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.wait, list) {
+ requester = NULL;
- if (!LIST_ISEMPTY(&resolution->requester.wait))
- requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
- else if (!LIST_ISEMPTY(&resolution->requester.curr))
- requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
+ if (!LIST_ISEMPTY(&resolution->requester.wait))
+ requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list);
+ else if (!LIST_ISEMPTY(&resolution->requester.curr))
+ requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list);
- if (!requester)
- continue;
+ if (!requester)
+ continue;
- if ((query_type == requester->prefered_query_type) &&
- (strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
- return resolution;
+ if ((query_type == requester->prefered_query_type) &&
+ (resolution->hostname_dn &&
+ strcmp(hostname_dn, resolution->hostname_dn) == 0)) {
+ return resolution;
+ }
}
}
-
/* take the first one (hopefully) from the pool */
list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.pool, list) {
if (LIST_ISEMPTY(&resolution->requester.wait)) {
@@ -2261,6 +2483,9 @@ void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dn
case OBJ_TYPE_SERVER:
hostname_dn = objt_server(requester->requester)->hostname_dn;
break;
+ case OBJ_TYPE_SRVRQ:
+ hostname_dn = objt_dns_srvrq(requester->requester)->hostname_dn;
+ break;
case OBJ_TYPE_NONE:
default:
hostname_dn = NULL;
@@ -2290,6 +2515,11 @@ void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dn
resolution->hostname_dn = objt_server(tmprequester->requester)->hostname_dn;
resolution->hostname_dn_len = objt_server(tmprequester->requester)->hostname_dn_len;
break;
+ case OBJ_TYPE_SRVRQ:
+ resolution->hostname_dn = objt_dns_srvrq(tmprequester->requester)->hostname_dn;
+ resolution->hostname_dn_len = objt_dns_srvrq(tmprequester->requester)->hostname_dn_len;
+ break;
+
case OBJ_TYPE_NONE:
default:
;;
@@ -2302,6 +2532,9 @@ void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dn
case OBJ_TYPE_SERVER:
objt_server(requester->requester)->resolution = NULL;
break;
+ case OBJ_TYPE_SRVRQ:
+ objt_dns_srvrq(requester->requester)->resolution = NULL;
+ break;
case OBJ_TYPE_NONE:
default:
;;
diff --git a/src/proxy.c b/src/proxy.c
index 641d4fa1..bd2031ee 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -754,6 +754,7 @@ void init_new_proxy(struct proxy *p)
LIST_INIT(&p->conf.args.list);
LIST_INIT(&p->tcpcheck_rules);
LIST_INIT(&p->filter_configs);
+ LIST_INIT(&p->srvrq_list);
/* Timeouts are defined as -1 */
proxy_reset_timeouts(p);
diff --git a/src/server.c b/src/server.c
index ef62a63f..b341919c 100644
--- a/src/server.c
+++ b/src/server.c
@@ -2285,18 +2285,68 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
/* save hostname and create associated name resolution */
if (fqdn) {
- if (srv_prepare_for_resolution(newsrv, fqdn) == -1) {
- Alert("parsing [%s:%d] : Can't create DNS resolution for server '%s'\n",
- file, linenum, newsrv->id);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
+ if (fqdn[0] == '_') {
+ struct dns_srvrq *srvrq = NULL;
+ int found = 0;
+ /* SRV record */
+ /* Check if a SRV request already exists, and if not, create it */
+ list_for_each_entry(srvrq, &curproxy->srvrq_list, list) {
+ if (!strcmp(srvrq->name, fqdn)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0) {
+ int hostname_dn_len;
+
+ srvrq = calloc(1, sizeof(*srvrq));
+ if (!srvrq) {
+ Alert("Failed to allocate memory");
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ srvrq->obj_type = OBJ_TYPE_SRVRQ;
+ srvrq->proxy = proxy;
+ srvrq->name = strdup(fqdn);
+ srvrq->inter = 2000;
+ hostname_dn_len = dns_str_to_dn_label_len(fqdn);
+ if (hostname_dn_len == -1) {
+ Alert("Failed to parse domaine name '%s'", fqdn);
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ srvrq->hostname_dn = malloc(hostname_dn_len + 1);
+ srvrq->hostname_dn_len = hostname_dn_len;
+ if (!srvrq->hostname_dn) {
+ Alert("Failed to alloc memory");
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (!dns_str_to_dn_label(fqdn,
+ srvrq->hostname_dn,
+ hostname_dn_len + 1)) {
+ Alert("Failed to parse domain name '%s'", fqdn);
+ err_code = ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ LIST_ADDQ(&proxy->srvrq_list, &srvrq->list);
+
+ }
+ newsrv->srvrq = srvrq;
+
+
+ } else if (srv_prepare_for_resolution(newsrv, fqdn) == -1) {
+ Alert("parsing [%s:%d] : Can't create DNS resolution for server '%s'\n",
+ file, linenum, newsrv->id);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
}
}
newsrv->addr = *sk;
newsrv->svc_port = port;
- if (!newsrv->hostname && !protocol_by_family(newsrv->addr.ss_family)) {
+ if (!newsrv->srvrq && !newsrv->hostname && !protocol_by_family(newsrv->addr.ss_family)) {
Alert("parsing [%s:%d] : Unknown protocol family %d '%s'\n",
file, linenum, newsrv->addr.ss_family, args[cur_arg]);
err_code |= ERR_ALERT | ERR_FATAL;
@@ -2528,6 +2578,8 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
goto out;
}
newsrv->check.inter = val;
+ if (newsrv->srvrq)
+ newsrv->srvrq->inter = val;
cur_arg += 2;
}
else if (!strcmp(args[cur_arg], "fastinter")) {
@@ -4043,6 +4095,7 @@ int srv_set_fqdn(struct server *srv, const char *hostname)
{
struct dns_resolution *resolution;
int hostname_dn_len;
+ int did_set_reso = 0;
/* run time DNS resolution was not active for this server
* and we can't enable it at run time for now.
@@ -4065,17 +4118,23 @@ int srv_set_fqdn(struct server *srv, const char *hostname)
return -1;
- /* get a resolution from the curr or wait queues, or a brand new one from the pool */
- resolution = dns_resolution_list_get(srv->resolvers, trash.str, srv->dns_requester->prefered_query_type);
- if (!resolution)
- return -1;
+ if (srv->resolution->hostname_dn) {
+ /* get a resolution from the curr or wait queues, or a brand new one from the pool */
+ resolution = dns_resolution_list_get(srv->resolvers, trash.str, srv->dns_requester->prefered_query_type);
+ if (!resolution)
+ return -1;
- /* in this case, the new hostanme is the same than the old one */
- if (srv->resolution == resolution)
- return 0;
+ /* in this case, the new hostanme is the same than the old one */
+ if (srv->resolution == resolution && srv->hostname)
+ return 0;
- /* first, we need to unlink our server from its current resolution */
- srv_free_from_resolution(srv);
+ /* first, we need to unlink our server from its current resolution */
+ srv_free_from_resolution(srv);
+ } else {
+ resolution = srv->resolution;
+ resolution->last_resolution = now_ms;
+ did_set_reso = 1;
+ }
/* now we update server's parameters */
free(srv->hostname);
@@ -4085,6 +4144,11 @@ int srv_set_fqdn(struct server *srv, const char *hostname)
srv->hostname_dn_len = hostname_dn_len;
if (!srv->hostname || !srv->hostname_dn)
return -1;
+ if (did_set_reso) {
+ resolution->query_type = srv->dns_requester->prefered_query_type;
+ resolution->hostname_dn = srv->hostname_dn;
+ resolution->hostname_dn_len = hostname_dn_len;
+ }
/* then we can link srv to its new resolution */
dns_link_resolution(srv, OBJ_TYPE_SERVER, resolution);
@@ -4223,7 +4287,7 @@ const char *update_server_fqdn(struct server *server, const char *fqdn, const ch
msg = get_trash_chunk();
chunk_reset(msg);
- if (!strcmp(fqdn, server->hostname)) {
+ if (server->hostname && !strcmp(fqdn, server->hostname)) {
chunk_appendf(msg, "no need to change the FDQN");
goto out;
}
--
2.13.3
From 899413f3a965feeaa96ffb5b284b504dbdb01fa1 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouchard@haproxy.com>
Date: Fri, 4 Aug 2017 18:39:01 +0200
Subject: [PATCH 4/4] MINOR: check: Fix checks when using SRV records.
When started, a server may not yet have an associated protocol, so don't
bother trying to run the checks until it is there.
---
src/checks.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/checks.c b/src/checks.c
index 7938b873..fc92a243 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1557,7 +1557,7 @@ static int connect_conn_chk(struct task *t)
}
ret = SF_ERR_INTERNAL;
- if (proto->connect)
+ if (proto && proto->connect)
ret = proto->connect(conn, check->type, quickack ? 2 : 0);
if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
conn->send_proxy_ofs = 1;
--
2.13.3