MEDIUM: resolvers: add a ref between servers and srv request or used SRV record

This patch add a ref into servers to register them onto the
record answer item used to set their hostnames.

It also adds a head list into 'srvrq' to register servers free
to be affected to a SRV record.

A head of a tree is also added to srvrq to put servers which
present a hotname in server state file. To re-link them fastly
to the matching record as soon an item present the same name.

This results in better performances on SRV record response
parsing.

This is an optimization but it could avoid to trigger the haproxy's
internal wathdog in some circumstances. And for this reason
it should be backported as far we can (2.0 ?)
This commit is contained in:
Emeric Brun 2021-06-11 10:48:45 +02:00 committed by Christopher Faulet
parent bd78c912fd
commit 3406766d57
6 changed files with 161 additions and 68 deletions

View File

@ -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 */
};

View File

@ -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);

View File

@ -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 */

View File

@ -19,6 +19,8 @@
#include <sys/types.h>
#include <import/ebistree.h>
#include <haproxy/action.h>
#include <haproxy/api.h>
#include <haproxy/cfgparse.h>
@ -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 <safe> 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 <safe> 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);

View File

@ -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);

View File

@ -14,6 +14,7 @@
#include <import/eb64tree.h>
#include <import/xxhash.h>
#include <import/ebistree.h>
#include <haproxy/api.h>
#include <haproxy/backend.h>
@ -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.