Welcome! Log In Create A New Profile

Advanced

[PATCHES] SRV record support

Posted by Olivier Houchard 
Olivier Houchard
[PATCHES] SRV record support
August 04, 2017 07:00PM
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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
Willy Tarreau
Re: [PATCHES] SRV record support
August 04, 2017 07:10PM
Hi Olivier,

On Fri, Aug 04, 2017 at 06:49:43PM +0200, Olivier Houchard wrote:
> Hi guys,
>
> Following Baptiste's work on DNS, the attached patchset adds support for DNS
> obsolescence, and SRV support.
(...)

Really cool, thank you. Let's wait for some feedback.

Willy
Willy Tarreau
Re: [PATCHES] SRV record support
August 04, 2017 09:30PM
Just a few questions and minor comments below :

On Fri, Aug 04, 2017 at 06:49:43PM +0200, Olivier Houchard wrote:
> 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.

What makes the distinction between an SRV record and a real hostname here ?
Just the leading underscore, or the plain "_http." maybe ? I'm not expecting
any problem with this given that the underscore is not allowed as a regular
hostname character (except under windows). But at least this will deserve
a mention in the doc where the server's address is described, so that anyone
experiencing trouble could spot this easily.

> From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
> From: Olivier Houchard <[email protected]>
> 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

Do you have an estimate of the worst case increase of memory usage incurred
by using the max name size for every name component ? I understand that it
might not be possible to use a shared area anymore for all entries once you
start to deal with obsolescence, but it's just to get an idea of what the
worst DNS response could have as impact.

> @@ -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 */

Same here.

> @@ -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++) {

"for (int i ...)" will not build here, it's only C99 and we don't enforce
this (and personally I hate this form as it's the only exception I know
of where a variable declaration is used outside of the scope it's declared
in.

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

I'm just realizing that when I merge the minimalist indirect string
manipulation functions I'm using for HTTP/2, it will simplify such
code, as dealing all the time with memcpy() and memcmp() for strings
is a pain. It will also give me ideas of what to improve there before
it represents a perceptible benefit. We'll probably see this for 1.9.

> From a2e3bba11a5c1a3c69e9f33c566d7c285c9121f2 Mon Sep 17 00:00:00 2001
> From: Olivier Houchard <[email protected]>
> 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/src/dns.c b/src/dns.c
> index 0ce63c91..2931752a 100644
> --- a/src/dns.c
> +++ b/src/dns.c
> @@ -478,14 +497,104 @@ void dns_resolve_recv(struct dgram_conn *dgram)
> break;
(...)
> + /* 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);


Next time, you can use ultoa() as a replacement for snprintf(str, size, "%d"),
which also ensures you'll use a large enough string for the largest supported
integer types, there's also U2A() which uses a rotating buffer and which can
be used multiple times in a single function call.

> + reader += sizeof(uint16_t);
> + memcpy(&dns_answer_record->weight,
> + reader, sizeof(uint16_t));
> + dns_answer_record->weight = ntohs(dns_answer_record->weight);

I guess there's an alignment issue there, am I right ?

I think we'll have to add some easier conversion functions, like unaligned
reads using something like this to make such manipulation less of a pain :

static inline uint16_t readu16(const void *p)
{
union { const void *p; uint16_t u16; } __attribute__((packed)) u = { .p = p };
return u.u16;
}

static inline uint16_t readbe16(const void *p)
{
return ((unsigned char *)p)[0] << 8 +
((unsigned char *)p)[1];
}

The the code above simply becomes :

reader += sizeof(uint16_t);
dns_answer_record->weight = readbe16(reader);

and for cases where native-endian word is needed instead :

reader += sizeof(uint16_t);
dns_answer_record->weight = readu16(reader);

> @@ -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++) {

Note, "for (int i" here as well.

> From 899413f3a965feeaa96ffb5b284b504dbdb01fa1 Mon Sep 17 00:00:00 2001
> From: Olivier Houchard <[email protected]>
> 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;

Do you think this one might be triggered before SRV records ? If so
we'd need to tag it for backporting. If you're unsure we should also
backport it, of course :-)

Thanks!
Willy
Jerry Scharf
Re: [PATCHES] SRV record support
August 04, 2017 10:00PM
Willy,

I can't answer for the code but I can talk about SRV records. I helped
bury some of the bodies in DNS. :)

A SRV record in DNS is a different type of record, like MX, PTR, TXT,...

You can think of SRV as MX generalized for any listener. The naming form
is _http._tcp to keep it from colliding with names, but that was mostly
for clarity as requested by the RFC editor. In the DNS protocol you will
never get SRV records when doing normal host lookup (A/AAAA records.)
When you ask for SRV records, you may also get back the A/AAAA records
for the hosts it points to as "additional data."

jerry


On 8/4/17 12:18 PM, Willy Tarreau wrote:
> Just a few questions and minor comments below :
>
> On Fri, Aug 04, 2017 at 06:49:43PM +0200, Olivier Houchard wrote:
>> 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.
> What makes the distinction between an SRV record and a real hostname here ?
> Just the leading underscore, or the plain "_http." maybe ? I'm not expecting
> any problem with this given that the underscore is not allowed as a regular
> hostname character (except under windows). But at least this will deserve
> a mention in the doc where the server's address is described, so that anyone
> experiencing trouble could spot this easily.
>
>> From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
>> From: Olivier Houchard <[email protected]>
>> 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
> Do you have an estimate of the worst case increase of memory usage incurred
> by using the max name size for every name component ? I understand that it
> might not be possible to use a shared area anymore for all entries once you
> start to deal with obsolescence, but it's just to get an idea of what the
> worst DNS response could have as impact.
>
>> @@ -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 */
> Same here.
>
>> @@ -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++) {
> "for (int i ...)" will not build here, it's only C99 and we don't enforce
> this (and personally I hate this form as it's the only exception I know
> of where a variable declaration is used outside of the scope it's declared
> in.
>
>> @@ -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;
> I'm just realizing that when I merge the minimalist indirect string
> manipulation functions I'm using for HTTP/2, it will simplify such
> code, as dealing all the time with memcpy() and memcmp() for strings
> is a pain. It will also give me ideas of what to improve there before
> it represents a perceptible benefit. We'll probably see this for 1.9.
>
>> From a2e3bba11a5c1a3c69e9f33c566d7c285c9121f2 Mon Sep 17 00:00:00 2001
>> From: Olivier Houchard <[email protected]>
>> 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/src/dns.c b/src/dns.c
>> index 0ce63c91..2931752a 100644
>> --- a/src/dns.c
>> +++ b/src/dns.c
>> @@ -478,14 +497,104 @@ void dns_resolve_recv(struct dgram_conn *dgram)
>> break;
> (...)
>> + /* 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);
>
> Next time, you can use ultoa() as a replacement for snprintf(str, size, "%d"),
> which also ensures you'll use a large enough string for the largest supported
> integer types, there's also U2A() which uses a rotating buffer and which can
> be used multiple times in a single function call.
>
>> + reader += sizeof(uint16_t);
>> + memcpy(&dns_answer_record->weight,
>> + reader, sizeof(uint16_t));
>> + dns_answer_record->weight = ntohs(dns_answer_record->weight);
> I guess there's an alignment issue there, am I right ?
>
> I think we'll have to add some easier conversion functions, like unaligned
> reads using something like this to make such manipulation less of a pain :
>
> static inline uint16_t readu16(const void *p)
> {
> union { const void *p; uint16_t u16; } __attribute__((packed)) u = { .p = p };
> return u.u16;
> }
>
> static inline uint16_t readbe16(const void *p)
> {
> return ((unsigned char *)p)[0] << 8 +
> ((unsigned char *)p)[1];
> }
>
> The the code above simply becomes :
>
> reader += sizeof(uint16_t);
> dns_answer_record->weight = readbe16(reader);
>
> and for cases where native-endian word is needed instead :
>
> reader += sizeof(uint16_t);
> dns_answer_record->weight = readu16(reader);
>
>> @@ -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++) {
> Note, "for (int i" here as well.
>
>> From 899413f3a965feeaa96ffb5b284b504dbdb01fa1 Mon Sep 17 00:00:00 2001
>> From: Olivier Houchard <[email protected]>
>> 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;
> Do you think this one might be triggered before SRV records ? If so
> we'd need to tag it for backporting. If you're unsure we should also
> backport it, of course :-)
>
> Thanks!
> Willy
>

--
Soundhound Devops
"What could possibly go wrong?"
Willy Tarreau
Re: [PATCHES] SRV record support
August 04, 2017 10:30PM
Hi Jerry,

On Fri, Aug 04, 2017 at 12:47:51PM -0700, Jerry Scharf wrote:
> Willy,
>
> I can't answer for the code but I can talk about SRV records. I helped bury
> some of the bodies in DNS. :)
>
> A SRV record in DNS is a different type of record, like MX, PTR, TXT,...
>
> You can think of SRV as MX generalized for any listener. The naming form is
> _http._tcp to keep it from colliding with names, but that was mostly for
> clarity as requested by the RFC editor. In the DNS protocol you will never
> get SRV records when doing normal host lookup (A/AAAA records.) When you ask
> for SRV records, you may also get back the A/AAAA records for the hosts it
> points to as "additional data."

OK but what I meant is that I suspected the type of request was altered
by the naming, though I can be wrong. I don't know well the DNS code
here. I'm aware however of the naming to avoid collisions :-)

Thanks,
Willy
Olivier Houchard
Re: [PATCHES] SRV record support
August 07, 2017 07:40PM
Hi,

On Fri, Aug 04, 2017 at 09:18:30PM +0200, Willy Tarreau wrote:
> Just a few questions and minor comments below :
>
> On Fri, Aug 04, 2017 at 06:49:43PM +0200, Olivier Houchard wrote:
> > 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.
>
> What makes the distinction between an SRV record and a real hostname here ?
> Just the leading underscore, or the plain "_http." maybe ? I'm not expecting
> any problem with this given that the underscore is not allowed as a regular
> hostname character (except under windows). But at least this will deserve
> a mention in the doc where the server's address is described, so that anyone
> experiencing trouble could spot this easily.
>

Yes, whatever starts with an underscore, I didn't want to rely on _http, as
it may not be http. As underscores aren't supposed to be present in hostnames,
and I certainly hope even people using them don't have hostname starting with
one, I thought it was OK.
It is documented in the updated patches attached.

> > From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
> > From: Olivier Houchard <[email protected]>
> > 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
>
> Do you have an estimate of the worst case increase of memory usage incurred
> by using the max name size for every name component ? I understand that it
> might not be possible to use a shared area anymore for all entries once you
> start to deal with obsolescence, but it's just to get an idea of what the
> worst DNS response could have as impact.
>
> > @@ -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 */
>
> Same here.
>

Not really, I don't think it'll matter much, there shouldn't be that many
DNS answers, but I have no measure.

> > @@ -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++) {
>
> "for (int i ...)" will not build here, it's only C99 and we don't enforce
> this (and personally I hate this form as it's the only exception I know
> of where a variable declaration is used outside of the scope it's declared
> in.
>

Bah, I happen to like it, but I understand C99 is still a bit experimental,
so it's gone in the updated patch set.

> > @@ -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;
>
> I'm just realizing that when I merge the minimalist indirect string
> manipulation functions I'm using for HTTP/2, it will simplify such
> code, as dealing all the time with memcpy() and memcmp() for strings
> is a pain. It will also give me ideas of what to improve there before
> it represents a perceptible benefit. We'll probably see this for 1.9.
>
> > From a2e3bba11a5c1a3c69e9f33c566d7c285c9121f2 Mon Sep 17 00:00:00 2001
> > From: Olivier Houchard <[email protected]>
> > 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/src/dns.c b/src/dns.c
> > index 0ce63c91..2931752a 100644
> > --- a/src/dns.c
> > +++ b/src/dns.c
> > @@ -478,14 +497,104 @@ void dns_resolve_recv(struct dgram_conn *dgram)
> > break;
> (...)
> > + /* 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);
>
>
> Next time, you can use ultoa() as a replacement for snprintf(str, size, "%d"),
> which also ensures you'll use a large enough string for the largest supported
> integer types, there's also U2A() which uses a rotating buffer and which can
> be used multiple times in a single function call.
>

Hmm, if we ever want to move to a multithreaded version, I'd rather not use
ultoa() :)

> > + reader += sizeof(uint16_t);
> > + memcpy(&dns_answer_record->weight,
> > + reader, sizeof(uint16_t));
> > + dns_answer_record->weight = ntohs(dns_answer_record->weight);
>
> I guess there's an alignment issue there, am I right ?
>

You're right.

> I think we'll have to add some easier conversion functions, like unaligned
> reads using something like this to make such manipulation less of a pain :
>
> static inline uint16_t readu16(const void *p)
> {
> union { const void *p; uint16_t u16; } __attribute__((packed)) u = { .p = p };
> return u.u16;
> }
>
> static inline uint16_t readbe16(const void *p)
> {
> return ((unsigned char *)p)[0] << 8 +
> ((unsigned char *)p)[1];
> }
>
> The the code above simply becomes :
>
> reader += sizeof(uint16_t);
> dns_answer_record->weight = readbe16(reader);
>
> and for cases where native-endian word is needed instead :
>
> reader += sizeof(uint16_t);
> dns_answer_record->weight = readu16(reader);
>

Ok you got me convinced gcc can do the right thing for architectures that
have hard alignment requirements, so as we discussed, the updated patch
provides those kind of helper functions.

> > @@ -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++) {
>
> Note, "for (int i" here as well.
>

Fixed.

> > From 899413f3a965feeaa96ffb5b284b504dbdb01fa1 Mon Sep 17 00:00:00 2001
> > From: Olivier Houchard <[email protected]>
> > 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;
>
> Do you think this one might be triggered before SRV records ? If so
> we'd need to tag it for backporting. If you're unsure we should also
> backport it, of course :-)
>

That is a good question, I think not, but I wouldn't swear it :)

Regards,

Olivier
From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Thu, 6 Jul 2017 18:46:47 +0200
Subject: [PATCH 1/6] 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 <[email protected]>
Date: Fri, 4 Aug 2017 18:31:56 +0200
Subject: [PATCH 2/6] 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 c402aab0752b953d4601abb7959e43de867a26b7 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Mon, 7 Aug 2017 19:20:04 +0200
Subject: [PATCH 3/6] [MINOR]: Add a few functions to do unaligned access.

Add a few functions to read 16bits and 32bits integers that may be
unaligned, both in host and network order.
---
include/common/net_helper.h | 75 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 include/common/net_helper.h

diff --git a/include/common/net_helper.h b/include/common/net_helper.h
new file mode 100644
index 00000000..d88641d9
--- /dev/null
+++ b/include/common/net_helper.h
@@ -0,0 +1,75 @@
+/*
+ * include/proto/proto_tcp.h
+ * This file contains miscellaneous network helper functions.
+ *
+ * Copyright (C) 2017 Olivier Houchard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _COMMON_NET_HELPER_H
+#define _COMMON_NET_HELPER_H
+
+#include <arpa/inet.h>
+
+/* Functions to read various integer that may be unaligned */
+
+/* Read a uint16_t */
+uint16_t readu16(const void *p)
+{
+ const union { uint16_t u16; } __attribute__((packed))*u = p;
+ return u->u16;
+}
+
+/* Read a int16_t */
+int16_t readi16(const void *p)
+{
+ const union { int16_t i16; } __attribute__((packed))*u = p;
+ return u->i16;
+}
+
+/* Read a uint16_t, and convert from network order to host order */
+uint16_t readn16(const void *p)
+{
+ const union { uint16_t u16; } __attribute__((packed))*u = p;
+ return ntohs(u->u16);
+}
+
+/* Read a uint32_t */
+uint32_t readu32(const void *p)
+{
+ const union { uint32_t u32; } __attribute__((packed))*u = p;
+ return u->u32;
+}
+
+/* Read a int32_t */
+int16_t readi32(const void *p)
+{
+ const union { int32_t i32; } __attribute__((packed))*u = p;
+ return u->i32;
+}
+
+/* Read a uint32_t, and convert from network order to host order */
+uint32_t readn32(const void *p)
+{
+ const union { uint32_t u32; } __attribute__((packed))*u = p;
+ return ntohl(u->u32);
+}
+
+#endif /* COMMON_NET_HELPER_H */
--
2.13.3

From 34d678c8dd9002547edb11112dc851406067cb92 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Fri, 4 Aug 2017 18:35:36 +0200
Subject: [PATCH 4/6] 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 | 312 ++++++++++++++++++++++++++++++++++++++++++-------
src/proxy.c | 1 +
src/server.c | 96 ++++++++++++---
9 files changed, 393 insertions(+), 58 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..00f7b10c 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -21,6 +21,7 @@

#include <common/time.h>
#include <common/ticks.h>
+#include <common/net_helper.h>

#include <import/lru.h>
#include <import/xxhash.h>
@@ -153,6 +154,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 +217,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 +371,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 +498,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 +686,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 +818,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 +890,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 +908,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:
;;
@@ -911,6 +1032,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
struct dns_answer_item *dns_answer_record, *tmp_record;
struct dns_response_packet *dns_p;
int found = 0;
+ int i;

reader = resp;
len = 0;
@@ -1028,7 +1150,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct

/* now parsing response records */
nb_saved_records = 0;
- for (int i = 0; i < dns_p->header.ancount; i++) {
+ for (i = 0; i < dns_p->header.ancount; i++) {
if (reader >= bufend)
return DNS_RESP_INVALID;

@@ -1151,6 +1273,36 @@ 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;
+ }
+ dns_answer_record->priority = readn16(reader);
+ reader += sizeof(uint16_t);
+ dns_answer_record->weight = readn16(reader);
+ reader += sizeof(uint16_t);
+ dns_answer_record->port = readn16(reader);
+ 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 +1324,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 +1339,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 +1797,28 @@ 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;
+ int i;
+
+ for (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 +2011,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 +2041,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 +2152,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 +2217,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 +2237,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 +2263,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 +2301,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 +2358,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 +2480,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 +2512,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 +2529,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 e423d3cb66c336560f28d5128e62cc96ffc8d1ec Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Fri, 4 Aug 2017 18:39:01 +0200
Subject: [PATCH 5/6] 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

From 2423d384886d21f190592a14d9a1359be266ead4 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Mon, 7 Aug 2017 17:30:03 +0200
Subject: [PATCH 6/6] MINOR: doc: Document SRV label usage.

---
doc/configuration.txt | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index f4674387..b62f367a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11637,6 +11637,13 @@ A few other events can trigger a name resolution at run time:
because the server has a new IP address. So we need to trigger a name
resolution to know this new IP.

+When using resolvers, the server name can either be a hostname, or s SRV label.
+HAProxy considers anything that starts with an underscore a SRV label.
+If a SRV label is specified, then the corresponding SRV records will be
+retrieved from the DNS server, and the provided hostnames will be used. The
+SRV lable will be checked periodically, and if any server are added or removed,
+haproxy will automatically do the same.
+
A few things important to notice:
- all the name servers are queried in the mean time. HAProxy will process the
first valid response.
--
2.13.3
Willy Tarreau
Re: [PATCHES] SRV record support
August 07, 2017 08:00PM
Hi Olivier,

On Mon, Aug 07, 2017 at 07:29:23PM +0200, Olivier Houchard wrote:
> > What makes the distinction between an SRV record and a real hostname here ?
> > Just the leading underscore, or the plain "_http." maybe ? I'm not expecting
> > any problem with this given that the underscore is not allowed as a regular
> > hostname character (except under windows). But at least this will deserve
> > a mention in the doc where the server's address is described, so that anyone
> > experiencing trouble could spot this easily.
> >
>
> Yes, whatever starts with an underscore, I didn't want to rely on _http, as
> it may not be http. As underscores aren't supposed to be present in hostnames,
> and I certainly hope even people using them don't have hostname starting with
> one, I thought it was OK.
> It is documented in the updated patches attached.

OK fine, thanks!

> > > - 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++) {
> >
> > "for (int i ...)" will not build here, it's only C99 and we don't enforce
> > this (and personally I hate this form as it's the only exception I know
> > of where a variable declaration is used outside of the scope it's declared
> > in.
> >
>
> Bah, I happen to like it, but I understand C99 is still a bit experimental,

Not experimental, it's in the future... on some of my machines :-)

> so it's gone in the updated patch set.

Thank you.

> > > + char weight[9];
> > > +
> > > + snprintf(weight, sizeof(weight),
> > > + "%d", item1->weight);
> > > + server_parse_weight_change_request(srv, weight);
> >
> >
> > Next time, you can use ultoa() as a replacement for snprintf(str, size, "%d"),
> > which also ensures you'll use a large enough string for the largest supported
> > integer types, there's also U2A() which uses a rotating buffer and which can
> > be used multiple times in a single function call.
> >
>
> Hmm, if we ever want to move to a multithreaded version, I'd rather not use
> ultoa() :)

Good point but no in fact. It's used a lot (via U2A and I don't remember
which one as well) for stats dump because it's convenient. I understood
that the buffers were simply made thread-local instead of global. They're
very small, nobody will notice.

> > I think we'll have to add some easier conversion functions, like unaligned
> > reads using something like this to make such manipulation less of a pain :
> >
> > static inline uint16_t readu16(const void *p)
> > {
> > union { const void *p; uint16_t u16; } __attribute__((packed)) u = { .p = p };
> > return u.u16;
> > }
> >
> > static inline uint16_t readbe16(const void *p)
> > {
> > return ((unsigned char *)p)[0] << 8 +
> > ((unsigned char *)p)[1];
> > }
> >
> > The the code above simply becomes :
> >
> > reader += sizeof(uint16_t);
> > dns_answer_record->weight = readbe16(reader);
> >
> > and for cases where native-endian word is needed instead :
> >
> > reader += sizeof(uint16_t);
> > dns_answer_record->weight = readu16(reader);
> >
>
> Ok you got me convinced gcc can do the right thing for architectures that
> have hard alignment requirements, so as we discussed, the updated patch
> provides those kind of helper functions.

Thank you, this way we may even think about doing some cleanup passes
on other areas before the release :-)

> > > From 899413f3a965feeaa96ffb5b284b504dbdb01fa1 Mon Sep 17 00:00:00 2001
> > > From: Olivier Houchard <[email protected]>
> > > 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;
> >
> > Do you think this one might be triggered before SRV records ? If so
> > we'd need to tag it for backporting. If you're unsure we should also
> > backport it, of course :-)
> >
>
> That is a good question, I think not, but I wouldn't swear it :)

OK, let's not backport it then.

Thanks. I'll wait for Baptiste's ack and will take it if OK.
Willy
Olivier Houchard
Re: [PATCHES] SRV record support
August 09, 2017 04:10PM
Hi,

After some review and tests by Baptiste, here comes an updated patchset,
with a few bugfixes.
This one is probably mergeable.

Regards,

Olivier
From 1b408464590fea38d8a45b2b7fed5c615465a858 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Thu, 6 Jul 2017 18:46:47 +0200
Subject: [PATCH 1/6] 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 <[email protected]>
Date: Fri, 4 Aug 2017 18:31:56 +0200
Subject: [PATCH 2/6] 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 bb29aa5f46162a797d85a438d1d5e43f6eabc2cf Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Mon, 7 Aug 2017 19:20:04 +0200
Subject: [PATCH 3/6] [MINOR]: Add a few functions to do unaligned access.

Add a few functions to read 16bits and 32bits integers that may be
unaligned, both in host and network order.
---
include/common/net_helper.h | 75 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 include/common/net_helper.h

diff --git a/include/common/net_helper.h b/include/common/net_helper.h
new file mode 100644
index 00000000..86809a68
--- /dev/null
+++ b/include/common/net_helper.h
@@ -0,0 +1,75 @@
+/*
+ * include/common/net_helper.h
+ * This file contains miscellaneous network helper functions.
+ *
+ * Copyright (C) 2017 Olivier Houchard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _COMMON_NET_HELPER_H
+#define _COMMON_NET_HELPER_H
+
+#include <arpa/inet.h>
+
+/* Functions to read various integer that may be unaligned */
+
+/* Read a uint16_t */
+uint16_t readu16(const void *p)
+{
+ const union { uint16_t u16; } __attribute__((packed))*u = p;
+ return u->u16;
+}
+
+/* Read a int16_t */
+int16_t readi16(const void *p)
+{
+ const union { int16_t i16; } __attribute__((packed))*u = p;
+ return u->i16;
+}
+
+/* Read a uint16_t, and convert from network order to host order */
+uint16_t readn16(const void *p)
+{
+ const union { uint16_t u16; } __attribute__((packed))*u = p;
+ return ntohs(u->u16);
+}
+
+/* Read a uint32_t */
+uint32_t readu32(const void *p)
+{
+ const union { uint32_t u32; } __attribute__((packed))*u = p;
+ return u->u32;
+}
+
+/* Read a int32_t */
+int16_t readi32(const void *p)
+{
+ const union { int32_t i32; } __attribute__((packed))*u = p;
+ return u->i32;
+}
+
+/* Read a uint32_t, and convert from network order to host order */
+uint32_t readn32(const void *p)
+{
+ const union { uint32_t u32; } __attribute__((packed))*u = p;
+ return ntohl(u->u32);
+}
+
+#endif /* COMMON_NET_HELPER_H */
--
2.13.3

From a3e3b12137ae8219e2e90ce3b614d36bd098cece Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Fri, 4 Aug 2017 18:35:36 +0200
Subject: [PATCH 4/6] 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 | 312 ++++++++++++++++++++++++++++++++++++++++++-------
src/proxy.c | 1 +
src/server.c | 98 +++++++++++++---
9 files changed, 395 insertions(+), 58 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..00f7b10c 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -21,6 +21,7 @@

#include <common/time.h>
#include <common/ticks.h>
+#include <common/net_helper.h>

#include <import/lru.h>
#include <import/xxhash.h>
@@ -153,6 +154,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 +217,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 +371,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 +498,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 +686,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 +818,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 +890,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 +908,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:
;;
@@ -911,6 +1032,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct
struct dns_answer_item *dns_answer_record, *tmp_record;
struct dns_response_packet *dns_p;
int found = 0;
+ int i;

reader = resp;
len = 0;
@@ -1028,7 +1150,7 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct

/* now parsing response records */
nb_saved_records = 0;
- for (int i = 0; i < dns_p->header.ancount; i++) {
+ for (i = 0; i < dns_p->header.ancount; i++) {
if (reader >= bufend)
return DNS_RESP_INVALID;

@@ -1151,6 +1273,36 @@ 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;
+ }
+ dns_answer_record->priority = readn16(reader);
+ reader += sizeof(uint16_t);
+ dns_answer_record->weight = readn16(reader);
+ reader += sizeof(uint16_t);
+ dns_answer_record->port = readn16(reader);
+ 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 +1324,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 +1339,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 +1797,28 @@ 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;
+ int i;
+
+ for (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 +2011,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 +2041,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 +2152,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 +2217,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 +2237,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 +2263,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 +2301,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 +2358,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 +2480,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 +2512,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 +2529,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..cfbdb3d7 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1820,6 +1820,8 @@ static void srv_settings_cpy(struct server *srv, struct server *src, int srv_tmp
#ifdef TCP_USER_TIMEOUT
srv->tcp_ut = src->tcp_ut;
#endif
+ if (srv_tmpl)
+ srv->srvrq = src->srvrq;
}

static struct server *new_server(struct proxy *proxy)
@@ -2285,18 +2287,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 +2580,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 +4097,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 +4120,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 +4146,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 +4289,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 f4dcf59ac931a610f08042ca3d71119efe2c4c3f Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Fri, 4 Aug 2017 18:39:01 +0200
Subject: [PATCH 5/6] 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

From 4a8db4f2d0b35ea633ded8decb7c6f147189d325 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <[email protected]>
Date: Mon, 7 Aug 2017 17:30:03 +0200
Subject: [PATCH 6/6] MINOR: doc: Document SRV label usage.

---
doc/configuration.txt | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index f4674387..731b2857 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11637,6 +11637,13 @@ A few other events can trigger a name resolution at run time:
because the server has a new IP address. So we need to trigger a name
resolution to know this new IP.

+When using resolvers, the server name can either be a hostname, or s SRV label.
+HAProxy considers anything that starts with an underscore a SRV label.
+If a SRV label is specified, then the corresponding SRV records will be
+retrieved from the DNS server, and the provided hostnames will be used. The
+SRV label will be checked periodically, and if any server are added or removed,
+haproxy will automatically do the same.
+
A few things important to notice:
- all the name servers are queried in the mean time. HAProxy will process the
first valid response.
--
2.13.3
Willy Tarreau
Re: [PATCHES] SRV record support
August 09, 2017 04:50PM
On Wed, Aug 09, 2017 at 04:00:04PM +0200, Olivier Houchard wrote:
>
> Hi,
>
> After some review and tests by Baptiste, here comes an updated patchset,
> with a few bugfixes.
> This one is probably mergeable.

Now merged, thanks for this! I'm sure some people will quickly have
interesting ideas to use it (and Baptiste will probably want to build
magic setups to impress us :-)).

Willy
Baptiste
Re: [PATCHES] SRV record support
August 09, 2017 05:40PM
On Wed, Aug 9, 2017 at 4:40 PM, Willy Tarreau <[email protected]> wrote:

> On Wed, Aug 09, 2017 at 04:00:04PM +0200, Olivier Houchard wrote:
> >
> > Hi,
> >
> > After some review and tests by Baptiste, here comes an updated patchset,
> > with a few bugfixes.
> > This one is probably mergeable.
>
> Now merged, thanks for this! I'm sure some people will quickly have
> interesting ideas to use it (and Baptiste will probably want to build
> magic setups to impress us :-)).
>
> Willy
>
>

Yep. I count on my friend, Kubernetes, to use this new toy-ish feature :)

Baptiste
Sorry, only registered users may post in this forum.

Click here to login