%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ares_event_win32.c |
/* MIT License * * Copyright (c) 2024 Brad House * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX-License-Identifier: MIT */ #include "ares_setup.h" #include "ares.h" #include "ares_private.h" #include "ares_event.h" #include "ares_event_win32.h" #ifdef HAVE_LIMITS_H # include <limits.h> #endif #ifdef _WIN32 /* IMPLEMENTATION NOTES * ==================== * * This implementation uses some undocumented functionality within Windows for * monitoring sockets. The Ancillary Function Driver (AFD) is the low level * implementation that Winsock2 sits on top of. Winsock2 unfortunately does * not expose the equivalent of epoll() or kqueue(), but it is possible to * access AFD directly and use along with IOCP to simulate the functionality. * We want to use IOCP if possible as it gives us the ability to monitor more * than just sockets (WSAPoll is not an option), and perform arbitrary callbacks * which means we can hook in non-socket related events. * * The information for this implementation was gathered from "wepoll" and * "libuv" which both use slight variants on this, but this implementation * doesn't directly follow either methodology. * * Initialization: * 1. Dynamically load the NtDeviceIoControlFile and NtCancelIoFileEx internal * symbols from ntdll.dll. These functions are used to submit the AFD POLL * request and to cancel a prior request, respectively. * 2. Create an IO Completion Port base handle via CreateIoCompletionPort() * that all socket events will be delivered through. * 3. Create a callback to be used to be able to interrupt waiting for IOCP * events, this may be called for allowing enqueuing of additional socket * events or removing socket events. PostQueuedCompletionStatus() is the * obvious choice. Use the same container structure as used with a Socket * but tagged indicating it is not as the CompletionKey (important!). * * Socket Add: * 1. Create/Allocate a container for holding metadata about a socket: * - SOCKET base_socket; * - SOCKET peer_socket; * - OVERLAPPED overlapped; -- Used by AFD POLL * - AFD_POLL_INFO afd_poll_info; -- Used by AFD POLL * 2. Call WSAIoctl(..., SIO_BASE_HANDLE, ...) to unwrap the SOCKET and get * the "base socket" we can use for polling. It appears this may fail so * we should call WSAIoctl(..., SIO_BSP_HANDLE_POLL, ...) as a fallback. * 3. The SOCKET handle we have is most likely not capable of supporting * OVERLAPPED, and we need to have a way to unbind a socket from IOCP * (which is done via a simple closesocket()) so we need to duplicate the * "base socket" using WSADuplicateSocketW() followed by * WSASocketW(..., WSA_FLAG_OVERLAPPED) to create this "peer socket" for * submitting AFD POLL requests. * 4. Bind to IOCP using CreateIoCompletionPort() referencing the "peer * socket" and the base IOCP handle from "Initialization". Use the * pointer to the socket container as the "CompletionKey" which will be * returned when an event occurs. * 5. Submit AFD POLL request (see "AFD POLL Request" section) * * Socket Delete: * 1. Call "AFD Poll Cancel" (see Section of same name) * 2. If a cancel was requested (not bypassed due to no events, etc), tag the * "container" for the socket as pending delete, and when the next IOCP * event for the socket is dequeued, cleanup. * 3. Otherwise, call closesocket(peer_socket) then free() the container * which will officially delete it. * NOTE: Deferring delete may be completely unnecessary. In theory closing * the peer_socket() should guarantee no additional events will be * delivered. But maybe if there's a pending event that hasn't been * read yet but already trigggered it would be an issue, so this is * "safer" unless we can prove its not necessary. * * Socket Modify: * 1. Call "AFD Poll Cancel" (see Section of same name) * 2. If a cancel was not enqueued because there is no pending request, * submit AFD POLL request (see "AFD POLL Request" section), otherwise * defer until next socket event. * * Event Wait: * 1. Call GetQueuedCompletionStatusEx() with the base IOCP handle, a * stack allocated array of OVERLAPPED_ENTRY's, and an appropriate * timeout. * 2. Iterate across returned events, the CompletionKey is a pointer to the * container registered with CreateIoCompletionPort() or * PostQueuedCompletionStatus() * 3. If object indicates it is pending delete, go ahead and * closesocket(peer_socket) and free() the container. Go to the next event. * 4. Submit AFD POLL Request (see "AFD POLL Request"). We must re-enable * the request each time we receive a response, it is not persistent. * 5. Notify of any events received as indicated in the AFD_POLL_INFO * Handles[0].Events (NOTE: check NumberOfHandles first, make sure it is * > 0, otherwise we might not have events such as if our last request * was cancelled). * * AFD Poll Request: * 1. Initialize the AFD_POLL_INFO structure: * Exclusive = TRUE; // Auto cancel duplicates for same socket * NumberOfHandles = 1; * Timeout.QuadPart = LLONG_MAX; * Handles[0].Handle = (HANDLE)base_socket; * Handles[0].Status = 0; * Handles[0].Events = ... set as appropriate AFD_POLL_RECEIVE, etc; * 2. Zero out the OVERLAPPED structure * 3. Create an IO_STATUS_BLOCK pointer (iosb) and set it to the address of * the OVERLAPPED "Internal" member. * 4. Set the "Status" member of IO_STATUS_BLOCK to STATUS_PENDING * 5. Call * NtDeviceIoControlFile((HANDLE)peer_socket, NULL, NULL, &overlapped, * iosb, IOCTL_AFD_POLL * &afd_poll_info, sizeof(afd_poll_info), * &afd_poll_info, sizeof(afd_poll_info)); * NOTE: Its not clear to me if the IO_STATUS_BLOCK pointing to OVERLAPPED * is for efficiency or if its a requirement for AFD. This is what * libuv does, so I'm doing it here too. * * AFD Poll Cancel: * 1. Check to see if the IO_STATUS_BLOCK "Status" member for the socket * is still STATUS_PENDING, if not, no cancel request is necessary. * 2. Call * NtCancelIoFileEx((HANDLE)peer_socket, iosb, &temp_iosb); * * * References: * - https://github.com/piscisaureus/wepoll/ * - https://github.com/libuv/libuv/ */ typedef struct { /* Dynamically loaded symbols */ NtDeviceIoControlFile_t NtDeviceIoControlFile; NtCancelIoFileEx_t NtCancelIoFileEx; /* Implementation details */ HANDLE iocp_handle; } ares_evsys_win32_t; typedef struct { /*! Pointer to parent event container */ ares_event_t *event; /*! Socket passed in to monitor */ SOCKET socket; /*! Base socket derived from provided socket */ SOCKET base_socket; /*! New socket (duplicate base_socket handle) supporting OVERLAPPED operation */ SOCKET peer_socket; /*! Structure for submitting AFD POLL requests (Internals!) */ AFD_POLL_INFO afd_poll_info; /*! Overlapped structure submitted with AFD POLL requests and returned with * IOCP results */ OVERLAPPED overlapped; } ares_evsys_win32_eventdata_t; static void ares_iocpevent_signal(const ares_event_t *event) { ares_event_thread_t *e = event->e; ares_evsys_win32_t *ew = e->ev_sys_data; if (e == NULL) { return; } PostQueuedCompletionStatus(ew->iocp_handle, 0, (ULONG_PTR)event->data, NULL); } static void ares_iocpevent_cb(ares_event_thread_t *e, ares_socket_t fd, void *data, ares_event_flags_t flags) { (void)e; (void)data; (void)fd; (void)flags; } static ares_event_t *ares_iocpevent_create(ares_event_thread_t *e) { ares_event_t *event = NULL; ares_status_t status; status = ares_event_update(&event, e, ARES_EVENT_FLAG_OTHER, ares_iocpevent_cb, ARES_SOCKET_BAD, NULL, NULL, ares_iocpevent_signal); if (status != ARES_SUCCESS) { return NULL; } return event; } static void ares_evsys_win32_destroy(ares_event_thread_t *e) { ares_evsys_win32_t *ew = NULL; if (e == NULL) { return; } ew = e->ev_sys_data; if (ew == NULL) { return; } if (ew->iocp_handle != NULL) { CloseHandle(ew->iocp_handle); } ares_free(ew); e->ev_sys_data = NULL; } static ares_bool_t ares_evsys_win32_init(ares_event_thread_t *e) { ares_evsys_win32_t *ew = NULL; HMODULE ntdll; ew = ares_malloc_zero(sizeof(*ew)); if (ew == NULL) { return ARES_FALSE; } e->ev_sys_data = ew; /* All apps should have ntdll.dll already loaded, so just get a handle to * this */ ntdll = GetModuleHandleA("ntdll.dll"); if (ntdll == NULL) { goto fail; } /* Load Internal symbols not typically accessible */ ew->NtDeviceIoControlFile = (NtDeviceIoControlFile_t)(void *)GetProcAddress( ntdll, "NtDeviceIoControlFile"); ew->NtCancelIoFileEx = (NtCancelIoFileEx_t)(void *)GetProcAddress(ntdll, "NtCancelIoFileEx"); if (ew->NtCancelIoFileEx == NULL || ew->NtDeviceIoControlFile == NULL) { goto fail; } ew->iocp_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (ew->iocp_handle == NULL) { goto fail; } e->ev_signal = ares_iocpevent_create(e); if (e->ev_signal == NULL) { goto fail; } return ARES_TRUE; fail: ares_evsys_win32_destroy(e); return ARES_FALSE; } static ares_socket_t ares_evsys_win32_basesocket(ares_socket_t socket) { while (1) { DWORD bytes; /* Not used */ ares_socket_t base_socket = ARES_SOCKET_BAD; int rv; rv = WSAIoctl(socket, SIO_BASE_HANDLE, NULL, 0, &base_socket, sizeof(base_socket), &bytes, NULL, NULL); if (rv != SOCKET_ERROR && base_socket != ARES_SOCKET_BAD) { socket = base_socket; break; } /* If we're here, an error occurred */ if (GetLastError() == WSAENOTSOCK) { /* This is critical, exit */ return ARES_SOCKET_BAD; } /* Work around known bug in Komodia based LSPs, use ARES_BSP_HANDLE_POLL * to retrieve the underlying socket to then loop and get the base socket: * https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls * https://www.komodia.com/newwiki/index.php?title=Komodia%27s_Redirector_bug_fixes#Version_2.2.2.6 */ base_socket = ARES_SOCKET_BAD; rv = WSAIoctl(socket, SIO_BSP_HANDLE_POLL, NULL, 0, &base_socket, sizeof(base_socket), &bytes, NULL, NULL); if (rv != SOCKET_ERROR && base_socket != ARES_SOCKET_BAD && base_socket != socket) { socket = base_socket; continue; /* loop! */ } return ARES_SOCKET_BAD; } return socket; } static ares_bool_t ares_evsys_win32_afd_enqueue(ares_event_t *event, ares_event_flags_t flags) { ares_event_thread_t *e = event->e; ares_evsys_win32_t *ew = e->ev_sys_data; ares_evsys_win32_eventdata_t *ed = event->data; NTSTATUS status; IO_STATUS_BLOCK *iosb_ptr; if (e == NULL || ed == NULL || ew == NULL) { return ARES_FALSE; } /* Enqueue AFD Poll */ ed->afd_poll_info.Exclusive = TRUE; ed->afd_poll_info.NumberOfHandles = 1; ed->afd_poll_info.Timeout.QuadPart = LLONG_MAX; ed->afd_poll_info.Handles[0].Handle = (HANDLE)ed->base_socket; ed->afd_poll_info.Handles[0].Status = 0; ed->afd_poll_info.Handles[0].Events = 0; if (flags & ARES_EVENT_FLAG_READ) { ed->afd_poll_info.Handles[0].Events |= (AFD_POLL_RECEIVE | AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT | AFD_POLL_ABORT); } if (flags & ARES_EVENT_FLAG_WRITE) { ed->afd_poll_info.Handles[0].Events |= (AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL); } if (flags == 0) { ed->afd_poll_info.Handles[0].Events |= AFD_POLL_DISCONNECT; } memset(&ed->overlapped, 0, sizeof(ed->overlapped)); iosb_ptr = (IO_STATUS_BLOCK *)&ed->overlapped.Internal; iosb_ptr->Status = STATUS_PENDING; status = ew->NtDeviceIoControlFile( (HANDLE)ed->peer_socket, NULL, NULL, &ed->overlapped, iosb_ptr, IOCTL_AFD_POLL, &ed->afd_poll_info, sizeof(ed->afd_poll_info), &ed->afd_poll_info, sizeof(ed->afd_poll_info)); if (status != STATUS_SUCCESS && status != STATUS_PENDING) { printf("%s(): failed to perform IOCTL_AFD_POLL operation\n", __FUNCTION__); fflush(stdout); return ARES_FALSE; } return ARES_TRUE; } static ares_bool_t ares_evsys_win32_afd_cancel(ares_evsys_win32_eventdata_t *ed) { IO_STATUS_BLOCK *iosb_ptr; IO_STATUS_BLOCK cancel_iosb; ares_evsys_win32_t *ew; NTSTATUS status; /* Detached due to destroy */ if (ed->event == NULL) { return ARES_FALSE; } iosb_ptr = (IO_STATUS_BLOCK *)&ed->overlapped.Internal; /* Not pending, nothing to do */ if (iosb_ptr->Status != STATUS_PENDING) { return ARES_FALSE; } ew = ed->event->e->ev_sys_data; status = ew->NtCancelIoFileEx((HANDLE)ed->peer_socket, iosb_ptr, &cancel_iosb); /* NtCancelIoFileEx() may return STATUS_NOT_FOUND if the operation completed * just before calling NtCancelIoFileEx(), but we have not yet received the * notifiction (but it should be queued for the next IOCP event). */ if (status == STATUS_SUCCESS || status == STATUS_NOT_FOUND) { return ARES_TRUE; } return ARES_FALSE; } static void ares_evsys_win32_eventdata_destroy(ares_evsys_win32_eventdata_t *ed) { if (ed == NULL) { return; } if (ed->peer_socket != ARES_SOCKET_BAD) { closesocket(ed->peer_socket); } ares_free(ed); } static ares_bool_t ares_evsys_win32_event_add(ares_event_t *event) { ares_event_thread_t *e = event->e; ares_evsys_win32_t *ew = e->ev_sys_data; ares_evsys_win32_eventdata_t *ed; WSAPROTOCOL_INFOW protocol_info; ed = ares_malloc_zero(sizeof(*ed)); ed->event = event; ed->socket = event->fd; ed->base_socket = ARES_SOCKET_BAD; ed->peer_socket = ARES_SOCKET_BAD; /* Likely a signal event, not something we will directly handle. We create * the ares_evsys_win32_eventdata_t as the placeholder to use as the * IOCP Completion Key */ if (ed->socket == ARES_SOCKET_BAD) { event->data = ed; return ARES_TRUE; } ed->base_socket = ares_evsys_win32_basesocket(ed->socket); if (ed->base_socket == ARES_SOCKET_BAD) { fprintf(stderr, "%s(): could not determine base socket for fd %d\n", __FUNCTION__, (int)event->fd); ares_evsys_win32_eventdata_destroy(ed); return ARES_FALSE; } /* Create a peer socket that supports OVERLAPPED so we can use IOCP on the * socket handle */ if (WSADuplicateSocketW(ed->base_socket, GetCurrentProcessId(), &protocol_info) != 0) { fprintf(stderr, "%s(): could not retrieve protocol info for creating peer socket\n", __FUNCTION__); ares_evsys_win32_eventdata_destroy(ed); return ARES_FALSE; } ed->peer_socket = WSASocketW(protocol_info.iAddressFamily, protocol_info.iSocketType, protocol_info.iProtocol, &protocol_info, 0, WSA_FLAG_OVERLAPPED); if (ed->peer_socket == ARES_SOCKET_BAD) { fprintf(stderr, "%s(): could not create peer socket\n", __FUNCTION__); ares_evsys_win32_eventdata_destroy(ed); return ARES_FALSE; } SetHandleInformation((HANDLE)ed->peer_socket, HANDLE_FLAG_INHERIT, 0); if (CreateIoCompletionPort((HANDLE)ed->peer_socket, ew->iocp_handle, (ULONG_PTR)ed, 0) == NULL) { fprintf(stderr, "%s(): failed to bind peer socket to IOCP\n", __FUNCTION__); ares_evsys_win32_eventdata_destroy(ed); return ARES_FALSE; } event->data = ed; if (!ares_evsys_win32_afd_enqueue(event, event->flags)) { event->data = NULL; ares_evsys_win32_eventdata_destroy(ed); return ARES_FALSE; } return ARES_TRUE; } static void ares_evsys_win32_event_del(ares_event_t *event) { ares_evsys_win32_eventdata_t *ed = event->data; ares_event_thread_t *e = event->e; if (event->fd == ARES_SOCKET_BAD || !e->isup || ed == NULL || !ares_evsys_win32_afd_cancel(ed)) { /* Didn't need to enqueue a cancellation, for one of these reasons: * - Not an IOCP socket * - This is during shutdown of the event thread, no more signals can be * delivered. * - It has been determined there is no AFD POLL queued currently for the * socket. */ ares_evsys_win32_eventdata_destroy(ed); event->data = NULL; } else { /* Detach from event, so when the cancel event comes through, * it will clean up */ ed->event = NULL; event->data = NULL; } } static void ares_evsys_win32_event_mod(ares_event_t *event, ares_event_flags_t new_flags) { ares_evsys_win32_eventdata_t *ed = event->data; /* Not for us */ if (event->fd == ARES_SOCKET_BAD || ed == NULL) { return; } /* Try to cancel any current outstanding poll, if one is not running, * go ahead and queue it up */ if (!ares_evsys_win32_afd_cancel(ed)) { ares_evsys_win32_afd_enqueue(event, new_flags); } } static size_t ares_evsys_win32_wait(ares_event_thread_t *e, unsigned long timeout_ms) { ares_evsys_win32_t *ew = e->ev_sys_data; OVERLAPPED_ENTRY entries[16]; ULONG nentries = sizeof(entries) / sizeof(*entries); BOOL status; size_t i; size_t cnt = 0; status = GetQueuedCompletionStatusEx( ew->iocp_handle, entries, nentries, &nentries, (timeout_ms == 0) ? INFINITE : (DWORD)timeout_ms, FALSE); if (!status) { return 0; } for (i = 0; i < (size_t)nentries; i++) { ares_event_flags_t flags = 0; ares_evsys_win32_eventdata_t *ed = (ares_evsys_win32_eventdata_t *)entries[i].lpCompletionKey; ares_event_t *event = ed->event; if (ed->socket == ARES_SOCKET_BAD) { /* Some sort of signal event */ flags = ARES_EVENT_FLAG_OTHER; } else { /* Process events */ if (ed->afd_poll_info.NumberOfHandles > 0) { if (ed->afd_poll_info.Handles[0].Events & (AFD_POLL_RECEIVE | AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT | AFD_POLL_ABORT)) { flags |= ARES_EVENT_FLAG_READ; } if (ed->afd_poll_info.Handles[0].Events & (AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL)) { flags |= ARES_EVENT_FLAG_WRITE; } /* XXX: Handle ed->afd_poll_info.Handles[0].Events & * AFD_POLL_LOCAL_CLOSE */ } if (event == NULL) { /* This means we need to cleanup the private event data as we've been * detached */ ares_evsys_win32_eventdata_destroy(ed); } else { /* Re-enqueue so we can get more events on the socket */ ares_evsys_win32_afd_enqueue(event, event->flags); } } if (event != NULL && flags != 0) { cnt++; event->cb(e, event->fd, event->data, flags); } } return cnt; } const ares_event_sys_t ares_evsys_win32 = { "win32", ares_evsys_win32_init, ares_evsys_win32_destroy, ares_evsys_win32_event_add, ares_evsys_win32_event_del, ares_evsys_win32_event_mod, ares_evsys_win32_wait }; #endif