]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: server: add DNS-based server name resolution
authorBaptiste Assmann <bedis9@gmail.com>
Mon, 13 Apr 2015 23:15:08 +0000 (01:15 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 13 Jun 2015 20:07:35 +0000 (22:07 +0200)
Relies on the DNS protocol freshly implemented in HAProxy.
It performs a server IP addr resolution based on a server hostname.

include/proto/checks.h
include/proto/server.h
include/types/server.h
src/cfgparse.c
src/checks.c
src/server.c
src/standard.c

index 67d659fb5abe450e6b09f71d67c9856b3706b5bb..9ab3e509b43f1c5cd094a2016408237b4207a774 100644 (file)
@@ -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;
 
index 64f2327063579578d492950bb1b6cc2f666c2e9a..0a0ccc396b7d1a606971b08e5a91f22e50f0f16b 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <common/config.h>
 #include <common/time.h>
+#include <types/dns.h>
 #include <types/proxy.h>
 #include <types/queue.h>
 #include <types/server.h>
@@ -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)
 {
index f987e25b5e9452eb9a7051199ee4437c38a38d3a..4b44f22eb47976149f8f137cb89f029ef4bf3cd9 100644 (file)
@@ -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 {
index e48cf93be1b37b09a382f0409381279cbcd85ad9..129cf17dc990952cc29656289b78779549b6017c 100644 (file)
@@ -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;
                }
index 8d8c31c803031d98d8d618fc300715b978fd31c2..2179d4fc7c718cd579fbb2d5bdf26830799549f7 100644 (file)
@@ -38,6 +38,7 @@
 
 #include <types/global.h>
 #include <types/mailers.h>
+#include <types/dns.h>
 
 #ifdef USE_OPENSSL
 #include <types/ssl_sock.h>
@@ -59,6 +60,9 @@
 #include <proto/server.h>
 #include <proto/stream_interface.h>
 #include <proto/task.h>
+#include <proto/log.h>
+#include <proto/dns.h>
+#include <proto/proto_udp.h>
 
 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,
index 2bde246290e9e1cfc869ff5adc998d687a20e323..ee52903f6e889990508d96c644d675f55073d506 100644 (file)
@@ -20,6 +20,7 @@
 #include <common/time.h>
 
 #include <types/global.h>
+#include <types/dns.h>
 
 #include <proto/checks.h>
 #include <proto/port_range.h>
@@ -29,6 +30,7 @@
 #include <proto/server.h>
 #include <proto/stream.h>
 #include <proto/task.h>
+#include <proto/dns.h>
 
 
 /* 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:
+ *  - <nameserver>: the name server which answered the valid response
+ *  - <response>: buffer containing a valid DNS response
+ *  - <response_len>: size of <response>
+ * 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
index 709db8b942e1aa3704a581eb4e82d74e6aa6c4e0..4e458c25c1eae761fa5dd3fe9250a6ec2fcefc20 100644 (file)
@@ -25,6 +25,7 @@
 #include <common/config.h>
 #include <common/standard.h>
 #include <types/global.h>
+#include <proto/dns.h>
 #include <eb32tree.h>
 
 /* 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;