diff --git a/include/proto/checks.h b/include/proto/checks.h index 67d659fb5..9ab3e509b 100644 --- a/include/proto/checks.h +++ b/include/proto/checks.h @@ -29,6 +29,7 @@ const char *get_check_status_description(short check_status); const char *get_check_status_info(short check_status); int start_checks(); 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/server.h b/include/proto/server.h index 64f232706..0a0ccc396 100644 --- a/include/proto/server.h +++ b/include/proto/server.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,11 @@ int srv_getinter(const struct check *check); int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy); int update_server_addr(struct server *s, void *ip, int ip_sin_family, char *updater); +/* 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, unsigned char *response, int response_len); +int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code); + /* increase the number of cumulated connections on the designated server */ static void inline srv_inc_sess_ctr(struct server *s) { diff --git a/include/types/server.h b/include/types/server.h index f987e25b5..4b44f22eb 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -205,6 +205,11 @@ struct server { struct check check; /* health-check specific configuration */ struct check agent; /* agent specific configuration */ + char *resolvers_id; /* resolvers section used by this server */ + char *hostname; /* server hostname */ + struct dns_resolution *resolution; /* server name resolution */ + int resolver_family_priority; /* which IP family should the resolver use when both are returned */ + #ifdef USE_OPENSSL int use_ssl; /* ssl enabled */ struct { diff --git a/src/cfgparse.c b/src/cfgparse.c index e48cf93be..129cf17dc 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -8103,6 +8103,47 @@ out_uri_auth_compat: free(newsrv->trackit); newsrv->trackit = NULL; } + + /* + * resolve server's resolvers name and update the resolvers pointer + * accordingly + */ + if (newsrv->resolvers_id) { + struct dns_resolvers *curr_resolvers; + int found; + + found = 0; + list_for_each_entry(curr_resolvers, &dns_resolvers, list) { + if (!strcmp(curr_resolvers->id, newsrv->resolvers_id)) { + found = 1; + break; + } + } + + if (!found) { + Alert("config : %s '%s', server '%s': unable to find required resolvers '%s'\n", + proxy_type_str(curproxy), curproxy->id, + newsrv->id, newsrv->resolvers_id); + cfgerr++; + } else { + free(newsrv->resolvers_id); + newsrv->resolvers_id = NULL; + if (newsrv->resolution) + newsrv->resolution->resolvers = curr_resolvers; + } + } + else { + /* if no resolvers section associated to this server + * we can clean up the associated resolution structure + */ + if (newsrv->resolution) { + free(newsrv->resolution->hostname_dn); + newsrv->resolution->hostname_dn = NULL; + free(newsrv->resolution); + newsrv->resolution = NULL; + } + } + next_srv: newsrv = newsrv->next; } diff --git a/src/checks.c b/src/checks.c index 8d8c31c80..2179d4fc7 100644 --- a/src/checks.c +++ b/src/checks.c @@ -38,6 +38,7 @@ #include #include +#include #ifdef USE_OPENSSL #include @@ -59,6 +60,9 @@ #include #include #include +#include +#include +#include static int httpchk_expect(struct server *s, int done); static int tcpcheck_get_step_id(struct check *); @@ -680,6 +684,14 @@ static void chk_report_conn_err(struct connection *conn, int errno_bck, int expi set_server_check_status(check, HCHK_STATUS_L4CON, err_msg); else if (expired) set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg); + + /* + * might be due to a server IP change. + * 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); + } else if ((conn->flags & (CO_FL_CONNECTED|CO_FL_WAIT_L6_CONN)) == CO_FL_WAIT_L6_CONN) { /* L6 not established (yet) */ @@ -2132,10 +2144,93 @@ 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; + + /* 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 (tick_is_expired(tick_add(resolution->last_resolution, resolution->resolvers->hold.valid), now_ms)) { + trigger_resolution(s); + } + } + } if (check->type == PR_O2_EXT_CHK) return process_chk_proc(t); return process_chk_conn(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; + struct dns_resolvers *resolvers; + int query_id; + int i; + + resolution = s->resolution; + resolvers = resolution->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; + resolution->query_type = DNS_RTYPE_ANY; + resolution->try = 0; + resolution->try_cname = 0; + resolution->nb_responses = 0; + resolution->resolver_family_priority = s->resolver_family_priority; + eb32_insert(&resolvers->query_ids, &resolution->qid); + + dns_send_query(resolution); + + /* 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, diff --git a/src/server.c b/src/server.c index 2bde24629..ee52903f6 100644 --- a/src/server.c +++ b/src/server.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include /* List head of all known server keywords */ @@ -865,6 +867,7 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr struct sockaddr_storage *sk; int port1, port2; struct protocol *proto; + struct dns_resolution *curr_resolution; if ((newsrv = (struct server *)calloc(1, sizeof(struct server))) == NULL) { Alert("parsing [%s:%d] : out of memory.\n", file, linenum); @@ -928,6 +931,53 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr realport = port1; } + /* save hostname and create associated name resolution */ + switch (sk->ss_family) { + case AF_INET: { + /* remove the port if any */ + char *c; + if ((c = rindex(args[2], ':')) != NULL) { + newsrv->hostname = my_strndup(args[2], c - args[2]); + } + else { + newsrv->hostname = strdup(args[2]); + } + } + break; + case AF_INET6: + newsrv->hostname = strdup(args[2]); + break; + default: + goto skip_name_resolution; + } + + /* no name resolution if an IP has been provided */ + if (inet_pton(sk->ss_family, newsrv->hostname, trash.str) == 1) + goto skip_name_resolution; + + if ((curr_resolution = calloc(1, sizeof(struct dns_resolution))) == NULL) + goto skip_name_resolution; + + curr_resolution->hostname_dn_len = dns_str_to_dn_label_len(newsrv->hostname); + if ((curr_resolution->hostname_dn = calloc(curr_resolution->hostname_dn_len + 1, sizeof(char))) == NULL) + goto skip_name_resolution; + if ((dns_str_to_dn_label(newsrv->hostname, curr_resolution->hostname_dn, curr_resolution->hostname_dn_len + 1)) == NULL) { + Alert("parsing [%s:%d] : Invalid hostname '%s'\n", + file, linenum, args[2]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curr_resolution->requester = newsrv; + curr_resolution->requester_cb = snr_resolution_cb; + curr_resolution->requester_error_cb = snr_resolution_error_cb; + curr_resolution->status = RSLV_STATUS_NONE; + curr_resolution->step = RSLV_STEP_NONE; + /* a first resolution has been done by the configuration parser */ + curr_resolution->last_resolution = now_ms; + newsrv->resolution = curr_resolution; + + skip_name_resolution: newsrv->addr = *sk; newsrv->xprt = newsrv->check.xprt = newsrv->agent.xprt = &raw_sock; @@ -975,11 +1025,15 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr newsrv->agent.fall = curproxy->defsrv.agent.fall; newsrv->agent.health = newsrv->agent.rise; /* up, but will fall down at first failure */ newsrv->agent.server = newsrv; + newsrv->resolver_family_priority = curproxy->defsrv.resolver_family_priority; + if (newsrv->resolver_family_priority == AF_UNSPEC) + newsrv->resolver_family_priority = AF_INET6; cur_arg = 3; } else { newsrv = &curproxy->defsrv; cur_arg = 1; + newsrv->resolver_family_priority = AF_INET6; } while (*args[cur_arg]) { @@ -1019,6 +1073,23 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr newsrv->rdr_len = strlen(args[cur_arg + 1]); cur_arg += 2; } + else if (!strcmp(args[cur_arg], "resolvers")) { + newsrv->resolvers_id = strdup(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "resolve-prefer")) { + if (!strcmp(args[cur_arg + 1], "ipv4")) + newsrv->resolver_family_priority = AF_INET; + else if (!strcmp(args[cur_arg + 1], "ipv6")) + newsrv->resolver_family_priority = AF_INET6; + else { + Alert("parsing [%s:%d]: '%s' expects either ipv4 or ipv6 as argument.\n", + file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + cur_arg += 2; + } else if (!strcmp(args[cur_arg], "rise")) { if (!*args[cur_arg + 1]) { Alert("parsing [%s:%d]: '%s' expects an integer argument.\n", @@ -1677,6 +1748,9 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr goto out; } + if (newsrv->resolution) + newsrv->resolution->resolver_family_priority = newsrv->resolver_family_priority; + newsrv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED; } @@ -1785,6 +1859,238 @@ int update_server_addr(struct server *s, void *ip, int ip_sin_family, char *upda return 0; } +/* + * update server status based on result of name resolution + * returns: + * 0 if server status is updated + * 1 if server status has not changed + */ +int snr_update_srv_status(struct server *s) +{ + struct dns_resolution *resolution = s->resolution; + + switch (resolution->status) { + case RSLV_STATUS_NONE: + /* status when HAProxy has just (re)started */ + trigger_resolution(s); + break; + + default: + break; + } + + return 1; +} + +/* + * Server Name Resolution valid response callback + * It expects: + * - : the name server which answered the valid response + * - : buffer containing a valid DNS response + * - : size of + * It performs the following actions: + * - ignore response if current ip found and server family not met + * - update with first new ip found if family is met and current IP is not found + * returns: + * 0 on error + * 1 when no error or safe ignore + */ +int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver, unsigned char *response, int response_len) +{ + struct server *s; + void *serverip, *firstip; + short server_sin_family, firstip_sin_family; + unsigned char *response_end; + int ret; + struct chunk *chk = get_trash_chunk(); + + /* initializing variables */ + response_end = response + response_len; /* pointer to mark the end of the response */ + 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 = (struct server *)resolution->requester; + + /* initializing server IP pointer */ + server_sin_family = s->addr.ss_family; + switch (server_sin_family) { + case AF_INET: + serverip = &((struct sockaddr_in *)&s->addr)->sin_addr.s_addr; + break; + + case AF_INET6: + serverip = &((struct sockaddr_in6 *)&s->addr)->sin6_addr.s6_addr; + break; + + default: + goto invalid; + } + + ret = dns_get_ip_from_response(response, response_end, resolution->hostname_dn, resolution->hostname_dn_len, + serverip, server_sin_family, resolution->resolver_family_priority, &firstip, + &firstip_sin_family); + + 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; + + 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; + + default: + goto invalid; + + } + + save_ip: + nameserver->counters.update += 1; + if (resolution->status != RSLV_STATUS_VALID) { + resolution->status = RSLV_STATUS_VALID; + resolution->last_status_change = now_ms; + } + + /* save the first ip we found */ + chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id); + 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); + + LIST_DEL(&resolution->list); + dns_update_resolvers_timeout(nameserver->resolvers); + + snr_update_srv_status(s); + return 0; + + invalid: + nameserver->counters.invalid += 1; + if (resolution->nb_responses >= nameserver->resolvers->count_nameservers) + goto stop_resolution; + + snr_update_srv_status(s); + return 0; +} + +/* + * Server Name Resolution error management callback + * returns: + * 0 on error + * 1 when no error or safe ignore + */ +int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code) +{ + struct server *s; + struct dns_resolvers *resolvers; + + /* shortcut to the server whose name is being resolved */ + s = (struct server *)resolution->requester; + resolvers = resolution->resolvers; + + /* can be ignored if this is not the last response */ + if ((error_code != DNS_RESP_TIMEOUT) && (resolution->nb_responses < resolvers->count_nameservers)) { + 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_ERROR: + if (resolution->query_type == DNS_RTYPE_ANY) { + /* let's change the query type */ + if (resolution->resolver_family_priority == AF_INET6) + resolution->query_type = DNS_RTYPE_AAAA; + else + resolution->query_type = DNS_RTYPE_A; + + 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(&resolvers->curr_resolution); + /* 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_CNAME_ERROR: + 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; +} + /* * Local variables: * c-indent-level: 8 diff --git a/src/standard.c b/src/standard.c index 709db8b94..4e458c25c 100644 --- a/src/standard.c +++ b/src/standard.c @@ -25,6 +25,7 @@ #include #include #include +#include #include /* enough to store NB_ITOA_STR integers of : @@ -608,6 +609,9 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i if (!resolve) return NULL; + if (!dns_hostname_validation(str, NULL)) + return NULL; + #ifdef USE_GETADDRINFO if (global.tune.options & GTUNE_USE_GAI) { struct addrinfo hints, *result;