Hi,

attached is my shot at supporting negotiating UDP message sizes > 512 bytes
in the Haproxy DNS implementation. The default DNS size of 512 bytes can
often lead to truncated responses, which are discarded.

With the attached patch and adding "udp_msg_size 2048" to my resolvers
section, I can succesfully use a hostname that yields ~100 A records in our
company network, something that did not work before.

This was an approach suggested by Baptiste a long time ago, unfortunately I
got distracted with other things for a while. Nevertheless, I'd also like
to discuss

a) whether this is really a sufficient replacement for DNS over TCP support
b) why truncated responses are immediately discarded, even if they contain
   one or more records that satisfy the original request

But such discussion could optionally be moved to a seperate thread.

Thanks for all the hard work,
Conrad
-- 
Conrad Hoffmann
Traffic Engineer

SoundCloud Ltd. | Rheinsberger Str. 76/77, 10115 Berlin, Germany

Managing Director: Alexander Ljung | Incorporated in England & Wales
with Company No. 6343600 | Local Branch Office | AG Charlottenburg |
HRB 110657B
From 460f879bdb669bfe0f389269113fbfbdfbec3e8f Mon Sep 17 00:00:00 2001
From: Conrad Hoffmann <con...@soundcloud.com>
Date: Thu, 10 Sep 2015 11:52:27 +0200
Subject: [PATCH] MINOR: dns: support advertising UDP message size.

By default, DNS has a maximum UDP message size of 512 bytes. OPT records are an
RFC compliant way of advertising the client's support for larger messages.

This commit adds a new config option "udp_msg_size" to the "resolvers" section.
It takes a single integer argument. If used, the value is advertised as message
size in an OPT record sent with each query.

If unsure, 2048 is a good value to start, as it is the minimum size expected to
be handled by DNS servers supporting the EDNS(0) extensions (of which OPT
records are a part).
---
 doc/configuration.txt | 15 +++++++++++++++
 include/proto/dns.h   |  2 +-
 include/types/dns.h   | 11 +++++++++++
 src/cfgparse.c        | 10 ++++++++++
 src/dns.c             | 25 ++++++++++++++++++++++---
 5 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 45e2e06..0382131 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11126,6 +11126,21 @@ timeout <event> <time>
      <time>  : time related to the event. It follows the HAProxy time format.
                <time> is expressed in milliseconds.
 
+udp_msg_size <nb>
+  Advertise a maximum UDP message size of <nb> bytes to a server when
+  resolving names.
+  Default value: 0 (do not advertise anything)
+
+  Standard DNS uses 512 bytes as maximum UDP message size. This can be too
+  small for answers containing a lot or response records. A client can
+  advertise support for larger message sizes by including an OPT record in
+  the query, specifying a new maximum message size. Setting this to a value
+  > 0 causes haproxy to advertise this value with each query.
+
+  Note that setting this to an arbitrarily large value might not be helpful.
+  If needed, start with 2048, which is the minimum size supported by all
+  servers that support this DNS extension.
+
 Example of a resolvers section (with default values):
 
    resolvers mydns
diff --git a/include/proto/dns.h b/include/proto/dns.h
index 170eefa..e121e5f 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -28,7 +28,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);
 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);
+int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostname_dn_len, int udp_msg_size, char *buf, int bufsize);
 struct task *dns_process_resolve(struct task *t);
 int dns_init_resolvers(void);
 uint16_t dns_rnd16(void);
diff --git a/include/types/dns.h b/include/types/dns.h
index 50636fd..88f95c7 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -44,6 +44,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_OPT		41	/* OPT record */
 #define DNS_RTYPE_ANY		255	/* all records */
 
 /* dns rcode values */
@@ -85,6 +86,15 @@ struct dns_question {
 	unsigned short	qclass;		/* query class */
 };
 
+/* structure to describe a DNS OPT record */
+struct dns_opt_record {
+	unsigned short	qtype;		/* record type (41) */
+	unsigned short	qclass;		/* requestor's UDP payload size */
+	unsigned int	ttl;		/* extended RCODE and flags */
+	unsigned short	rdlen;		/* length of RDATA */
+	unsigned char	rdata[0];	/* {attribute,value} pairs */
+};
+
 /*
  * resolvers section and parameters. It is linked to the name servers
  * servers points to it.
@@ -100,6 +110,7 @@ struct dns_resolvers {
 	struct list nameserver_list;	/* dns server list */
 	int count_nameservers;			/* total number of nameservers in a resolvers section */
 	int resolve_retries;		/* number of retries before giving up */
+	int udp_msg_size;		/* Max. UDP message size to advertise, 0 means do not send OPT record */
 	struct {			/* time to: */
 		int retry;		/*   wait for a response before retrying */
 	} timeout;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index d9afd84..f744ec7 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2334,6 +2334,7 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
 		curr_resolvers->hold.valid = 10000;
 		curr_resolvers->timeout.retry = 1000;
 		curr_resolvers->resolve_retries = 3;
+		curr_resolvers->udp_msg_size = 0;
 		LIST_INIT(&curr_resolvers->nameserver_list);
 		LIST_INIT(&curr_resolvers->curr_resolution);
 	}
@@ -2448,6 +2449,15 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
 		}
 		curr_resolvers->resolve_retries = atoi(args[1]);
 	}
+	else if (strcmp(args[0], "udp_msg_size") == 0) {
+		if (!*args[1]) {
+			Alert("parsing [%s:%d] : '%s' expects <nb> as argument.\n",
+				file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		curr_resolvers->udp_msg_size = atoi(args[1]);
+	}
 	else if (strcmp(args[0], "timeout") == 0) {
 		if (!*args[1]) {
 			Alert("parsing [%s:%d] : '%s' expects 'retry' and <time> as arguments.\n",
diff --git a/src/dns.c b/src/dns.c
index 3b3dfc5..d2293c9 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -286,7 +286,7 @@ int dns_send_query(struct dns_resolution *resolution)
 
 	ret = send_error = 0;
 	bufsize = dns_build_query(resolution->query_id, resolution->query_type, resolution->hostname_dn,
-			resolution->hostname_dn_len, trash.str, trash.size);
+			resolution->hostname_dn_len, resolvers->udp_msg_size, trash.str, trash.size);
 
 	if (bufsize == -1)
 		return 0;
@@ -965,16 +965,18 @@ int dns_init_resolvers(void)
  *  - <query_type>: DNS_RTYPE_* request DNS record type (A, AAAA, ANY, etc...)
  *  - <hostname_dn>: hostname in domain name format
  *  - <hostname_dn_len>: length of <hostname_dn>
+ *  - <udp_msg_size>: If > 0, advertise this value as max UDP message size in OPT record
  * To store the query, the caller must pass a buffer <buf> and its size <bufsize>
  *
  * the DNS query is stored in <buf>
  * returns:
  *  -1 if <buf> is too short
  */
-int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostname_dn_len, char *buf, int bufsize)
+int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostname_dn_len, int udp_msg_size, char *buf, int bufsize)
 {
 	struct dns_header *dns;
 	struct dns_question qinfo;
+	struct dns_opt_record opt;
 	char *ptr, *bufend;
 
 	memset(buf, '\0', bufsize);
@@ -999,7 +1001,7 @@ int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostnam
 	dns->qdcount = htons(1);	/* 1 question */
 	dns->ancount = 0;
 	dns->nscount = 0;
-	dns->arcount = 0;
+	dns->arcount = udp_msg_size > 0 ? htons(1) : 0;
 
 	/* move forward ptr */
 	ptr += sizeof(struct dns_header);
@@ -1026,6 +1028,23 @@ int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostnam
 
 	ptr += sizeof(struct dns_question);
 
+	if (udp_msg_size > 0) {
+		/* check if there is enough room for OPT record*/
+		if (ptr + sizeof(struct dns_opt_record) + 1 >= bufend)
+			return -1;
+
+		/* set up OPT record */
+		*ptr = '\0'; /* root must be NULL for OPT records. */
+		ptr += 1;
+		opt.qtype = htons(DNS_RTYPE_OPT);
+		opt.qclass = htons(udp_msg_size);
+		opt.ttl = 0;
+		opt.rdlen = 0;
+		memcpy(ptr, &opt, sizeof(opt));
+
+		ptr += sizeof(struct dns_opt_record);
+	}
+
 	return ptr - buf;
 }
 
-- 
2.9.0

Reply via email to