diff --git a/Makefile b/Makefile index 2059da93e..f36ed5b3c 100644 --- a/Makefile +++ b/Makefile @@ -733,7 +733,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocol.o \ src/session.o src/stream.o src/hdr_idx.o src/ev_select.o src/signal.o \ src/acl.o src/sample.o src/memory.o src/freq_ctr.o src/auth.o src/proto_udp.o \ src/compression.o src/payload.o src/hash.o src/pattern.o src/map.o \ - src/namespace.o src/mailers.o + src/namespace.o src/mailers.o src/dns.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \ $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \ diff --git a/include/proto/dns.h b/include/proto/dns.h new file mode 100644 index 000000000..328298e5b --- /dev/null +++ b/include/proto/dns.h @@ -0,0 +1,49 @@ +/* + * include/proto/dns.h + * This file provides functions related to DNS protocol + * + * Copyright (C) 2014 Baptiste Assmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PROTO_DNS_H +#define _PROTO_DNS_H + +#include +#include + +char *dns_str_to_dn_label(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); +struct task *dns_process_resolve(struct task *t); +int dns_init_resolvers(void); +uint16_t dns_rnd16(void); +int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *dn_name, int dn_name_len); +int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end, + char *dn_name, int dn_name_len, void *currentip, + short currentip_sin_family, int family_priority, + void **newip, short *newip_sin_family); +void dns_resolve_send(struct dgram_conn *dgram); +void dns_resolve_recv(struct dgram_conn *dgram); +int dns_send_query(struct dns_resolution *resolution); +void dns_print_current_resolutions(struct dns_resolvers *resolvers); +void dns_update_resolvers_timeout(struct dns_resolvers *resolvers); +void dns_reset_resolution(struct dns_resolution *resolution); +int dns_check_resolution_queue(struct dns_resolvers *resolvers); +int dns_response_get_query_id(unsigned char *resp); + +#endif // _PROTO_DNS_H diff --git a/include/types/dns.h b/include/types/dns.h new file mode 100644 index 000000000..d5431a524 --- /dev/null +++ b/include/types/dns.h @@ -0,0 +1,210 @@ +/* + * include/types/dns.h + * This file provides structures and types for DNS. + * + * Copyright (C) 2014 Baptiste Assmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TYPES_DNS_H +#define _TYPES_DNS_H + +/*DNS maximum values */ +/* + * Maximum issued from RFC: + * RFC 1035: https://www.ietf.org/rfc/rfc1035.txt chapter 2.3.4 + * RFC 2671: http://tools.ietf.org/html/rfc2671 + */ +#define DNS_MAX_LABEL_SIZE 63 +#define DNS_MAX_NAME_SIZE 255 +#define DNS_MAX_UDP_MESSAGE 4096 + +/* DNS error messages */ +#define DNS_TOO_LONG_FQDN "hostname too long" +#define DNS_LABEL_TOO_LONG "one label too long" +#define DNS_INVALID_CHARACTER "found an invalid character" + +/* dns query class */ +#define DNS_RCLASS_IN 1 /* internet class */ + +/* dns record types (non exhaustive list) */ +#define DNS_RTYPE_A 1 /* IPv4 address */ +#define DNS_RTYPE_CNAME 5 /* canonical name */ +#define DNS_RTYPE_AAAA 28 /* IPv6 address */ +#define DNS_RTYPE_ANY 255 /* all records */ + +/* dns rcode values */ +#define DNS_RCODE_NO_ERROR 0 /* no error */ +#define DNS_RCODE_NX_DOMAIN 3 /* non existent domain */ +#define DNS_RCODE_REFUSED 5 /* query refused */ + +/* DNS request or response header structure */ +struct dns_header { + unsigned short id:16; /* identifier */ + unsigned char rd :1; /* recursion desired 0: no, 1: yes */ + unsigned char tc :1; /* truncation 0:no, 1: yes */ + unsigned char aa :1; /* authoritative answer 0: no, 1: yes */ + unsigned char opcode :4; /* operation code */ + unsigned char qr :1; /* query/response 0: query, 1: response */ + unsigned char rcode :4; /* response code */ + unsigned char z :1; /* no used */ + unsigned char ad :1; /* authentic data */ + unsigned char cd :1; /* checking disabled */ + unsigned char ra :1; /* recursion available 0: no, 1: yes */ + unsigned short qdcount :16; /* question count */ + unsigned short ancount :16; /* answer count */ + unsigned short nscount :16; /* authority count */ + unsigned short arcount :16; /* additional count */ +}; + +/* short structure to describe a DNS question */ +struct dns_question { + unsigned short qtype; /* question type */ + unsigned short qclass; /* query class */ +}; + +/* + * resolvers section and parameters. It is linked to the name servers + * servers points to it. + * current resolution are stored in a FIFO list. + */ +struct dns_resolvers { + struct list list; /* resolvers list */ + char *id; /* resolvers unique identifier */ + struct { + const char *file; /* file where the section appears */ + int line; /* line where the section appears */ + } conf; /* config information */ + 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 */ + struct { /* time to: */ + int retry; /* wait for a response before retrying */ + } timeout; + struct { /* time to hold current data when */ + int valid; /* a response is valid */ + } hold; + struct task *t; /* timeout management */ + struct list curr_resolution; /* current running resolutions */ + struct eb_root query_ids; /* tree to quickly lookup/retrieve query ids currently in use */ + /* used by each nameserver, but stored in resolvers since there must */ + /* be a unique relation between an eb_root and an eb_node (resolution) */ +}; + +/* + * structure describing a name server used during name resolution. + * A name server belongs to a resolvers section. + */ +struct dns_nameserver { + struct list list; /* nameserver chained list */ + char *id; /* nameserver unique identifier */ + struct { + const char *file; /* file where the section appears */ + int line; /* line where the section appears */ + } conf; /* config information */ + struct dns_resolvers *resolvers; + struct dgram_conn *dgram; /* transport layer */ + struct sockaddr_storage addr; /* IP address */ + struct { /* numbers relted to this name server: */ + long int sent; /* - queries sent */ + long int valid; /* - valid response */ + long int update; /* - valid response used to update server's IP */ + long int cname; /* - CNAME response requiring new resolution */ + long int cname_error; /* - error when resolving CNAMEs */ + long int any_err; /* - void response (usually because ANY qtype) */ + long int nx; /* - NX response */ + long int timeout; /* - queries which reached timeout */ + long int refused; /* - queries refused */ + long int other; /* - other type of response */ + long int invalid; /* - malformed DNS response */ + long int too_big; /* - too big response */ + long int outdated; /* - outdated response (server slower than the other ones) */ + } counters; +}; + +/* + * resolution structure associated to single server and used to manage name resolution for + * this server. + * The only link between the resolution and a nameserver is through the query_id. + */ +struct dns_resolution { + struct list list; /* resolution list */ + struct dns_resolvers *resolvers; /* resolvers section associated to this resolution */ + void *requester; /* owner of this name resolution */ + int (*requester_cb)(struct dns_resolution *, struct dns_nameserver *, unsigned char *, int); + /* requester callback for valid response */ + int (*requester_error_cb)(struct dns_resolution *, int); + /* requester callback, for error management */ + char *hostname_dn; /* server hostname in domain name label format */ + int hostname_dn_len; /* server domain name label len */ + int resolver_family_priority; /* which IP family should the resolver use when both are returned */ + time_t last_resolution; /* time of the lastest valid resolution */ + time_t last_sent_packet; /* time of the latest DNS packet sent */ + time_t last_status_change; /* time of the latest DNS resolution status change */ + int query_id; /* DNS query ID dedicated for this resolution */ + struct eb32_node qid; /* ebtree query id */ + int query_type; /* query type to send. By default DNS_RTYPE_ANY */ + int status; /* status of the resolution being processed RSLV_STATUS_* */ + int step; /* */ + int try; /* current resolution try */ + int try_cname; /* number of CNAME requests sent */ + int nb_responses; /* count number of responses received */ +}; + +/* last resolution status code */ +enum { + RSLV_STATUS_NONE = 0, /* no resolution occured yet */ + RSLV_STATUS_VALID, /* no error */ + RSLV_STATUS_INVALID, /* invalid responses */ + RSLV_STATUS_ERROR, /* error */ + RSLV_STATUS_NX, /* NXDOMAIN */ + RSLV_STATUS_REFUSED, /* server refused our query */ + RSLV_STATUS_TIMEOUT, /* no response from DNS servers */ + RSLV_STATUS_OTHER, /* other errors */ +}; + +/* current resolution step */ +enum { + RSLV_STEP_NONE = 0, /* nothing happening currently */ + RSLV_STEP_RUNNING, /* resolution is running */ +}; + +/* return codes after analyzing a DNS response */ +enum { + DNS_RESP_VALID = 0, /* valid response */ + DNS_RESP_INVALID, /* invalid response (various type of errors can trigger it) */ + DNS_RESP_ERROR, /* DNS error code */ + DNS_RESP_NX_DOMAIN, /* resolution unsuccessful */ + DNS_RESP_REFUSED, /* DNS server refused to answer */ + DNS_RESP_ANCOUNT_ZERO, /* no answers in the response */ + DNS_RESP_WRONG_NAME, /* response does not match query name */ + DNS_RESP_CNAME_ERROR, /* error when resolving a CNAME in an atomic response */ + DNS_RESP_TIMEOUT, /* DNS server has not answered in time */ +}; + +/* return codes after searching an IP in a DNS response buffer, using a family preference */ +enum { + DNS_UPD_NO = 1, /* provided IP was found and preference is matched + * OR provided IP found and preference is not matched, but no IP + * matching preference was found */ + DNS_UPD_SRVIP_NOT_FOUND, /* provided IP not found + * OR provided IP found and preference is not match and an IP + * matching preference was found */ + DNS_UPD_CNAME, /* CNAME without any IP provided in the response */ + DNS_UPD_NAME_ERROR, /* name in the response did not match the query */ +}; + +#endif /* _TYPES_DNS_H */ diff --git a/include/types/global.h b/include/types/global.h index 2996dda5c..921ef83d1 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -222,6 +222,7 @@ extern char localpeer[MAX_HOSTNAME_LEN]; extern struct list global_listener_queue; /* list of the temporarily limited listeners */ extern struct task *global_listener_queue_task; extern unsigned int warned; /* bitfield of a few warnings to emit just once */ +extern struct list dns_resolvers; /* bit values to go with "warned" above */ #define WARN_BLOCK_DEPRECATED 0x00000001 diff --git a/src/cfgparse.c b/src/cfgparse.c index 3bfacade8..e48cf93be 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -2173,6 +2174,191 @@ out: return err_code; } +/* + * Parse a section. + * Returns the error code, 0 if OK, or any combination of : + * - ERR_ABORT: must abort ASAP + * - ERR_FATAL: we can continue parsing but not start the service + * - ERR_WARN: a warning has been emitted + * - ERR_ALERT: an alert has been emitted + * Only the two first ones can stop processing, the two others are just + * indicators. + */ +int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm) +{ + static struct dns_resolvers *curr_resolvers = NULL; + struct dns_nameserver *newnameserver = NULL; + const char *err; + int err_code = 0; + char *errmsg = NULL; + + if (strcmp(args[0], "resolvers") == 0) { /* new resolvers section */ + if (!*args[1]) { + Alert("parsing [%s:%d] : missing name for resolvers section.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + err = invalid_char(args[1]); + if (err) { + Alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n", + file, linenum, *err, args[0], args[1]); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + list_for_each_entry(curr_resolvers, &dns_resolvers, list) { + /* Error if two resolvers owns the same name */ + if (strcmp(curr_resolvers->id, args[1]) == 0) { + Alert("Parsing [%s:%d]: resolvers '%s' has same name as another resolvers (declared at %s:%d).\n", + file, linenum, args[1], curr_resolvers->conf.file, curr_resolvers->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + } + } + + if ((curr_resolvers = (struct dns_resolvers *)calloc(1, sizeof(struct dns_resolvers))) == NULL) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + /* default values */ + LIST_ADDQ(&dns_resolvers, &curr_resolvers->list); + curr_resolvers->conf.file = strdup(file); + curr_resolvers->conf.line = linenum; + curr_resolvers->id = strdup(args[1]); + curr_resolvers->query_ids = EB_ROOT; + /* default hold period for valid is 10s */ + curr_resolvers->hold.valid = 10; + curr_resolvers->timeout.retry = 1; + curr_resolvers->resolve_retries = 3; + LIST_INIT(&curr_resolvers->nameserver_list); + LIST_INIT(&curr_resolvers->curr_resolution); + } + else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition */ + struct sockaddr_storage *sk; + int port1, port2; + struct protocol *proto; + + if (!*args[2]) { + Alert("parsing [%s:%d] : '%s' expects and [:] as arguments.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err = invalid_char(args[1]); + if (err) { + Alert("parsing [%s:%d] : character '%c' is not permitted in server name '%s'.\n", + file, linenum, *err, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if ((newnameserver = (struct dns_nameserver *)calloc(1, sizeof(struct dns_nameserver))) == NULL) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + /* the nameservers are linked backward first */ + LIST_ADDQ(&curr_resolvers->nameserver_list, &newnameserver->list); + curr_resolvers->count_nameservers++; + newnameserver->resolvers = curr_resolvers; + newnameserver->conf.file = strdup(file); + newnameserver->conf.line = linenum; + newnameserver->id = strdup(args[1]); + + sk = str2sa_range(args[2], &port1, &port2, &errmsg, NULL); + if (!sk) { + Alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + proto = protocol_by_family(sk->ss_family); + if (!proto || !proto->connect) { + Alert("parsing [%s:%d] : '%s %s' : connect() not supported for this address family.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (port1 != port2) { + Alert("parsing [%s:%d] : '%s %s' : port ranges and offsets are not allowed in '%s'\n", + file, linenum, args[0], args[1], args[2]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + newnameserver->addr = *sk; + } + else if (strcmp(args[0], "hold") == 0) { /* hold periods */ + const char *res; + unsigned int time; + + if (!*args[2]) { + Alert("parsing [%s:%d] : '%s' expects an and a