diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c index a48f3f4ef..8646d72e9 100644 --- a/lib/asyn-thread.c +++ b/lib/asyn-thread.c @@ -54,6 +54,7 @@ # define RESOLVER_ENOMEM ENOMEM #endif +#include "system_win32.h" #include "urldata.h" #include "sendf.h" #include "hostip.h" @@ -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; @@ -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; @@ -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 @@ -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); @@ -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; @@ -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 @@ -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); diff --git a/lib/system_win32.c b/lib/system_win32.c index 9408d026b..dd34140d7 100644 --- a/lib/system_win32.c +++ b/lib/system_win32.c @@ -38,6 +38,7 @@ LARGE_INTEGER Curl_freq; bool Curl_isVistaOrGreater; +bool Curl_isWindows8OrGreater; /* Handle of iphlpapp.dll */ static HMODULE s_hIpHlpApiDll = NULL; @@ -45,9 +46,19 @@ 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. */ @@ -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, @@ -113,6 +136,13 @@ 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; } @@ -120,6 +150,9 @@ CURLcode Curl_win32_init(long flags) /* 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; diff --git a/lib/system_win32.h b/lib/system_win32.h index 256676668..42bbb143f 100644 --- a/lib/system_win32.h +++ b/lib/system_win32.h @@ -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); @@ -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 */