From 2af08fe3de1a816bed2ec7b68064b6361a76bcce Mon Sep 17 00:00:00 2001 From: Baptiste Assmann Date: Mon, 14 Aug 2017 00:13:01 +0200 Subject: [PATCH] MINOR: dns: enabled edns0 extension and make accpeted payload size tunable Edns extensions may be used to negotiate some settings between a DNS client and a server. For now we only use it to announce the maximum response payload size accpeted by HAProxy. This size can be set through a configuration parameter in the resolvers section. If not set, it defaults to 512 bytes. --- doc/configuration.txt | 9 +++++++++ include/proto/dns.h | 2 +- include/types/dns.h | 15 +++++++++++++++ src/cfgparse.c | 11 +++++++++++ src/dns.c | 22 ++++++++++++++++++---- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 731b2857e3..8aafbe3ce6 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -11710,6 +11710,15 @@ resolvers A resolvers section accept the following parameters: +accepted_payload_size + Defines the maxium payload size accepted by HAProxy and announced to all the + naeservers configured in this resolvers section. + is in bytes. If not set, HAProxy announces 512. (minimal value defined + by RFC 6891) + + Note: to get biggers response but still be sure that responses won't be + dropped on the wire, one can choose a value between 1280 and 1410. + nameserver : DNS server description: : label of the server, should be unique diff --git a/include/proto/dns.h b/include/proto/dns.h index a84f07c4a3..5aed46e78c 100644 --- a/include/proto/dns.h +++ b/include/proto/dns.h @@ -29,7 +29,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); +int dns_build_query(int query_id, int query_type, unsigned int accepted_payload_size, char *hostname_dn, int hostname_dn_len, char *buf, int bufsize); struct task *dns_process_resolve(struct task *t); int dns_init_resolvers(int close_socket); uint16_t dns_rnd16(void); diff --git a/include/types/dns.h b/include/types/dns.h index 9bf3c7e917..0f9c1b9707 100644 --- a/include/types/dns.h +++ b/include/types/dns.h @@ -112,6 +112,20 @@ struct dns_query_item { unsigned short class; /* query class */ }; +/* NOTE: big endian structure */ +struct dns_additional_record { + uint8_t name; /* domain name, must be 0 (RFC 6891) */ + uint16_t type; /* record type DNS_RTYPE_OPT (41) */ + uint16_t udp_payload_size; /* maximum size accepted for the response */ + uint32_t extension; /* extended rcode and flags, not used for now */ + uint16_t data_length; /* data length */ +/* as of today, we don't support yet edns options, that said I already put a placeholder here + * for this purpose. We may need to define a dns_option_record structure which itself should + * point to different type of data, based on the extension set (client subnet, tcp keepalive, + * etc...)*/ +// struct list options; /* list of option records */ +} __attribute__ ((packed)); + /* NOTE: big endian structure */ struct dns_answer_item { struct list list; @@ -150,6 +164,7 @@ struct dns_resolvers { int line; /* line where the section appears */ } conf; /* config information */ struct list nameserver_list; /* dns server list */ + unsigned int accepted_payload_size; /* maximum payload size we accept for responses */ int count_nameservers; /* total number of nameservers in a resolvers section */ int resolve_retries; /* number of retries before giving up */ struct { /* time to: */ diff --git a/src/cfgparse.c b/src/cfgparse.c index a8e54aa78c..eebd72c114 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2162,6 +2162,8 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm) curr_resolvers->conf.line = linenum; curr_resolvers->id = strdup(args[1]); curr_resolvers->query_ids = EB_ROOT; + /* default maximum response size */ + curr_resolvers->accepted_payload_size = 512; /* default hold period for nx, other, refuse and timeout is 30s */ curr_resolvers->hold.nx = 30000; curr_resolvers->hold.other = 30000; @@ -2291,6 +2293,15 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm) } } + else if (strcmp(args[0], "accepted_payload_size") == 0) { + if (!*args[1]) { + Alert("parsing [%s:%d] : '%s' expects as argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curr_resolvers->accepted_payload_size = atoi(args[1]); + } else if (strcmp(args[0], "resolution_pool_size") == 0) { if (!*args[1]) { Alert("parsing [%s:%d] : '%s' expects as argument.\n", diff --git a/src/dns.c b/src/dns.c index 09ba8f687e..d46160fcf7 100644 --- a/src/dns.c +++ b/src/dns.c @@ -897,8 +897,8 @@ int dns_send_query(struct dns_resolution *resolution) if (!resolvers) return 0; - bufsize = dns_build_query(resolution->query_id, resolution->query_type, resolution->hostname_dn, - resolution->hostname_dn_len, trash.str, trash.size); + bufsize = dns_build_query(resolution->query_id, resolution->query_type, resolvers->accepted_payload_size, + resolution->hostname_dn, resolution->hostname_dn_len, trash.str, trash.size); if (bufsize == -1) return 0; @@ -1820,10 +1820,11 @@ int dns_alloc_resolution_pool(struct dns_resolvers *resolvers) * returns: * -1 if 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, unsigned int accepted_payload_size, char *hostname_dn, int hostname_dn_len, char *buf, int bufsize) { struct dns_header *dns; struct dns_question qinfo; + struct dns_additional_record edns; char *ptr, *bufend; memset(buf, '\0', bufsize); @@ -1841,7 +1842,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 = htons(1); /* move forward ptr */ ptr += sizeof(struct dns_header); @@ -1868,6 +1869,19 @@ int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostnam ptr += sizeof(struct dns_question); + /* check if there is enough room for additional records */ + if (ptr + sizeof(edns) >= bufend) + return -1; + + /* set the DNS extension */ + edns.name = 0; + edns.type = htons(DNS_RTYPE_OPT); + edns.udp_payload_size = htons(accepted_payload_size); + edns.extension = 0; + edns.data_length = 0; + memcpy(ptr, &edns, sizeof(edns)); + ptr += sizeof(edns); + return ptr - buf; }