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