asyn-thread: use GetAddrInfoExW on >= Windows 8

For doing async DNS resolution instead of starting a thread for each
request.

Fixes #12481
Closes #12482
This commit is contained in:
Pavel P 2023-11-17 10:56:08 +02:00 коммит произвёл Daniel Stenberg
Родитель a719be81e9
Коммит a6bbc87f9e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 5CC908FDB71E12C2
3 изменённых файлов: 276 добавлений и 1 удалений

Просмотреть файл

@ -54,6 +54,7 @@
# define RESOLVER_ENOMEM ENOMEM # define RESOLVER_ENOMEM ENOMEM
#endif #endif
#include "system_win32.h"
#include "urldata.h" #include "urldata.h"
#include "sendf.h" #include "sendf.h"
#include "hostip.h" #include "hostip.h"
@ -144,9 +145,22 @@ static bool init_resolve_thread(struct Curl_easy *data,
const char *hostname, int port, const char *hostname, int port,
const struct addrinfo *hints); const struct addrinfo *hints);
#ifdef _WIN32
/* Thread sync data used by GetAddrInfoExW for win8+ */
struct thread_sync_data_w8
{
OVERLAPPED overlapped;
ADDRINFOEXW_ *res;
HANDLE cancel_ev;
ADDRINFOEXW_ hints;
};
#endif
/* Data for synchronization between resolver thread and its parent */ /* Data for synchronization between resolver thread and its parent */
struct thread_sync_data { struct thread_sync_data {
#ifdef _WIN32
struct thread_sync_data_w8 w8;
#endif
curl_mutex_t *mtx; curl_mutex_t *mtx;
int done; int done;
int port; int port;
@ -165,6 +179,9 @@ struct thread_sync_data {
}; };
struct thread_data { struct thread_data {
#ifdef _WIN32
HANDLE complete_ev;
#endif
curl_thread_t thread_hnd; curl_thread_t thread_hnd;
unsigned int poll_interval; unsigned int poll_interval;
timediff_t interval_end; timediff_t interval_end;
@ -276,6 +293,144 @@ static CURLcode getaddrinfo_complete(struct Curl_easy *data)
return result; return result;
} }
#ifdef _WIN32
static VOID WINAPI
query_complete(DWORD err, DWORD bytes, LPWSAOVERLAPPED overlapped)
{
size_t ss_size;
const ADDRINFOEXW_ *ai;
struct Curl_addrinfo *ca;
struct Curl_addrinfo *cafirst = NULL;
struct Curl_addrinfo *calast = NULL;
struct thread_sync_data *tsd =
CONTAINING_RECORD(overlapped, struct thread_sync_data, w8.overlapped);
struct thread_data *td = tsd->td;
const ADDRINFOEXW_ *res = tsd->w8.res;
int error = (int)err;
(void)bytes;
if(error == ERROR_SUCCESS) {
/* traverse the addrinfo list */
for(ai = res; ai != NULL; ai = ai->ai_next) {
size_t namelen = ai->ai_canonname ? wcslen(ai->ai_canonname) + 1 : 0;
/* ignore elements with unsupported address family, */
/* settle family-specific sockaddr structure size. */
if(ai->ai_family == AF_INET)
ss_size = sizeof(struct sockaddr_in);
#ifdef ENABLE_IPV6
else if(ai->ai_family == AF_INET6)
ss_size = sizeof(struct sockaddr_in6);
#endif
else
continue;
/* ignore elements without required address info */
if(!ai->ai_addr || !(ai->ai_addrlen > 0))
continue;
/* ignore elements with bogus address size */
if((size_t)ai->ai_addrlen < ss_size)
continue;
ca = malloc(sizeof(struct Curl_addrinfo) + ss_size + namelen);
if(!ca) {
error = EAI_MEMORY;
break;
}
/* copy each structure member individually, member ordering, */
/* size, or padding might be different for each platform. */
ca->ai_flags = ai->ai_flags;
ca->ai_family = ai->ai_family;
ca->ai_socktype = ai->ai_socktype;
ca->ai_protocol = ai->ai_protocol;
ca->ai_addrlen = (curl_socklen_t)ss_size;
ca->ai_addr = NULL;
ca->ai_canonname = NULL;
ca->ai_next = NULL;
ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
memcpy(ca->ai_addr, ai->ai_addr, ss_size);
if(namelen) {
size_t i;
ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size);
for(i = 0; i < namelen; ++i) /* convert wide string to ascii */
ca->ai_canonname[i] = (char)ai->ai_canonname[i];
ca->ai_canonname[namelen] = '\0';
}
/* if the return list is empty, this becomes the first element */
if(!cafirst)
cafirst = ca;
/* add this element last in the return list */
if(calast)
calast->ai_next = ca;
calast = ca;
}
/* if we failed, also destroy the Curl_addrinfo list */
if(error) {
Curl_freeaddrinfo(cafirst);
cafirst = NULL;
}
else if(!cafirst) {
#ifdef EAI_NONAME
/* rfc3493 conformant */
error = EAI_NONAME;
#else
/* rfc3493 obsoleted */
error = EAI_NODATA;
#endif
#ifdef USE_WINSOCK
SET_SOCKERRNO(error);
#endif
}
tsd->res = cafirst;
}
if(tsd->w8.res) {
Curl_FreeAddrInfoExW(tsd->w8.res);
tsd->w8.res = NULL;
}
if(error) {
tsd->sock_error = SOCKERRNO?SOCKERRNO:error;
if(tsd->sock_error == 0)
tsd->sock_error = RESOLVER_ENOMEM;
}
else {
Curl_addrinfo_set_port(tsd->res, tsd->port);
}
Curl_mutex_acquire(tsd->mtx);
if(tsd->done) {
/* too late, gotta clean up the mess */
Curl_mutex_release(tsd->mtx);
destroy_thread_sync_data(tsd);
free(td);
}
else {
#ifndef CURL_DISABLE_SOCKETPAIR
char buf[1];
if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
/* DNS has been resolved, signal client task */
buf[0] = 1;
if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
/* update sock_erro to errno */
tsd->sock_error = SOCKERRNO;
}
}
#endif
tsd->done = 1;
Curl_mutex_release(tsd->mtx);
if(td->complete_ev)
SetEvent(td->complete_ev); /* Notify caller that the query completed */
}
}
#endif
#ifdef HAVE_GETADDRINFO #ifdef HAVE_GETADDRINFO
@ -391,9 +546,21 @@ static void destroy_async_data(struct Curl_async *async)
Curl_mutex_release(td->tsd.mtx); Curl_mutex_release(td->tsd.mtx);
if(!done) { if(!done) {
#ifdef _WIN32
if(td->complete_ev)
CloseHandle(td->complete_ev);
else
#endif
Curl_thread_destroy(td->thread_hnd); Curl_thread_destroy(td->thread_hnd);
} }
else { else {
#ifdef _WIN32
if(td->complete_ev) {
Curl_GetAddrInfoExCancel(&td->tsd.w8.cancel_ev);
WaitForSingleObject(td->complete_ev, INFINITE);
CloseHandle(td->complete_ev);
}
#endif
if(td->thread_hnd != curl_thread_t_null) if(td->thread_hnd != curl_thread_t_null)
Curl_thread_join(&td->thread_hnd); Curl_thread_join(&td->thread_hnd);
@ -439,6 +606,9 @@ static bool init_resolve_thread(struct Curl_easy *data,
asp->status = 0; asp->status = 0;
asp->dns = NULL; asp->dns = NULL;
td->thread_hnd = curl_thread_t_null; td->thread_hnd = curl_thread_t_null;
#ifdef _WIN32
td->complete_ev = NULL;
#endif
if(!init_thread_sync_data(td, hostname, port, hints)) { if(!init_thread_sync_data(td, hostname, port, hints)) {
asp->tdata = NULL; asp->tdata = NULL;
@ -454,6 +624,41 @@ static bool init_resolve_thread(struct Curl_easy *data,
/* The thread will set this to 1 when complete. */ /* The thread will set this to 1 when complete. */
td->tsd.done = 0; td->tsd.done = 0;
#ifdef _WIN32
if(Curl_isWindows8OrGreater && Curl_FreeAddrInfoExW &&
Curl_GetAddrInfoExCancel && Curl_GetAddrInfoExW) {
#define MAX_NAME_LEN 256 /* max domain name is 253 chars */
#define MAX_PORT_LEN 8
WCHAR namebuf[MAX_NAME_LEN];
WCHAR portbuf[MAX_PORT_LEN];
/* calculate required length */
int w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, hostname,
-1, NULL, 0);
if((w_len > 0) && (w_len < MAX_NAME_LEN)) {
/* do utf8 conversion */
w_len = MultiByteToWideChar(CP_UTF8, 0, hostname, -1, namebuf, w_len);
if((w_len > 0) && (w_len < MAX_NAME_LEN)) {
swprintf(portbuf, MAX_PORT_LEN, L"%d", port);
td->tsd.w8.hints.ai_family = hints->ai_family;
td->tsd.w8.hints.ai_socktype = hints->ai_socktype;
td->complete_ev = CreateEvent(NULL, TRUE, FALSE, NULL);
if(!td->complete_ev) {
/* failed to start, mark it as done here for proper cleanup. */
td->tsd.done = 1;
goto err_exit;
}
err = Curl_GetAddrInfoExW(namebuf, portbuf, NS_DNS,
NULL, &td->tsd.w8.hints, &td->tsd.w8.res,
NULL, &td->tsd.w8.overlapped,
&query_complete, &td->tsd.w8.cancel_ev);
if(err != WSA_IO_PENDING)
query_complete(err, 0, &td->tsd.w8.overlapped);
return TRUE;
}
}
}
#endif
#ifdef HAVE_GETADDRINFO #ifdef HAVE_GETADDRINFO
td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
#else #else
@ -490,9 +695,22 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data,
DEBUGASSERT(data); DEBUGASSERT(data);
td = data->state.async.tdata; td = data->state.async.tdata;
DEBUGASSERT(td); DEBUGASSERT(td);
#ifdef _WIN32
DEBUGASSERT(td->complete_ev || td->thread_hnd != curl_thread_t_null);
#else
DEBUGASSERT(td->thread_hnd != curl_thread_t_null); DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
#endif
/* wait for the thread to resolve the name */ /* wait for the thread to resolve the name */
#ifdef _WIN32
if(td->complete_ev) {
WaitForSingleObject(td->complete_ev, INFINITE);
CloseHandle(td->complete_ev);
if(entry)
result = getaddrinfo_complete(data);
}
else
#endif
if(Curl_thread_join(&td->thread_hnd)) { if(Curl_thread_join(&td->thread_hnd)) {
if(entry) if(entry)
result = getaddrinfo_complete(data); result = getaddrinfo_complete(data);

Просмотреть файл

@ -38,6 +38,7 @@
LARGE_INTEGER Curl_freq; LARGE_INTEGER Curl_freq;
bool Curl_isVistaOrGreater; bool Curl_isVistaOrGreater;
bool Curl_isWindows8OrGreater;
/* Handle of iphlpapp.dll */ /* Handle of iphlpapp.dll */
static HMODULE s_hIpHlpApiDll = NULL; static HMODULE s_hIpHlpApiDll = NULL;
@ -45,9 +46,19 @@ static HMODULE s_hIpHlpApiDll = NULL;
/* Pointer to the if_nametoindex function */ /* Pointer to the if_nametoindex function */
IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL; IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL;
void(WSAAPI *Curl_FreeAddrInfoExW)(ADDRINFOEXW_ *pAddrInfoEx) = NULL;
int(WSAAPI *Curl_GetAddrInfoExCancel)(LPHANDLE lpHandle) = NULL;
int(WSAAPI *Curl_GetAddrInfoExW)(PCWSTR pName, PCWSTR pServiceName,
DWORD dwNameSpace, LPGUID lpNspId, const ADDRINFOEXW_ *hints,
ADDRINFOEXW_ **ppResult, struct timeval *timeout, LPOVERLAPPED lpOverlapped,
LOOKUP_COMPLETION lpCompletionRoutine, LPHANDLE lpHandle) = NULL;
/* Curl_win32_init() performs win32 global initialization */ /* Curl_win32_init() performs win32 global initialization */
CURLcode Curl_win32_init(long flags) CURLcode Curl_win32_init(long flags)
{ {
#ifdef USE_WINSOCK
HANDLE ws2_32Dll;
#endif
/* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which
is just for Winsock at the moment. Any required win32 initialization is just for Winsock at the moment. Any required win32 initialization
should take place after this block. */ should take place after this block. */
@ -104,6 +115,18 @@ CURLcode Curl_win32_init(long flags)
Curl_if_nametoindex = pIfNameToIndex; Curl_if_nametoindex = pIfNameToIndex;
} }
#ifdef USE_WINSOCK
ws2_32Dll = GetModuleHandleA("ws2_32");
if(ws2_32Dll) {
*(FARPROC*)&Curl_FreeAddrInfoExW = GetProcAddress(ws2_32Dll,
"FreeAddrInfoExW");
*(FARPROC*)&Curl_GetAddrInfoExCancel = GetProcAddress(ws2_32Dll,
"GetAddrInfoExCancel");
*(FARPROC*)&Curl_GetAddrInfoExW = GetProcAddress(ws2_32Dll,
"GetAddrInfoExW");
}
#endif
/* curlx_verify_windows_version must be called during init at least once /* curlx_verify_windows_version must be called during init at least once
because it has its own initialization routine. */ because it has its own initialization routine. */
if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
@ -113,6 +136,13 @@ CURLcode Curl_win32_init(long flags)
else else
Curl_isVistaOrGreater = FALSE; Curl_isVistaOrGreater = FALSE;
if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
VERSION_GREATER_THAN_EQUAL)) {
Curl_isWindows8OrGreater = TRUE;
}
else
Curl_isWindows8OrGreater = FALSE;
QueryPerformanceFrequency(&Curl_freq); QueryPerformanceFrequency(&Curl_freq);
return CURLE_OK; return CURLE_OK;
} }
@ -120,6 +150,9 @@ CURLcode Curl_win32_init(long flags)
/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */ /* Curl_win32_cleanup() is the opposite of Curl_win32_init() */
void Curl_win32_cleanup(long init_flags) void Curl_win32_cleanup(long init_flags)
{ {
Curl_FreeAddrInfoExW = NULL;
Curl_GetAddrInfoExCancel = NULL;
Curl_GetAddrInfoExW = NULL;
if(s_hIpHlpApiDll) { if(s_hIpHlpApiDll) {
FreeLibrary(s_hIpHlpApiDll); FreeLibrary(s_hIpHlpApiDll);
s_hIpHlpApiDll = NULL; s_hIpHlpApiDll = NULL;

Просмотреть файл

@ -26,10 +26,11 @@
#include "curl_setup.h" #include "curl_setup.h"
#if defined(_WIN32) #ifdef _WIN32
extern LARGE_INTEGER Curl_freq; extern LARGE_INTEGER Curl_freq;
extern bool Curl_isVistaOrGreater; extern bool Curl_isVistaOrGreater;
extern bool Curl_isWindows8OrGreater;
CURLcode Curl_win32_init(long flags); CURLcode Curl_win32_init(long flags);
void Curl_win32_cleanup(long init_flags); void Curl_win32_cleanup(long init_flags);
@ -40,6 +41,29 @@ typedef unsigned int(WINAPI *IF_NAMETOINDEX_FN)(const char *);
/* This is used instead of if_nametoindex if available on Windows */ /* This is used instead of if_nametoindex if available on Windows */
extern IF_NAMETOINDEX_FN Curl_if_nametoindex; extern IF_NAMETOINDEX_FN Curl_if_nametoindex;
/* Identical copy of addrinfoexW/ADDRINFOEXW */
typedef struct addrinfoexW_
{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
PWSTR ai_canonname;
struct sockaddr *ai_addr;
void *ai_blob;
size_t ai_bloblen;
LPGUID ai_provider;
struct addrinfoexW_ *ai_next;
} ADDRINFOEXW_;
typedef void(CALLBACK *LOOKUP_COMPLETION)(DWORD, DWORD, LPWSAOVERLAPPED);
extern void(WSAAPI *Curl_FreeAddrInfoExW)(ADDRINFOEXW_*);
extern int(WSAAPI *Curl_GetAddrInfoExCancel)(LPHANDLE);
extern int(WSAAPI *Curl_GetAddrInfoExW)(PCWSTR, PCWSTR, DWORD, LPGUID,
const ADDRINFOEXW_*, ADDRINFOEXW_**, struct timeval*, LPOVERLAPPED,
LOOKUP_COMPLETION, LPHANDLE);
/* This is used to dynamically load DLLs */ /* This is used to dynamically load DLLs */
HMODULE Curl_load_library(LPCTSTR filename); HMODULE Curl_load_library(LPCTSTR filename);
#else /* _WIN32 */ #else /* _WIN32 */