>From 2806fbc7135ead2871d1d51b96f486338a9b202f Mon Sep 17 00:00:00 2001 From: Matthieu Patou Date: Tue, 7 Feb 2017 11:29:55 -0800 Subject: [PATCH] Move to async name resolution for names returned by SRV lookups For small setups and correctly configured one, DNS servers will add additional records in the SRV response with the A/AAAA entries associated with the name that are returned. But if it's too large or if the server is not correctly configured then the additional names are not returned and we have to resort on calling getaddrinfo. Traditionally we have been doing it in sequence but for large/slow network it can be an issue. With this change resolution is now asynchronous (using pthreadpool_tevent framework), all the DNS requests are fired at once and then we wait for all the threads to provide the results before assembling the ip_list response Signed-off-by: Matthieu Patou Change-Id: Ib755bbe98a5bd649d4959bd2474fc883f0ae65df --- source3/libsmb/namequery.c | 339 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 263 insertions(+), 76 deletions(-) diff --git a/source3/libsmb/namequery.c b/source3/libsmb/namequery.c index 945fc64..5c4d226 100644 --- a/source3/libsmb/namequery.c +++ b/source3/libsmb/namequery.c @@ -19,6 +19,7 @@ */ #include "includes.h" +#include "system/network.h" #include "../lib/util/tevent_ntstatus.h" #include "libads/sitename_cache.h" #include "../lib/addns/dnsquery.h" @@ -28,9 +29,12 @@ #include "libsmb/nmblib.h" #include "../libcli/nbt/libnbt.h" #include "libads/kerberos_proto.h" +#include "lib/pthreadpool/pthreadpool_tevent.h" /* nmbd.c sets this to True. */ bool global_in_nmbd = False; +/* Could be 2 with IPv6 because nowdays/soon all DCs will have IPv4 and IPv6*/ +static const int DEFAULT_IP_PER_DC = 1; /**************************** * SERVER AFFINITY ROUTINES * @@ -2345,7 +2349,7 @@ static NTSTATUS resolve_hosts(const char *name, int name_type, mem_ctx, *return_iplist, struct sockaddr_storage, *return_count); if (!*return_iplist) { - DEBUG(3,("resolve_hosts: malloc fail !\n")); + DEBUG(3,("resolve_hosts: malloc failed !\n")); freeaddrinfo(ailist); return NT_STATUS_NO_MEMORY; } @@ -2361,6 +2365,148 @@ static NTSTATUS resolve_hosts(const char *name, int name_type, return NT_STATUS_UNSUCCESSFUL; } +struct state_async_resolv +{ + const char* hostname; + struct addrinfo *res; + int ret; +}; + +static void resolve_one_name(void *private_data) +{ + struct state_async_resolv *state = private_data; + struct addrinfo *res = NULL; + struct addrinfo hints; + int ret; + + if (unlikely(state == NULL)) { + /* Unlikely but better safe than sorry */ + return; + } + ZERO_STRUCT(hints); + /* By default make sure it supports TCP. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; +#if defined(HAVE_IPV6) + hints.ai_family = AF_UNSPEC; +#else + hints.ai_family = AF_INET; +#endif + + /* Linux man page on getaddrinfo() says port will be + uninitialized when service string is NULL */ + + ret = getaddrinfo(state->hostname, NULL, &hints, &res); + + if (ret) { + /* + * It's not a good idea to do DEBUG in the thread + * so we store in the state so that we can debug it in the main thread + */ + state->ret = ret; + state->res = NULL; + return; + } + state->res = res; +} + +static NTSTATUS iterate_one_dcrecord(struct dns_rr_srv *dcs, int *numaddrs, + struct ip_service **return_iplist, + int *return_count, int allocated_addrs, + bool last_entry, struct addrinfo* res) +{ + int j; + int delta = 0; + + if (!dcs->ss_s && !res) { + /* We already have tried to get the IP for this name + * seems to have failed, it's not clear if try one + * or two, three, ... more time(s) will actually help + * So just skip it. + */ + DEBUG(0, ("resolve_ads: sockaddr_storage still NULL even after"\ + " trying to resolve it for the name %s\n", + dcs->hostname)); + return NT_STATUS_OK; + } + /* There is a least one name left so make room for it too*/ + if (!last_entry) { + delta += DEFAULT_IP_PER_DC; + } + /* + * If res is set, use it and so we don't know exactly how many IPs we got + */ + if (res != NULL) { + delta += DEFAULT_IP_PER_DC; + } + /* + * This is a bit of change with how it used to be done before + * We now use all the initial space that has been allocated + * (on the basis that each name will return 1 IP) + * and then only when we extend the array to meet the size requirements + */ + if ((*numaddrs + dcs->num_ips + delta) > allocated_addrs) { + allocated_addrs = *numaddrs + dcs->num_ips + delta; + *return_iplist = SMB_REALLOC_ARRAY(*return_iplist, struct ip_service, + allocated_addrs); + if (*return_iplist == NULL) { + DEBUG(0, ("resolve_ads: Failed to allocate memroy\n")); + return NT_STATUS_NO_MEMORY; + } + } + if (res != NULL) { + struct addrinfo *p; + /* If we need to reallocate, reallocate only with enough + * for the next record + next DC + */ + delta -= DEFAULT_IP_PER_DC; + for (p = res; p; p = p->ai_next) { + if ((*numaddrs + delta) == allocated_addrs) { + allocated_addrs++; + *return_iplist = SMB_REALLOC_ARRAY(*return_iplist, + struct ip_service, + allocated_addrs); + if (NULL == *return_iplist) { + DEBUG(0, ("resolve_ads: Failed to allocate memroy\n")); + return NT_STATUS_NO_MEMORY; + } + } + (*return_iplist)[*return_count].port = dcs->port; + memcpy(&(*return_iplist)[*return_count].ss, p->ai_addr, p->ai_addrlen); + (*numaddrs)++; + (*return_count)++; + } + return NT_STATUS_OK; + } + for (j = 0; j < dcs->num_ips; j++) { + (*return_iplist)[*return_count].port = dcs->port; + (*return_iplist)[*return_count].ss = dcs->ss_s[j]; + if (is_zero_addr(&(*return_iplist)[*return_count].ss)) { + return NT_STATUS_OK; + } + (*return_count)++; + (*numaddrs)++; + if (*return_count >= allocated_addrs) { + /* + * This should never happen but if it's the last + * DC and the last entry actually the condition + * is not really unexpected. + */ + if (!((*return_count == allocated_addrs) && + (j == (dcs->num_ips - 1)) && + last_entry)) { + DEBUG(0,("resolve_ads: unexpected condition met "\ + " return_count > allocated_addrs (%d vs %d)"\ + " while processing %s\n" , + *return_count, + allocated_addrs, + dcs->hostname)); + return NT_STATUS_OK; + } + } + } + return NT_STATUS_OK; +} /******************************************************** Resolve via "ADS" method. *********************************************************/ @@ -2380,6 +2526,13 @@ static NTSTATUS resolve_ads(const char *name, struct dns_rr_srv *dcs = NULL; int numdcs = 0; int numaddrs = 0; + int allocated_addrs = 0; + int ret; + struct pthreadpool_tevent *pth_tevent; + struct tevent_context *ev; + struct tevent_req *req1, **allreqs; + bool exec_continue = true; + struct state_async_resolv *allstates; if ((name_type != 0x1c) && (name_type != KDC_NAME_TYPE) && (name_type != 0x1b)) { @@ -2437,99 +2590,133 @@ static NTSTATUS resolve_ads(const char *name, return NT_STATUS_OK; } - for (i=0;i= allocated_addrs); - for (i = 0; i < numdcs && (*return_countai_next) { - struct sockaddr_storage ss; - memcpy(&ss, p->ai_addr, p->ai_addrlen); - if (is_zero_addr(&ss)) { - continue; - } - extra_addrs++; - } - if (extra_addrs > 1) { - /* We need to expand the return_iplist array - as we only budgeted for one address. */ - numaddrs += (extra_addrs-1); - *return_iplist = SMB_REALLOC_ARRAY(*return_iplist, - struct ip_service, - numaddrs); - if (*return_iplist == NULL) { - if (res) { - freeaddrinfo(res); - } - talloc_destroy(ctx); - return NT_STATUS_NO_MEMORY; - } - } - for (p = res; p; p = p->ai_next) { - (*return_iplist)[*return_count].port = dcs[i].port; - memcpy(&(*return_iplist)[*return_count].ss, - p->ai_addr, - p->ai_addrlen); - if (is_zero_addr(&(*return_iplist)[*return_count].ss)) { - continue; - } - (*return_count)++; - /* Should never happen, but still... */ - if (*return_count>=numaddrs) { - break; - } - } - if (res) { - freeaddrinfo(res); - } - } else { - /* use all the IP addresses from the SRV sresponse */ - int j; - for (j = 0; j < dcs[i].num_ips; j++) { - (*return_iplist)[*return_count].port = dcs[i].port; - (*return_iplist)[*return_count].ss = dcs[i].ss_s[j]; - if (is_zero_addr(&(*return_iplist)[*return_count].ss)) { - continue; - } - (*return_count)++; - /* Should never happen, but still... */ - if (*return_count>=numaddrs) { - break; + status = iterate_one_dcrecord(&dcs[i], &numaddrs, return_iplist, + return_count, allocated_addrs, + (i == (numdcs -1)), NULL); + if (!NT_STATUS_IS_OK(status)) { + exec_continue = false; } + continue; + } + /* else case, that's when we had to do a resolution using getaddrinfo*/ + req1 = allreqs[i]; + ok = tevent_req_poll(req1, ev); + if (!ok) { + DEBUG(0,("resolve_ads: tevent_req_poll failed for index %d: %s\n", + i, strerror(ret))); + exec_continue = false; + TALLOC_FREE(req1); + continue; + } + if (state->ret) { + DEBUG(3, ("resolve_ads: " "getaddrinfo failed for name %s [%s]\n", + state->hostname, gai_strerror(state->ret))); + skip = true; + } + + if (!overcapacity && exec_continue && !skip) { + status = iterate_one_dcrecord(&dcs[i], &numaddrs, return_iplist, + return_count, allocated_addrs, + (i == (numdcs -1)), + state->res); + if (!NT_STATUS_IS_OK(status)) { + exec_continue = false; } + } else if (overcapacity) { + DEBUG(0, ("Reaching capacity limits of allocated "\ + " IPs when dealing with %s\n", + dcs[i].hostname)); } + if (state->res) { + freeaddrinfo(state->res); + } + TALLOC_FREE(req1); + } + if (!exec_continue) { + *return_count = 0; + talloc_destroy(ctx); + return NT_STATUS_UNSUCCESSFUL; } + TALLOC_FREE(allreqs); + + DEBUG(10, ("resolve_ads: returning %d entries\n", *return_count)); + talloc_destroy(ctx); return NT_STATUS_OK; } -- 2.1.4