diff --git a/doc/configuration.txt b/doc/configuration.txt index 78dfe23ea..b002f409d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -11597,9 +11597,6 @@ resolution at run time. Whether run time server name resolution has been enable or not, HAProxy will carry on doing the first resolution when parsing the configuration. -Bear in mind that DNS resolution is triggered by health checks. This makes -health checks mandatory to allow DNS resolution. - 5.3.1. Global overview ---------------------- @@ -11703,6 +11700,11 @@ hold resolution is triggered after modulo the parameter of the healch check. +resolution_pool_size + Defines the number of resolutions available in the pool for this resolvers. + If not defines, it defaults to 64. If your configuration requires more than + , then HAProxy will return an error when parsing the configuration. + resolve_retries Defines the number of queries to send to resolve a server name before giving up. diff --git a/include/proto/checks.h b/include/proto/checks.h index eb26c9a27..853daad45 100644 --- a/include/proto/checks.h +++ b/include/proto/checks.h @@ -28,7 +28,6 @@ const char *get_check_status_description(short check_status); const char *get_check_status_info(short check_status); void __health_adjust(struct server *s, short status); -int trigger_resolution(struct server *s); extern struct data_cb check_conn_cb; diff --git a/include/proto/dns.h b/include/proto/dns.h index 00a6f4a10..6675d50fe 100644 --- a/include/proto/dns.h +++ b/include/proto/dns.h @@ -44,11 +44,24 @@ int dns_send_query(struct dns_resolution *resolution); void dns_print_current_resolutions(struct dns_resolvers *resolvers); void dns_update_resolvers_timeout(struct dns_resolvers *resolvers); void dns_reset_resolution(struct dns_resolution *resolution); +void dns_resolution_free(struct dns_resolvers *resolvers, struct dns_resolution *resolution); +void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dns_resolution *resolution); int dns_check_resolution_queue(struct dns_resolvers *resolvers); unsigned short dns_response_get_query_id(unsigned char *resp); struct dns_resolution *dns_alloc_resolution(void); void dns_free_resolution(struct dns_resolution *resolution); struct chunk *dns_cache_key(int query_type, char *hostname_dn, int hostname_dn_len, struct chunk *buf); struct lru64 *dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int valid_period, void *cache_domain); +int dns_link_resolution(void *requester, int requester_type, struct dns_resolution *resolution); +struct dns_resolution *dns_resolution_list_get(struct dns_resolvers *resolvers, char *hostname_dn, int query_type); +int dns_trigger_resolution(struct dns_resolution *resolution); +int dns_alloc_resolution_pool(struct dns_resolvers *resolvers); + +void dump_dns_config(void); + +/* + * erases all information of a dns_requester structure + */ +#define dns_clear_requester(requester) memset(requester, '\0', sizeof(*requester)); #endif // _PROTO_DNS_H diff --git a/include/proto/server.h b/include/proto/server.h index 53df24147..43e4e4251 100644 --- a/include/proto/server.h +++ b/include/proto/server.h @@ -53,8 +53,8 @@ struct server *cli_find_server(struct appctx *appctx, char *arg); /* functions related to server name resolution */ int snr_update_srv_status(struct server *s); -int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver); -int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code); +int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver); +int snr_resolution_error_cb(struct dns_requester *requester, int error_code); struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char *ip_family); /* increase the number of cumulated connections on the designated server */ diff --git a/include/types/dns.h b/include/types/dns.h index de9c71331..7a19aa374 100644 --- a/include/types/dns.h +++ b/include/types/dns.h @@ -82,6 +82,9 @@ /* DNS header size */ #define DNS_HEADER_SIZE sizeof(struct dns_header) +/* DNS resolution pool size, per resolvers section */ +#define DNS_DEFAULT_RESOLUTION_POOL_SIZE 64 + /* DNS request or response header structure */ struct dns_header { uint16_t id; @@ -157,7 +160,12 @@ struct dns_resolvers { int other; /* other dns response errors */ } hold; struct task *t; /* timeout management */ - struct list curr_resolution; /* current running resolutions */ + int resolution_pool_size; /* size of the resolution pool associated to this resolvers section */ + struct { + struct list pool; /* resolution pool dedicated to this resolvers section */ + struct list wait; /* resolutions managed to this resolvers section */ + struct list curr; /* current running resolutions */ + } resolution; 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) */ @@ -218,11 +226,15 @@ struct dns_options { */ struct dns_resolution { struct list list; /* resolution list */ - void *requester; /* owner of this name resolution */ + struct { + struct list wait; /* list of standby requesters for this resolution */ + struct list curr; /* list of requesters currently active on this resolution */ + } requester; int (*requester_cb)(struct dns_resolution *, struct dns_nameserver *); /* requester callback for valid response */ int (*requester_error_cb)(struct dns_resolution *, int); /* requester callback, for error management */ + 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 lastest valid resolution */ @@ -244,6 +256,19 @@ struct dns_resolution { struct chunk response_buffer; /* buffer used as a data store for above TODO: optimize the size (might be smaller) */ }; +/* + * structure used to describe the owner of a DNS resolution. + */ +struct dns_requester { + struct list list; /* requester list */ + enum obj_type *requester; /* pointer to the requester */ + int prefered_query_type; /* prefered query type */ + int (*requester_cb)(struct dns_requester *, struct dns_nameserver *); + /* requester callback for valid response */ + int (*requester_error_cb)(struct dns_requester *, int); + /* requester callback, for error management */ +}; + /* last resolution status code */ enum { RSLV_STATUS_NONE = 0, /* no resolution occured yet */ diff --git a/include/types/server.h b/include/types/server.h index 7c6df5a9b..8fb6f2ec4 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -254,6 +254,7 @@ struct server { struct check check; /* health-check specific configuration */ struct check agent; /* agent specific configuration */ + struct dns_requester *dns_requester; /* used to link a server to its DNS resolution */ char *resolvers_id; /* resolvers section used by this server */ struct dns_resolvers *resolvers; /* pointer to the resolvers structure used by this server */ char *hostname; /* server hostname */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 7c720fc18..261a0ebbe 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -2164,8 +2165,12 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm) curr_resolvers->hold.valid = 10000; curr_resolvers->timeout.retry = 1000; curr_resolvers->resolve_retries = 3; + /* default resolution pool size */ + curr_resolvers->resolution_pool_size = DNS_DEFAULT_RESOLUTION_POOL_SIZE; LIST_INIT(&curr_resolvers->nameserver_list); - LIST_INIT(&curr_resolvers->curr_resolution); + LIST_INIT(&curr_resolvers->resolution.curr); + LIST_INIT(&curr_resolvers->resolution.wait); + LIST_INIT(&curr_resolvers->resolution.pool); } else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition */ struct sockaddr_storage *sk; @@ -2277,6 +2282,15 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm) } } + else if (strcmp(args[0], "resolution_pool_size") == 0) { + if (!*args[1]) { + Alert("parsing [%s:%d] : '%s' expects as argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curr_resolvers->resolution_pool_size = atoi(args[1]); + } else if (strcmp(args[0], "resolve_retries") == 0) { if (!*args[1]) { Alert("parsing [%s:%d] : '%s' expects as argument.\n", @@ -7365,6 +7379,7 @@ int check_config_validity() unsigned int next_pxid = 1; struct bind_conf *bind_conf; char *err; + struct dns_resolvers *curr_resolvers; bind_conf = NULL; /* @@ -7385,6 +7400,15 @@ int check_config_validity() pool2_capture = create_pool("capture", global.tune.cookie_len, MEM_F_SHARED); + /* allocate pool of resolution per resolvers */ + list_for_each_entry(curr_resolvers, &dns_resolvers, list) { + if (dns_alloc_resolution_pool(curr_resolvers) != 0) { + /* error message is already displayed by dns_alloc_resolution_pool() */ + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + } + /* Post initialisation of the users and groups lists. */ err_code = userlist_postinit(); if (err_code != ERR_NONE) @@ -8514,8 +8538,15 @@ out_uri_auth_compat: newsrv->id, newsrv->resolvers_id); cfgerr++; } else { - if (newsrv->resolution) + if (newsrv->hostname_dn) { newsrv->resolvers = curr_resolvers; + if (dns_link_resolution(newsrv, OBJ_TYPE_SERVER, NULL) != 0) { + Alert("config : %s '%s', server '%s': unable to set DNS resolution\n", + proxy_type_str(curproxy), curproxy->id, + newsrv->id); + cfgerr++; + } + } } } else { diff --git a/src/checks.c b/src/checks.c index b0616479b..1af862e9b 100644 --- a/src/checks.c +++ b/src/checks.c @@ -695,7 +695,7 @@ static void chk_report_conn_err(struct connection *conn, int errno_bck, int expi * Let's trigger a DNS resolution if none are currently running. */ if ((check->server->resolution) && (check->server->resolution->step == RSLV_STEP_NONE)) - trigger_resolution(check->server); + dns_trigger_resolution(check->server->resolution); } else if ((conn->flags & (CO_FL_CONNECTED|CO_FL_WAIT_L6_CONN)) == CO_FL_WAIT_L6_CONN) { @@ -2207,23 +2207,6 @@ static struct task *process_chk_conn(struct task *t) static struct task *process_chk(struct task *t) { struct check *check = t->context; - struct server *s = check->server; - struct dns_resolution *resolution = s->resolution; - struct dns_resolvers *resolvers = s->resolvers; - - /* trigger name resolution */ - if ((s->check.state & CHK_ST_ENABLED) && (resolution)) { - /* check if a no resolution is running for this server */ - if (resolution->step == RSLV_STEP_NONE) { - /* - * if there has not been any name resolution for a longer period than - * hold.valid, let's trigger a new one. - */ - if (!resolution->last_resolution || tick_is_expired(tick_add(resolution->last_resolution, resolvers->hold.valid), now_ms)) { - trigger_resolution(s); - } - } - } if (check->type == PR_O2_EXT_CHK) return process_chk_proc(t); @@ -2231,76 +2214,6 @@ static struct task *process_chk(struct task *t) } -/* - * Initiates a new name resolution: - * - generates a query id - * - configure the resolution structure - * - startup the resolvers task if required - * - * returns: - * - 0 in case of error or if resolution already running - * - 1 if everything started properly - */ -int trigger_resolution(struct server *s) -{ - struct dns_resolution *resolution = NULL; - struct dns_resolvers *resolvers = NULL; - int query_id; - int i; - - resolution = s->resolution; - resolvers = s->resolvers; - - /* - * 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 query id */ - i = 0; - do { - query_id = dns_rnd16(); - /* we do try only 100 times to find a free query id */ - if (i++ > 100) { - chunk_printf(&trash, "could not generate a query id for %s/%s, in resolvers %s", - s->proxy->id, s->id, resolvers->id); - - send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.str); - return 0; - } - } while (eb32_lookup(&resolvers->query_ids, query_id)); - - LIST_ADDQ(&resolvers->curr_resolution, &resolution->list); - - /* now update resolution parameters */ - resolution->query_id = query_id; - resolution->qid.key = query_id; - resolution->step = RSLV_STEP_RUNNING; - if (s->dns_opts.family_prio == AF_INET) { - resolution->query_type = DNS_RTYPE_A; - } else { - resolution->query_type = DNS_RTYPE_AAAA; - } - resolution->try = resolvers->resolve_retries; - resolution->try_cname = 0; - resolution->nb_responses = 0; - eb32_insert(&resolvers->query_ids, &resolution->qid); - - dns_send_query(resolution); - resolution->try -= 1; - - /* update wakeup date if this resolution is the only one in the FIFO list */ - if (dns_check_resolution_queue(resolvers) == 1) { - /* update task timeout */ - dns_update_resolvers_timeout(resolvers); - task_queue(resolvers->t); - } - - return 1; -} - static int start_check_task(struct check *check, int mininter, int nbcheck, int srvpos) { diff --git a/src/dns.c b/src/dns.c index cab1ad30b..cb7de6ab6 100644 --- a/src/dns.c +++ b/src/dns.c @@ -57,6 +57,9 @@ struct dgram_data_cb resolve_dgram_cb = { .send = dns_resolve_send, }; +/* local function prototypes */ +static int dns_run_resolution(struct dns_requester *requester); + #if DEBUG /* * go through the resolutions associated to a resolvers section and print the ID and hostname in @@ -65,12 +68,201 @@ struct dgram_data_cb resolve_dgram_cb = { */ void dns_print_current_resolutions(struct dns_resolvers *resolvers) { - list_for_each_entry(resolution, &resolvers->curr_resolution, list) { + list_for_each_entry(resolution, &resolvers->resolution.curr, list) { printf(" resolution %d for %s\n", resolution->query_id, resolution->hostname_dn); } } #endif +void dump_dns_config() +{ + struct dns_resolvers *curr_resolvers = NULL; + struct dns_nameserver *curr_nameserver = NULL; + struct dns_resolution *curr_resolution = NULL; + struct dns_requester *curr_requester = NULL; + + printf("===============\n"); + list_for_each_entry(curr_resolvers, &dns_resolvers, list) { + printf("Resolvers: %s\n", curr_resolvers->id); + + printf(" nameservers:\n"); + list_for_each_entry(curr_nameserver, &curr_resolvers->nameserver_list, list) { + printf(" %s\n", curr_nameserver->id); + } + +/* + printf(" resolution.pool list:\n"); + list_for_each_entry(curr_resolution, &curr_resolvers->resolution.pool, list) { + printf(" %p\n", curr_resolution); + } +*/ + + printf(" resolution.wait list:\n"); + list_for_each_entry(curr_resolution, &curr_resolvers->resolution.wait, list) { + printf(" %p %s\n", curr_resolution, curr_resolution->hostname_dn); + printf(" requester.wait list:\n"); + list_for_each_entry(curr_requester, &curr_resolution->requester.wait, list) { + printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type); + } + printf(" requester.curr list:\n"); + list_for_each_entry(curr_requester, &curr_resolution->requester.curr, list) { + printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type); + } + } + printf(" resolution.curr list:\n"); + list_for_each_entry(curr_resolution, &curr_resolvers->resolution.curr, list) { + printf(" %p %s\n", curr_resolution, curr_resolution->hostname_dn); + printf(" requester.wait list:\n"); + list_for_each_entry(curr_requester, &curr_resolution->requester.wait, list) { + printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type); + } + printf(" requester.curr list:\n"); + list_for_each_entry(curr_requester, &curr_resolution->requester.curr, list) { + printf(" %p %s %d\n", curr_requester, objt_server(curr_requester->requester)->id, curr_requester->prefered_query_type); + } + } + } + + printf("===============\n"); +} + +/* + * Initiates a new name resolution: + * - generates a query id + * - configure the resolution structure + * - startup the resolvers task if required + * + * returns: + * - 0 if everything started properly + * - -1 in case of error or if resolution already running + */ +int dns_trigger_resolution(struct dns_resolution *resolution) +{ + struct dns_requester *requester = NULL, *tmprequester; + struct dns_resolvers *resolvers = NULL; + int inter; + + /* process the element of the wait queue */ + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.wait, list) { + inter = 0; + + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + inter = objt_server(requester->requester)->check.inter; + resolvers = objt_server(requester->requester)->resolvers; + break; + case OBJ_TYPE_NONE: + default: + return -1; + } + + /* if data is fresh enough, let's use it */ + if (!tick_is_expired(tick_add(resolution->last_resolution, inter), now_ms)) { + /* we only use cache if the response there is valid. + * If not valid, we run the resolution and move the requester to + * the run queue. */ + if (resolution->status != RSLV_STATUS_VALID) { + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.curr, &requester->list); + dns_run_resolution(requester); + continue; + } + + requester->requester_cb(requester, NULL); + } + else { + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.curr, &requester->list); + dns_run_resolution(requester); + } + } + + if (resolvers) + dns_update_resolvers_timeout(resolvers); + + return 0; +} + +/* + * Prepare and send a DNS resolution. + * + * Return code: + * - 0 if no error occured + * - -1 in case of error + */ +static int +dns_run_resolution(struct dns_requester *requester) +{ + struct dns_resolution *resolution; + struct dns_resolvers *resolvers; + int query_id, query_type, i; + struct proxy *proxy; + + resolution = NULL; + resolvers = NULL; + proxy = NULL; + query_type = -1; + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + resolution = objt_server(requester->requester)->resolution; + resolvers = objt_server(requester->requester)->resolvers; + proxy = objt_server(requester->requester)->proxy; + query_type = requester->prefered_query_type; + break; + case OBJ_TYPE_NONE: + default: + return -1; + } + + /* + * 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 query id */ + i = 0; + do { + query_id = dns_rnd16(); + /* we do try only 100 times to find a free query id */ + if (i++ > 100) { + chunk_printf(&trash, "could not generate a query id for %s, in resolvers %s", + resolution->hostname_dn, resolvers->id); + + if (proxy) + send_log(proxy, LOG_NOTICE, "%s.\n", trash.str); + return -1; + } + } while (eb32_lookup(&resolvers->query_ids, query_id)); + + /* move the resolution into the run queue */ + LIST_DEL(&resolution->list); + LIST_ADDQ(&resolvers->resolution.curr, &resolution->list); + + /* now update resolution parameters */ + resolution->query_id = query_id; + resolution->qid.key = query_id; + resolution->step = RSLV_STEP_RUNNING; + resolution->query_type = query_type; + resolution->try = resolvers->resolve_retries; + resolution->try_cname = 0; + resolution->nb_responses = 0; + eb32_insert(&resolvers->query_ids, &resolution->qid); + + dns_send_query(resolution); + resolution->try -= 1; + + /* update wakeup date if this resolution is the only one in the FIFO list */ + if (dns_check_resolution_queue(resolvers) == 1) { + /* update task timeout */ + dns_update_resolvers_timeout(resolvers); + task_queue(resolvers->t); + } + + return 0; +} + /* * check if there is more than 1 resolution in the resolver's resolution list * return value: @@ -81,22 +273,22 @@ void dns_print_current_resolutions(struct dns_resolvers *resolvers) int dns_check_resolution_queue(struct dns_resolvers *resolvers) { - if (LIST_ISEMPTY(&resolvers->curr_resolution)) + if (LIST_ISEMPTY(&resolvers->resolution.curr)) return 0; - if ((resolvers->curr_resolution.n) && (resolvers->curr_resolution.n == resolvers->curr_resolution.p)) + if ((resolvers->resolution.curr.n) && (resolvers->resolution.curr.n == resolvers->resolution.curr.p)) return 1; - if (! ((resolvers->curr_resolution.n == resolvers->curr_resolution.p) - && (&resolvers->curr_resolution != resolvers->curr_resolution.n))) + if (! ((resolvers->resolution.curr.n == resolvers->resolution.curr.p) + && (&resolvers->resolution.curr != resolvers->resolution.curr.n))) return 2; return 0; } /* - * reset all parameters of a DNS resolution to 0 (or equivalent) - * and clean it up from all associated lists (resolution->qid and resolution->list) + * reset some resolution parameters to initial values and also delete the + * query ID from the resolver's tree. */ void dns_reset_resolution(struct dns_resolution *resolution) { @@ -112,9 +304,6 @@ void dns_reset_resolution(struct dns_resolution *resolution) eb32_delete(&resolution->qid); resolution->query_id = 0; resolution->qid.key = 0; - - /* the second resolution in the queue becomes the first one */ - LIST_DEL(&resolution->list); } /* @@ -127,16 +316,17 @@ void dns_reset_resolution(struct dns_resolution *resolution) */ void dns_resolve_recv(struct dgram_conn *dgram) { - struct dns_nameserver *nameserver; + struct dns_nameserver *nameserver, *tmpnameserver; struct dns_resolvers *resolvers; - struct dns_resolution *resolution; + struct dns_resolution *resolution = NULL; struct dns_query_item *query; unsigned char buf[DNS_MAX_UDP_MESSAGE + 1]; unsigned char *bufend; - int fd, buflen, ret; + int fd, buflen, dns_resp, need_resend; unsigned short query_id; struct eb32_node *eb; struct lru64 *lru = NULL; + struct dns_requester *requester = NULL, *tmprequester = NULL; fd = dgram->t.sock.fd; @@ -195,51 +385,178 @@ void dns_resolve_recv(struct dgram_conn *dgram) /* number of responses received */ resolution->nb_responses += 1; - ret = dns_validate_dns_response(buf, bufend, resolution); + dns_resp = dns_validate_dns_response(buf, bufend, resolution); - /* treat only errors */ - switch (ret) { - case DNS_RESP_QUERY_COUNT_ERROR: - case DNS_RESP_INVALID: - nameserver->counters.invalid += 1; - resolution->requester_error_cb(resolution, DNS_RESP_INVALID); - continue; + /* treat errors first + * need_resend flag could be set to 0 by default before the 'switch' and then + * set to 1 only where needed, but I think it's better this way to make people + * aware they have to think twice how to set this flag when updating this portion + * of the code + */ + switch (dns_resp) { + case DNS_RESP_VALID: + need_resend = 0; + break; - case DNS_RESP_INTERNAL: - case DNS_RESP_ERROR: - nameserver->counters.other += 1; - resolution->requester_error_cb(resolution, DNS_RESP_ERROR); - continue; + case DNS_RESP_INVALID: + case DNS_RESP_QUERY_COUNT_ERROR: + case DNS_RESP_WRONG_NAME: + if (resolution->status != RSLV_STATUS_INVALID) { + resolution->status = RSLV_STATUS_INVALID; + resolution->last_status_change = now_ms; + } + nameserver->counters.invalid += 1; + need_resend = 0; + break; - case DNS_RESP_ANCOUNT_ZERO: - nameserver->counters.any_err += 1; - resolution->requester_error_cb(resolution, DNS_RESP_ANCOUNT_ZERO); - continue; + case DNS_RESP_ANCOUNT_ZERO: + if (resolution->status != RSLV_STATUS_OTHER) { + resolution->status = RSLV_STATUS_OTHER; + resolution->last_status_change = now_ms; + } + nameserver->counters.any_err += 1; + need_resend = 1; + break; - case DNS_RESP_NX_DOMAIN: - nameserver->counters.nx += 1; - resolution->requester_error_cb(resolution, DNS_RESP_NX_DOMAIN); - continue; + case DNS_RESP_NX_DOMAIN: + if (resolution->status != RSLV_STATUS_NX) { + resolution->status = RSLV_STATUS_NX; + resolution->last_status_change = now_ms; + } + nameserver->counters.nx += 1; + need_resend = 0; + break; - case DNS_RESP_REFUSED: - nameserver->counters.refused += 1; - resolution->requester_error_cb(resolution, DNS_RESP_REFUSED); - continue; + case DNS_RESP_REFUSED: + if (resolution->status != RSLV_STATUS_REFUSED) { + resolution->status = RSLV_STATUS_REFUSED; + resolution->last_status_change = now_ms; + } + nameserver->counters.refused += 1; + need_resend = 0; + break; - case DNS_RESP_CNAME_ERROR: - nameserver->counters.cname_error += 1; - resolution->requester_error_cb(resolution, DNS_RESP_CNAME_ERROR); - continue; + case DNS_RESP_CNAME_ERROR: + if (resolution->status != RSLV_STATUS_OTHER) { + resolution->status = RSLV_STATUS_OTHER; + resolution->last_status_change = now_ms; + } + nameserver->counters.cname_error += 1; + need_resend = 1; + break; - case DNS_RESP_TRUNCATED: - nameserver->counters.truncated += 1; - resolution->requester_error_cb(resolution, DNS_RESP_TRUNCATED); - continue; + case DNS_RESP_TRUNCATED: + if (resolution->status != RSLV_STATUS_OTHER) { + resolution->status = RSLV_STATUS_OTHER; + resolution->last_status_change = now_ms; + } + nameserver->counters.truncated += 1; + need_resend = 1; + break; - case DNS_RESP_NO_EXPECTED_RECORD: - nameserver->counters.other += 1; - resolution->requester_error_cb(resolution, DNS_RESP_NO_EXPECTED_RECORD); - continue; + case DNS_RESP_NO_EXPECTED_RECORD: + if (resolution->status != RSLV_STATUS_OTHER) { + resolution->status = RSLV_STATUS_OTHER; + resolution->last_status_change = now_ms; + } + nameserver->counters.other += 1; + need_resend = 1; + break; + + case DNS_RESP_ERROR: + case DNS_RESP_INTERNAL: + if (resolution->status != RSLV_STATUS_OTHER) { + resolution->status = RSLV_STATUS_OTHER; + resolution->last_status_change = now_ms; + } + nameserver->counters.other += 1; + need_resend = 1; + break; + } + + /* some error codes trigger a re-send of the query, but switching the + * query type. + * This is the case for the following error codes: + * DNS_RESP_ANCOUNT_ZERO + * DNS_RESP_TRUNCATED + * DNS_RESP_ERROR + * DNS_RESP_INTERNAL + * DNS_RESP_NO_EXPECTED_RECORD + * DNS_RESP_CNAME_ERROR + */ + if (need_resend) { + int family_prio; + int res_preferred_afinet, res_preferred_afinet6; + + requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list); + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + family_prio = objt_server(requester->requester)->dns_opts.family_prio; + break; + case OBJ_TYPE_NONE: + default: + family_prio = AF_INET6; + } + res_preferred_afinet = family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A; + res_preferred_afinet6 = family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA; + if ((res_preferred_afinet || res_preferred_afinet6) + || (resolution->try > 0)) { + /* let's change the query type */ + if (res_preferred_afinet6) { + /* fallback from AAAA to A */ + resolution->query_type = DNS_RTYPE_A; + } + else if (res_preferred_afinet) { + /* fallback from A to AAAA */ + resolution->query_type = DNS_RTYPE_AAAA; + } + else { + resolution->try -= 1; + if (family_prio == AF_INET) { + resolution->query_type = DNS_RTYPE_A; + } else { + resolution->query_type = DNS_RTYPE_AAAA; + } + } + + dns_send_query(resolution); + /* + * move the resolution to the last element of the FIFO queue + * and update timeout wakeup based on the new first entry + */ + if (dns_check_resolution_queue(resolvers) > 1) { + /* second resolution becomes first one */ + LIST_DEL(&resolution->list); + /* ex first resolution goes to the end of the queue */ + LIST_ADDQ(&resolvers->resolution.curr, &resolution->list); + } + + dns_update_resolvers_timeout(resolvers); + goto next_packet; + } + + /* if we're there, this means that we already ran out of chances to re-send + * the query */ + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + requester->requester_error_cb(requester, dns_resp); + } + goto next_packet; + } + + /* now processing those error codes only: + * DNS_RESP_NX_DOMAIN + * DNS_RESP_REFUSED + */ + if (dns_resp != DNS_RESP_VALID) { + /* now parse list of requesters currently waiting for this resolution */ + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + requester->requester_error_cb(requester, dns_resp); + + /* we can move the requester the wait queue */ + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.wait, &requester->list); + } + goto next_packet; } /* Now let's check the query's dname corresponds to the one we sent. @@ -248,8 +565,14 @@ void dns_resolve_recv(struct dgram_conn *dgram) query = LIST_NEXT(&resolution->response.query_list, struct dns_query_item *, list); if (query && memcmp(query->name, resolution->hostname_dn, resolution->hostname_dn_len) != 0) { nameserver->counters.other += 1; - resolution->requester_error_cb(resolution, DNS_RESP_WRONG_NAME); - continue; + /* now parse list of requesters currently waiting for this resolution */ + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + requester->requester_error_cb(requester, DNS_RESP_WRONG_NAME); + /* we can move the requester the wait queue */ + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.wait, &requester->list); + } + goto next_packet; } /* no errors, we can save the response in the cache */ @@ -260,11 +583,17 @@ void dns_resolve_recv(struct dgram_conn *dgram) chunk_reset(buf); tmp = dns_cache_key(resolution->query_type, resolution->hostname_dn, - resolution->hostname_dn_len, buf); + resolution->hostname_dn_len, buf); if (!tmp) { nameserver->counters.other += 1; - resolution->requester_error_cb(resolution, DNS_RESP_ERROR); - continue; + /* now parse list of requesters currently waiting for this resolution */ + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + requester->requester_error_cb(requester, DNS_RESP_ERROR); + /* we can move the requester the wait queue */ + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.wait, &requester->list); + } + goto next_packet; } lru = lru64_get(XXH64(buf->str, buf->len, seed), @@ -273,9 +602,40 @@ void dns_resolve_recv(struct dgram_conn *dgram) lru64_commit(lru, resolution, nameserver->resolvers, 1, NULL); } + if (resolution->status != RSLV_STATUS_VALID) { + resolution->status = RSLV_STATUS_VALID; + resolution->last_status_change = now_ms; + } + nameserver->counters.valid += 1; - resolution->requester_cb(resolution, nameserver); - } + /* now parse list of requesters currently waiting for this resolution */ + tmpnameserver = nameserver; + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + requester->requester_cb(requester, tmpnameserver); + /* we can move the requester the wait queue */ + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.wait, &requester->list); + /* first response is managed by the server, others are from the cache */ + tmpnameserver = NULL; + } + + next_packet: + /* resolution may be NULL when we receive an ICMP unreachable packet */ + if (resolution && LIST_ISEMPTY(&resolution->requester.curr)) { + /* move the resolution into the wait queue */ + LIST_DEL(&resolution->list); + LIST_ADDQ(&resolvers->resolution.wait, &resolution->list); + /* update last resolution date and time */ + resolution->last_resolution = now_ms; + /* reset current status flag */ + resolution->step = RSLV_STEP_NONE; + /* reset values */ + dns_reset_resolution(resolution); + } + + } // end of while "packets" loop + + dns_update_resolvers_timeout(nameserver->resolvers); } /* @@ -303,7 +663,7 @@ void dns_resolve_send(struct dgram_conn *dgram) return; resolvers = nameserver->resolvers; - resolution = LIST_NEXT(&resolvers->curr_resolution, struct dns_resolution *, list); + resolution = LIST_NEXT(&resolvers->resolution.curr, struct dns_resolution *, list); dns_send_query(resolution); dns_update_resolvers_timeout(resolvers); @@ -320,9 +680,23 @@ int dns_send_query(struct dns_resolution *resolution) { struct dns_resolvers *resolvers = NULL; struct dns_nameserver *nameserver; + struct dns_requester *requester = NULL; int ret, bufsize, fd; - resolvers = ((struct server *)resolution->requester)->resolvers; + /* nothing to do */ + if (LIST_ISEMPTY(&resolution->requester.curr)) + return 0; + + requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list); + + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + resolvers = objt_server(requester->requester)->resolvers; + break; + case OBJ_TYPE_NONE: + default: + return 0; + } if (!resolvers) return 0; @@ -363,15 +737,67 @@ int dns_send_query(struct dns_resolution *resolution) void dns_update_resolvers_timeout(struct dns_resolvers *resolvers) { struct dns_resolution *resolution; + struct dns_requester *requester; - if (LIST_ISEMPTY(&resolvers->curr_resolution)) { - /* no more resolution pending, so no wakeup anymore */ + if ((LIST_ISEMPTY(&resolvers->resolution.curr)) && (LIST_ISEMPTY(&resolvers->resolution.wait))) { resolvers->t->expire = TICK_ETERNITY; } - else { - resolution = LIST_NEXT(&resolvers->curr_resolution, struct dns_resolution *, list); - resolvers->t->expire = tick_add(resolution->last_sent_packet, resolvers->timeout.retry); + else if (!LIST_ISEMPTY(&resolvers->resolution.curr)) { + resolution = LIST_NEXT(&resolvers->resolution.curr, struct dns_resolution *, list); + if (!resolvers->t->expire || tick_is_le(resolvers->t->expire, tick_add(resolution->last_sent_packet, resolvers->timeout.retry))) { + resolvers->t->expire = tick_add(resolution->last_sent_packet, resolvers->timeout.retry); + } } + else if (!LIST_ISEMPTY(&resolvers->resolution.wait)) { + int valid_period, inter, need_wakeup; + struct dns_resolution *res_back; + need_wakeup = 0; + list_for_each_entry_safe(resolution, res_back, &resolvers->resolution.wait, list) { + valid_period = 0; + inter = 0; + + requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list); + + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + valid_period = objt_server(requester->requester)->check.inter; + break; + case OBJ_TYPE_NONE: + default: + continue; + } + + if (resolvers->hold.valid < valid_period) + inter = resolvers->hold.valid; + else + inter = valid_period; + + if (tick_is_expired(tick_add(resolution->last_resolution, inter), now_ms)) { + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + dns_trigger_resolution(objt_server(requester->requester)->resolution); + break; + case OBJ_TYPE_NONE: + default: + ;; + } + } + else { + need_wakeup = 1; + } + } + /* in such case, we wake up in 1s */ + if (need_wakeup) { + int r = 1000; + + resolution = LIST_NEXT(&resolvers->resolution.wait, struct dns_resolution *, list); + if (tick_is_le(resolvers->t->expire, tick_add(now_ms, r))) + resolvers->t->expire = tick_add(now_ms, r); + resolvers->t->expire = tick_add(now_ms, 1000); + } + } + + task_queue(resolvers->t); } /* @@ -979,6 +1405,7 @@ int dns_init_resolvers(int close_socket) { struct dns_resolvers *curr_resolvers; struct dns_nameserver *curnameserver; + struct dns_resolution *resolution, *res_back; struct dgram_conn *dgram; struct task *t; int fd; @@ -1000,12 +1427,17 @@ int dns_init_resolvers(int close_socket) /* update task's parameters */ t->process = dns_process_resolve; t->context = curr_resolvers; - t->expire = TICK_ETERNITY; + t->expire = 0; - curr_resolvers->t = t; + /* no need to keep the new task if one is already affected to our resolvers + * section */ + if (!curr_resolvers->t) + curr_resolvers->t = t; + else + task_free(t); list_for_each_entry(curnameserver, &curr_resolvers->nameserver_list, list) { - dgram = NULL; + dgram = NULL; if (close_socket == 1) { if (curnameserver->dgram) { @@ -1061,6 +1493,18 @@ int dns_init_resolvers(int close_socket) continue; } + if (close_socket == 0) + continue; + + /* now, we can trigger DNS resolution */ + list_for_each_entry_safe(resolution, res_back, &curr_resolvers->resolution.wait, list) { + /* if there is no requester in the wait queue, no need to trigger the resolution */ + if (LIST_ISEMPTY(&resolution->requester.wait)) + continue; + + dns_trigger_resolution(resolution); + } + /* task can be queued */ task_queue(t); } @@ -1068,6 +1512,37 @@ int dns_init_resolvers(int close_socket) return 1; } +/* + * Allocate a pool of resolution to a resolvers section. + * Each resolution is associated with a UUID. + * + * Return code: + * - 0 if everything went smoothly + * - -1 if an error occured + */ +int dns_alloc_resolution_pool(struct dns_resolvers *resolvers) +{ + int i; + struct dns_resolution *resolution; + + /* return if a pool has already been set for this resolvers */ + if (!LIST_ISEMPTY(&resolvers->resolution.pool)) { + return 0; + } + + for (i = 0; i < resolvers->resolution_pool_size; i++) { + resolution = dns_alloc_resolution(); + if (!resolution) { + Alert("Starting [%s] resolvers: can't allocate memory for DNS resolution pool.\n", resolvers->id); + return -1; + } + resolution->uuid = i; + LIST_ADDQ(&resolvers->resolution.pool, &resolution->list); + } + + return 0; +} + /* * Forge a DNS query. It needs the following information from the caller: * - : the DNS query id corresponding to this query @@ -1268,35 +1743,75 @@ struct task *dns_process_resolve(struct task *t) int res_preferred_afinet, res_preferred_afinet6; struct dns_options *dns_opts = NULL; - /* timeout occurs inevitably for the first element of the FIFO queue */ - if (LIST_ISEMPTY(&resolvers->curr_resolution)) { + /* if both there is no resolution in the run queue, we can re-schedule a wake up */ + if (LIST_ISEMPTY(&resolvers->resolution.curr)) { /* no first entry, so wake up was useless */ - t->expire = TICK_ETERNITY; + dns_update_resolvers_timeout(resolvers); return t; } /* look for the first resolution which is not expired */ - list_for_each_entry_safe(resolution, res_back, &resolvers->curr_resolution, list) { + list_for_each_entry_safe(resolution, res_back, &resolvers->resolution.curr, list) { + struct dns_requester *requester = NULL; + /* when we find the first resolution in the future, then we can stop here */ if (tick_is_le(now_ms, resolution->last_sent_packet)) goto out; + if (LIST_ISEMPTY(&resolution->requester.curr)) + goto out; + /* * if current resolution has been tried too many times and finishes in timeout * we update its status and remove it from the list */ if (resolution->try <= 0) { + struct dns_requester *tmprequester; /* clean up resolution information and remove from the list */ dns_reset_resolution(resolution); - /* notify the result to the requester */ - resolution->requester_error_cb(resolution, DNS_RESP_TIMEOUT); + LIST_DEL(&resolution->list); + LIST_ADDQ(&resolvers->resolution.wait, &resolution->list); + + if (resolution->status != RSLV_STATUS_TIMEOUT) { + resolution->status = RSLV_STATUS_TIMEOUT; + resolution->last_status_change = now_ms; + } + + /* notify the result to the requesters */ + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + requester->requester_error_cb(requester, DNS_RESP_TIMEOUT); + LIST_DEL(&requester->list); + LIST_ADDQ(&resolution->requester.wait, &requester->list); + } goto out; } resolution->try -= 1; - dns_opts = &((struct server *)resolution->requester)->dns_opts; + /* running queue is empty, nothing to do but wait */ + if (LIST_ISEMPTY(&resolution->requester.curr)) + goto out; + + requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list); + + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + dns_opts = &(objt_server(requester->requester)->dns_opts); + break; + + case OBJ_TYPE_NONE: + default: + /* clean up resolution information and remove from the list */ + dns_reset_resolution(resolution); + + LIST_DEL(&resolution->list); + LIST_ADDQ(&resolvers->resolution.wait, &resolution->list); + + /* notify the result to the requester */ + requester->requester_error_cb(requester, DNS_RESP_INTERNAL); + goto out; + } res_preferred_afinet = dns_opts->family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A; res_preferred_afinet6 = dns_opts->family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA; @@ -1318,7 +1833,7 @@ struct task *dns_process_resolve(struct task *t) if (dns_check_resolution_queue(resolvers) > 1) { /* move the rsolution to the end of the list */ LIST_DEL(&resolution->list); - LIST_ADDQ(&resolvers->curr_resolution, &resolution->list); + LIST_ADDQ(&resolvers->resolution.curr, &resolution->list); } } @@ -1386,6 +1901,7 @@ dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int val struct lru64 *elem = NULL; struct dns_resolution *resolution = NULL; struct dns_resolvers *resolvers = NULL; + struct dns_requester *requester = NULL; int inter = 0; struct chunk *buf = get_trash_chunk(); struct chunk *tmp = NULL; @@ -1415,7 +1931,16 @@ dns_cache_lookup(int query_type, char *hostname_dn, int hostname_dn_len, int val return NULL; } - resolvers = ((struct server *)resolution->requester)->resolvers; + requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list); + + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + resolvers = objt_server(requester->requester)->resolvers; + break; + case OBJ_TYPE_NONE: + default: + return NULL; + } if (!resolvers) return NULL; @@ -1452,6 +1977,187 @@ static int cli_parse_stat_resolvers(char **args, struct appctx *appctx, void *pr return 0; } +/* + * if is provided, then the function skips the memory allocation part. + * It does the linking only. + * + * if is NULL, the function links a dns resolution to a requester: + * - it allocates memory for the struct requester used to link + * the resolution to the requester + * - it configures the resolution if this is the first requester to be linked to it + * - it updates the requester with a pointer to the resolution + * + * Return code: + * - 0 if everything happened smoothly + * - -1 if an error occured. Of course, no resolution is linked to the requester + */ +int dns_link_resolution(void *requester, int requester_type, struct dns_resolution *resolution) +{ + struct dns_resolution *tmpresolution = NULL; + struct dns_requester *tmprequester = NULL; + struct dns_resolvers *resolvers = NULL; + char *hostname_dn = NULL; + int new_resolution; + + if (!resolution) { + tmprequester = calloc(1, sizeof(*tmprequester)); + if (!tmprequester) + return -1; + + switch (requester_type) { + case OBJ_TYPE_SERVER: + tmprequester->requester = &((struct server *)requester)->obj_type; + hostname_dn = objt_server(tmprequester->requester)->hostname_dn; + resolvers = objt_server(tmprequester->requester)->resolvers; + switch (objt_server(tmprequester->requester)->dns_opts.family_prio) { + case AF_INET: + tmprequester->prefered_query_type = DNS_RTYPE_A; + break; + default: + tmprequester->prefered_query_type = DNS_RTYPE_AAAA; + } + + break; + case OBJ_TYPE_NONE: + default: + free(tmprequester); + return -1; + } + + /* get a resolution from the resolvers' wait queue or pool */ + tmpresolution = dns_resolution_list_get(resolvers, hostname_dn, tmprequester->prefered_query_type); + if (!tmpresolution) { + free(tmprequester); + return -1; + } + } + else { + tmpresolution = resolution; + + switch (requester_type) { + case OBJ_TYPE_SERVER: + tmprequester = ((struct server *)requester)->dns_requester; + resolvers = ((struct server *)requester)->resolvers; + break; + case OBJ_TYPE_NONE: + default: + return -1; + } + } + + /* flag this resolution as NEW if applicable (not already linked to any requester). + * this is required to decide which parameters we have to update on the resolution. + * If new, it means we pulled up the resolution from the resolvers' pool. + */ + if (LIST_ISEMPTY(&tmpresolution->requester.wait)) { + new_resolution = 1; + } + else + new_resolution = 0; + + /* those parameters are related to the requester type */ + switch (obj_type(tmprequester->requester)) { + case OBJ_TYPE_SERVER: + /* some parameters should be set only if the resolution is brand new */ + if (new_resolution) { + tmpresolution->query_type = tmprequester->prefered_query_type; + tmpresolution->hostname_dn = objt_server(tmprequester->requester)->hostname_dn; + tmpresolution->hostname_dn_len = objt_server(tmprequester->requester)->hostname_dn_len; + } + + /* update requester as well, only if we just allocated it */ + objt_server(tmprequester->requester)->resolution = tmpresolution; + if (!resolution) { + tmprequester->requester_cb = snr_resolution_cb; + tmprequester->requester_error_cb = snr_resolution_error_cb; + objt_server(tmprequester->requester)->dns_requester = tmprequester; + } + break; + case OBJ_TYPE_NONE: + default: + free(tmprequester); + return -1; + } + + /* update some parameters only if this is a brand new resolution */ + if (new_resolution) { + /* move the resolution to the requesters' wait queue */ + LIST_DEL(&tmpresolution->list); + LIST_ADDQ(&resolvers->resolution.wait, &tmpresolution->list); + + tmpresolution->status = RSLV_STATUS_NONE; + tmpresolution->step = RSLV_STEP_NONE; + tmpresolution->revision = 1; + } + + /* add the requester to the resolution's wait queue */ + if (resolution) + LIST_DEL(&tmprequester->list); + LIST_ADDQ(&tmpresolution->requester.wait, &tmprequester->list); + + return 0; +} + +/* + * pick up an available resolution from the different resolution list associated to a resolvers section, + * in this order: + * 1. check in resolution.curr for the same hostname and query_type + * 2. check in resolution.wait for the same hostname and query_type + * 3. take an available resolution from resolution.pool + * + * return an available resolution, NULL if none found. + */ +struct dns_resolution *dns_resolution_list_get(struct dns_resolvers *resolvers, char *hostname_dn, int query_type) +{ + struct dns_resolution *resolution, *tmpresolution; + struct dns_requester *requester; + + /* search for same hostname and query type in resolution.curr */ + list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.curr, list) { + requester = NULL; + + if (!LIST_ISEMPTY(&resolution->requester.wait)) + requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list); + else if (!LIST_ISEMPTY(&resolution->requester.curr)) + requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list); + + if (!requester) + continue; + + if ((query_type == requester->prefered_query_type) && + (strcmp(hostname_dn, resolution->hostname_dn) == 0)) { + return resolution; + } + } + + /* search for same hostname and query type in resolution.wait */ + list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.wait, list) { + requester = NULL; + + if (!LIST_ISEMPTY(&resolution->requester.wait)) + requester = LIST_NEXT(&resolution->requester.wait, struct dns_requester *, list); + else if (!LIST_ISEMPTY(&resolution->requester.curr)) + requester = LIST_NEXT(&resolution->requester.curr, struct dns_requester *, list); + + if (!requester) + continue; + + if ((query_type == requester->prefered_query_type) && + (strcmp(hostname_dn, resolution->hostname_dn) == 0)) { + return resolution; + } + } + + /* take the first one (hopefully) from the pool */ + list_for_each_entry_safe(resolution, tmpresolution, &resolvers->resolution.pool, list) { + if (LIST_ISEMPTY(&resolution->requester.wait)) { + return resolution; + } + } + + return NULL; +} + /* This function allocates memory for a DNS resolution structure. * It's up to the caller to set the parameters * Returns a pointer to the structure resolution or NULL if memory could @@ -1472,6 +2178,8 @@ struct dns_resolution *dns_alloc_resolution(void) } chunk_init(&resolution->response_buffer, buffer, global.tune.bufsize); + LIST_INIT(&resolution->requester.wait); + LIST_INIT(&resolution->requester.curr); return resolution; } @@ -1485,6 +2193,93 @@ void dns_free_resolution(struct dns_resolution *resolution) return; } +/* this function free a resolution from its requester(s) and move it back to the pool */ +void dns_resolution_free(struct dns_resolvers *resolvers, struct dns_resolution *resolution) +{ + struct dns_requester *requester, *tmprequester; + + /* clean up configuration */ + dns_reset_resolution(resolution); + resolution->hostname_dn = NULL; + resolution->hostname_dn_len = 0; + + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.wait, list) { + LIST_DEL(&requester->list); + } + list_for_each_entry_safe(requester, tmprequester, &resolution->requester.curr, list) { + LIST_DEL(&requester->list); + } + + LIST_DEL(&resolution->list); + LIST_ADDQ(&resolvers->resolution.pool, &resolution->list); + + return; +} + +/* + * this function remove a requester from a resolution + * and takes care of all the consequences. + * It also cleans up some parameters from the requester + */ +void dns_rm_requester_from_resolution(struct dns_requester *requester, struct dns_resolution *resolution) +{ + char *hostname_dn; + struct dns_requester *tmprequester; + + /* resolution is still used by other requesters, we need to move + * some pointers to an other requester if needed + */ + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + hostname_dn = objt_server(requester->requester)->hostname_dn; + break; + case OBJ_TYPE_NONE: + default: + hostname_dn = NULL; + break; + } + + if (resolution->hostname_dn != hostname_dn) + return; + + /* First, we need to find this other requester */ + tmprequester = NULL; + list_for_each_entry(tmprequester, &resolution->requester.wait, list) { + if (tmprequester != requester) + break; + } + if (!tmprequester) { + /* if we can't find it in wait queue, let's get one in run queue */ + list_for_each_entry(tmprequester, &resolution->requester.curr, list) { + if (tmprequester != requester) + break; + } + } + + /* move hostname_dn related pointers to the next requester */ + switch (obj_type(tmprequester->requester)) { + case OBJ_TYPE_SERVER: + resolution->hostname_dn = objt_server(tmprequester->requester)->hostname_dn; + resolution->hostname_dn_len = objt_server(tmprequester->requester)->hostname_dn_len; + break; + case OBJ_TYPE_NONE: + default: + ;; + } + + + /* clean up the requester */ + LIST_DEL(&requester->list); + switch (obj_type(requester->requester)) { + case OBJ_TYPE_SERVER: + objt_server(requester->requester)->resolution = NULL; + break; + case OBJ_TYPE_NONE: + default: + ;; + } +} + /* This function 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 diff --git a/src/server.c b/src/server.c index 9d0714a8c..74450a165 100644 --- a/src/server.c +++ b/src/server.c @@ -47,7 +47,6 @@ static void srv_update_state(struct server *srv, int version, char **params); static int srv_apply_lastaddr(struct server *srv, int *err_code); static int srv_set_fqdn(struct server *srv, const char *fqdn); -static void srv_free_dns_resolution(struct server *srv); /* List head of all known server keywords */ static struct srv_kw_list srv_keywords = { @@ -1652,60 +1651,29 @@ static void srv_ssl_settings_cpy(struct server *srv, struct server *src) #endif /* - * Allocate server dns resolution. + * Prepare for hostname resolution. * May be safely called with a default server as argument (without hostname). * Returns -1 in case of any allocation failure, 0 if not. */ -static int srv_alloc_dns_resolution(struct server *srv, const char *hostname) +static int srv_prepare_for_resolution(struct server *srv, const char *hostname) { - struct dns_resolution *dst_dns_rslt; - if (!hostname) return 0; free(srv->hostname); srv->hostname = strdup(hostname); - dst_dns_rslt = dns_alloc_resolution(); srv->hostname_dn_len = dns_str_to_dn_label_len(hostname); srv->hostname_dn = calloc(srv->hostname_dn_len + 1, sizeof(char)); - if (!srv->hostname || !dst_dns_rslt || !srv->hostname_dn) + if (!srv->hostname || !srv->hostname_dn) goto err; - srv->resolution = dst_dns_rslt; - if (!dns_str_to_dn_label(srv->hostname, srv->hostname_dn, srv->hostname_dn_len + 1)) goto err; - srv->resolution->hostname_dn = srv->hostname_dn; - srv->resolution->hostname_dn_len = srv->hostname_dn_len; - srv->resolution->revision = 1; - srv->resolution->requester = srv; - srv->resolution->requester_cb = snr_resolution_cb; - srv->resolution->requester_error_cb = snr_resolution_error_cb; - srv->resolution->status = RSLV_STATUS_NONE; - srv->resolution->step = RSLV_STEP_NONE; - /* a first resolution has been done by the configuration parser */ - srv->resolution->last_resolution = 0; - - if (srv->resolvers_id) { - struct dns_resolvers *curr_resolvers; - int found = 0; - - list_for_each_entry(curr_resolvers, &dns_resolvers, list) { - if (!strcmp(curr_resolvers->id, srv->resolvers_id)) { - found = 1; - break; - } - } - if (!found) - goto err; - srv->resolvers = curr_resolvers; - } - return 0; err: @@ -1713,17 +1681,44 @@ static int srv_alloc_dns_resolution(struct server *srv, const char *hostname) srv->hostname = NULL; free(srv->hostname_dn); srv->hostname_dn = NULL; - dns_free_resolution(dst_dns_rslt); return -1; } -static void srv_free_dns_resolution(struct server *srv) +/* + * Free the link between a server and its resolution. + * It also performs the following tasks: + * - check if resolution can be moved back in the resolvers' pool + * (and do it) + * - move resolution's hostname_dn and hostname_dn_len to the next requester + * available (when applied) + */ +static void srv_free_from_resolution(struct server *srv) { - if (!srv->resolution) - return; + struct dns_requester *requester; + int count; - dns_free_resolution(srv->resolution); - srv->resolution = NULL; + /* check if we can move the resolution back to the pool. + * if is greater than 1, then we can't */ + count = 0; + list_for_each_entry(requester, &srv->resolution->requester.wait, list) { + ++count; + if (count > 1) + break; + } + list_for_each_entry(requester, &srv->resolution->requester.curr, list) { + ++count; + if (count > 1) + break; + } + if (count <= 1) { + /* move the resolution back to the pool */ + dns_resolution_free(srv->resolvers, srv->resolution); + return; + } + + dns_rm_requester_from_resolution(srv->dns_requester, srv->resolution); + + return; } /* @@ -2114,7 +2109,7 @@ static int server_template_init(struct server *srv, struct proxy *px) goto err; srv_settings_cpy(newsrv, srv, 1); - srv_alloc_dns_resolution(newsrv, srv->hostname); + srv_prepare_for_resolution(newsrv, srv->hostname); #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (newsrv->sni_expr) { newsrv->ssl_ctx.sni = srv_sni_sample_parse_expr(newsrv, px, NULL, 0, NULL); @@ -2290,7 +2285,7 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr /* save hostname and create associated name resolution */ if (fqdn) { - if (srv_alloc_dns_resolution(newsrv, fqdn) == -1) { + if (srv_prepare_for_resolution(newsrv, fqdn) == -1) { Alert("parsing [%s:%d] : Can't create DNS resolution for server '%s'\n", file, linenum, newsrv->id); err_code |= ERR_ALERT | ERR_FATAL; @@ -3749,8 +3744,8 @@ int snr_update_srv_status(struct server *s) switch (resolution->status) { case RSLV_STATUS_NONE: - /* status when HAProxy has just (re)started */ - trigger_resolution(s); + /* status when HAProxy has just (re)started. + * Nothing to do, since the task is already automatically started */ break; case RSLV_STATUS_VALID: @@ -3825,23 +3820,27 @@ int snr_update_srv_status(struct server *s) * 0 on error * 1 when no error or safe ignore */ -int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver) +int snr_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver) { - struct server *s; + struct server *s = NULL; + struct dns_resolution *resolution = NULL; void *serverip, *firstip; short server_sin_family, firstip_sin_family; int ret; struct chunk *chk = get_trash_chunk(); + s = objt_server(requester->requester); + if (!s) + return 1; + + resolution = s->resolution; + /* initializing variables */ firstip = NULL; /* pointer to the first valid response found */ /* it will be used as the new IP if a change is required */ firstip_sin_family = AF_UNSPEC; serverip = NULL; /* current server IP address */ - /* shortcut to the server whose name is being resolved */ - s = resolution->requester; - /* initializing server IP pointer */ server_sin_family = s->addr.ss_family; switch (server_sin_family) { @@ -3866,20 +3865,12 @@ int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver * switch (ret) { case DNS_UPD_NO: - if (resolution->status != RSLV_STATUS_VALID) { - resolution->status = RSLV_STATUS_VALID; - resolution->last_status_change = now_ms; - } - goto stop_resolution; + goto update_status; case DNS_UPD_SRVIP_NOT_FOUND: goto save_ip; case DNS_UPD_CNAME: - if (resolution->status != RSLV_STATUS_VALID) { - resolution->status = RSLV_STATUS_VALID; - resolution->last_status_change = now_ms; - } goto invalid; case DNS_UPD_NO_IP_FOUND: @@ -3887,18 +3878,18 @@ int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver * resolution->status = RSLV_STATUS_OTHER; resolution->last_status_change = now_ms; } - goto stop_resolution; + goto update_status; case DNS_UPD_NAME_ERROR: /* if this is not the last expected response, we ignore it */ - if (resolution->nb_responses < nameserver->resolvers->count_nameservers) + if (nameserver && (resolution->nb_responses < nameserver->resolvers->count_nameservers)) return 0; /* update resolution status to OTHER error type */ if (resolution->status != RSLV_STATUS_OTHER) { resolution->status = RSLV_STATUS_OTHER; resolution->last_status_change = now_ms; } - goto stop_resolution; + goto update_status; default: goto invalid; @@ -3906,33 +3897,25 @@ int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver * } save_ip: - nameserver->counters.update += 1; - if (resolution->status != RSLV_STATUS_VALID) { - resolution->status = RSLV_STATUS_VALID; - resolution->last_status_change = now_ms; - } + if (nameserver) + nameserver->counters.update += 1; /* save the first ip we found */ - chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id); + if (nameserver) + chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id); + else + chunk_printf(chk, "DNS cache"); update_server_addr(s, firstip, firstip_sin_family, (char *)chk->str); - stop_resolution: - /* update last resolution date and time */ - resolution->last_resolution = now_ms; - /* reset current status flag */ - resolution->step = RSLV_STEP_NONE; - /* reset values */ - dns_reset_resolution(resolution); - - dns_update_resolvers_timeout(nameserver->resolvers); - + update_status: snr_update_srv_status(s); - return 0; + return 1; invalid: - nameserver->counters.invalid += 1; + if (nameserver) + nameserver->counters.invalid += 1; if (resolution->nb_responses >= nameserver->resolvers->count_nameservers) - goto stop_resolution; + goto update_status; snr_update_srv_status(s); return 0; @@ -3944,14 +3927,17 @@ int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver * * 0 on error * 1 when no error or safe ignore */ -int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code) +int snr_resolution_error_cb(struct dns_requester *requester, int error_code) { - struct server *s; - struct dns_resolvers *resolvers; - int res_preferred_afinet, res_preferred_afinet6; + struct server *s = NULL; + struct dns_resolution *resolution = NULL; + struct dns_resolvers *resolvers = NULL; - /* shortcut to the server whose name is being resolved */ - s = resolution->requester; + s = objt_server(requester->requester); + if (!s) + return 1; + + resolution = s->resolution; resolvers = s->resolvers; /* can be ignored if this is not the last response */ @@ -3959,99 +3945,6 @@ int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code) return 1; } - switch (error_code) { - case DNS_RESP_INVALID: - case DNS_RESP_WRONG_NAME: - if (resolution->status != RSLV_STATUS_INVALID) { - resolution->status = RSLV_STATUS_INVALID; - resolution->last_status_change = now_ms; - } - break; - - case DNS_RESP_ANCOUNT_ZERO: - case DNS_RESP_TRUNCATED: - case DNS_RESP_ERROR: - case DNS_RESP_NO_EXPECTED_RECORD: - case DNS_RESP_CNAME_ERROR: - res_preferred_afinet = s->dns_opts.family_prio == AF_INET && resolution->query_type == DNS_RTYPE_A; - res_preferred_afinet6 = s->dns_opts.family_prio == AF_INET6 && resolution->query_type == DNS_RTYPE_AAAA; - - if ((res_preferred_afinet || res_preferred_afinet6) - || (resolution->try > 0)) { - /* let's change the query type */ - if (res_preferred_afinet6) { - /* fallback from AAAA to A */ - resolution->query_type = DNS_RTYPE_A; - } - else if (res_preferred_afinet) { - /* fallback from A to AAAA */ - resolution->query_type = DNS_RTYPE_AAAA; - } - else { - resolution->try -= 1; - if (s->dns_opts.family_prio == AF_INET) { - resolution->query_type = DNS_RTYPE_A; - } else { - resolution->query_type = DNS_RTYPE_AAAA; - } - } - - dns_send_query(resolution); - - /* - * move the resolution to the last element of the FIFO queue - * and update timeout wakeup based on the new first entry - */ - if (dns_check_resolution_queue(resolvers) > 1) { - /* second resolution becomes first one */ - LIST_DEL(&resolution->list); - /* ex first resolution goes to the end of the queue */ - LIST_ADDQ(&resolvers->curr_resolution, &resolution->list); - } - dns_update_resolvers_timeout(resolvers); - goto leave; - } - else { - if (resolution->status != RSLV_STATUS_OTHER) { - resolution->status = RSLV_STATUS_OTHER; - resolution->last_status_change = now_ms; - } - } - break; - - case DNS_RESP_NX_DOMAIN: - if (resolution->status != RSLV_STATUS_NX) { - resolution->status = RSLV_STATUS_NX; - resolution->last_status_change = now_ms; - } - break; - - case DNS_RESP_REFUSED: - if (resolution->status != RSLV_STATUS_REFUSED) { - resolution->status = RSLV_STATUS_REFUSED; - resolution->last_status_change = now_ms; - } - break; - - case DNS_RESP_TIMEOUT: - if (resolution->status != RSLV_STATUS_TIMEOUT) { - resolution->status = RSLV_STATUS_TIMEOUT; - resolution->last_status_change = now_ms; - } - break; - } - - /* update last resolution date and time */ - resolution->last_resolution = now_ms; - /* reset current status flag */ - resolution->step = RSLV_STEP_NONE; - /* reset values */ - dns_reset_resolution(resolution); - - LIST_DEL(&resolution->list); - dns_update_resolvers_timeout(resolvers); - - leave: snr_update_srv_status(s); return 1; } @@ -4080,9 +3973,9 @@ struct server *snr_check_ip_callback(struct server *srv, void *ip, unsigned char * one used for the server found in the backend * * the server found in the backend is not our current server */ - if ((tmpsrv->resolution == NULL) || - (srv->resolution->hostname_dn_len != tmpsrv->resolution->hostname_dn_len) || - (strcmp(srv->resolution->hostname_dn, tmpsrv->resolution->hostname_dn) != 0) || + if ((tmpsrv->hostname_dn == NULL) || + (srv->hostname_dn_len != tmpsrv->hostname_dn_len) || + (strcmp(srv->hostname_dn, tmpsrv->hostname_dn) != 0) || (srv->puid == tmpsrv->puid)) continue; @@ -4121,9 +4014,55 @@ int srv_set_addr_via_libc(struct server *srv, int *err_code) */ int srv_set_fqdn(struct server *srv, const char *hostname) { - srv_free_dns_resolution(srv); + struct dns_resolution *resolution; + int hostname_dn_len; - return srv_alloc_dns_resolution(srv, hostname); + /* run time DNS resolution was not active for this server + * and we can't enable it at run time for now. + */ + if (!srv->dns_requester) + return -1; + + chunk_reset(&trash); + + /* check if hostname is really a hostname and if we have enough + * room to save it in its domain name format + */ + hostname_dn_len = dns_str_to_dn_label_len(hostname); + if (hostname_dn_len == -1 || hostname_dn_len + 1 > trash.size) + return -1; + + if (!dns_str_to_dn_label(hostname, + trash.str, + hostname_dn_len + 1)) + return -1; + + + /* get a resolution from the curr or wait queues, or a brand new one from the pool */ + resolution = dns_resolution_list_get(srv->resolvers, trash.str, srv->dns_requester->prefered_query_type); + if (!resolution) + return -1; + + /* in this case, the new hostanme is the same than the old one */ + if (srv->resolution == resolution) + return 0; + + /* first, we need to unlink our server from its current resolution */ + srv_free_from_resolution(srv); + + /* now we update server's parameters */ + free(srv->hostname); + free(srv->hostname_dn); + srv->hostname = strdup(hostname); + srv->hostname_dn = strdup(trash.str); + srv->hostname_dn_len = hostname_dn_len; + if (!srv->hostname || !srv->hostname_dn) + return -1; + + /* then we can link srv to its new resolution */ + dns_link_resolution(srv, OBJ_TYPE_SERVER, resolution); + + return 0; } /* Sets the server's address (srv->addr) from srv->lastaddr which was filled