diff --git a/Makefile b/Makefile index 81d9b3b4b..613067f75 100644 --- a/Makefile +++ b/Makefile @@ -845,7 +845,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/http_ana.o src/stream.o \ src/mux_h1.o src/stats.o src/flt_spoe.o src/backend.o \ src/tcpcheck.o src/server.o src/tools.o src/cli.o \ src/cfgparse.o src/log.o src/cfgparse-listen.o src/check.o \ - src/stick_table.o src/peers.o src/dns.o src/stream_interface.o \ + src/stick_table.o src/peers.o src/resolvers.o src/stream_interface.o \ src/sample.o src/http_htx.o src/haproxy.o src/http_act.o \ src/proxy.o src/pattern.o src/listener.o src/cache.o \ src/http_fetch.o src/session.o src/connection.o src/sink.o \ @@ -871,7 +871,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/http_ana.o src/stream.o \ src/ebimtree.o src/uri_auth.o src/freq_ctr.o src/ebsttree.o \ src/ebistree.o src/auth.o src/wdt.o src/http_acl.o \ src/hpack-enc.o src/hpack-huff.o src/ebtree.o src/base64.o \ - src/hash.o src/dgram.o src/version.o src/fix.o src/mqtt.o + src/hash.o src/dgram.o src/version.o src/fix.o src/mqtt.o src/dns.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/include/haproxy/dns-t.h b/include/haproxy/dns-t.h index f6e2889d2..22615706f 100644 --- a/include/haproxy/dns-t.h +++ b/include/haproxy/dns-t.h @@ -25,73 +25,14 @@ #include #include +#include #include #include +#include #include #include #include -extern struct pool_head *resolv_requester_pool; - -/*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 8192 - -/* DNS minimum record size: 1 char + 1 NULL + type + class */ -#define DNS_MIN_RECORD_SIZE (1 + 1 + 2 + 2) - -/* DNS smallest fqdn 'a.gl' size */ -# define DNS_SMALLEST_FQDN_SIZE 4 - -/* maximum number of query records in a DNS response - * For now, we allow only one */ -#define DNS_MAX_QUERY_RECORDS 1 - -/* maximum number of answer record in a DNS response */ -#define DNS_MAX_ANSWER_RECORDS ((DNS_MAX_UDP_MESSAGE - DNS_HEADER_SIZE) / DNS_MIN_RECORD_SIZE) - -/* size of dns_buffer used to store responses from the buffer - * dns_buffer is used to store data collected from records found in a response. - * Before using it, caller will always check that there is at least DNS_MAX_NAME_SIZE bytes - * available */ -#define DNS_ANALYZE_BUFFER_SIZE DNS_MAX_UDP_MESSAGE + DNS_MAX_NAME_SIZE - -/* 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_SRV 33 /* SRV record */ -#define DNS_RTYPE_OPT 41 /* OPT */ -#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 flags masks */ -#define DNS_FLAG_TRUNCATED 0x0200 /* mask for truncated flag */ -#define DNS_FLAG_REPLYCODE 0x000F /* mask for reply code */ - -/* max number of network preference entries are available from the - * configuration file. - */ -#define SRV_MAX_PREF_NET 5 - /* DNS header size */ #define DNS_HEADER_SIZE ((int)sizeof(struct dns_header)) @@ -115,13 +56,6 @@ struct dns_question { unsigned short qclass; /* query class */ } __attribute__ ((packed)); -/* NOTE: big endian structure */ -struct resolv_query_item { - char name[DNS_MAX_NAME_SIZE+1]; /* query name */ - unsigned short type; /* question type */ - unsigned short class; /* query class */ - struct list list; -}; /* NOTE: big endian structure */ struct dns_additional_record { @@ -136,77 +70,13 @@ struct dns_additional_record { * extension set (client subnet, tcp keepalive, etc...)*/ } __attribute__ ((packed)); -/* NOTE: big endian structure */ -struct resolv_answer_item { - /*For SRV type, name also includes service and protocol value */ - char name[DNS_MAX_NAME_SIZE+1]; /* answer name */ - int16_t type; /* question type */ - int16_t class; /* query class */ - int32_t ttl; /* response TTL */ - int16_t priority; /* SRV type priority */ - uint16_t weight; /* SRV type weight */ - uint16_t port; /* SRV type port */ - uint16_t data_len; /* number of bytes in target below */ - struct sockaddr address; /* IPv4 or IPv6, network format */ - char target[DNS_MAX_NAME_SIZE+1]; /* Response data: SRV or CNAME type target */ - time_t last_seen; /* When was the answer was last seen */ - struct resolv_answer_item *ar_item; /* pointer to a RRset from the additional section, if exists */ - struct list list; -}; - -struct resolv_response { - struct dns_header header; - struct list query_list; - struct list answer_list; - /* authority ignored for now */ -}; - -/* Resolvers section and parameters. It is linked to the name servers - * servers points to it. - * current resolution are stored in a FIFO list. - */ -struct resolvers { - __decl_thread(HA_SPINLOCK_T lock); - unsigned int accepted_payload_size; /* maximum payload size we accept for responses */ - int nb_nameservers; /* total number of active nameservers in a resolvers section */ - int resolve_retries; /* number of retries before giving up */ - struct { /* time to: */ - int resolve; /* wait between 2 queries for the same resolution */ - int retry; /* wait for a response before retrying */ - } timeout; - struct { /* time to hold current data when */ - int valid; /* a response is valid */ - int nx; /* a response doesn't exist */ - int timeout; /* no answer was delivered */ - int refused; /* dns server refused to answer */ - int other; /* other dns response errors */ - int obsolete; /* an answer hasn't been seen */ - } hold; - struct task *t; /* timeout management */ - struct { - struct list wait; /* resolutions managed to this resolvers section */ - struct list curr; /* current running resolutions */ - } 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) */ - struct list list; /* resolvers list */ - struct list nameservers; /* dns server 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 dns_dgram_server { struct dgram_conn conn; /* transport layer */ struct ring *ring_req; size_t ofs_req; // ring buffer reader offset }; -/* Structure describing a name server used during name resolution. - * A name server belongs to a resolvers section. +/* Structure describing a name server */ struct dns_nameserver { char *id; /* nameserver unique identifier */ @@ -225,6 +95,7 @@ struct dns_nameserver { struct list list; /* nameserver chained list */ }; +/* mixed dns and resolver counters, we will have to split them */ struct dns_counters { char *id; char *pid; @@ -245,127 +116,4 @@ struct dns_counters { long long truncated; /* - truncated response */; }; -struct resolv_options { - int family_prio; /* which IP family should the resolver use when both are returned */ - struct { - int family; - union { - struct in_addr in4; - struct in6_addr in6; - } addr; - union { - struct in_addr in4; - struct in6_addr in6; - } mask; - } pref_net[SRV_MAX_PREF_NET]; - int pref_net_nb; /* The number of registered preferred networks. */ - int accept_duplicate_ip; /* flag to indicate whether the associated object can use an IP address - already set to an other object of the same group */ - int ignore_weight; /* flag to indicate whether to ignore the weight within the record */ -}; - -/* 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 resolv_resolution { - struct resolvers *resolvers; /* pointer to the resolvers structure owning the resolution */ - struct list requesters; /* list of requesters using this resolution */ - int uuid; /* unique id (used for debugging purpose) */ - char *hostname_dn; /* server hostname in domain name label format */ - int hostname_dn_len; /* server domain name label len */ - unsigned int last_resolution; /* time of the last resolution */ - unsigned int last_query; /* time of the last query sent */ - unsigned int last_valid; /* time of the last valid response */ - int query_id; /* DNS query ID dedicated for this resolution */ - struct eb32_node qid; /* ebtree query id */ - int prefered_query_type; /* preferred query type */ - int query_type; /* current query type */ - int status; /* status of the resolution being processed RSLV_STATUS_* */ - int step; /* RSLV_STEP_* */ - int try; /* current resolution try */ - int nb_queries; /* count number of queries sent */ - int nb_responses; /* count number of responses received */ - - struct resolv_response response; /* structure hosting the DNS response */ - struct resolv_query_item response_query_records[DNS_MAX_QUERY_RECORDS]; /* query records */ - - struct list list; /* resolution list */ -}; - -/* Structure used to describe the owner of a DNS resolution. */ -struct resolv_requester { - enum obj_type *owner; /* pointer to the owner (server or dns_srvrq) */ - struct resolv_resolution *resolution; /* pointer to the owned DNS resolution */ - - int (*requester_cb)(struct resolv_requester *, struct dns_counters *); /* requester callback for valid response */ - int (*requester_error_cb)(struct resolv_requester *, int); /* requester callback, for error management */ - - struct list list; /* requester list */ -}; - -/* Last resolution status code */ -enum { - RSLV_STATUS_NONE = 0, /* no resolution occurred 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 { - RSLV_RESP_VALID = 0, /* valid response */ - RSLV_RESP_INVALID, /* invalid response (various type of errors can trigger it) */ - RSLV_RESP_ERROR, /* DNS error code */ - RSLV_RESP_NX_DOMAIN, /* resolution unsuccessful */ - RSLV_RESP_REFUSED, /* DNS server refused to answer */ - RSLV_RESP_ANCOUNT_ZERO, /* no answers in the response */ - RSLV_RESP_WRONG_NAME, /* response does not match query name */ - RSLV_RESP_CNAME_ERROR, /* error when resolving a CNAME in an atomic response */ - RSLV_RESP_TIMEOUT, /* DNS server has not answered in time */ - RSLV_RESP_TRUNCATED, /* DNS response is truncated */ - RSLV_RESP_NO_EXPECTED_RECORD, /* No expected records were found in the response */ - RSLV_RESP_QUERY_COUNT_ERROR, /* we did not get the expected number of queries in the response */ - RSLV_RESP_INTERNAL, /* internal resolver error */ -}; - -/* Return codes after searching an IP in a DNS response buffer, using a family - * preference - */ -enum { - RSLV_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 */ - RSLV_UPD_SRVIP_NOT_FOUND, /* provided IP not found - * OR provided IP found and preference is not match and an IP - * matching preference was found */ - RSLV_UPD_CNAME, /* CNAME without any IP provided in the response */ - RSLV_UPD_NAME_ERROR, /* name in the response did not match the query */ - RSLV_UPD_NO_IP_FOUND, /* no IP could be found in the response */ - RSLV_UPD_OBSOLETE_IP, /* The server IP was obsolete, and no other IP was found */ -}; - -struct proxy; -struct resolv_srvrq { - enum obj_type obj_type; /* object type == OBJ_TYPE_SRVRQ */ - struct resolvers *resolvers; /* pointer to the resolvers structure used for this server template */ - struct proxy *proxy; /* associated proxy */ - char *name; - char *hostname_dn; /* server hostname in Domain Name format */ - int hostname_dn_len; /* string length of the server hostname in Domain Name format */ - struct resolv_requester *requester; /* used to link to its DNS resolution */ - struct list list; /* Next SRV RQ for the same proxy */ -}; - #endif /* _HAPROXY_DNS_T_H */ diff --git a/include/haproxy/dns.h b/include/haproxy/dns.h index 5b6f0b281..dd3477d76 100644 --- a/include/haproxy/dns.h +++ b/include/haproxy/dns.h @@ -2,7 +2,7 @@ * include/haproxy/dns.h * This file provides functions related to DNS protocol * - * Copyright (C) 2014 Baptiste Assmann + * Copyright (C) 2020 Haproxy Technologies * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,37 +22,10 @@ #ifndef _HAPROXY_DNS_H #define _HAPROXY_DNS_H -#include #include -extern struct list sec_resolvers; -extern unsigned int resolv_failed_resolutions; - -struct resolvers *find_resolvers_by_id(const char *id); -struct resolv_srvrq *find_srvrq_by_name(const char *name, struct proxy *px); -struct resolv_srvrq *new_resolv_srvrq(struct server *srv, char *fqdn); - -int resolv_str_to_dn_label(const char *str, int str_len, char *dn, int dn_len); -int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len); - -int resolv_hostname_validation(const char *string, char **err); -int resolv_get_ip_from_response(struct resolv_response *r_res, - struct resolv_options *resolv_opts, void *currentip, - short currentip_sin_family, - void **newip, short *newip_sin_family, - void *owner); - -int resolv_link_resolution(void *requester, int requester_type, int requester_locked); -void resolv_unlink_resolution(struct resolv_requester *requester); -void resolv_trigger_resolution(struct resolv_requester *requester); -enum act_parse_ret resolv_parse_do_resolve(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err); -int check_action_do_resolve(struct act_rule *rule, struct proxy *px, char **err); - -int stats_dump_resolvers(struct stream_interface *si, - struct field *stats, size_t stats_count, - struct list *stat_modules); -void resolv_stats_clear_counters(int clrall, struct list *stat_modules); -int resolv_allocate_counters(struct list *stat_modules); +int dns_send_nameserver(struct dns_nameserver *ns, void *buf, size_t len); +ssize_t dns_recv_nameserver(struct dns_nameserver *ns, void *data, size_t size); int dns_dgram_init(struct dns_nameserver *ns, struct sockaddr_storage *sk); #endif // _HAPROXY_DNS_H diff --git a/include/haproxy/resolvers-t.h b/include/haproxy/resolvers-t.h new file mode 100644 index 000000000..4e1654526 --- /dev/null +++ b/include/haproxy/resolvers-t.h @@ -0,0 +1,292 @@ +/* + * include/haproxy/dns-t.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 _HAPROXY_RESOLVERS_T_H +#define _HAPROXY_RESOLVERS_T_H + +#include + +#include +#include +#include +#include +#include +#include + +extern struct pool_head *resolv_requester_pool; + +/*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 8192 + +/* DNS minimum record size: 1 char + 1 NULL + type + class */ +#define DNS_MIN_RECORD_SIZE (1 + 1 + 2 + 2) + +/* DNS smallest fqdn 'a.gl' size */ +# define DNS_SMALLEST_FQDN_SIZE 4 + +/* maximum number of query records in a DNS response + * For now, we allow only one */ +#define DNS_MAX_QUERY_RECORDS 1 + +/* maximum number of answer record in a DNS response */ +#define DNS_MAX_ANSWER_RECORDS ((DNS_MAX_UDP_MESSAGE - DNS_HEADER_SIZE) / DNS_MIN_RECORD_SIZE) + +/* size of dns_buffer used to store responses from the buffer + * dns_buffer is used to store data collected from records found in a response. + * Before using it, caller will always check that there is at least DNS_MAX_NAME_SIZE bytes + * available */ +#define DNS_ANALYZE_BUFFER_SIZE DNS_MAX_UDP_MESSAGE + DNS_MAX_NAME_SIZE + +/* 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_SRV 33 /* SRV record */ +#define DNS_RTYPE_OPT 41 /* OPT */ +#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 flags masks */ +#define DNS_FLAG_TRUNCATED 0x0200 /* mask for truncated flag */ +#define DNS_FLAG_REPLYCODE 0x000F /* mask for reply code */ + +/* max number of network preference entries are available from the + * configuration file. + */ +#define SRV_MAX_PREF_NET 5 + +/* NOTE: big endian structure */ +struct resolv_query_item { + char name[DNS_MAX_NAME_SIZE+1]; /* query name */ + unsigned short type; /* question type */ + unsigned short class; /* query class */ + struct list list; +}; + +/* NOTE: big endian structure */ +struct resolv_answer_item { + /*For SRV type, name also includes service and protocol value */ + char name[DNS_MAX_NAME_SIZE+1]; /* answer name */ + int16_t type; /* question type */ + int16_t class; /* query class */ + int32_t ttl; /* response TTL */ + int16_t priority; /* SRV type priority */ + uint16_t weight; /* SRV type weight */ + uint16_t port; /* SRV type port */ + uint16_t data_len; /* number of bytes in target below */ + struct sockaddr address; /* IPv4 or IPv6, network format */ + char target[DNS_MAX_NAME_SIZE+1]; /* Response data: SRV or CNAME type target */ + time_t last_seen; /* When was the answer was last seen */ + struct resolv_answer_item *ar_item; /* pointer to a RRset from the additional section, if exists */ + struct list list; +}; + +struct resolv_response { + struct dns_header header; + struct list query_list; + struct list answer_list; + /* authority ignored for now */ +}; + +/* Resolvers section and parameters. It is linked to the name servers + * servers points to it. + * current resolution are stored in a FIFO list. + */ +struct resolvers { + __decl_thread(HA_SPINLOCK_T lock); + unsigned int accepted_payload_size; /* maximum payload size we accept for responses */ + int nb_nameservers; /* total number of active nameservers in a resolvers section */ + int resolve_retries; /* number of retries before giving up */ + struct { /* time to: */ + int resolve; /* wait between 2 queries for the same resolution */ + int retry; /* wait for a response before retrying */ + } timeout; + struct { /* time to hold current data when */ + int valid; /* a response is valid */ + int nx; /* a response doesn't exist */ + int timeout; /* no answer was delivered */ + int refused; /* dns server refused to answer */ + int other; /* other dns response errors */ + int obsolete; /* an answer hasn't been seen */ + } hold; + struct task *t; /* timeout management */ + struct { + struct list wait; /* resolutions managed to this resolvers section */ + struct list curr; /* current running resolutions */ + } 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) */ + struct list list; /* resolvers list */ + struct list nameservers; /* dns server list */ + struct proxy *px; /* px to handle connections to DNS servers */ + 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 resolv_options { + int family_prio; /* which IP family should the resolver use when both are returned */ + struct { + int family; + union { + struct in_addr in4; + struct in6_addr in6; + } addr; + union { + struct in_addr in4; + struct in6_addr in6; + } mask; + } pref_net[SRV_MAX_PREF_NET]; + int pref_net_nb; /* The number of registered preferred networks. */ + int accept_duplicate_ip; /* flag to indicate whether the associated object can use an IP address + already set to an other object of the same group */ + int ignore_weight; /* flag to indicate whether to ignore the weight within the record */ +}; + +/* 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 resolv_resolution { + struct resolvers *resolvers; /* pointer to the resolvers structure owning the resolution */ + struct list requesters; /* list of requesters using this resolution */ + int uuid; /* unique id (used for debugging purpose) */ + char *hostname_dn; /* server hostname in domain name label format */ + int hostname_dn_len; /* server domain name label len */ + unsigned int last_resolution; /* time of the last resolution */ + unsigned int last_query; /* time of the last query sent */ + unsigned int last_valid; /* time of the last valid response */ + int query_id; /* DNS query ID dedicated for this resolution */ + struct eb32_node qid; /* ebtree query id */ + int prefered_query_type; /* preferred query type */ + int query_type; /* current query type */ + int status; /* status of the resolution being processed RSLV_STATUS_* */ + int step; /* RSLV_STEP_* */ + int try; /* current resolution try */ + int nb_queries; /* count number of queries sent */ + int nb_responses; /* count number of responses received */ + + struct resolv_response response; /* structure hosting the DNS response */ + struct resolv_query_item response_query_records[DNS_MAX_QUERY_RECORDS]; /* query records */ + + struct list list; /* resolution list */ +}; + +/* Structure used to describe the owner of a DNS resolution. */ +struct resolv_requester { + enum obj_type *owner; /* pointer to the owner (server or dns_srvrq) */ + struct resolv_resolution *resolution; /* pointer to the owned DNS resolution */ + + int (*requester_cb)(struct resolv_requester *, struct dns_counters *); /* requester callback for valid response */ + int (*requester_error_cb)(struct resolv_requester *, int); /* requester callback, for error management */ + + struct list list; /* requester list */ +}; + +/* Last resolution status code */ +enum { + RSLV_STATUS_NONE = 0, /* no resolution occurred 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 { + RSLV_RESP_VALID = 0, /* valid response */ + RSLV_RESP_INVALID, /* invalid response (various type of errors can trigger it) */ + RSLV_RESP_ERROR, /* DNS error code */ + RSLV_RESP_NX_DOMAIN, /* resolution unsuccessful */ + RSLV_RESP_REFUSED, /* DNS server refused to answer */ + RSLV_RESP_ANCOUNT_ZERO, /* no answers in the response */ + RSLV_RESP_WRONG_NAME, /* response does not match query name */ + RSLV_RESP_CNAME_ERROR, /* error when resolving a CNAME in an atomic response */ + RSLV_RESP_TIMEOUT, /* DNS server has not answered in time */ + RSLV_RESP_TRUNCATED, /* DNS response is truncated */ + RSLV_RESP_NO_EXPECTED_RECORD, /* No expected records were found in the response */ + RSLV_RESP_QUERY_COUNT_ERROR, /* we did not get the expected number of queries in the response */ + RSLV_RESP_INTERNAL, /* internal resolver error */ +}; + +/* Return codes after searching an IP in a DNS response buffer, using a family + * preference + */ +enum { + RSLV_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. + */ + RSLV_UPD_SRVIP_NOT_FOUND, /* provided IP not found + * OR provided IP found and preference is not match and an IP + * matching preference was found. + */ + RSLV_UPD_CNAME, /* CNAME without any IP provided in the response */ + RSLV_UPD_NAME_ERROR, /* name in the response did not match the query */ + RSLV_UPD_NO_IP_FOUND, /* no IP could be found in the response */ + RSLV_UPD_OBSOLETE_IP, /* The server IP was obsolete, and no other IP was found */ +}; + +struct proxy; +struct resolv_srvrq { + enum obj_type obj_type; /* object type == OBJ_TYPE_SRVRQ */ + struct resolvers *resolvers; /* pointer to the resolvers structure used for this server template */ + struct proxy *proxy; /* associated proxy */ + char *name; + char *hostname_dn; /* server hostname in Domain Name format */ + int hostname_dn_len; /* string length of the server hostname in Domain Name format */ + struct resolv_requester *requester; /* used to link to its DNS resolution */ + struct list list; /* Next SRV RQ for the same proxy */ +}; + +#endif /* _HAPROXY_RESOLVERS_T_H */ diff --git a/include/haproxy/resolvers.h b/include/haproxy/resolvers.h new file mode 100644 index 000000000..342c1f199 --- /dev/null +++ b/include/haproxy/resolvers.h @@ -0,0 +1,58 @@ +/* + * include/haproxy/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 _HAPROXY_RESOLVERS_H +#define _HAPROXY_RESOLVERS_H + +#include +#include + +extern struct list sec_resolvers; +extern unsigned int resolv_failed_resolutions; + +struct resolvers *find_resolvers_by_id(const char *id); +struct resolv_srvrq *find_srvrq_by_name(const char *name, struct proxy *px); +struct resolv_srvrq *new_resolv_srvrq(struct server *srv, char *fqdn); + +int resolv_str_to_dn_label(const char *str, int str_len, char *dn, int dn_len); +int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len); + +int resolv_hostname_validation(const char *string, char **err); +int resolv_get_ip_from_response(struct resolv_response *r_res, + struct resolv_options *resolv_opts, void *currentip, + short currentip_sin_family, + void **newip, short *newip_sin_family, + void *owner); + +int resolv_link_resolution(void *requester, int requester_type, int requester_locked); +void resolv_unlink_resolution(struct resolv_requester *requester); +void resolv_trigger_resolution(struct resolv_requester *requester); +enum act_parse_ret resolv_parse_do_resolve(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err); +int check_action_do_resolve(struct act_rule *rule, struct proxy *px, char **err); + +int stats_dump_resolvers(struct stream_interface *si, + struct field *stats, size_t stats_count, + struct list *stat_modules); +void resolv_stats_clear_counters(int clrall, struct list *stat_modules); +int resolv_allocate_counters(struct list *stat_modules); +int dns_dgram_init(struct dns_nameserver *ns, struct sockaddr_storage *sk); + +#endif // _HAPROXY_RESOLVER_H diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index b1840fd8a..7f58eddde 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -32,11 +32,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include diff --git a/include/haproxy/server.h b/include/haproxy/server.h index 987a3852b..bbf21742d 100644 --- a/include/haproxy/server.h +++ b/include/haproxy/server.h @@ -26,9 +26,9 @@ #include #include -#include #include #include +#include #include #include #include diff --git a/src/cfgparse.c b/src/cfgparse.c index 0c0638e70..8ceecf2db 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -44,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -66,6 +65,7 @@ #include #include #include +#include #include #include #include diff --git a/src/check.c b/src/check.c index 4f45c2393..d37a55201 100644 --- a/src/check.c +++ b/src/check.c @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -55,6 +54,7 @@ #include #include #include +#include #include #include #include diff --git a/src/dns.c b/src/dns.c index ceca0a3dd..5fc656f47 100644 --- a/src/dns.c +++ b/src/dns.c @@ -1,7 +1,7 @@ /* * Name server resolution * - * Copyright 2014 Baptiste Assmann + * Copyright 2020 Haproxy Technologies * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,238 +29,11 @@ #include #include #include -#include -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include static THREAD_LOCAL char *dns_msg_trash; -struct list sec_resolvers = LIST_HEAD_INIT(sec_resolvers); -struct list resolv_srvrq_list = LIST_HEAD_INIT(resolv_srvrq_list); - -static THREAD_LOCAL uint64_t resolv_query_id_seed = 0; /* random seed */ -struct resolvers *curr_resolvers = NULL; - -DECLARE_STATIC_POOL(resolv_answer_item_pool, "resolv_answer_item", sizeof(struct resolv_answer_item)); -DECLARE_STATIC_POOL(resolv_resolution_pool, "resolv_resolution", sizeof(struct resolv_resolution)); -DECLARE_POOL(resolv_requester_pool, "resolv_requester", sizeof(struct resolv_requester)); - -static unsigned int resolution_uuid = 1; -unsigned int resolv_failed_resolutions = 0; - -enum { - DNS_STAT_ID, - DNS_STAT_PID, - DNS_STAT_SENT, - DNS_STAT_SND_ERROR, - DNS_STAT_VALID, - DNS_STAT_UPDATE, - DNS_STAT_CNAME, - DNS_STAT_CNAME_ERROR, - DNS_STAT_ANY_ERR, - DNS_STAT_NX, - DNS_STAT_TIMEOUT, - DNS_STAT_REFUSED, - DNS_STAT_OTHER, - DNS_STAT_INVALID, - DNS_STAT_TOO_BIG, - DNS_STAT_TRUNCATED, - DNS_STAT_OUTDATED, - DNS_STAT_END, -}; - -static struct name_desc dns_stats[] = { - [DNS_STAT_ID] = { .name = "id", .desc = "ID" }, - [DNS_STAT_PID] = { .name = "pid", .desc = "Parent ID" }, - [DNS_STAT_SENT] = { .name = "sent", .desc = "Sent" }, - [DNS_STAT_SND_ERROR] = { .name = "send_error", .desc = "Send error" }, - [DNS_STAT_VALID] = { .name = "valid", .desc = "Valid" }, - [DNS_STAT_UPDATE] = { .name = "update", .desc = "Update" }, - [DNS_STAT_CNAME] = { .name = "cname", .desc = "CNAME" }, - [DNS_STAT_CNAME_ERROR] = { .name = "cname_error", .desc = "CNAME error" }, - [DNS_STAT_ANY_ERR] = { .name = "any_err", .desc = "Any errors" }, - [DNS_STAT_NX] = { .name = "nx", .desc = "NX" }, - [DNS_STAT_TIMEOUT] = { .name = "timeout", .desc = "Timeout" }, - [DNS_STAT_REFUSED] = { .name = "refused", .desc = "Refused" }, - [DNS_STAT_OTHER] = { .name = "other", .desc = "Other" }, - [DNS_STAT_INVALID] = { .name = "invalid", .desc = "Invalid" }, - [DNS_STAT_TOO_BIG] = { .name = "too_big", .desc = "Too big" }, - [DNS_STAT_TRUNCATED] = { .name = "truncated", .desc = "Truncated" }, - [DNS_STAT_OUTDATED] = { .name = "outdated", .desc = "Outdated" }, -}; - -static struct dns_counters dns_counters; - -static void dns_fill_stats(void *d, struct field *stats) -{ - struct dns_counters *counters = d; - stats[DNS_STAT_ID] = mkf_str(FO_CONFIG, counters->id); - stats[DNS_STAT_PID] = mkf_str(FO_CONFIG, counters->pid); - stats[DNS_STAT_SENT] = mkf_u64(FN_GAUGE, counters->sent); - stats[DNS_STAT_SND_ERROR] = mkf_u64(FN_GAUGE, counters->snd_error); - stats[DNS_STAT_VALID] = mkf_u64(FN_GAUGE, counters->valid); - stats[DNS_STAT_UPDATE] = mkf_u64(FN_GAUGE, counters->update); - stats[DNS_STAT_CNAME] = mkf_u64(FN_GAUGE, counters->cname); - stats[DNS_STAT_CNAME_ERROR] = mkf_u64(FN_GAUGE, counters->cname_error); - stats[DNS_STAT_ANY_ERR] = mkf_u64(FN_GAUGE, counters->any_err); - stats[DNS_STAT_NX] = mkf_u64(FN_GAUGE, counters->nx); - stats[DNS_STAT_TIMEOUT] = mkf_u64(FN_GAUGE, counters->timeout); - stats[DNS_STAT_REFUSED] = mkf_u64(FN_GAUGE, counters->refused); - stats[DNS_STAT_OTHER] = mkf_u64(FN_GAUGE, counters->other); - stats[DNS_STAT_INVALID] = mkf_u64(FN_GAUGE, counters->invalid); - stats[DNS_STAT_TOO_BIG] = mkf_u64(FN_GAUGE, counters->too_big); - stats[DNS_STAT_TRUNCATED] = mkf_u64(FN_GAUGE, counters->truncated); - stats[DNS_STAT_OUTDATED] = mkf_u64(FN_GAUGE, counters->outdated); -} - -static struct stats_module dns_stats_module = { - .name = "dns", - .domain_flags = STATS_DOMAIN_DNS << STATS_DOMAIN, - .fill_stats = dns_fill_stats, - .stats = dns_stats, - .stats_count = DNS_STAT_END, - .counters = &dns_counters, - .counters_size = sizeof(dns_counters), - .clearable = 0, -}; - -INITCALL1(STG_REGISTER, stats_register_module, &dns_stats_module); - -/* Returns a pointer to the resolvers matching the id . NULL is returned if - * no match is found. - */ -struct resolvers *find_resolvers_by_id(const char *id) -{ - struct resolvers *res; - - list_for_each_entry(res, &sec_resolvers, list) { - if (strcmp(res->id, id) == 0) - return res; - } - return NULL; -} - -/* Compare hostnames in a case-insensitive way . - * Returns 0 if they are the same, non-zero otherwise - */ -static __inline int resolv_hostname_cmp(const char *name1, const char *name2, int len) -{ - int i; - - for (i = 0; i < len; i++) - if (tolower((unsigned char)name1[i]) != tolower((unsigned char)name2[i])) - return -1; - return 0; -} - -/* Returns a pointer on the SRV request matching the name for the proxy - * . NULL is returned if no match is found. - */ -struct resolv_srvrq *find_srvrq_by_name(const char *name, struct proxy *px) -{ - struct resolv_srvrq *srvrq; - - list_for_each_entry(srvrq, &resolv_srvrq_list, list) { - if (srvrq->proxy == px && strcmp(srvrq->name, name) == 0) - return srvrq; - } - return NULL; -} - -/* Allocates a new SRVRQ for the given server with the name . It returns - * NULL if an error occurred. */ -struct resolv_srvrq *new_resolv_srvrq(struct server *srv, char *fqdn) -{ - struct proxy *px = srv->proxy; - struct resolv_srvrq *srvrq = NULL; - int fqdn_len, hostname_dn_len; - - fqdn_len = strlen(fqdn); - hostname_dn_len = resolv_str_to_dn_label(fqdn, fqdn_len + 1, trash.area, - trash.size); - if (hostname_dn_len == -1) { - ha_alert("config : %s '%s', server '%s': failed to parse FQDN '%s'\n", - proxy_type_str(px), px->id, srv->id, fqdn); - goto err; - } - - if ((srvrq = calloc(1, sizeof(*srvrq))) == NULL) { - ha_alert("config : %s '%s', server '%s': out of memory\n", - proxy_type_str(px), px->id, srv->id); - goto err; - } - srvrq->obj_type = OBJ_TYPE_SRVRQ; - srvrq->proxy = px; - srvrq->name = strdup(fqdn); - srvrq->hostname_dn = strdup(trash.area); - srvrq->hostname_dn_len = hostname_dn_len; - if (!srvrq->name || !srvrq->hostname_dn) { - ha_alert("config : %s '%s', server '%s': out of memory\n", - proxy_type_str(px), px->id, srv->id); - goto err; - } - LIST_ADDQ(&resolv_srvrq_list, &srvrq->list); - return srvrq; - - err: - if (srvrq) { - free(srvrq->name); - free(srvrq->hostname_dn); - free(srvrq); - } - return NULL; -} - - -/* 2 bytes random generator to generate DNS query ID */ -static inline uint16_t resolv_rnd16(void) -{ - if (!resolv_query_id_seed) - resolv_query_id_seed = now_ms; - resolv_query_id_seed ^= resolv_query_id_seed << 13; - resolv_query_id_seed ^= resolv_query_id_seed >> 7; - resolv_query_id_seed ^= resolv_query_id_seed << 17; - return resolv_query_id_seed; -} - - -static inline int resolv_resolution_timeout(struct resolv_resolution *res) -{ - return res->resolvers->timeout.resolve; -} - -/* Updates a resolvers' task timeout for next wake up and queue it */ -static void resolv_update_resolvers_timeout(struct resolvers *resolvers) -{ - struct resolv_resolution *res; - int next; - - next = tick_add(now_ms, resolvers->timeout.resolve); - if (!LIST_ISEMPTY(&resolvers->resolutions.curr)) { - res = LIST_NEXT(&resolvers->resolutions.curr, struct resolv_resolution *, list); - next = MIN(next, tick_add(res->last_query, resolvers->timeout.retry)); - } - - list_for_each_entry(res, &resolvers->resolutions.wait, list) - next = MIN(next, tick_add(res->last_resolution, resolv_resolution_timeout(res))); - - resolvers->t->expire = next; - task_queue(resolvers->t); -} - /* Opens an UDP socket on the namesaver's IP/Port, if required. Returns 0 on * success, -1 otherwise. */ @@ -277,15 +50,15 @@ static int dns_connect_nameserver(struct dns_nameserver *ns) /* Create an UDP socket and connect it on the nameserver's IP/Port */ if ((fd = socket(dgram->addr.to.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { send_log(NULL, LOG_WARNING, - "DNS : resolvers '%s': can't create socket for nameserver '%s'.\n", + "DNS : section '%s': can't create socket for nameserver '%s'.\n", ns->counters->pid, ns->id); return -1; } if (connect(fd, (struct sockaddr*)&dgram->addr.to, get_addr_len(&dgram->addr.to)) == -1) { send_log(NULL, LOG_WARNING, - "DNS : resolvers '%s': can't connect socket for nameserver '%s'.\n", + "DNS : section '%s': can't connect socket for nameserver '%s'.\n", ns->counters->id, ns->id); - close(fd); + close(fd); return -1; } @@ -296,64 +69,8 @@ static int dns_connect_nameserver(struct dns_nameserver *ns) dgram->t.sock.fd = fd; fd_insert(fd, dgram, dgram_fd_handler, MAX_THREADS_MASK); fd_want_recv(fd); - } - return 0; -} - -/* Forges a DNS query. It needs the following information from the caller: - * - : the DNS query id corresponding to this query - * - : DNS_RTYPE_* request DNS record type (A, AAAA, ANY...) - * - : hostname in domain name format - * - : length of - * - * To store the query, the caller must pass a buffer and its size - * . It returns the number of written bytes in success, -1 if is - * too short. - */ -static int resolv_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_hdr; - struct dns_question qinfo; - struct dns_additional_record edns; - char *p = buf; - - if (sizeof(dns_hdr) + sizeof(qinfo) + sizeof(edns) + hostname_dn_len >= bufsize) - return -1; - - memset(buf, 0, bufsize); - - /* Set dns query headers */ - dns_hdr.id = (unsigned short) htons(query_id); - dns_hdr.flags = htons(0x0100); /* qr=0, opcode=0, aa=0, tc=0, rd=1, ra=0, z=0, rcode=0 */ - dns_hdr.qdcount = htons(1); /* 1 question */ - dns_hdr.ancount = 0; - dns_hdr.nscount = 0; - dns_hdr.arcount = htons(1); - memcpy(p, &dns_hdr, sizeof(dns_hdr)); - p += sizeof(dns_hdr); - - /* Set up query hostname */ - memcpy(p, hostname_dn, hostname_dn_len); - p += hostname_dn_len; - *p++ = 0; - - /* Set up query info (type and class) */ - qinfo.qtype = htons(query_type); - qinfo.qclass = htons(DNS_RCLASS_IN); - memcpy(p, &qinfo, sizeof(qinfo)); - p += sizeof(qinfo); - - /* 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(p, &edns, sizeof(edns)); - p += sizeof(edns); - - return (p - buf); + } + return 0; } /* Sends a message to a name server @@ -551,2886 +268,17 @@ out: } -/* Sends a DNS query to resolvers associated to a resolution. It returns 0 on - * success, -1 otherwise. - */ -static int resolv_send_query(struct resolv_resolution *resolution) -{ - struct resolvers *resolvers = resolution->resolvers; - struct dns_nameserver *ns; - int len; - - /* Update resolution */ - resolution->nb_queries = 0; - resolution->nb_responses = 0; - resolution->last_query = now_ms; - - len = resolv_build_query(resolution->query_id, resolution->query_type, - resolvers->accepted_payload_size, - resolution->hostname_dn, resolution->hostname_dn_len, - trash.area, trash.size); - - list_for_each_entry(ns, &resolvers->nameservers, list) { - if (len < 0) { - ns->counters->snd_error++; - continue; - } - - if (dns_send_nameserver(ns, trash.area, len) < 0) - ns->counters->snd_error++; - else - resolution->nb_queries++; - } - - /* Push the resolution at the end of the active list */ - LIST_DEL(&resolution->list); - LIST_ADDQ(&resolvers->resolutions.curr, &resolution->list); - return 0; -} - -/* Prepares and sends a DNS resolution. It returns 1 if the query was sent, 0 if - * skipped and -1 if an error occurred. - */ -static int -resolv_run_resolution(struct resolv_resolution *resolution) -{ - struct resolvers *resolvers = resolution->resolvers; - int query_id, i; - - /* Avoid sending requests for resolutions that don't yet have an - * hostname, ie resolutions linked to servers that do not yet have an - * fqdn */ - if (!resolution->hostname_dn) - return 0; - - /* Check if a resolution has already been started for this server return - * directly to avoid resolution pill up. */ - if (resolution->step != RSLV_STEP_NONE) - return 0; - - /* Generates a new query id. We try at most 100 times to find a free - * query id */ - for (i = 0; i < 100; ++i) { - query_id = resolv_rnd16(); - if (!eb32_lookup(&resolvers->query_ids, query_id)) - break; - query_id = -1; - } - if (query_id == -1) { - send_log(NULL, LOG_NOTICE, - "could not generate a query id for %s, in resolvers %s.\n", - resolution->hostname_dn, resolvers->id); - return -1; - } - - /* Update resolution parameters */ - resolution->query_id = query_id; - resolution->qid.key = query_id; - resolution->step = RSLV_STEP_RUNNING; - resolution->query_type = resolution->prefered_query_type; - resolution->try = resolvers->resolve_retries; - eb32_insert(&resolvers->query_ids, &resolution->qid); - - /* Send the DNS query */ - resolution->try -= 1; - resolv_send_query(resolution); - return 1; -} - -/* Performs a name resolution for the requester */ -void resolv_trigger_resolution(struct resolv_requester *req) -{ - struct resolvers *resolvers; - struct resolv_resolution *res; - int exp; - - if (!req || !req->resolution) - return; - res = req->resolution; - resolvers = res->resolvers; - - /* The resolution must not be triggered yet. Use the cached response, if - * valid */ - exp = tick_add(res->last_resolution, resolvers->hold.valid); - if (resolvers->t && (res->status != RSLV_STATUS_VALID || - !tick_isset(res->last_resolution) || tick_is_expired(exp, now_ms))) - task_wakeup(resolvers->t, TASK_WOKEN_OTHER); -} - - -/* Resets some resolution parameters to initial values and also delete the query - * ID from the resolver's tree. - */ -static void resolv_reset_resolution(struct resolv_resolution *resolution) -{ - /* update resolution status */ - resolution->step = RSLV_STEP_NONE; - resolution->try = 0; - resolution->last_resolution = now_ms; - resolution->nb_queries = 0; - resolution->nb_responses = 0; - resolution->query_type = resolution->prefered_query_type; - - /* clean up query id */ - eb32_delete(&resolution->qid); - resolution->query_id = 0; - resolution->qid.key = 0; -} - -/* Returns the query id contained in a DNS response */ -static inline unsigned short resolv_response_get_query_id(unsigned char *resp) -{ - return resp[0] * 256 + resp[1]; -} - - -/* Analyses, re-builds and copies the name from the DNS response packet - * . must point to the 'data_len' information or pointer 'c0' - * for compressed data. The result is copied into , ensuring we don't - * overflow using Returns the number of bytes the caller can move - * forward. If 0 it means an error occurred while parsing the name. is - * the number of bytes the caller could move forward. - */ -int resolv_read_name(unsigned char *buffer, unsigned char *bufend, - unsigned char *name, char *destination, int dest_len, - int *offset, unsigned int depth) -{ - int nb_bytes = 0, n = 0; - int label_len; - unsigned char *reader = name; - char *dest = destination; - - while (1) { - if (reader >= bufend) - goto err; - - /* Name compression is in use */ - if ((*reader & 0xc0) == 0xc0) { - if (reader + 1 >= bufend) - goto err; - - /* Must point BEFORE current position */ - if ((buffer + reader[1]) > reader) - goto err; - - if (depth++ > 100) - goto err; - - n = resolv_read_name(buffer, bufend, buffer + (*reader & 0x3f)*256 + reader[1], - dest, dest_len - nb_bytes, offset, depth); - if (n == 0) - goto err; - - dest += n; - nb_bytes += n; - goto out; - } - - label_len = *reader; - if (label_len == 0) - goto out; - - /* Check if: - * - we won't read outside the buffer - * - there is enough place in the destination - */ - if ((reader + label_len >= bufend) || (nb_bytes + label_len >= dest_len)) - goto err; - - /* +1 to take label len + label string */ - label_len++; - - memcpy(dest, reader, label_len); - - dest += label_len; - nb_bytes += label_len; - reader += label_len; - } - - out: - /* offset computation: - * parse from until finding either NULL or a pointer "c0xx" - */ - reader = name; - *offset = 0; - while (reader < bufend) { - if ((reader[0] & 0xc0) == 0xc0) { - *offset += 2; - break; - } - else if (*reader == 0) { - *offset += 1; - break; - } - *offset += 1; - ++reader; - } - return nb_bytes; - - err: - return 0; -} - -/* Checks for any obsolete record, also identify any SRV request, and try to - * find a corresponding server. -*/ -static void resolv_check_response(struct resolv_resolution *res) -{ - struct resolvers *resolvers = res->resolvers; - struct resolv_requester *req, *reqback; - struct resolv_answer_item *item, *itemback; - struct server *srv; - struct resolv_srvrq *srvrq; - - list_for_each_entry_safe(item, itemback, &res->response.answer_list, list) { - struct resolv_answer_item *ar_item = item->ar_item; - - /* clean up obsolete Additional record */ - if (ar_item && (ar_item->last_seen + resolvers->hold.obsolete / 1000) < now.tv_sec) { - pool_free(resolv_answer_item_pool, ar_item); - item->ar_item = NULL; - } - - /* Remove obsolete items */ - if ((item->last_seen + resolvers->hold.obsolete / 1000) < now.tv_sec) { - if (item->type != DNS_RTYPE_SRV) - goto rm_obselete_item; - - list_for_each_entry_safe(req, reqback, &res->requesters, list) { - if ((srvrq = objt_resolv_srvrq(req->owner)) == NULL) - continue; - - /* Remove any associated server */ - for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) { - HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->srvrq == srvrq && srv->svc_port == item->port && - item->data_len == srv->hostname_dn_len && - !resolv_hostname_cmp(srv->hostname_dn, item->target, item->data_len)) { - snr_update_srv_status(srv, 1); - free(srv->hostname); - free(srv->hostname_dn); - srv->hostname = NULL; - srv->hostname_dn = NULL; - srv->hostname_dn_len = 0; - resolv_unlink_resolution(srv->resolv_requester); - } - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - } - } - - rm_obselete_item: - LIST_DEL(&item->list); - if (item->ar_item) { - pool_free(resolv_answer_item_pool, item->ar_item); - item->ar_item = NULL; - } - pool_free(resolv_answer_item_pool, item); - continue; - } - - if (item->type != DNS_RTYPE_SRV) - continue; - - /* Now process SRV records */ - list_for_each_entry_safe(req, reqback, &res->requesters, list) { - if ((srvrq = objt_resolv_srvrq(req->owner)) == NULL) - continue; - - /* Check if a server already uses that hostname */ - for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) { - HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->srvrq == srvrq && srv->svc_port == item->port && - item->data_len == srv->hostname_dn_len && - !resolv_hostname_cmp(srv->hostname_dn, item->target, item->data_len)) { - break; - } - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - } - - /* If not, try to find a server with undefined hostname */ - if (!srv) { - for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) { - HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->srvrq == srvrq && !srv->hostname_dn) - break; - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - } - } - - /* And update this server, if found (srv is locked here) */ - if (srv) { - /* Check if an Additional Record is associated to this SRV record. - * Perform some sanity checks too to ensure the record can be used. - * If all fine, we simply pick up the IP address found and associate - * it to the server. - */ - if ((item->ar_item != NULL) && - (item->ar_item->type == DNS_RTYPE_A || item->ar_item->type == DNS_RTYPE_AAAA)) - { - - switch (item->ar_item->type) { - case DNS_RTYPE_A: - update_server_addr(srv, &(((struct sockaddr_in*)&item->ar_item->address)->sin_addr), AF_INET, "DNS additional record"); - break; - case DNS_RTYPE_AAAA: - update_server_addr(srv, &(((struct sockaddr_in6*)&item->ar_item->address)->sin6_addr), AF_INET6, "DNS additional record"); - break; - } - - srv->flags |= SRV_F_NO_RESOLUTION; - } - - if (!srv->hostname_dn) { - const char *msg = NULL; - char hostname[DNS_MAX_NAME_SIZE]; - - if (resolv_dn_label_to_str(item->target, item->data_len+1, - hostname, DNS_MAX_NAME_SIZE) == -1) { - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - continue; - } - msg = update_server_fqdn(srv, hostname, "SRV record", 1); - if (msg) - send_log(srv->proxy, LOG_NOTICE, "%s", msg); - } - - /* now we have an IP address associated to this server, we can update its status */ - snr_update_srv_status(srv, 0); - - srv->svc_port = item->port; - srv->flags &= ~SRV_F_MAPPORTS; - - if (!srv->resolv_opts.ignore_weight) { - char weight[9]; - int ha_weight; - - /* DNS weight range if from 0 to 65535 - * HAProxy weight is from 0 to 256 - * The rule below ensures that weight 0 is well respected - * while allowing a "mapping" from DNS weight into HAProxy's one. - */ - ha_weight = (item->weight + 255) / 256; - - snprintf(weight, sizeof(weight), "%d", ha_weight); - server_parse_weight_change_request(srv, weight); - } - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - } - } - } -} - -/* Validates that the buffer DNS response provided in and finishing - * before is valid from a DNS protocol point of view. - * - * The result is stored in ' response, buf_response, - * response_query_records and response_answer_records members. - * - * This function returns one of the RSLV_RESP_* code to indicate the type of - * error found. - */ -static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufend, - struct resolv_resolution *resolution, int max_answer_records) -{ - unsigned char *reader; - char *previous_dname, tmpname[DNS_MAX_NAME_SIZE]; - int len, flags, offset; - int query_record_id; - int nb_saved_records; - struct resolv_query_item *query; - struct resolv_answer_item *answer_record, *tmp_record; - struct resolv_response *r_res; - int i, found = 0; - int cause = RSLV_RESP_ERROR; - - reader = resp; - len = 0; - previous_dname = NULL; - query = NULL; - answer_record = NULL; - - /* Initialization of response buffer and structure */ - r_res = &resolution->response; - - /* query id */ - if (reader + 2 >= bufend) - goto invalid_resp; - - r_res->header.id = reader[0] * 256 + reader[1]; - reader += 2; - - /* Flags and rcode are stored over 2 bytes - * First byte contains: - * - response flag (1 bit) - * - opcode (4 bits) - * - authoritative (1 bit) - * - truncated (1 bit) - * - recursion desired (1 bit) - */ - if (reader + 2 >= bufend) - goto invalid_resp; - - flags = reader[0] * 256 + reader[1]; - - if ((flags & DNS_FLAG_REPLYCODE) != DNS_RCODE_NO_ERROR) { - if ((flags & DNS_FLAG_REPLYCODE) == DNS_RCODE_NX_DOMAIN) { - cause = RSLV_RESP_NX_DOMAIN; - goto return_error; - } - else if ((flags & DNS_FLAG_REPLYCODE) == DNS_RCODE_REFUSED) { - cause = RSLV_RESP_REFUSED; - goto return_error; - } - else { - cause = RSLV_RESP_ERROR; - goto return_error; - } - } - - /* Move forward 2 bytes for flags */ - reader += 2; - - /* 2 bytes for question count */ - if (reader + 2 >= bufend) - goto invalid_resp; - r_res->header.qdcount = reader[0] * 256 + reader[1]; - /* (for now) we send one query only, so we expect only one in the - * response too */ - if (r_res->header.qdcount != 1) { - cause = RSLV_RESP_QUERY_COUNT_ERROR; - goto return_error; - } - - if (r_res->header.qdcount > DNS_MAX_QUERY_RECORDS) - goto invalid_resp; - reader += 2; - - /* 2 bytes for answer count */ - if (reader + 2 >= bufend) - goto invalid_resp; - r_res->header.ancount = reader[0] * 256 + reader[1]; - if (r_res->header.ancount == 0) { - cause = RSLV_RESP_ANCOUNT_ZERO; - goto return_error; - } - - /* Check if too many records are announced */ - if (r_res->header.ancount > max_answer_records) - goto invalid_resp; - reader += 2; - - /* 2 bytes authority count */ - if (reader + 2 >= bufend) - goto invalid_resp; - r_res->header.nscount = reader[0] * 256 + reader[1]; - reader += 2; - - /* 2 bytes additional count */ - if (reader + 2 >= bufend) - goto invalid_resp; - r_res->header.arcount = reader[0] * 256 + reader[1]; - reader += 2; - - /* Parsing dns queries */ - LIST_INIT(&r_res->query_list); - for (query_record_id = 0; query_record_id < r_res->header.qdcount; query_record_id++) { - /* Use next pre-allocated resolv_query_item after ensuring there is - * still one available. - * It's then added to our packet query list. */ - if (query_record_id > DNS_MAX_QUERY_RECORDS) - goto invalid_resp; - query = &resolution->response_query_records[query_record_id]; - LIST_ADDQ(&r_res->query_list, &query->list); - - /* Name is a NULL terminated string in our case, since we have - * one query per response and the first one can't be compressed - * (using the 0x0c format) */ - offset = 0; - len = resolv_read_name(resp, bufend, reader, query->name, DNS_MAX_NAME_SIZE, &offset, 0); - - if (len == 0) - goto invalid_resp; - - reader += offset; - previous_dname = query->name; - - /* move forward 2 bytes for question type */ - if (reader + 2 >= bufend) - goto invalid_resp; - query->type = reader[0] * 256 + reader[1]; - reader += 2; - - /* move forward 2 bytes for question class */ - if (reader + 2 >= bufend) - goto invalid_resp; - query->class = reader[0] * 256 + reader[1]; - reader += 2; - } - - /* TRUNCATED flag must be checked after we could read the query type - * because a TRUNCATED SRV query type response can still be exploited */ - if (query->type != DNS_RTYPE_SRV && flags & DNS_FLAG_TRUNCATED) { - cause = RSLV_RESP_TRUNCATED; - goto return_error; - } - - /* now parsing response records */ - nb_saved_records = 0; - for (i = 0; i < r_res->header.ancount; i++) { - if (reader >= bufend) - goto invalid_resp; - - answer_record = pool_alloc(resolv_answer_item_pool); - if (answer_record == NULL) - goto invalid_resp; - - /* initialization */ - answer_record->ar_item = NULL; - - offset = 0; - len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset, 0); - - if (len == 0) - goto invalid_resp; - - /* Check if the current record dname is valid. previous_dname - * points either to queried dname or last CNAME target */ - if (query->type != DNS_RTYPE_SRV && resolv_hostname_cmp(previous_dname, tmpname, len) != 0) { - if (i == 0) { - /* First record, means a mismatch issue between - * queried dname and dname found in the first - * record */ - goto invalid_resp; - } - else { - /* If not the first record, this means we have a - * CNAME resolution error. - */ - cause = RSLV_RESP_CNAME_ERROR; - goto return_error; - } - - } - - memcpy(answer_record->name, tmpname, len); - answer_record->name[len] = 0; - - reader += offset; - if (reader >= bufend) - goto invalid_resp; - - /* 2 bytes for record type (A, AAAA, CNAME, etc...) */ - if (reader + 2 > bufend) - goto invalid_resp; - - answer_record->type = reader[0] * 256 + reader[1]; - reader += 2; - - /* 2 bytes for class (2) */ - if (reader + 2 > bufend) - goto invalid_resp; - - answer_record->class = reader[0] * 256 + reader[1]; - reader += 2; - - /* 4 bytes for ttl (4) */ - if (reader + 4 > bufend) - goto invalid_resp; - - answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536 - + reader[2] * 256 + reader[3]; - reader += 4; - - /* Now reading data len */ - if (reader + 2 > bufend) - goto invalid_resp; - - answer_record->data_len = reader[0] * 256 + reader[1]; - - /* Move forward 2 bytes for data len */ - reader += 2; - - if (reader + answer_record->data_len > bufend) - goto invalid_resp; - - /* Analyzing record content */ - switch (answer_record->type) { - case DNS_RTYPE_A: - /* ipv4 is stored on 4 bytes */ - if (answer_record->data_len != 4) - goto invalid_resp; - - answer_record->address.sa_family = AF_INET; - memcpy(&(((struct sockaddr_in *)&answer_record->address)->sin_addr), - reader, answer_record->data_len); - break; - - case DNS_RTYPE_CNAME: - /* Check if this is the last record and update the caller about the status: - * no IP could be found and last record was a CNAME. Could be triggered - * by a wrong query type - * - * + 1 because answer_record_id starts at 0 - * while number of answers is an integer and - * starts at 1. - */ - if (i + 1 == r_res->header.ancount) { - cause = RSLV_RESP_CNAME_ERROR; - goto return_error; - } - - offset = 0; - len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset, 0); - if (len == 0) - goto invalid_resp; - - memcpy(answer_record->target, tmpname, len); - answer_record->target[len] = 0; - previous_dname = answer_record->target; - break; - - - case DNS_RTYPE_SRV: - /* Answer must contain : - * - 2 bytes for the priority - * - 2 bytes for the weight - * - 2 bytes for the port - * - the target hostname - */ - if (answer_record->data_len <= 6) - goto invalid_resp; - - answer_record->priority = read_n16(reader); - reader += sizeof(uint16_t); - answer_record->weight = read_n16(reader); - reader += sizeof(uint16_t); - answer_record->port = read_n16(reader); - reader += sizeof(uint16_t); - offset = 0; - len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset, 0); - if (len == 0) - goto invalid_resp; - - answer_record->data_len = len; - memcpy(answer_record->target, tmpname, len); - answer_record->target[len] = 0; - if (answer_record->ar_item != NULL) { - pool_free(resolv_answer_item_pool, answer_record->ar_item); - answer_record->ar_item = NULL; - } - break; - - case DNS_RTYPE_AAAA: - /* ipv6 is stored on 16 bytes */ - if (answer_record->data_len != 16) - goto invalid_resp; - - answer_record->address.sa_family = AF_INET6; - memcpy(&(((struct sockaddr_in6 *)&answer_record->address)->sin6_addr), - reader, answer_record->data_len); - break; - - } /* switch (record type) */ - - /* Increment the counter for number of records saved into our - * local response */ - nb_saved_records++; - - /* Move forward answer_record->data_len for analyzing next - * record in the response */ - reader += ((answer_record->type == DNS_RTYPE_SRV) - ? offset - : answer_record->data_len); - - /* Lookup to see if we already had this entry */ - found = 0; - list_for_each_entry(tmp_record, &r_res->answer_list, list) { - if (tmp_record->type != answer_record->type) - continue; - - switch(tmp_record->type) { - case DNS_RTYPE_A: - if (!memcmp(&((struct sockaddr_in *)&answer_record->address)->sin_addr, - &((struct sockaddr_in *)&tmp_record->address)->sin_addr, - sizeof(in_addr_t))) - found = 1; - break; - - case DNS_RTYPE_AAAA: - if (!memcmp(&((struct sockaddr_in6 *)&answer_record->address)->sin6_addr, - &((struct sockaddr_in6 *)&tmp_record->address)->sin6_addr, - sizeof(struct in6_addr))) - found = 1; - break; - - case DNS_RTYPE_SRV: - if (answer_record->data_len == tmp_record->data_len && - !resolv_hostname_cmp(answer_record->target, tmp_record->target, answer_record->data_len) && - answer_record->port == tmp_record->port) { - tmp_record->weight = answer_record->weight; - found = 1; - } - break; - - default: - break; - } - - if (found == 1) - break; - } - - if (found == 1) { - tmp_record->last_seen = now.tv_sec; - pool_free(resolv_answer_item_pool, answer_record); - answer_record = NULL; - } - else { - answer_record->last_seen = now.tv_sec; - answer_record->ar_item = NULL; - LIST_ADDQ(&r_res->answer_list, &answer_record->list); - answer_record = NULL; - } - } /* for i 0 to ancount */ - - /* Save the number of records we really own */ - r_res->header.ancount = nb_saved_records; - - /* now parsing additional records for SRV queries only */ - if (query->type != DNS_RTYPE_SRV) - goto skip_parsing_additional_records; - - /* if we find Authority records, just skip them */ - for (i = 0; i < r_res->header.nscount; i++) { - offset = 0; - len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, - &offset, 0); - if (len == 0) - continue; - - if (reader + offset + 10 >= bufend) - goto invalid_resp; - - reader += offset; - /* skip 2 bytes for class */ - reader += 2; - /* skip 2 bytes for type */ - reader += 2; - /* skip 4 bytes for ttl */ - reader += 4; - /* read data len */ - len = reader[0] * 256 + reader[1]; - reader += 2; - - if (reader + len >= bufend) - goto invalid_resp; - - reader += len; - } - - nb_saved_records = 0; - for (i = 0; i < r_res->header.arcount; i++) { - if (reader >= bufend) - goto invalid_resp; - - answer_record = pool_alloc(resolv_answer_item_pool); - if (answer_record == NULL) - goto invalid_resp; - - offset = 0; - len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset, 0); - - if (len == 0) { - pool_free(resolv_answer_item_pool, answer_record); - answer_record = NULL; - continue; - } - - memcpy(answer_record->name, tmpname, len); - answer_record->name[len] = 0; - - reader += offset; - if (reader >= bufend) - goto invalid_resp; - - /* 2 bytes for record type (A, AAAA, CNAME, etc...) */ - if (reader + 2 > bufend) - goto invalid_resp; - - answer_record->type = reader[0] * 256 + reader[1]; - reader += 2; - - /* 2 bytes for class (2) */ - if (reader + 2 > bufend) - goto invalid_resp; - - answer_record->class = reader[0] * 256 + reader[1]; - reader += 2; - - /* 4 bytes for ttl (4) */ - if (reader + 4 > bufend) - goto invalid_resp; - - answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536 - + reader[2] * 256 + reader[3]; - reader += 4; - - /* Now reading data len */ - if (reader + 2 > bufend) - goto invalid_resp; - - answer_record->data_len = reader[0] * 256 + reader[1]; - - /* Move forward 2 bytes for data len */ - reader += 2; - - if (reader + answer_record->data_len > bufend) - goto invalid_resp; - - /* Analyzing record content */ - switch (answer_record->type) { - case DNS_RTYPE_A: - /* ipv4 is stored on 4 bytes */ - if (answer_record->data_len != 4) - goto invalid_resp; - - answer_record->address.sa_family = AF_INET; - memcpy(&(((struct sockaddr_in *)&answer_record->address)->sin_addr), - reader, answer_record->data_len); - break; - - case DNS_RTYPE_AAAA: - /* ipv6 is stored on 16 bytes */ - if (answer_record->data_len != 16) - goto invalid_resp; - - answer_record->address.sa_family = AF_INET6; - memcpy(&(((struct sockaddr_in6 *)&answer_record->address)->sin6_addr), - reader, answer_record->data_len); - break; - - default: - pool_free(resolv_answer_item_pool, answer_record); - answer_record = NULL; - continue; - - } /* switch (record type) */ - - /* Increment the counter for number of records saved into our - * local response */ - nb_saved_records++; - - /* Move forward answer_record->data_len for analyzing next - * record in the response */ - reader += ((answer_record->type == DNS_RTYPE_SRV) - ? offset - : answer_record->data_len); - - /* Lookup to see if we already had this entry */ - found = 0; - list_for_each_entry(tmp_record, &r_res->answer_list, list) { - if (tmp_record->type != answer_record->type) - continue; - - switch(tmp_record->type) { - case DNS_RTYPE_A: - if (!memcmp(&((struct sockaddr_in *)&answer_record->address)->sin_addr, - &((struct sockaddr_in *)&tmp_record->address)->sin_addr, - sizeof(in_addr_t))) - found = 1; - break; - - case DNS_RTYPE_AAAA: - if (!memcmp(&((struct sockaddr_in6 *)&answer_record->address)->sin6_addr, - &((struct sockaddr_in6 *)&tmp_record->address)->sin6_addr, - sizeof(struct in6_addr))) - found = 1; - break; - - default: - break; - } - - if (found == 1) - break; - } - - if (found == 1) { - tmp_record->last_seen = now.tv_sec; - pool_free(resolv_answer_item_pool, answer_record); - answer_record = NULL; - } - else { - answer_record->last_seen = now.tv_sec; - answer_record->ar_item = NULL; - - // looking for the SRV record in the response list linked to this additional record - list_for_each_entry(tmp_record, &r_res->answer_list, list) { - if (tmp_record->type == DNS_RTYPE_SRV && - tmp_record->ar_item == NULL && - !resolv_hostname_cmp(tmp_record->target, answer_record->name, tmp_record->data_len)) { - /* Always use the received additional record to refresh info */ - if (tmp_record->ar_item) - pool_free(resolv_answer_item_pool, tmp_record->ar_item); - tmp_record->ar_item = answer_record; - break; - } - } - if (tmp_record->ar_item != answer_record) - pool_free(resolv_answer_item_pool, answer_record); - answer_record = NULL; - } - } /* for i 0 to arcount */ - - skip_parsing_additional_records: - - /* Save the number of records we really own */ - r_res->header.arcount = nb_saved_records; - - resolv_check_response(resolution); - return RSLV_RESP_VALID; - - invalid_resp: - cause = RSLV_RESP_INVALID; - - return_error: - pool_free(resolv_answer_item_pool, answer_record); - return cause; -} - -/* Searches dn_name resolution in resp. - * If existing IP not found, return the first IP matching family_priority, - * otherwise, first ip found - * The following tasks are the responsibility of the caller: - * - contains an error free DNS response - * For both cases above, resolv_validate_dns_response is required - * returns one of the RSLV_UPD_* code - */ -int resolv_get_ip_from_response(struct resolv_response *r_res, - struct resolv_options *resolv_opts, void *currentip, - short currentip_sin_family, - void **newip, short *newip_sin_family, - void *owner) -{ - struct resolv_answer_item *record; - int family_priority; - int currentip_found; - unsigned char *newip4, *newip6; - int currentip_sel; - int j; - int score, max_score; - int allowed_duplicated_ip; - - family_priority = resolv_opts->family_prio; - allowed_duplicated_ip = resolv_opts->accept_duplicate_ip; - *newip = newip4 = newip6 = NULL; - currentip_found = 0; - *newip_sin_family = AF_UNSPEC; - max_score = -1; - - /* Select an IP regarding configuration preference. - * Top priority is the preferred network ip version, - * second priority is the preferred network. - * the last priority is the currently used IP, - * - * For these three priorities, a score is calculated. The - * weight are: - * 8 - preferred ip version. - * 4 - preferred network. - * 2 - if the ip in the record is not affected to any other server in the same backend (duplication) - * 1 - current ip. - * The result with the biggest score is returned. - */ - - list_for_each_entry(record, &r_res->answer_list, list) { - void *ip; - unsigned char ip_type; - - if (record->type == DNS_RTYPE_A) { - ip = &(((struct sockaddr_in *)&record->address)->sin_addr); - ip_type = AF_INET; - } - else if (record->type == DNS_RTYPE_AAAA) { - ip_type = AF_INET6; - ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr); - } - else - continue; - score = 0; - - /* Check for preferred ip protocol. */ - if (ip_type == family_priority) - score += 8; - - /* Check for preferred network. */ - for (j = 0; j < resolv_opts->pref_net_nb; j++) { - - /* Compare only the same addresses class. */ - if (resolv_opts->pref_net[j].family != ip_type) - continue; - - if ((ip_type == AF_INET && - in_net_ipv4(ip, - &resolv_opts->pref_net[j].mask.in4, - &resolv_opts->pref_net[j].addr.in4)) || - (ip_type == AF_INET6 && - in_net_ipv6(ip, - &resolv_opts->pref_net[j].mask.in6, - &resolv_opts->pref_net[j].addr.in6))) { - score += 4; - break; - } - } - - /* Check if the IP found in the record is already affected to a - * member of a group. If not, the score should be incremented - * by 2. */ - if (owner && snr_check_ip_callback(owner, ip, &ip_type)) { - if (!allowed_duplicated_ip) { - continue; - } - } else { - score += 2; - } - - /* Check for current ip matching. */ - if (ip_type == currentip_sin_family && - ((currentip_sin_family == AF_INET && - !memcmp(ip, currentip, 4)) || - (currentip_sin_family == AF_INET6 && - !memcmp(ip, currentip, 16)))) { - score++; - currentip_sel = 1; - } - else - currentip_sel = 0; - - /* Keep the address if the score is better than the previous - * score. The maximum score is 15, if this value is reached, we - * break the parsing. Implicitly, this score is reached the ip - * selected is the current ip. */ - if (score > max_score) { - if (ip_type == AF_INET) - newip4 = ip; - else - newip6 = ip; - currentip_found = currentip_sel; - if (score == 15) - return RSLV_UPD_NO; - max_score = score; - } - } /* list for each record entries */ - - /* No IP found in the response */ - if (!newip4 && !newip6) - return RSLV_UPD_NO_IP_FOUND; - - /* Case when the caller looks first for an IPv4 address */ - if (family_priority == AF_INET) { - if (newip4) { - *newip = newip4; - *newip_sin_family = AF_INET; - } - else if (newip6) { - *newip = newip6; - *newip_sin_family = AF_INET6; - } - if (!currentip_found) - goto not_found; - } - /* Case when the caller looks first for an IPv6 address */ - else if (family_priority == AF_INET6) { - if (newip6) { - *newip = newip6; - *newip_sin_family = AF_INET6; - } - else if (newip4) { - *newip = newip4; - *newip_sin_family = AF_INET; - } - if (!currentip_found) - goto not_found; - } - /* Case when the caller have no preference (we prefer IPv6) */ - else if (family_priority == AF_UNSPEC) { - if (newip6) { - *newip = newip6; - *newip_sin_family = AF_INET6; - } - else if (newip4) { - *newip = newip4; - *newip_sin_family = AF_INET; - } - if (!currentip_found) - goto not_found; - } - - /* No reason why we should change the server's IP address */ - return RSLV_UPD_NO; - - not_found: - list_for_each_entry(record, &r_res->answer_list, list) { - /* Move the first record to the end of the list, for internal - * round robin */ - LIST_DEL(&record->list); - LIST_ADDQ(&r_res->answer_list, &record->list); - break; - } - return RSLV_UPD_SRVIP_NOT_FOUND; -} - -/* Turns a domain name label into a string. - * - * must be a null-terminated string. must include the terminating - * null byte. must be allocated and its size must be passed in . - * - * In case of error, -1 is returned, otherwise, the number of bytes copied in - * (including the terminating null byte). - */ -int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len) -{ - char *ptr; - int i, sz; - - if (str_len < dn_len - 1) - return -1; - - ptr = str; - for (i = 0; i < dn_len-1; ++i) { - sz = dn[i]; - if (i) - *ptr++ = '.'; - memcpy(ptr, dn+i+1, sz); - ptr += sz; - i += sz; - } - *ptr++ = '\0'; - return (ptr - str); -} - -/* Turns a string into domain name label: www.haproxy.org into 3www7haproxy3org - * - * must be a null-terminated string. must include the - * terminating null byte. buffer must be allocated and its size must be - * passed in . - * - * In case of error, -1 is returned, otherwise, the number of bytes copied in - * (excluding the terminating null byte). - */ -int resolv_str_to_dn_label(const char *str, int str_len, char *dn, int dn_len) -{ - int i, offset; - - if (dn_len < str_len + 1) - return -1; - - /* First byte of dn will be used to store the length of the first - * label */ - offset = 0; - for (i = 0; i < str_len; ++i) { - if (str[i] == '.') { - /* 2 or more consecutive dots is invalid */ - if (i == offset) - return -1; - - /* ignore trailing dot */ - if (i + 2 == str_len) { - i++; - break; - } - - dn[offset] = (i - offset); - offset = i+1; - continue; - } - dn[i+1] = str[i]; - } - dn[offset] = (i - offset - 1); - dn[i] = '\0'; - return i; -} - -/* Validates host name: - * - total size - * - each label size individually - * returns: - * 0 in case of error. If is not NULL, an error message is stored there. - * 1 when no error. is left unaffected. - */ -int resolv_hostname_validation(const char *string, char **err) -{ - int i; - - if (strlen(string) > DNS_MAX_NAME_SIZE) { - if (err) - *err = DNS_TOO_LONG_FQDN; - return 0; - } - - while (*string) { - i = 0; - while (*string && *string != '.' && i < DNS_MAX_LABEL_SIZE) { - if (!(*string == '-' || *string == '_' || - (*string >= 'a' && *string <= 'z') || - (*string >= 'A' && *string <= 'Z') || - (*string >= '0' && *string <= '9'))) { - if (err) - *err = DNS_INVALID_CHARACTER; - return 0; - } - i++; - string++; - } - - if (!(*string)) - break; - - if (*string != '.' && i >= DNS_MAX_LABEL_SIZE) { - if (err) - *err = DNS_LABEL_TOO_LONG; - return 0; - } - - string++; - } - return 1; -} - -/* Picks up an available resolution from the different resolution list - * associated to a resolvers section, in this order: - * 1. check in resolutions.curr for the same hostname and query_type - * 2. check in resolutions.wait for the same hostname and query_type - * 3. Get a new resolution from resolution pool - * - * Returns an available resolution, NULL if none found. - */ -static struct resolv_resolution *resolv_pick_resolution(struct resolvers *resolvers, - char **hostname_dn, int hostname_dn_len, - int query_type) -{ - struct resolv_resolution *res; - - if (!*hostname_dn) - goto from_pool; - - /* Search for same hostname and query type in resolutions.curr */ - list_for_each_entry(res, &resolvers->resolutions.curr, list) { - if (!res->hostname_dn) - continue; - if ((query_type == res->prefered_query_type) && - hostname_dn_len == res->hostname_dn_len && - !resolv_hostname_cmp(*hostname_dn, res->hostname_dn, hostname_dn_len)) - return res; - } - - /* Search for same hostname and query type in resolutions.wait */ - list_for_each_entry(res, &resolvers->resolutions.wait, list) { - if (!res->hostname_dn) - continue; - if ((query_type == res->prefered_query_type) && - hostname_dn_len == res->hostname_dn_len && - !resolv_hostname_cmp(*hostname_dn, res->hostname_dn, hostname_dn_len)) - return res; - } - - from_pool: - /* No resolution could be found, so let's allocate a new one */ - res = pool_alloc(resolv_resolution_pool); - if (res) { - memset(res, 0, sizeof(*res)); - res->resolvers = resolvers; - res->uuid = resolution_uuid; - res->status = RSLV_STATUS_NONE; - res->step = RSLV_STEP_NONE; - res->last_valid = now_ms; - - LIST_INIT(&res->requesters); - LIST_INIT(&res->response.answer_list); - - res->prefered_query_type = query_type; - res->query_type = query_type; - res->hostname_dn = *hostname_dn; - res->hostname_dn_len = hostname_dn_len; - - ++resolution_uuid; - - /* Move the resolution to the resolvers wait queue */ - LIST_ADDQ(&resolvers->resolutions.wait, &res->list); - } - return res; -} - -/* Releases a resolution from its requester(s) and move it back to the pool */ -static void resolv_free_resolution(struct resolv_resolution *resolution) -{ - struct resolv_requester *req, *reqback; - struct resolv_answer_item *item, *itemback; - - /* clean up configuration */ - resolv_reset_resolution(resolution); - resolution->hostname_dn = NULL; - resolution->hostname_dn_len = 0; - - list_for_each_entry_safe(req, reqback, &resolution->requesters, list) { - LIST_DEL(&req->list); - req->resolution = NULL; - } - - list_for_each_entry_safe(item, itemback, &resolution->response.answer_list, list) { - LIST_DEL(&item->list); - if (item->ar_item) { - pool_free(resolv_answer_item_pool, item->ar_item); - item->ar_item = NULL; - } - pool_free(resolv_answer_item_pool, item); - } - - LIST_DEL(&resolution->list); - pool_free(resolv_resolution_pool, resolution); -} - -/* Links a requester (a server or a resolv_srvrq) with a resolution. It returns 0 - * on success, -1 otherwise. - */ -int resolv_link_resolution(void *requester, int requester_type, int requester_locked) -{ - struct resolv_resolution *res = NULL; - struct resolv_requester *req; - struct resolvers *resolvers; - struct server *srv = NULL; - struct resolv_srvrq *srvrq = NULL; - struct stream *stream = NULL; - char **hostname_dn; - int hostname_dn_len, query_type; - - switch (requester_type) { - case OBJ_TYPE_SERVER: - srv = (struct server *)requester; - hostname_dn = &srv->hostname_dn; - hostname_dn_len = srv->hostname_dn_len; - resolvers = srv->resolvers; - query_type = ((srv->resolv_opts.family_prio == AF_INET) - ? DNS_RTYPE_A - : DNS_RTYPE_AAAA); - break; - - case OBJ_TYPE_SRVRQ: - srvrq = (struct resolv_srvrq *)requester; - hostname_dn = &srvrq->hostname_dn; - hostname_dn_len = srvrq->hostname_dn_len; - resolvers = srvrq->resolvers; - query_type = DNS_RTYPE_SRV; - break; - - case OBJ_TYPE_STREAM: - stream = (struct stream *)requester; - hostname_dn = &stream->resolv_ctx.hostname_dn; - hostname_dn_len = stream->resolv_ctx.hostname_dn_len; - resolvers = stream->resolv_ctx.parent->arg.resolv.resolvers; - query_type = ((stream->resolv_ctx.parent->arg.resolv.opts->family_prio == AF_INET) - ? DNS_RTYPE_A - : DNS_RTYPE_AAAA); - break; - default: - goto err; - } - - /* Get a resolution from the resolvers' wait queue or pool */ - if ((res = resolv_pick_resolution(resolvers, hostname_dn, hostname_dn_len, query_type)) == NULL) - goto err; - - if (srv) { - if (!requester_locked) - HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->resolv_requester == NULL) { - if ((req = pool_alloc(resolv_requester_pool)) == NULL) { - if (!requester_locked) - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - goto err; - } - req->owner = &srv->obj_type; - srv->resolv_requester = req; - } - else - req = srv->resolv_requester; - if (!requester_locked) - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - - req->requester_cb = snr_resolution_cb; - req->requester_error_cb = snr_resolution_error_cb; - } - else if (srvrq) { - if (srvrq->requester == NULL) { - if ((req = pool_alloc(resolv_requester_pool)) == NULL) - goto err; - req->owner = &srvrq->obj_type; - srvrq->requester = req; - } - else - req = srvrq->requester; - - req->requester_cb = snr_resolution_cb; - req->requester_error_cb = snr_resolution_error_cb; - } - else if (stream) { - if (stream->resolv_ctx.requester == NULL) { - if ((req = pool_alloc(resolv_requester_pool)) == NULL) - goto err; - req->owner = &stream->obj_type; - stream->resolv_ctx.requester = req; - } - else - req = stream->resolv_ctx.requester; - - req->requester_cb = act_resolution_cb; - req->requester_error_cb = act_resolution_error_cb; - } - else - goto err; - - req->resolution = res; - - LIST_ADDQ(&res->requesters, &req->list); - return 0; - - err: - if (res && LIST_ISEMPTY(&res->requesters)) - resolv_free_resolution(res); - return -1; -} - -/* Removes a requester from a DNS resolution. It takes takes care of all the - * consequences. It also cleans up some parameters from the requester. - */ -void resolv_unlink_resolution(struct resolv_requester *requester) -{ - struct resolv_resolution *res; - struct resolv_requester *req; - - /* Nothing to do */ - if (!requester || !requester->resolution) - return; - res = requester->resolution; - - /* Clean up the requester */ - LIST_DEL(&requester->list); - requester->resolution = NULL; - - /* We need to find another requester linked on this resolution */ - if (!LIST_ISEMPTY(&res->requesters)) - req = LIST_NEXT(&res->requesters, struct resolv_requester *, list); - else { - resolv_free_resolution(res); - return; - } - - /* Move hostname_dn related pointers to the next requester */ - switch (obj_type(req->owner)) { - case OBJ_TYPE_SERVER: - res->hostname_dn = __objt_server(req->owner)->hostname_dn; - res->hostname_dn_len = __objt_server(req->owner)->hostname_dn_len; - break; - case OBJ_TYPE_SRVRQ: - res->hostname_dn = __objt_resolv_srvrq(req->owner)->hostname_dn; - res->hostname_dn_len = __objt_resolv_srvrq(req->owner)->hostname_dn_len; - break; - case OBJ_TYPE_STREAM: - res->hostname_dn = __objt_stream(req->owner)->resolv_ctx.hostname_dn; - res->hostname_dn_len = __objt_stream(req->owner)->resolv_ctx.hostname_dn_len; - break; - default: - res->hostname_dn = NULL; - res->hostname_dn_len = 0; - break; - } -} - -/* Called when a network IO is generated on a name server socket for an incoming - * packet. It performs the following actions: - * - check if the packet requires processing (not outdated resolution) - * - ensure the DNS packet received is valid and call requester's callback - * - call requester's error callback if invalid response - * - check the dn_name in the packet against the one sent - */ -static int resolv_process_responses(struct dns_nameserver *ns) -{ - struct dns_counters *tmpcounters; - struct resolvers *resolvers; - struct resolv_resolution *res; - struct resolv_query_item *query; - unsigned char buf[DNS_MAX_UDP_MESSAGE + 1]; - unsigned char *bufend; - int buflen, dns_resp; - int max_answer_records; - unsigned short query_id; - struct eb32_node *eb; - struct resolv_requester *req; - - resolvers = ns->parent; - HA_SPIN_LOCK(DNS_LOCK, &resolvers->lock); - - /* process all pending input messages */ - while (1) { - /* read message received */ - memset(buf, '\0', resolvers->accepted_payload_size + 1); - if ((buflen = dns_recv_nameserver(ns, (void *)buf, sizeof(buf))) <= 0) { - /* TO DO: handle error case */ - break; - } - - /* message too big */ - if (buflen > resolvers->accepted_payload_size) { - ns->counters->too_big++; - continue; - } - - /* initializing variables */ - bufend = buf + buflen; /* pointer to mark the end of the buffer */ - - /* read the query id from the packet (16 bits) */ - if (buf + 2 > bufend) { - ns->counters->invalid++; - continue; - } - query_id = resolv_response_get_query_id(buf); - - /* search the query_id in the pending resolution tree */ - eb = eb32_lookup(&resolvers->query_ids, query_id); - if (eb == NULL) { - /* unknown query id means an outdated response and can be safely ignored */ - ns->counters->outdated++; - continue; - } - - /* known query id means a resolution in progress */ - res = eb32_entry(eb, struct resolv_resolution, qid); - /* number of responses received */ - res->nb_responses++; - - max_answer_records = (resolvers->accepted_payload_size - DNS_HEADER_SIZE) / DNS_MIN_RECORD_SIZE; - dns_resp = resolv_validate_dns_response(buf, bufend, res, max_answer_records); - - switch (dns_resp) { - case RSLV_RESP_VALID: - break; - - case RSLV_RESP_INVALID: - case RSLV_RESP_QUERY_COUNT_ERROR: - case RSLV_RESP_WRONG_NAME: - res->status = RSLV_STATUS_INVALID; - ns->counters->invalid++; - break; - - case RSLV_RESP_NX_DOMAIN: - res->status = RSLV_STATUS_NX; - ns->counters->nx++; - break; - - case RSLV_RESP_REFUSED: - res->status = RSLV_STATUS_REFUSED; - ns->counters->refused++; - break; - - case RSLV_RESP_ANCOUNT_ZERO: - res->status = RSLV_STATUS_OTHER; - ns->counters->any_err++; - break; - - case RSLV_RESP_CNAME_ERROR: - res->status = RSLV_STATUS_OTHER; - ns->counters->cname_error++; - break; - - case RSLV_RESP_TRUNCATED: - res->status = RSLV_STATUS_OTHER; - ns->counters->truncated++; - break; - - case RSLV_RESP_NO_EXPECTED_RECORD: - case RSLV_RESP_ERROR: - case RSLV_RESP_INTERNAL: - res->status = RSLV_STATUS_OTHER; - ns->counters->other++; - break; - } - - /* Wait all nameservers response to handle errors */ - if (dns_resp != RSLV_RESP_VALID && res->nb_responses < resolvers->nb_nameservers) - continue; - - /* Process error codes */ - if (dns_resp != RSLV_RESP_VALID) { - if (res->prefered_query_type != res->query_type) { - /* The fallback on the query type was already performed, - * so check the try counter. If it falls to 0, we can - * report an error. Else, wait the next attempt. */ - if (!res->try) - goto report_res_error; - } - else { - /* Fallback from A to AAAA or the opposite and re-send - * the resolution immediately. try counter is not - * decremented. */ - if (res->prefered_query_type == DNS_RTYPE_A) { - res->query_type = DNS_RTYPE_AAAA; - resolv_send_query(res); - } - else if (res->prefered_query_type == DNS_RTYPE_AAAA) { - res->query_type = DNS_RTYPE_A; - resolv_send_query(res); - } - } - continue; - } - - /* Now let's check the query's dname corresponds to the one we - * sent. We can check only the first query of the list. We send - * one query at a time so we get one query in the response */ - query = LIST_NEXT(&res->response.query_list, struct resolv_query_item *, list); - if (query && resolv_hostname_cmp(query->name, res->hostname_dn, res->hostname_dn_len) != 0) { - dns_resp = RSLV_RESP_WRONG_NAME; - ns->counters->other++; - goto report_res_error; - } - - /* So the resolution succeeded */ - res->status = RSLV_STATUS_VALID; - res->last_valid = now_ms; - ns->counters->valid++; - goto report_res_success; - - report_res_error: - list_for_each_entry(req, &res->requesters, list) - req->requester_error_cb(req, dns_resp); - resolv_reset_resolution(res); - LIST_DEL(&res->list); - LIST_ADDQ(&resolvers->resolutions.wait, &res->list); - continue; - - report_res_success: - /* Only the 1rst requester s managed by the server, others are - * from the cache */ - tmpcounters = ns->counters; - list_for_each_entry(req, &res->requesters, list) { - struct server *s = objt_server(req->owner); - - if (s) - HA_SPIN_LOCK(SERVER_LOCK, &s->lock); - req->requester_cb(req, tmpcounters); - if (s) - HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock); - tmpcounters = NULL; - } - - resolv_reset_resolution(res); - LIST_DEL(&res->list); - LIST_ADDQ(&resolvers->resolutions.wait, &res->list); - continue; - } - resolv_update_resolvers_timeout(resolvers); - HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock); - - return buflen; -} - -/* Processes DNS resolution. First, it checks the active list to detect expired - * resolutions and retry them if possible. Else a timeout is reported. Then, it - * checks the wait list to trigger new resolutions. - */ -static struct task *process_resolvers(struct task *t, void *context, unsigned short state) -{ - struct resolvers *resolvers = context; - struct resolv_resolution *res, *resback; - int exp; - - HA_SPIN_LOCK(DNS_LOCK, &resolvers->lock); - - /* Handle all expired resolutions from the active list */ - list_for_each_entry_safe(res, resback, &resolvers->resolutions.curr, list) { - /* When we find the first resolution in the future, then we can - * stop here */ - exp = tick_add(res->last_query, resolvers->timeout.retry); - if (!tick_is_expired(exp, now_ms)) - break; - - /* If current resolution has been tried too many times and - * finishes in timeout we update its status and remove it from - * the list */ - if (!res->try) { - struct resolv_requester *req; - - /* Notify the result to the requesters */ - if (!res->nb_responses) - res->status = RSLV_STATUS_TIMEOUT; - list_for_each_entry(req, &res->requesters, list) - req->requester_error_cb(req, res->status); - - /* Clean up resolution info and remove it from the - * current list */ - resolv_reset_resolution(res); - LIST_DEL(&res->list); - LIST_ADDQ(&resolvers->resolutions.wait, &res->list); - } - else { - /* Otherwise resend the DNS query and requeue the resolution */ - if (!res->nb_responses || res->prefered_query_type != res->query_type) { - /* No response received (a real timeout) or fallback already done */ - res->query_type = res->prefered_query_type; - res->try--; - } - else { - /* Fallback from A to AAAA or the opposite and re-send - * the resolution immediately. try counter is not - * decremented. */ - if (res->prefered_query_type == DNS_RTYPE_A) - res->query_type = DNS_RTYPE_AAAA; - else if (res->prefered_query_type == DNS_RTYPE_AAAA) - res->query_type = DNS_RTYPE_A; - else - res->try--; - } - resolv_send_query(res); - } - } - - /* Handle all resolutions in the wait list */ - list_for_each_entry_safe(res, resback, &resolvers->resolutions.wait, list) { - exp = tick_add(res->last_resolution, resolv_resolution_timeout(res)); - if (tick_isset(res->last_resolution) && !tick_is_expired(exp, now_ms)) - continue; - - if (resolv_run_resolution(res) != 1) { - res->last_resolution = now_ms; - LIST_DEL(&res->list); - LIST_ADDQ(&resolvers->resolutions.wait, &res->list); - } - } - - resolv_update_resolvers_timeout(resolvers); - HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock); - return t; -} - /* proto_udp callback functions for a DNS resolution */ struct dgram_data_cb dns_dgram_cb = { .recv = dns_resolve_recv, .send = dns_resolve_send, }; -/* Release memory allocated by DNS */ -static void resolvers_deinit(void) -{ - struct resolvers *resolvers, *resolversback; - struct dns_nameserver *ns, *nsback; - struct resolv_resolution *res, *resback; - struct resolv_requester *req, *reqback; - struct resolv_srvrq *srvrq, *srvrqback; - - list_for_each_entry_safe(resolvers, resolversback, &sec_resolvers, list) { - list_for_each_entry_safe(ns, nsback, &resolvers->nameservers, list) { - free(ns->id); - free((char *)ns->conf.file); - if (ns->dgram) { - if (ns->dgram->conn.t.sock.fd != -1) { - fd_delete(ns->dgram->conn.t.sock.fd); - close(ns->dgram->conn.t.sock.fd); - } - if (ns->dgram->ring_req) - ring_free(ns->dgram->ring_req); - free(ns->dgram); - } - LIST_DEL(&ns->list); - EXTRA_COUNTERS_FREE(ns->extra_counters); - free(ns); - } - - list_for_each_entry_safe(res, resback, &resolvers->resolutions.curr, list) { - list_for_each_entry_safe(req, reqback, &res->requesters, list) { - LIST_DEL(&req->list); - pool_free(resolv_requester_pool, req); - } - resolv_free_resolution(res); - } - - list_for_each_entry_safe(res, resback, &resolvers->resolutions.wait, list) { - list_for_each_entry_safe(req, reqback, &res->requesters, list) { - LIST_DEL(&req->list); - pool_free(resolv_requester_pool, req); - } - resolv_free_resolution(res); - } - - free(resolvers->id); - free((char *)resolvers->conf.file); - task_destroy(resolvers->t); - LIST_DEL(&resolvers->list); - free(resolvers); - } - - list_for_each_entry_safe(srvrq, srvrqback, &resolv_srvrq_list, list) { - free(srvrq->name); - free(srvrq->hostname_dn); - LIST_DEL(&srvrq->list); - free(srvrq); - } -} - -/* Finalizes the DNS configuration by allocating required resources and checking - * live parameters. - * Returns 0 on success, ERR_* flags otherwise. - */ -static int resolvers_finalize_config(void) -{ - struct resolvers *resolvers; - struct proxy *px; - int err_code = 0; - - /* allocate pool of resolution per resolvers */ - list_for_each_entry(resolvers, &sec_resolvers, list) { - struct dns_nameserver *ns; - struct task *t; - - /* Check if we can create the socket with nameservers info */ - list_for_each_entry(ns, &resolvers->nameservers, list) { - int fd; - - if (ns->dgram) { - /* Check nameserver info */ - if ((fd = socket(ns->dgram->conn.addr.to.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { - ha_alert("config : resolvers '%s': can't create socket for nameserver '%s'.\n", - resolvers->id, ns->id); - err_code |= (ERR_ALERT|ERR_ABORT); - continue; - } - if (connect(fd, (struct sockaddr*)&ns->dgram->conn.addr.to, get_addr_len(&ns->dgram->conn.addr.to)) == -1) { - ha_alert("config : resolvers '%s': can't connect socket for nameserver '%s'.\n", - resolvers->id, ns->id); - close(fd); - err_code |= (ERR_ALERT|ERR_ABORT); - continue; - } - close(fd); - } - } - - /* Create the task associated to the resolvers section */ - if ((t = task_new(MAX_THREADS_MASK)) == NULL) { - ha_alert("config : resolvers '%s' : out of memory.\n", resolvers->id); - err_code |= (ERR_ALERT|ERR_ABORT); - goto err; - } - - /* Update task's parameters */ - t->process = process_resolvers; - t->context = resolvers; - resolvers->t = t; - task_wakeup(t, TASK_WOKEN_INIT); - } - - for (px = proxies_list; px; px = px->next) { - struct server *srv; - - for (srv = px->srv; srv; srv = srv->next) { - struct resolvers *resolvers; - - if (!srv->resolvers_id) - continue; - - if ((resolvers = find_resolvers_by_id(srv->resolvers_id)) == NULL) { - ha_alert("config : %s '%s', server '%s': unable to find required resolvers '%s'\n", - proxy_type_str(px), px->id, srv->id, srv->resolvers_id); - err_code |= (ERR_ALERT|ERR_ABORT); - continue; - } - srv->resolvers = resolvers; - - if (srv->srvrq && !srv->srvrq->resolvers) { - srv->srvrq->resolvers = srv->resolvers; - if (resolv_link_resolution(srv->srvrq, OBJ_TYPE_SRVRQ, 0) == -1) { - ha_alert("config : %s '%s' : unable to set DNS resolution for server '%s'.\n", - proxy_type_str(px), px->id, srv->id); - err_code |= (ERR_ALERT|ERR_ABORT); - continue; - } - } - if (resolv_link_resolution(srv, OBJ_TYPE_SERVER, 0) == -1) { - ha_alert("config : %s '%s', unable to set DNS resolution for server '%s'.\n", - proxy_type_str(px), px->id, srv->id); - err_code |= (ERR_ALERT|ERR_ABORT); - continue; - } - } - } - - if (err_code & (ERR_ALERT|ERR_ABORT)) - goto err; - - return err_code; - err: - resolvers_deinit(); - return err_code; - -} - -static int stats_dump_resolv_to_buffer(struct stream_interface *si, - struct dns_nameserver *ns, - struct field *stats, size_t stats_count, - struct list *stat_modules) -{ - struct appctx *appctx = __objt_appctx(si->end); - struct channel *rep = si_ic(si); - struct stats_module *mod; - size_t idx = 0; - - memset(stats, 0, sizeof(struct field) * stats_count); - - list_for_each_entry(mod, stat_modules, list) { - struct counters_node *counters = EXTRA_COUNTERS_GET(ns->extra_counters, mod); - - mod->fill_stats(counters, stats + idx); - idx += mod->stats_count; - } - - if (!stats_dump_one_line(stats, idx, appctx)) - return 0; - - if (!stats_putchk(rep, NULL, &trash)) - goto full; - - return 1; - - full: - si_rx_room_rdy(si); - return 0; -} - -/* Uses as a pointer to the current resolver and - * as a pointer to the current nameserver. - */ -int stats_dump_resolvers(struct stream_interface *si, - struct field *stats, size_t stats_count, - struct list *stat_modules) -{ - struct appctx *appctx = __objt_appctx(si->end); - struct channel *rep = si_ic(si); - struct resolvers *resolver = appctx->ctx.stats.obj1; - struct dns_nameserver *ns = appctx->ctx.stats.obj2; - - if (!resolver) - resolver = LIST_NEXT(&sec_resolvers, struct resolvers *, list); - - /* dump resolvers */ - list_for_each_entry_from(resolver, &sec_resolvers, list) { - appctx->ctx.stats.obj1 = resolver; - - ns = appctx->ctx.stats.obj2 ? - appctx->ctx.stats.obj2 : - LIST_NEXT(&resolver->nameservers, struct dns_nameserver *, list); - - list_for_each_entry_from(ns, &resolver->nameservers, list) { - appctx->ctx.stats.obj2 = ns; - - if (buffer_almost_full(&rep->buf)) - goto full; - - if (!stats_dump_resolv_to_buffer(si, ns, - stats, stats_count, - stat_modules)) { - return 0; - } - } - - appctx->ctx.stats.obj2 = NULL; - } - - return 1; - - full: - si_rx_room_blk(si); - return 0; -} - -void resolv_stats_clear_counters(int clrall, struct list *stat_modules) -{ - struct resolvers *resolvers; - struct dns_nameserver *ns; - struct stats_module *mod; - void *counters; - - list_for_each_entry(mod, stat_modules, list) { - if (!mod->clearable && !clrall) - continue; - - list_for_each_entry(resolvers, &sec_resolvers, list) { - list_for_each_entry(ns, &resolvers->nameservers, list) { - counters = EXTRA_COUNTERS_GET(ns->extra_counters, mod); - memcpy(counters, mod->counters, mod->counters_size); - } - } - } - -} - -int resolv_allocate_counters(struct list *stat_modules) -{ - struct stats_module *mod; - struct resolvers *resolvers; - struct dns_nameserver *ns; - - list_for_each_entry(resolvers, &sec_resolvers, list) { - list_for_each_entry(ns, &resolvers->nameservers, list) { - EXTRA_COUNTERS_REGISTER(&ns->extra_counters, COUNTERS_DNS, - alloc_failed); - - list_for_each_entry(mod, stat_modules, list) { - EXTRA_COUNTERS_ADD(mod, - ns->extra_counters, - mod->counters, - mod->counters_size); - } - - EXTRA_COUNTERS_ALLOC(ns->extra_counters, alloc_failed); - - list_for_each_entry(mod, stat_modules, list) { - memcpy(ns->extra_counters->data + mod->counters_off[ns->extra_counters->type], - mod->counters, mod->counters_size); - - /* Store the ns counters pointer */ - if (strcmp(mod->name, "dns") == 0) { - ns->counters = (struct dns_counters *)ns->extra_counters->data + mod->counters_off[COUNTERS_DNS]; - ns->counters->id = ns->id; - ns->counters->pid = resolvers->id; - } - } - } - } - - return 1; - -alloc_failed: - return 0; -} - -/* if an arg is found, it sets the resolvers section pointer into cli.p0 */ -static int cli_parse_stat_resolvers(char **args, char *payload, struct appctx *appctx, void *private) -{ - struct resolvers *presolvers; - - if (*args[2]) { - list_for_each_entry(presolvers, &sec_resolvers, list) { - if (strcmp(presolvers->id, args[2]) == 0) { - appctx->ctx.cli.p0 = presolvers; - break; - } - } - if (appctx->ctx.cli.p0 == NULL) - return cli_err(appctx, "Can't find that resolvers section\n"); - } - return 0; -} - -/* Dumps counters from all resolvers section and associated name servers. It - * returns 0 if the output buffer is full and it needs to be called again, - * otherwise non-zero. It may limit itself to the resolver pointed to by - * if it's not null. - */ -static int cli_io_handler_dump_resolvers_to_buffer(struct appctx *appctx) -{ - struct stream_interface *si = appctx->owner; - struct resolvers *resolvers; - struct dns_nameserver *ns; - - chunk_reset(&trash); - - switch (appctx->st2) { - case STAT_ST_INIT: - appctx->st2 = STAT_ST_LIST; /* let's start producing data */ - /* fall through */ - - case STAT_ST_LIST: - if (LIST_ISEMPTY(&sec_resolvers)) { - chunk_appendf(&trash, "No resolvers found\n"); - } - else { - list_for_each_entry(resolvers, &sec_resolvers, list) { - if (appctx->ctx.cli.p0 != NULL && appctx->ctx.cli.p0 != resolvers) - continue; - - chunk_appendf(&trash, "Resolvers section %s\n", resolvers->id); - list_for_each_entry(ns, &resolvers->nameservers, list) { - chunk_appendf(&trash, " nameserver %s:\n", ns->id); - chunk_appendf(&trash, " sent: %lld\n", ns->counters->sent); - chunk_appendf(&trash, " snd_error: %lld\n", ns->counters->snd_error); - chunk_appendf(&trash, " valid: %lld\n", ns->counters->valid); - chunk_appendf(&trash, " update: %lld\n", ns->counters->update); - chunk_appendf(&trash, " cname: %lld\n", ns->counters->cname); - chunk_appendf(&trash, " cname_error: %lld\n", ns->counters->cname_error); - chunk_appendf(&trash, " any_err: %lld\n", ns->counters->any_err); - chunk_appendf(&trash, " nx: %lld\n", ns->counters->nx); - chunk_appendf(&trash, " timeout: %lld\n", ns->counters->timeout); - chunk_appendf(&trash, " refused: %lld\n", ns->counters->refused); - chunk_appendf(&trash, " other: %lld\n", ns->counters->other); - chunk_appendf(&trash, " invalid: %lld\n", ns->counters->invalid); - chunk_appendf(&trash, " too_big: %lld\n", ns->counters->too_big); - chunk_appendf(&trash, " truncated: %lld\n", ns->counters->truncated); - chunk_appendf(&trash, " outdated: %lld\n", ns->counters->outdated); - } - chunk_appendf(&trash, "\n"); - } - } - - /* display response */ - if (ci_putchk(si_ic(si), &trash) == -1) { - /* let's try again later from this session. We add ourselves into - * this session's users so that it can remove us upon termination. - */ - si_rx_room_blk(si); - return 0; - } - /* fall through */ - - default: - appctx->st2 = STAT_ST_FIN; - return 1; - } -} - -/* register cli keywords */ -static struct cli_kw_list cli_kws = {{ }, { - { { "show", "resolvers", NULL }, "show resolvers [id]: dumps counters from all resolvers section and\n" - " associated name servers", - cli_parse_stat_resolvers, cli_io_handler_dump_resolvers_to_buffer }, - {{},} - } -}; - -INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); - -/* - * Prepare for hostname resolution. - * Returns -1 in case of any allocation failure, 0 if not. - * On error, a global failure counter is also incremented. - */ -static int action_prepare_for_resolution(struct stream *stream, const char *hostname) -{ - char *hostname_dn; - int hostname_len, hostname_dn_len; - struct buffer *tmp = get_trash_chunk(); - - if (!hostname) - return 0; - - hostname_len = strlen(hostname); - hostname_dn = tmp->area; - hostname_dn_len = resolv_str_to_dn_label(hostname, hostname_len + 1, - hostname_dn, tmp->size); - if (hostname_dn_len == -1) - goto err; - - - stream->resolv_ctx.hostname_dn = strdup(hostname_dn); - stream->resolv_ctx.hostname_dn_len = hostname_dn_len; - if (!stream->resolv_ctx.hostname_dn) - goto err; - - return 0; - - err: - free(stream->resolv_ctx.hostname_dn); stream->resolv_ctx.hostname_dn = NULL; - resolv_failed_resolutions += 1; - return -1; -} - - -/* - * Execute the "do-resolution" action. May be called from {tcp,http}request. - */ -enum act_return resolv_action_do_resolve(struct act_rule *rule, struct proxy *px, - struct session *sess, struct stream *s, int flags) -{ - struct resolv_resolution *resolution; - struct sample *smp; - char *fqdn; - struct resolv_requester *req; - struct resolvers *resolvers; - struct resolv_resolution *res; - int exp, locked = 0; - enum act_return ret = ACT_RET_CONT; - - resolvers = rule->arg.resolv.resolvers; - - /* we have a response to our DNS resolution */ - use_cache: - if (s->resolv_ctx.requester && s->resolv_ctx.requester->resolution != NULL) { - resolution = s->resolv_ctx.requester->resolution; - if (!locked) { - HA_SPIN_LOCK(DNS_LOCK, &resolvers->lock); - locked = 1; - } - - if (resolution->step == RSLV_STEP_RUNNING) - goto yield; - if (resolution->step == RSLV_STEP_NONE) { - /* We update the variable only if we have a valid response. */ - if (resolution->status == RSLV_STATUS_VALID) { - struct sample smp; - short ip_sin_family = 0; - void *ip = NULL; - - resolv_get_ip_from_response(&resolution->response, rule->arg.resolv.opts, NULL, - 0, &ip, &ip_sin_family, NULL); - - switch (ip_sin_family) { - case AF_INET: - smp.data.type = SMP_T_IPV4; - memcpy(&smp.data.u.ipv4, ip, 4); - break; - case AF_INET6: - smp.data.type = SMP_T_IPV6; - memcpy(&smp.data.u.ipv6, ip, 16); - break; - default: - ip = NULL; - } - - if (ip) { - smp.px = px; - smp.sess = sess; - smp.strm = s; - - vars_set_by_name(rule->arg.resolv.varname, strlen(rule->arg.resolv.varname), &smp); - } - } - } - - goto release_requester; - } - - /* need to configure and start a new DNS resolution */ - smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.resolv.expr, SMP_T_STR); - if (smp == NULL) - goto end; - - fqdn = smp->data.u.str.area; - if (action_prepare_for_resolution(s, fqdn) == -1) - goto end; /* on error, ignore the action */ - - s->resolv_ctx.parent = rule; - - HA_SPIN_LOCK(DNS_LOCK, &resolvers->lock); - locked = 1; - - resolv_link_resolution(s, OBJ_TYPE_STREAM, 0); - - /* Check if there is a fresh enough response in the cache of our associated resolution */ - req = s->resolv_ctx.requester; - if (!req || !req->resolution) - goto release_requester; /* on error, ignore the action */ - res = req->resolution; - - exp = tick_add(res->last_resolution, resolvers->hold.valid); - if (resolvers->t && res->status == RSLV_STATUS_VALID && tick_isset(res->last_resolution) - && !tick_is_expired(exp, now_ms)) { - goto use_cache; - } - - resolv_trigger_resolution(s->resolv_ctx.requester); - - yield: - if (flags & ACT_OPT_FINAL) - goto release_requester; - ret = ACT_RET_YIELD; - - end: - if (locked) - HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock); - return ret; - - release_requester: - free(s->resolv_ctx.hostname_dn); - s->resolv_ctx.hostname_dn = NULL; - s->resolv_ctx.hostname_dn_len = 0; - if (s->resolv_ctx.requester) { - resolv_unlink_resolution(s->resolv_ctx.requester); - pool_free(resolv_requester_pool, s->resolv_ctx.requester); - s->resolv_ctx.requester = NULL; - } - goto end; -} - -static void release_resolv_action(struct act_rule *rule) -{ - release_sample_expr(rule->arg.resolv.expr); - free(rule->arg.resolv.varname); - free(rule->arg.resolv.resolvers_id); - free(rule->arg.resolv.opts); -} - - -/* parse "do-resolve" action - * This action takes the following arguments: - * do-resolve(,,) - * - * - is the variable name where the result of the DNS resolution will be stored - * (mandatory) - * - is the name of the resolvers section to use to perform the resolution - * (mandatory) - * - can be either 'ipv4' or 'ipv6' and is the IP family we would like to resolve first - * (optional), defaults to ipv6 - * - is an HAProxy expression used to fetch the name to be resolved - */ -enum act_parse_ret resolv_parse_do_resolve(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err) -{ - int cur_arg; - struct sample_expr *expr; - unsigned int where; - const char *beg, *end; - - /* orig_arg points to the first argument, but we need to analyse the command itself first */ - cur_arg = *orig_arg - 1; - - /* locate varName, which is mandatory */ - beg = strchr(args[cur_arg], '('); - if (beg == NULL) - goto do_resolve_parse_error; - beg = beg + 1; /* beg should points to the first character after opening parenthesis '(' */ - end = strchr(beg, ','); - if (end == NULL) - goto do_resolve_parse_error; - rule->arg.resolv.varname = my_strndup(beg, end - beg); - if (rule->arg.resolv.varname == NULL) - goto do_resolve_parse_error; - - - /* locate resolversSectionName, which is mandatory. - * Since next parameters are optional, the delimiter may be comma ',' - * or closing parenthesis ')' - */ - beg = end + 1; - end = strchr(beg, ','); - if (end == NULL) - end = strchr(beg, ')'); - if (end == NULL) - goto do_resolve_parse_error; - rule->arg.resolv.resolvers_id = my_strndup(beg, end - beg); - if (rule->arg.resolv.resolvers_id == NULL) - goto do_resolve_parse_error; - - - rule->arg.resolv.opts = calloc(1, sizeof(*rule->arg.resolv.opts)); - if (rule->arg.resolv.opts == NULL) - goto do_resolve_parse_error; - - /* Default priority is ipv6 */ - rule->arg.resolv.opts->family_prio = AF_INET6; - - /* optional arguments accepted for now: - * ipv4 or ipv6 - */ - while (*end != ')') { - beg = end + 1; - end = strchr(beg, ','); - if (end == NULL) - end = strchr(beg, ')'); - if (end == NULL) - goto do_resolve_parse_error; - - if (strncmp(beg, "ipv4", end - beg) == 0) { - rule->arg.resolv.opts->family_prio = AF_INET; - } - else if (strncmp(beg, "ipv6", end - beg) == 0) { - rule->arg.resolv.opts->family_prio = AF_INET6; - } - else { - goto do_resolve_parse_error; - } - } - - cur_arg = cur_arg + 1; - - expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL); - if (!expr) - goto do_resolve_parse_error; - - - where = 0; - if (px->cap & PR_CAP_FE) - where |= SMP_VAL_FE_HRQ_HDR; - if (px->cap & PR_CAP_BE) - where |= SMP_VAL_BE_HRQ_HDR; - - if (!(expr->fetch->val & where)) { - memprintf(err, - "fetch method '%s' extracts information from '%s', none of which is available here", - args[cur_arg-1], sample_src_names(expr->fetch->use)); - free(expr); - return ACT_RET_PRS_ERR; - } - rule->arg.resolv.expr = expr; - rule->action = ACT_CUSTOM; - rule->action_ptr = resolv_action_do_resolve; - *orig_arg = cur_arg; - - rule->check_ptr = check_action_do_resolve; - rule->release_ptr = release_resolv_action; - - return ACT_RET_PRS_OK; - - do_resolve_parse_error: - free(rule->arg.resolv.varname); rule->arg.resolv.varname = NULL; - free(rule->arg.resolv.resolvers_id); rule->arg.resolv.resolvers_id = NULL; - memprintf(err, "Can't parse '%s'. Expects 'do-resolve(,[,]) '. Available options are 'ipv4' and 'ipv6'", - args[cur_arg]); - return ACT_RET_PRS_ERR; -} - -static struct action_kw_list http_req_kws = { { }, { - { "do-resolve", resolv_parse_do_resolve, 1 }, - { /* END */ } -}}; - -INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_kws); - -static struct action_kw_list tcp_req_cont_actions = {ILH, { - { "do-resolve", resolv_parse_do_resolve, 1 }, - { /* END */ } -}}; - -INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_cont_actions); - -/* Check an "http-request do-resolve" action. - * - * The function returns 1 in success case, otherwise, it returns 0 and err is - * filled. - */ -int check_action_do_resolve(struct act_rule *rule, struct proxy *px, char **err) -{ - struct resolvers *resolvers = NULL; - - if (rule->arg.resolv.resolvers_id == NULL) { - memprintf(err,"Proxy '%s': %s", px->id, "do-resolve action without resolvers"); - return 0; - } - - resolvers = find_resolvers_by_id(rule->arg.resolv.resolvers_id); - if (resolvers == NULL) { - memprintf(err,"Can't find resolvers section '%s' for do-resolve action", rule->arg.resolv.resolvers_id); - return 0; - } - rule->arg.resolv.resolvers = resolvers; - - return 1; -} -/* - * 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) -{ - const char *err; - int err_code = 0; - char *errmsg = NULL; - - if (strcmp(args[0], "resolvers") == 0) { /* new resolvers section */ - if (!*args[1]) { - ha_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) { - ha_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, &sec_resolvers, list) { - /* Error if two resolvers owns the same name */ - if (strcmp(curr_resolvers->id, args[1]) == 0) { - ha_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 = calloc(1, sizeof(*curr_resolvers))) == NULL) { - ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); - err_code |= ERR_ALERT | ERR_ABORT; - goto out; - } - - /* default values */ - LIST_ADDQ(&sec_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 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; - curr_resolvers->hold.refused = 30000; - curr_resolvers->hold.timeout = 30000; - curr_resolvers->hold.obsolete = 0; - /* default hold period for valid is 10s */ - curr_resolvers->hold.valid = 10000; - curr_resolvers->timeout.resolve = 1000; - curr_resolvers->timeout.retry = 1000; - curr_resolvers->resolve_retries = 3; - curr_resolvers->nb_nameservers = 0; - LIST_INIT(&curr_resolvers->nameservers); - LIST_INIT(&curr_resolvers->resolutions.curr); - LIST_INIT(&curr_resolvers->resolutions.wait); - HA_SPIN_INIT(&curr_resolvers->lock); - } - else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition */ - struct dns_nameserver *newnameserver = NULL; - struct sockaddr_storage *sk; - int port1, port2; - - if (!*args[2]) { - ha_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) { - ha_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; - } - - list_for_each_entry(newnameserver, &curr_resolvers->nameservers, list) { - /* Error if two resolvers owns the same name */ - if (strcmp(newnameserver->id, args[1]) == 0) { - ha_alert("Parsing [%s:%d]: nameserver '%s' has same name as another nameserver (declared at %s:%d).\n", - file, linenum, args[1], newnameserver->conf.file, newnameserver->conf.line); - err_code |= ERR_ALERT | ERR_FATAL; - } - } - - sk = str2sa_range(args[2], NULL, &port1, &port2, NULL, NULL, - &errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_DGRAM); - if (!sk) { - ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - - if ((newnameserver = calloc(1, sizeof(*newnameserver))) == NULL) { - ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); - err_code |= ERR_ALERT | ERR_ABORT; - goto out; - } - - if (dns_dgram_init(newnameserver, sk) < 0) { - ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); - err_code |= ERR_ALERT | ERR_ABORT; - goto out; - } - - if ((newnameserver->conf.file = strdup(file)) == NULL) { - ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); - err_code |= ERR_ALERT | ERR_ABORT; - goto out; - } - - if ((newnameserver->id = strdup(args[1])) == NULL) { - ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); - err_code |= ERR_ALERT | ERR_ABORT; - goto out; - } - - newnameserver->parent = curr_resolvers; - newnameserver->process_responses = resolv_process_responses; - newnameserver->conf.line = linenum; - /* the nameservers are linked backward first */ - LIST_ADDQ(&curr_resolvers->nameservers, &newnameserver->list); - } - else if (strcmp(args[0], "parse-resolv-conf") == 0) { - struct dns_nameserver *newnameserver = NULL; - const char *whitespace = "\r\n\t "; - char *resolv_line = NULL; - int resolv_linenum = 0; - FILE *f = NULL; - char *address = NULL; - struct sockaddr_storage *sk = NULL; - struct protocol *proto; - int duplicate_name = 0; - - if ((resolv_line = malloc(sizeof(*resolv_line) * LINESIZE)) == NULL) { - ha_alert("parsing [%s:%d] : out of memory.\n", - file, linenum); - err_code |= ERR_ALERT | ERR_FATAL; - goto resolv_out; - } - - if ((f = fopen("/etc/resolv.conf", "r")) == NULL) { - ha_alert("parsing [%s:%d] : failed to open /etc/resolv.conf.\n", - file, linenum); - err_code |= ERR_ALERT | ERR_FATAL; - goto resolv_out; - } - - sk = calloc(1, sizeof(*sk)); - if (sk == NULL) { - ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", - resolv_linenum); - err_code |= ERR_ALERT | ERR_FATAL; - goto resolv_out; - } - - while (fgets(resolv_line, LINESIZE, f) != NULL) { - resolv_linenum++; - if (strncmp(resolv_line, "nameserver", 10) != 0) - continue; - - address = strtok(resolv_line + 10, whitespace); - if (address == resolv_line + 10) - continue; - - if (address == NULL) { - ha_warning("parsing [/etc/resolv.conf:%d] : nameserver line is missing address.\n", - resolv_linenum); - err_code |= ERR_WARN; - continue; - } - - duplicate_name = 0; - list_for_each_entry(newnameserver, &curr_resolvers->nameservers, list) { - if (strcmp(newnameserver->id, address) == 0) { - ha_warning("Parsing [/etc/resolv.conf:%d] : generated name for /etc/resolv.conf nameserver '%s' conflicts with another nameserver (declared at %s:%d), it appears to be a duplicate and will be excluded.\n", - resolv_linenum, address, newnameserver->conf.file, newnameserver->conf.line); - err_code |= ERR_WARN; - duplicate_name = 1; - } - } - - if (duplicate_name) - continue; - - memset(sk, 0, sizeof(*sk)); - if (!str2ip2(address, sk, 1)) { - ha_warning("parsing [/etc/resolv.conf:%d] : address '%s' could not be recognized, nameserver will be excluded.\n", - resolv_linenum, address); - err_code |= ERR_WARN; - continue; - } - - set_host_port(sk, 53); - - proto = protocol_by_family(sk->ss_family); - if (!proto || !proto->connect) { - ha_warning("parsing [/etc/resolv.conf:%d] : '%s' : connect() not supported for this address family.\n", - resolv_linenum, address); - err_code |= ERR_WARN; - continue; - } - - if ((newnameserver = calloc(1, sizeof(*newnameserver))) == NULL) { - ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); - err_code |= ERR_ALERT | ERR_FATAL; - goto resolv_out; - } - - if (dns_dgram_init(newnameserver, sk) < 0) { - ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); - err_code |= ERR_ALERT | ERR_FATAL; - free(newnameserver); - goto resolv_out; - } - - newnameserver->conf.file = strdup("/etc/resolv.conf"); - if (newnameserver->conf.file == NULL) { - ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); - err_code |= ERR_ALERT | ERR_FATAL; - free(newnameserver); - goto resolv_out; - } - - newnameserver->id = strdup(address); - if (newnameserver->id == NULL) { - ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); - err_code |= ERR_ALERT | ERR_FATAL; - free((char *)newnameserver->conf.file); - free(newnameserver); - goto resolv_out; - } - - newnameserver->parent = curr_resolvers; - newnameserver->process_responses = resolv_process_responses; - newnameserver->conf.line = resolv_linenum; - LIST_ADDQ(&curr_resolvers->nameservers, &newnameserver->list); - } - -resolv_out: - free(sk); - free(resolv_line); - if (f != NULL) - fclose(f); - } - else if (strcmp(args[0], "hold") == 0) { /* hold periods */ - const char *res; - unsigned int time; - - if (!*args[2]) { - ha_alert("parsing [%s:%d] : '%s' expects an and a