Skip to content

Commit

Permalink
asyn-thread: use GetAddrInfoExW on >= Windows 8
Browse files Browse the repository at this point in the history
For doing async DNS resolution instead of starting a thread for each
request.

Fixes curl#12481
Closes curl#12482
  • Loading branch information
pps83 authored and bagder committed Dec 21, 2023
1 parent a719be8 commit a6bbc87
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 1 deletion.
218 changes: 218 additions & 0 deletions lib/asyn-thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
# define RESOLVER_ENOMEM ENOMEM
#endif

#include "system_win32.h"
#include "urldata.h"
#include "sendf.h"
#include "hostip.h"
Expand Down Expand Up @@ -144,9 +145,22 @@ static bool init_resolve_thread(struct Curl_easy *data,
const char *hostname, int port,
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 */
struct thread_sync_data {
#ifdef _WIN32
struct thread_sync_data_w8 w8;
#endif
curl_mutex_t *mtx;
int done;
int port;
Expand All @@ -165,6 +179,9 @@ struct thread_sync_data {
};

struct thread_data {
#ifdef _WIN32
HANDLE complete_ev;
#endif
curl_thread_t thread_hnd;
unsigned int poll_interval;
timediff_t interval_end;
Expand Down Expand Up @@ -276,6 +293,144 @@ static CURLcode getaddrinfo_complete(struct Curl_easy *data)
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

Expand Down Expand Up @@ -391,9 +546,21 @@ static void destroy_async_data(struct Curl_async *async)
Curl_mutex_release(td->tsd.mtx);

if(!done) {
#ifdef _WIN32
if(td->complete_ev)
CloseHandle(td->complete_ev);
else
#endif
Curl_thread_destroy(td->thread_hnd);
}
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)
Curl_thread_join(&td->thread_hnd);

Expand Down Expand Up @@ -439,6 +606,9 @@ static bool init_resolve_thread(struct Curl_easy *data,
asp->status = 0;
asp->dns = NULL;
td->thread_hnd = curl_thread_t_null;
#ifdef _WIN32
td->complete_ev = NULL;
#endif

if(!init_thread_sync_data(td, hostname, port, hints)) {
asp->tdata = NULL;
Expand All @@ -454,6 +624,41 @@ static bool init_resolve_thread(struct Curl_easy *data,
/* The thread will set this to 1 when complete. */
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
td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
#else
Expand Down Expand Up @@ -490,9 +695,22 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data,
DEBUGASSERT(data);
td = data->state.async.tdata;
DEBUGASSERT(td);
#ifdef _WIN32
DEBUGASSERT(td->complete_ev || td->thread_hnd != curl_thread_t_null);
#else
DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
#endif

/* 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(entry)
result = getaddrinfo_complete(data);
Expand Down
33 changes: 33 additions & 0 deletions lib/system_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,27 @@

LARGE_INTEGER Curl_freq;
bool Curl_isVistaOrGreater;
bool Curl_isWindows8OrGreater;

/* Handle of iphlpapp.dll */
static HMODULE s_hIpHlpApiDll = NULL;

/* Pointer to the if_nametoindex function */
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 */
CURLcode Curl_win32_init(long flags)
{
#ifdef USE_WINSOCK
HANDLE ws2_32Dll;
#endif
/* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which
is just for Winsock at the moment. Any required win32 initialization
should take place after this block. */
Expand Down Expand Up @@ -104,6 +115,18 @@ CURLcode Curl_win32_init(long flags)
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
because it has its own initialization routine. */
if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
Expand All @@ -113,13 +136,23 @@ CURLcode Curl_win32_init(long flags)
else
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);
return CURLE_OK;
}

/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */
void Curl_win32_cleanup(long init_flags)
{
Curl_FreeAddrInfoExW = NULL;
Curl_GetAddrInfoExCancel = NULL;
Curl_GetAddrInfoExW = NULL;
if(s_hIpHlpApiDll) {
FreeLibrary(s_hIpHlpApiDll);
s_hIpHlpApiDll = NULL;
Expand Down
26 changes: 25 additions & 1 deletion lib/system_win32.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@

#include "curl_setup.h"

#if defined(_WIN32)
#ifdef _WIN32

extern LARGE_INTEGER Curl_freq;
extern bool Curl_isVistaOrGreater;
extern bool Curl_isWindows8OrGreater;

CURLcode Curl_win32_init(long flags);
void Curl_win32_cleanup(long init_flags);
Expand All @@ -40,6 +41,29 @@ typedef unsigned int(WINAPI *IF_NAMETOINDEX_FN)(const char *);
/* This is used instead of if_nametoindex if available on Windows */
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 */
HMODULE Curl_load_library(LPCTSTR filename);
#else /* _WIN32 */
Expand Down

0 comments on commit a6bbc87

Please sign in to comment.