• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* MIT License
2  *
3  * Copyright (c) 2024 Brad House
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to deal
7  * in the Software without restriction, including without limitation the rights
8  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9  * copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the next
13  * paragraph) shall be included in all copies or substantial portions of the
14  * Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  *
24  * SPDX-License-Identifier: MIT
25  */
26 
27 #include "ares_setup.h"
28 #include "ares.h"
29 #include "ares_private.h"
30 #include "ares_event.h"
31 #include "ares_event_win32.h"
32 #ifdef HAVE_LIMITS_H
33 #  include <limits.h>
34 #endif
35 
36 #ifdef _WIN32
37 
38 /* IMPLEMENTATION NOTES
39  * ====================
40  *
41  * This implementation uses some undocumented functionality within Windows for
42  * monitoring sockets. The Ancillary Function Driver (AFD) is the low level
43  * implementation that Winsock2 sits on top of.  Winsock2 unfortunately does
44  * not expose the equivalent of epoll() or kqueue(), but it is possible to
45  * access AFD directly and use along with IOCP to simulate the functionality.
46  * We want to use IOCP if possible as it gives us the ability to monitor more
47  * than just sockets (WSAPoll is not an option), and perform arbitrary callbacks
48  * which means we can hook in non-socket related events.
49  *
50  * The information for this implementation was gathered from "wepoll" and
51  * "libuv" which both use slight variants on this, but this implementation
52  * doesn't directly follow either methodology.
53  *
54  * Initialization:
55  *   1. Dynamically load the NtDeviceIoControlFile and NtCancelIoFileEx internal
56  *      symbols from ntdll.dll.  These functions are used to submit the AFD POLL
57  *      request and to cancel a prior request, respectively.
58  *   2. Create an IO Completion Port base handle via CreateIoCompletionPort()
59  *      that all socket events will be delivered through.
60  *   3. Create a callback to be used to be able to interrupt waiting for IOCP
61  *      events, this may be called for allowing enqueuing of additional socket
62  *      events or removing socket events. PostQueuedCompletionStatus() is the
63  *      obvious choice.  Use the same container structure as used with a Socket
64  *      but tagged indicating it is not as the CompletionKey (important!).
65  *
66  * Socket Add:
67  *   1. Create/Allocate a container for holding metadata about a socket:
68  *      - SOCKET base_socket;
69  *      - SOCKET peer_socket;
70  *      - OVERLAPPED overlapped; -- Used by AFD POLL
71  *      - AFD_POLL_INFO afd_poll_info; -- Used by AFD POLL
72  *   2. Call WSAIoctl(..., SIO_BASE_HANDLE, ...) to unwrap the SOCKET and get
73  *      the "base socket" we can use for polling.  It appears this may fail so
74  *      we should call WSAIoctl(..., SIO_BSP_HANDLE_POLL, ...) as a fallback.
75  *   3. The SOCKET handle we have is most likely not capable of supporting
76  *      OVERLAPPED, and we need to have a way to unbind a socket from IOCP
77  *      (which is done via a simple closesocket()) so we need to duplicate the
78  *      "base socket" using WSADuplicateSocketW() followed by
79  *      WSASocketW(..., WSA_FLAG_OVERLAPPED) to create this "peer socket" for
80  *      submitting AFD POLL requests.
81  *   4. Bind to IOCP using CreateIoCompletionPort() referencing the "peer
82  *      socket" and the base IOCP handle from "Initialization".  Use the
83  *      pointer to the socket container as the "CompletionKey" which will be
84  *      returned when an event occurs.
85  *   5. Submit AFD POLL request (see "AFD POLL Request" section)
86  *
87  * Socket Delete:
88  *   1. Call "AFD Poll Cancel" (see Section of same name)
89  *   2. If a cancel was requested (not bypassed due to no events, etc), tag the
90  *      "container" for the socket as pending delete, and when the next IOCP
91  *      event for the socket is dequeued, cleanup.
92  *   3. Otherwise, call closesocket(peer_socket) then free() the container
93  *      which will officially delete it.
94  *   NOTE: Deferring delete may be completely unnecessary.  In theory closing
95  *         the peer_socket() should guarantee no additional events will be
96  *         delivered.  But maybe if there's a pending event that hasn't been
97  *         read yet but already trigggered it would be an issue, so this is
98  *         "safer" unless we can prove its not necessary.
99  *
100  * Socket Modify:
101  *   1. Call "AFD Poll Cancel" (see Section of same name)
102  *   2. If a cancel was not enqueued because there is no pending request,
103  *      submit AFD POLL request (see "AFD POLL Request" section), otherwise
104  *      defer until next socket event.
105  *
106  * Event Wait:
107  *   1. Call GetQueuedCompletionStatusEx() with the base IOCP handle, a
108  *      stack allocated array of OVERLAPPED_ENTRY's, and an appropriate
109  *      timeout.
110  *   2. Iterate across returned events, the CompletionKey is a pointer to the
111  *      container registered with CreateIoCompletionPort() or
112  *      PostQueuedCompletionStatus()
113  *   3. If object indicates it is pending delete, go ahead and
114  *      closesocket(peer_socket) and free() the container. Go to the next event.
115  *   4. Submit AFD POLL Request (see "AFD POLL Request"). We must re-enable
116  *      the request each time we receive a response, it is not persistent.
117  *   5. Notify of any events received as indicated in the AFD_POLL_INFO
118  *      Handles[0].Events (NOTE: check NumberOfHandles first, make sure it is
119  *      > 0, otherwise we might not have events such as if our last request
120  *      was cancelled).
121  *
122  * AFD Poll Request:
123  *   1. Initialize the AFD_POLL_INFO structure:
124  *      Exclusive         = TRUE; // Auto cancel duplicates for same socket
125  *      NumberOfHandles   = 1;
126  *      Timeout.QuadPart  = LLONG_MAX;
127  *      Handles[0].Handle = (HANDLE)base_socket;
128  *      Handles[0].Status = 0;
129  *      Handles[0].Events = ... set as appropriate AFD_POLL_RECEIVE, etc;
130  *   2. Zero out the OVERLAPPED structure
131  *   3. Create an IO_STATUS_BLOCK pointer (iosb) and set it to the address of
132  *      the OVERLAPPED "Internal" member.
133  *   4. Set the "Status" member of IO_STATUS_BLOCK to STATUS_PENDING
134  *   5. Call
135  *      NtDeviceIoControlFile((HANDLE)peer_socket, NULL, NULL, &overlapped,
136  *                            iosb, IOCTL_AFD_POLL
137  *                            &afd_poll_info, sizeof(afd_poll_info),
138  *                            &afd_poll_info, sizeof(afd_poll_info));
139  *   NOTE: Its not clear to me if the IO_STATUS_BLOCK pointing to OVERLAPPED
140  *         is for efficiency or if its a requirement for AFD.  This is what
141  *         libuv does, so I'm doing it here too.
142  *
143  * AFD Poll Cancel:
144  *   1. Check to see if the IO_STATUS_BLOCK "Status" member for the socket
145  *      is still STATUS_PENDING, if not, no cancel request is necessary.
146  *   2. Call
147  *      NtCancelIoFileEx((HANDLE)peer_socket, iosb, &temp_iosb);
148  *
149  *
150  * References:
151  *   - https://github.com/piscisaureus/wepoll/
152  *   - https://github.com/libuv/libuv/
153  */
154 
155 typedef struct {
156   /* Dynamically loaded symbols */
157   NtDeviceIoControlFile_t NtDeviceIoControlFile;
158   NtCancelIoFileEx_t      NtCancelIoFileEx;
159 
160   /* Implementation details */
161   HANDLE                  iocp_handle;
162 } ares_evsys_win32_t;
163 
164 typedef struct {
165   /*! Pointer to parent event container */
166   ares_event_t *event;
167   /*! Socket passed in to monitor */
168   SOCKET        socket;
169   /*! Base socket derived from provided socket */
170   SOCKET        base_socket;
171   /*! New socket (duplicate base_socket handle) supporting OVERLAPPED operation
172    */
173   SOCKET        peer_socket;
174   /*! Structure for submitting AFD POLL requests (Internals!) */
175   AFD_POLL_INFO afd_poll_info;
176   /*! Overlapped structure submitted with AFD POLL requests and returned with
177    * IOCP results */
178   OVERLAPPED    overlapped;
179 } ares_evsys_win32_eventdata_t;
180 
ares_iocpevent_signal(const ares_event_t * event)181 static void ares_iocpevent_signal(const ares_event_t *event)
182 {
183   ares_event_thread_t *e  = event->e;
184   ares_evsys_win32_t  *ew = e->ev_sys_data;
185 
186   if (e == NULL) {
187     return;
188   }
189 
190   PostQueuedCompletionStatus(ew->iocp_handle, 0, (ULONG_PTR)event->data, NULL);
191 }
192 
ares_iocpevent_cb(ares_event_thread_t * e,ares_socket_t fd,void * data,ares_event_flags_t flags)193 static void ares_iocpevent_cb(ares_event_thread_t *e, ares_socket_t fd,
194                               void *data, ares_event_flags_t flags)
195 {
196   (void)e;
197   (void)data;
198   (void)fd;
199   (void)flags;
200 }
201 
ares_iocpevent_create(ares_event_thread_t * e)202 static ares_event_t *ares_iocpevent_create(ares_event_thread_t *e)
203 {
204   ares_event_t *event = NULL;
205   ares_status_t status;
206 
207   status =
208     ares_event_update(&event, e, ARES_EVENT_FLAG_OTHER, ares_iocpevent_cb,
209                       ARES_SOCKET_BAD, NULL, NULL, ares_iocpevent_signal);
210   if (status != ARES_SUCCESS) {
211     return NULL;
212   }
213 
214   return event;
215 }
216 
ares_evsys_win32_destroy(ares_event_thread_t * e)217 static void ares_evsys_win32_destroy(ares_event_thread_t *e)
218 {
219   ares_evsys_win32_t *ew = NULL;
220 
221   if (e == NULL) {
222     return;
223   }
224 
225   ew = e->ev_sys_data;
226   if (ew == NULL) {
227     return;
228   }
229 
230   if (ew->iocp_handle != NULL) {
231     CloseHandle(ew->iocp_handle);
232   }
233 
234   ares_free(ew);
235   e->ev_sys_data = NULL;
236 }
237 
ares_evsys_win32_init(ares_event_thread_t * e)238 static ares_bool_t ares_evsys_win32_init(ares_event_thread_t *e)
239 {
240   ares_evsys_win32_t *ew = NULL;
241   HMODULE             ntdll;
242 
243   ew = ares_malloc_zero(sizeof(*ew));
244   if (ew == NULL) {
245     return ARES_FALSE;
246   }
247 
248   e->ev_sys_data = ew;
249 
250   /* All apps should have ntdll.dll already loaded, so just get a handle to
251    * this */
252   ntdll = GetModuleHandleA("ntdll.dll");
253   if (ntdll == NULL) {
254     goto fail;
255   }
256 
257   /* Load Internal symbols not typically accessible */
258   ew->NtDeviceIoControlFile = (NtDeviceIoControlFile_t)(void *)GetProcAddress(
259     ntdll, "NtDeviceIoControlFile");
260   ew->NtCancelIoFileEx =
261     (NtCancelIoFileEx_t)(void *)GetProcAddress(ntdll, "NtCancelIoFileEx");
262 
263   if (ew->NtCancelIoFileEx == NULL || ew->NtDeviceIoControlFile == NULL) {
264     goto fail;
265   }
266 
267   ew->iocp_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
268   if (ew->iocp_handle == NULL) {
269     goto fail;
270   }
271 
272   e->ev_signal = ares_iocpevent_create(e);
273   if (e->ev_signal == NULL) {
274     goto fail;
275   }
276 
277   return ARES_TRUE;
278 
279 fail:
280   ares_evsys_win32_destroy(e);
281   return ARES_FALSE;
282 }
283 
ares_evsys_win32_basesocket(ares_socket_t socket)284 static ares_socket_t ares_evsys_win32_basesocket(ares_socket_t socket)
285 {
286   while (1) {
287     DWORD         bytes; /* Not used */
288     ares_socket_t base_socket = ARES_SOCKET_BAD;
289     int           rv;
290 
291     rv = WSAIoctl(socket, SIO_BASE_HANDLE, NULL, 0, &base_socket,
292                   sizeof(base_socket), &bytes, NULL, NULL);
293     if (rv != SOCKET_ERROR && base_socket != ARES_SOCKET_BAD) {
294       socket = base_socket;
295       break;
296     }
297 
298     /* If we're here, an error occurred */
299     if (GetLastError() == WSAENOTSOCK) {
300       /* This is critical, exit */
301       return ARES_SOCKET_BAD;
302     }
303 
304     /* Work around known bug in Komodia based LSPs, use ARES_BSP_HANDLE_POLL
305      * to retrieve the underlying socket to then loop and get the base socket:
306      *  https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls
307      *  https://www.komodia.com/newwiki/index.php?title=Komodia%27s_Redirector_bug_fixes#Version_2.2.2.6
308      */
309     base_socket = ARES_SOCKET_BAD;
310     rv          = WSAIoctl(socket, SIO_BSP_HANDLE_POLL, NULL, 0, &base_socket,
311                            sizeof(base_socket), &bytes, NULL, NULL);
312 
313     if (rv != SOCKET_ERROR && base_socket != ARES_SOCKET_BAD &&
314         base_socket != socket) {
315       socket = base_socket;
316       continue; /* loop! */
317     }
318 
319     return ARES_SOCKET_BAD;
320   }
321 
322   return socket;
323 }
324 
ares_evsys_win32_afd_enqueue(ares_event_t * event,ares_event_flags_t flags)325 static ares_bool_t ares_evsys_win32_afd_enqueue(ares_event_t      *event,
326                                                 ares_event_flags_t flags)
327 {
328   ares_event_thread_t          *e  = event->e;
329   ares_evsys_win32_t           *ew = e->ev_sys_data;
330   ares_evsys_win32_eventdata_t *ed = event->data;
331   NTSTATUS                      status;
332   IO_STATUS_BLOCK              *iosb_ptr;
333 
334   if (e == NULL || ed == NULL || ew == NULL) {
335     return ARES_FALSE;
336   }
337 
338   /* Enqueue AFD Poll */
339   ed->afd_poll_info.Exclusive         = TRUE;
340   ed->afd_poll_info.NumberOfHandles   = 1;
341   ed->afd_poll_info.Timeout.QuadPart  = LLONG_MAX;
342   ed->afd_poll_info.Handles[0].Handle = (HANDLE)ed->base_socket;
343   ed->afd_poll_info.Handles[0].Status = 0;
344   ed->afd_poll_info.Handles[0].Events = 0;
345 
346   if (flags & ARES_EVENT_FLAG_READ) {
347     ed->afd_poll_info.Handles[0].Events |=
348       (AFD_POLL_RECEIVE | AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT |
349        AFD_POLL_ABORT);
350   }
351   if (flags & ARES_EVENT_FLAG_WRITE) {
352     ed->afd_poll_info.Handles[0].Events |=
353       (AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL);
354   }
355   if (flags == 0) {
356     ed->afd_poll_info.Handles[0].Events |= AFD_POLL_DISCONNECT;
357   }
358 
359   memset(&ed->overlapped, 0, sizeof(ed->overlapped));
360   iosb_ptr         = (IO_STATUS_BLOCK *)&ed->overlapped.Internal;
361   iosb_ptr->Status = STATUS_PENDING;
362 
363   status = ew->NtDeviceIoControlFile(
364     (HANDLE)ed->peer_socket, NULL, NULL, &ed->overlapped, iosb_ptr,
365     IOCTL_AFD_POLL, &ed->afd_poll_info, sizeof(ed->afd_poll_info),
366     &ed->afd_poll_info, sizeof(ed->afd_poll_info));
367   if (status != STATUS_SUCCESS && status != STATUS_PENDING) {
368     printf("%s(): failed to perform IOCTL_AFD_POLL operation\n", __FUNCTION__);
369     fflush(stdout);
370     return ARES_FALSE;
371   }
372 
373   return ARES_TRUE;
374 }
375 
ares_evsys_win32_afd_cancel(ares_evsys_win32_eventdata_t * ed)376 static ares_bool_t ares_evsys_win32_afd_cancel(ares_evsys_win32_eventdata_t *ed)
377 {
378   IO_STATUS_BLOCK    *iosb_ptr;
379   IO_STATUS_BLOCK     cancel_iosb;
380   ares_evsys_win32_t *ew;
381   NTSTATUS            status;
382 
383   /* Detached due to destroy */
384   if (ed->event == NULL) {
385     return ARES_FALSE;
386   }
387 
388   iosb_ptr = (IO_STATUS_BLOCK *)&ed->overlapped.Internal;
389   /* Not pending, nothing to do */
390   if (iosb_ptr->Status != STATUS_PENDING) {
391     return ARES_FALSE;
392   }
393 
394   ew = ed->event->e->ev_sys_data;
395   status =
396     ew->NtCancelIoFileEx((HANDLE)ed->peer_socket, iosb_ptr, &cancel_iosb);
397 
398   /* NtCancelIoFileEx() may return STATUS_NOT_FOUND if the operation completed
399    * just before calling NtCancelIoFileEx(), but we have not yet received the
400    * notifiction (but it should be queued for the next IOCP event).  */
401   if (status == STATUS_SUCCESS || status == STATUS_NOT_FOUND) {
402     return ARES_TRUE;
403   }
404 
405   return ARES_FALSE;
406 }
407 
ares_evsys_win32_eventdata_destroy(ares_evsys_win32_eventdata_t * ed)408 static void ares_evsys_win32_eventdata_destroy(ares_evsys_win32_eventdata_t *ed)
409 {
410   if (ed == NULL) {
411     return;
412   }
413 
414   if (ed->peer_socket != ARES_SOCKET_BAD) {
415     closesocket(ed->peer_socket);
416   }
417 
418   ares_free(ed);
419 }
420 
ares_evsys_win32_event_add(ares_event_t * event)421 static ares_bool_t ares_evsys_win32_event_add(ares_event_t *event)
422 {
423   ares_event_thread_t          *e  = event->e;
424   ares_evsys_win32_t           *ew = e->ev_sys_data;
425   ares_evsys_win32_eventdata_t *ed;
426   WSAPROTOCOL_INFOW             protocol_info;
427 
428   ed              = ares_malloc_zero(sizeof(*ed));
429   ed->event       = event;
430   ed->socket      = event->fd;
431   ed->base_socket = ARES_SOCKET_BAD;
432   ed->peer_socket = ARES_SOCKET_BAD;
433 
434   /* Likely a signal event, not something we will directly handle.  We create
435    * the ares_evsys_win32_eventdata_t as the placeholder to use as the
436    * IOCP Completion Key */
437   if (ed->socket == ARES_SOCKET_BAD) {
438     event->data = ed;
439     return ARES_TRUE;
440   }
441 
442   ed->base_socket = ares_evsys_win32_basesocket(ed->socket);
443   if (ed->base_socket == ARES_SOCKET_BAD) {
444     fprintf(stderr, "%s(): could not determine base socket for fd %d\n",
445             __FUNCTION__, (int)event->fd);
446     ares_evsys_win32_eventdata_destroy(ed);
447     return ARES_FALSE;
448   }
449 
450   /* Create a peer socket that supports OVERLAPPED so we can use IOCP on the
451    * socket handle */
452   if (WSADuplicateSocketW(ed->base_socket, GetCurrentProcessId(),
453                           &protocol_info) != 0) {
454     fprintf(stderr,
455             "%s(): could not retrieve protocol info for creating peer socket\n",
456             __FUNCTION__);
457     ares_evsys_win32_eventdata_destroy(ed);
458     return ARES_FALSE;
459   }
460 
461   ed->peer_socket =
462     WSASocketW(protocol_info.iAddressFamily, protocol_info.iSocketType,
463                protocol_info.iProtocol, &protocol_info, 0, WSA_FLAG_OVERLAPPED);
464   if (ed->peer_socket == ARES_SOCKET_BAD) {
465     fprintf(stderr, "%s(): could not create peer socket\n", __FUNCTION__);
466     ares_evsys_win32_eventdata_destroy(ed);
467     return ARES_FALSE;
468   }
469 
470   SetHandleInformation((HANDLE)ed->peer_socket, HANDLE_FLAG_INHERIT, 0);
471 
472   if (CreateIoCompletionPort((HANDLE)ed->peer_socket, ew->iocp_handle,
473                              (ULONG_PTR)ed, 0) == NULL) {
474     fprintf(stderr, "%s(): failed to bind peer socket to IOCP\n", __FUNCTION__);
475     ares_evsys_win32_eventdata_destroy(ed);
476     return ARES_FALSE;
477   }
478 
479   event->data = ed;
480 
481   if (!ares_evsys_win32_afd_enqueue(event, event->flags)) {
482     event->data = NULL;
483     ares_evsys_win32_eventdata_destroy(ed);
484     return ARES_FALSE;
485   }
486 
487   return ARES_TRUE;
488 }
489 
ares_evsys_win32_event_del(ares_event_t * event)490 static void ares_evsys_win32_event_del(ares_event_t *event)
491 {
492   ares_evsys_win32_eventdata_t *ed = event->data;
493   ares_event_thread_t          *e  = event->e;
494 
495   if (event->fd == ARES_SOCKET_BAD || !e->isup || ed == NULL ||
496       !ares_evsys_win32_afd_cancel(ed)) {
497     /* Didn't need to enqueue a cancellation, for one of these reasons:
498      *  - Not an IOCP socket
499      *  - This is during shutdown of the event thread, no more signals can be
500      *    delivered.
501      *  - It has been determined there is no AFD POLL queued currently for the
502      *    socket.
503      */
504     ares_evsys_win32_eventdata_destroy(ed);
505     event->data = NULL;
506   } else {
507     /* Detach from event, so when the cancel event comes through,
508      * it will clean up */
509     ed->event   = NULL;
510     event->data = NULL;
511   }
512 }
513 
ares_evsys_win32_event_mod(ares_event_t * event,ares_event_flags_t new_flags)514 static void ares_evsys_win32_event_mod(ares_event_t      *event,
515                                        ares_event_flags_t new_flags)
516 {
517   ares_evsys_win32_eventdata_t *ed = event->data;
518 
519   /* Not for us */
520   if (event->fd == ARES_SOCKET_BAD || ed == NULL) {
521     return;
522   }
523 
524   /* Try to cancel any current outstanding poll, if one is not running,
525    * go ahead and queue it up */
526   if (!ares_evsys_win32_afd_cancel(ed)) {
527     ares_evsys_win32_afd_enqueue(event, new_flags);
528   }
529 }
530 
ares_evsys_win32_wait(ares_event_thread_t * e,unsigned long timeout_ms)531 static size_t ares_evsys_win32_wait(ares_event_thread_t *e,
532                                     unsigned long        timeout_ms)
533 {
534   ares_evsys_win32_t *ew = e->ev_sys_data;
535   OVERLAPPED_ENTRY    entries[16];
536   ULONG               nentries = sizeof(entries) / sizeof(*entries);
537   BOOL                status;
538   size_t              i;
539   size_t              cnt = 0;
540 
541   status = GetQueuedCompletionStatusEx(
542     ew->iocp_handle, entries, nentries, &nentries,
543     (timeout_ms == 0) ? INFINITE : (DWORD)timeout_ms, FALSE);
544 
545   if (!status) {
546     return 0;
547   }
548 
549   for (i = 0; i < (size_t)nentries; i++) {
550     ares_event_flags_t            flags = 0;
551     ares_evsys_win32_eventdata_t *ed =
552       (ares_evsys_win32_eventdata_t *)entries[i].lpCompletionKey;
553     ares_event_t *event = ed->event;
554 
555     if (ed->socket == ARES_SOCKET_BAD) {
556       /* Some sort of signal event */
557       flags = ARES_EVENT_FLAG_OTHER;
558     } else {
559       /* Process events */
560       if (ed->afd_poll_info.NumberOfHandles > 0) {
561         if (ed->afd_poll_info.Handles[0].Events &
562             (AFD_POLL_RECEIVE | AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT |
563              AFD_POLL_ABORT)) {
564           flags |= ARES_EVENT_FLAG_READ;
565         }
566         if (ed->afd_poll_info.Handles[0].Events &
567             (AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL)) {
568           flags |= ARES_EVENT_FLAG_WRITE;
569         }
570 
571         /* XXX: Handle ed->afd_poll_info.Handles[0].Events &
572          * AFD_POLL_LOCAL_CLOSE */
573       }
574 
575       if (event == NULL) {
576         /* This means we need to cleanup the private event data as we've been
577          * detached */
578         ares_evsys_win32_eventdata_destroy(ed);
579       } else {
580         /* Re-enqueue so we can get more events on the socket */
581         ares_evsys_win32_afd_enqueue(event, event->flags);
582       }
583     }
584 
585     if (event != NULL && flags != 0) {
586       cnt++;
587       event->cb(e, event->fd, event->data, flags);
588     }
589   }
590 
591   return cnt;
592 }
593 
594 const ares_event_sys_t ares_evsys_win32 = { "win32",
595                                             ares_evsys_win32_init,
596                                             ares_evsys_win32_destroy,
597                                             ares_evsys_win32_event_add,
598                                             ares_evsys_win32_event_del,
599                                             ares_evsys_win32_event_mod,
600                                             ares_evsys_win32_wait };
601 #endif
602