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