diff --git a/include/haproxy/resolvers-t.h b/include/haproxy/resolvers-t.h index 57d85f66c..97c0d9ee3 100644 --- a/include/haproxy/resolvers-t.h +++ b/include/haproxy/resolvers-t.h @@ -287,6 +287,8 @@ struct resolv_srvrq { char *hostname_dn; /* server hostname in Domain Name format */ int hostname_dn_len; /* string length of the server hostname in Domain Name format */ struct resolv_requester *requester; /* used to link to its DNS resolution */ + struct list attached_servers; /* List of the servers free to use */ + struct eb_root named_servers; /* tree of servers indexed by hostnames found in server state file */ struct list list; /* Next SRV RQ for the same proxy */ }; diff --git a/include/haproxy/resolvers.h b/include/haproxy/resolvers.h index ba426fa6c..f82406de9 100644 --- a/include/haproxy/resolvers.h +++ b/include/haproxy/resolvers.h @@ -46,6 +46,7 @@ int resolv_get_ip_from_response(struct resolv_response *r_res, void resolv_purge_resolution_answer_records(struct resolv_resolution *resolution); int resolv_link_resolution(void *requester, int requester_type, int requester_locked); void resolv_unlink_resolution(struct resolv_requester *requester, int safe); +void resolv_detach_from_resolution_answer_items(struct resolv_resolution *res, struct resolv_requester *req, int safe); void resolv_trigger_resolution(struct resolv_requester *requester); enum act_parse_ret resolv_parse_do_resolve(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err); int check_action_do_resolve(struct act_rule *rule, struct proxy *px, char **err); diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index 1d2a44943..e866efaa8 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -365,7 +365,9 @@ struct server { #endif #endif struct resolv_srvrq *srvrq; /* Pointer representing the DNS SRV requeest, if any */ + struct list srv_rec_item; /* to attach server to a srv record item */ struct list ip_rec_item; /* to attach server to a A or AAAA record item */ + struct ebpt_node host_dn; /* hostdn store for srvrq and state file matching*/ struct { const char *file; /* file where the section appears */ struct eb32_node id; /* place in the tree of used IDs */ diff --git a/src/resolvers.c b/src/resolvers.c index 88642403c..45e48bc9d 100644 --- a/src/resolvers.c +++ b/src/resolvers.c @@ -19,6 +19,8 @@ #include +#include + #include #include #include @@ -211,6 +213,8 @@ struct resolv_srvrq *new_resolv_srvrq(struct server *srv, char *fqdn) proxy_type_str(px), px->id, srv->id); goto err; } + LIST_INIT(&srvrq->attached_servers); + srvrq->named_servers = EB_ROOT; LIST_APPEND(&resolv_srvrq_list, &srvrq->list); return srvrq; @@ -599,26 +603,20 @@ static void resolv_check_response(struct resolv_resolution *res) } } else if (item->type == DNS_RTYPE_SRV) { - list_for_each_entry(req, &res->requesters, list) { - if ((srvrq = objt_resolv_srvrq(req->owner)) == NULL) - continue; - - /* Remove any associated server */ - for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) { - HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->srvrq == srvrq && srv->svc_port == item->port && - item->data_len == srv->hostname_dn_len && - !resolv_hostname_cmp(srv->hostname_dn, item->target, item->data_len)) { - resolv_unlink_resolution(srv->resolv_requester, 0); - srvrq_update_srv_status(srv, 1); - ha_free(&srv->hostname); - ha_free(&srv->hostname_dn); - srv->hostname_dn_len = 0; - memset(&srv->addr, 0, sizeof(srv->addr)); - srv->svc_port = 0; - } - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - } + /* Remove any associated server */ + list_for_each_entry_safe(srv, srvback, &item->attached_servers, srv_rec_item) { + resolv_unlink_resolution(srv->resolv_requester, 0); + HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); + srvrq_update_srv_status(srv, 1); + ha_free(&srv->hostname); + ha_free(&srv->hostname_dn); + srv->hostname_dn_len = 0; + memset(&srv->addr, 0, sizeof(srv->addr)); + srv->svc_port = 0; + srv->flags |= SRV_F_NO_RESOLUTION; + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); + LIST_DELETE(&srv->srv_rec_item); + LIST_APPEND(&srv->srvrq->attached_servers, &srv->srv_rec_item); } } @@ -636,30 +634,81 @@ static void resolv_check_response(struct resolv_resolution *res) /* Now process SRV records */ list_for_each_entry(req, &res->requesters, list) { + struct ebpt_node *node; + char target[DNS_MAX_NAME_SIZE+1]; + + int i; if ((srvrq = objt_resolv_srvrq(req->owner)) == NULL) continue; - /* Check if a server already uses that hostname */ - for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) { - HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->srvrq == srvrq && srv->svc_port == item->port && - item->data_len == srv->hostname_dn_len && - !resolv_hostname_cmp(srv->hostname_dn, item->target, item->data_len)) { - break; - } - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); - } - - /* If not, try to find a server with undefined hostname */ - if (!srv) { - for (srv = srvrq->proxy->srv; srv != NULL; srv = srv->next) { + /* Check if a server already uses that record */ + srv = NULL; + list_for_each_entry(srv, &item->attached_servers, srv_rec_item) { + if (srv->srvrq == srvrq) { HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); - if (srv->srvrq == srvrq && !srv->hostname_dn) - break; - HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); + goto srv_found; } } + + /* If not empty we try to match a server + * in server state file tree with the same hostname + */ + if (!eb_is_empty(&srvrq->named_servers)) { + srv = NULL; + + /* convert the key to lookup in lower case */ + for (i = 0 ; item->target[i] ; i++) + target[i] = tolower(item->target[i]); + + node = ebis_lookup(&srvrq->named_servers, target); + if (node) { + srv = ebpt_entry(node, struct server, host_dn); + HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); + + /* an entry was found with the same hostname + * let check this node if the port matches + * and try next node if the hostname + * is still the same + */ + while (1) { + if (srv->svc_port == item->port) { + /* server found, we remove it from tree */ + ebpt_delete(node); + free(srv->host_dn.key); + goto srv_found; + } + + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); + + node = ebpt_next(node); + if (!node) + break; + + srv = ebpt_entry(node, struct server, host_dn); + HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); + + if ((item->data_len != srv->hostname_dn_len) + || resolv_hostname_cmp(srv->hostname_dn, item->target, item->data_len)) { + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); + break; + } + } + } + } + + /* Pick the first server listed in srvrq (those ones don't + * have hostname and are free to use) + */ + srv = NULL; + list_for_each_entry(srv, &srvrq->attached_servers, srv_rec_item) { + LIST_DEL_INIT(&srv->srv_rec_item); + HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); + goto srv_found; + } + srv = NULL; + +srv_found: /* And update this server, if found (srv is locked here) */ if (srv) { /* re-enable DNS resolution for this server by default */ @@ -705,6 +754,9 @@ static void resolv_check_response(struct resolv_resolution *res) send_log(srv->proxy, LOG_NOTICE, "%s", msg); } + if (!LIST_INLIST(&srv->srv_rec_item)) + LIST_APPEND(&item->attached_servers, &srv->srv_rec_item); + if (!(srv->flags & SRV_F_NO_RESOLUTION)) { /* If there is no AR item responsible of the FQDN resolution, * trigger a dedicated DNS resolution @@ -1861,6 +1913,43 @@ int resolv_link_resolution(void *requester, int requester_type, int requester_lo return -1; } +/* This function removes all server/srvrq references on answer items + * if is set to 1, in case of srvrq, sub server resquesters unlink + * is called using safe == 1 to make it usable into callbacks + */ +void resolv_detach_from_resolution_answer_items(struct resolv_resolution *res, struct resolv_requester *req, int safe) +{ + struct resolv_answer_item *item, *itemback; + struct server *srv, *srvback; + struct resolv_srvrq *srvrq; + + if ((srv = objt_server(req->owner)) != NULL) { + LIST_DEL_INIT(&srv->ip_rec_item); + } + else if ((srvrq = objt_resolv_srvrq(req->owner)) != NULL) { + list_for_each_entry_safe(item, itemback, &res->response.answer_list, list) { + if (item->type == DNS_RTYPE_SRV) { + list_for_each_entry_safe(srv, srvback, &item->attached_servers, srv_rec_item) { + if (srv->srvrq == srvrq) { + HA_SPIN_LOCK(SERVER_LOCK, &srv->lock); + resolv_unlink_resolution(srv->resolv_requester, safe); + srvrq_update_srv_status(srv, 1); + ha_free(&srv->hostname); + ha_free(&srv->hostname_dn); + srv->hostname_dn_len = 0; + memset(&srv->addr, 0, sizeof(srv->addr)); + srv->svc_port = 0; + srv->flags |= SRV_F_NO_RESOLUTION; + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock); + LIST_DELETE(&srv->srv_rec_item); + LIST_APPEND(&srvrq->attached_servers, &srv->srv_rec_item); + } + } + } + } + } +} + /* Removes a requester from a DNS resolution. It takes takes care of all the * consequences. It also cleans up some parameters from the requester. * if is set to 1, the corresponding resolution is not released. @@ -1869,7 +1958,6 @@ void resolv_unlink_resolution(struct resolv_requester *requester, int safe) { struct resolv_resolution *res; struct resolv_requester *req; - struct server *srv; /* Nothing to do */ if (!requester || !requester->resolution) @@ -1877,9 +1965,7 @@ void resolv_unlink_resolution(struct resolv_requester *requester, int safe) res = requester->resolution; /* remove ref from the resolution answer item list to the requester */ - if ((srv = objt_server(requester->owner)) != NULL) { - LIST_DEL_INIT(&srv->ip_rec_item); - } + resolv_detach_from_resolution_answer_items(res, requester, safe); /* Clean up the requester */ LIST_DELETE(&requester->list); diff --git a/src/server.c b/src/server.c index 4a7044b4f..30ee13530 100644 --- a/src/server.c +++ b/src/server.c @@ -2160,6 +2160,7 @@ struct server *new_server(struct proxy *proxy) srv->proxy = proxy; srv->pendconns = EB_ROOT; LIST_APPEND(&servers_list, &srv->global_list); + LIST_INIT(&srv->srv_rec_item); LIST_INIT(&srv->ip_rec_item); srv->next_state = SRV_ST_RUNNING; /* early server setup */ @@ -2304,6 +2305,9 @@ static int _srv_parse_tmpl_init(struct server *srv, struct proxy *px) goto err; } #endif + /* append to list of servers available to receive an hostname */ + LIST_APPEND(&newsrv->srvrq->attached_servers, &newsrv->srv_rec_item); + /* Set this new server ID. */ _srv_parse_set_id_from_prefix(newsrv, srv->tmpl_info.prefix, i); @@ -3339,18 +3343,11 @@ int snr_resolution_cb(struct resolv_requester *requester, struct dns_counters *c return 1; if (s->srvrq) { - struct resolv_answer_item *srv_item; - - /* If DNS resolution is disabled ignore it. */ - if (s->flags & SRV_F_NO_RESOLUTION) - return 1; - - /* The server is based on a SRV record, thus, find the - * associated answer record. If not found, it means the SRV item - * has expired and this resolution must be ignored. + /* If DNS resolution is disabled ignore it. + * This is the case if the server was associated to + * a SRV record and this record is now expired. */ - srv_item = find_srvrq_answer_record(requester); - if (!srv_item) + if (s->flags & SRV_F_NO_RESOLUTION) return 1; } @@ -3444,7 +3441,6 @@ int snr_resolution_cb(struct resolv_requester *requester, struct dns_counters *c */ int srvrq_resolution_error_cb(struct resolv_requester *requester, int error_code) { - struct server *s; struct resolv_srvrq *srvrq; struct resolv_resolution *res; struct resolvers *resolvers; @@ -3488,20 +3484,8 @@ int srvrq_resolution_error_cb(struct resolv_requester *requester, int error_code return 1; } - /* Remove any associated server */ - for (s = srvrq->proxy->srv; s != NULL; s = s->next) { - HA_SPIN_LOCK(SERVER_LOCK, &s->lock); - if (s->srvrq == srvrq) { - resolv_unlink_resolution(s->resolv_requester, 1); - srvrq_update_srv_status(s, 1); - memset(&s->addr, 0, sizeof(s->addr)); - ha_free(&s->hostname); - ha_free(&s->hostname_dn); - s->hostname_dn_len = 0; - s->svc_port = 0; - } - HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock); - } + /* Remove any associated server ref */ + resolv_detach_from_resolution_answer_items(res, requester, 1); return 0; } @@ -3526,7 +3510,7 @@ int snr_resolution_error_cb(struct resolv_requester *requester, int error_code) if (!snr_update_srv_status(s, 1)) { memset(&s->addr, 0, sizeof(s->addr)); HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock); - LIST_DEL_INIT(&s->ip_rec_item); + resolv_detach_from_resolution_answer_items(requester->resolution, requester, 1); return 0; } HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock); diff --git a/src/server_state.c b/src/server_state.c index 6f42a397f..02524dd9a 100644 --- a/src/server_state.c +++ b/src/server_state.c @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -392,6 +393,8 @@ static void srv_state_srv_update(struct server *srv, int version, char **params) */ else if (fqdn && !srv->hostname && srvrecord) { int res; + int i; + char *tmp; /* we can't apply previous state if SRV record has changed */ if (srv->srvrq && strcmp(srv->srvrq->name, srvrecord) != 0) { @@ -414,6 +417,21 @@ static void srv_state_srv_update(struct server *srv, int version, char **params) goto out; } + /* Remove from available list and insert in tree + * since this server has an hostname + */ + LIST_DEL_INIT(&srv->srv_rec_item); + srv->host_dn.key = tmp = strdup(srv->hostname_dn); + + /* convert the key in lowercase because tree + * lookup is case sensitive but we don't care + */ + for (i = 0; tmp[i]; i++) + tmp[i] = tolower(tmp[i]); + + /* insert in tree */ + ebis_insert(&srv->srvrq->named_servers, &srv->host_dn); + /* Unset SRV_F_MAPPORTS for SRV records. * SRV_F_MAPPORTS is unfortunately set by parse_server() * because no ports are provided in the configuration file.