Event Subsystem: No longer require integrators to have their own (#696)

This PR implements an event thread to process all events on file descriptors registered by c-ares. Prior to this feature, integrators were required to understand the internals of c-ares and how to monitor file descriptors and timeouts and process events.

Implements OS-specific efficient polling such as epoll(), kqueue(), or IOCP, and falls back to poll() or select() if otherwise unsupported. At this point, it depends on basic threading primitives such as pthreads or windows threads.

If enabled via the ARES_OPT_EVENT_THREAD option passed to ares_init_options(), then socket callbacks cannot be used.

Fixes Bug: #611
Fix By: Brad House (@bradh352)
pull/698/head
Brad House 10 months ago committed by GitHub
parent 26642c1014
commit 7963c519fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 18
      CMakeLists.txt
  2. 23
      configure.ac
  3. 15
      docs/ares_fds.3
  4. 27
      docs/ares_getaddrinfo.3
  5. 27
      docs/ares_gethostbyaddr.3
  6. 27
      docs/ares_gethostbyname.3
  7. 27
      docs/ares_getnameinfo.3
  8. 19
      docs/ares_getsock.3
  9. 26
      docs/ares_init_options.3
  10. 32
      docs/ares_send.3
  11. 6
      docs/ares_set_socket_configure_callback.3
  12. 2
      docs/ares_set_socket_functions.3
  13. 6
      docs/ares_threadsafety.3
  14. 18
      include/ares.h
  15. 10
      src/lib/Makefile.inc
  16. 29
      src/lib/ares__htable.c
  17. 12
      src/lib/ares__htable.h
  18. 33
      src/lib/ares__htable_asvp.c
  19. 9
      src/lib/ares__htable_asvp.h
  20. 6
      src/lib/ares__socket.c
  21. 200
      src/lib/ares__threads.c
  22. 45
      src/lib/ares__threads.h
  23. 36
      src/lib/ares_config.h.cmake
  24. 5
      src/lib/ares_destroy.c
  25. 174
      src/lib/ares_event.h
  26. 198
      src/lib/ares_event_epoll.c
  27. 249
      src/lib/ares_event_kqueue.c
  28. 137
      src/lib/ares_event_poll.c
  29. 150
      src/lib/ares_event_select.c
  30. 447
      src/lib/ares_event_thread.c
  31. 166
      src/lib/ares_event_wake_pipe.c
  32. 601
      src/lib/ares_event_win32.c
  33. 119
      src/lib/ares_event_win32.h
  34. 8
      src/lib/ares_init.c
  35. 15
      src/lib/ares_options.c
  36. 12
      src/lib/ares_private.h
  37. 2
      src/lib/ares_process.c
  38. 3
      test/Makefile.inc
  39. 4
      test/ares-test-main.cc
  40. 16
      test/ares-test-mock-ai.cc
  41. 1396
      test/ares-test-mock-et.cc
  42. 36
      test/ares-test-mock.cc
  43. 256
      test/ares-test.cc
  44. 81
      test/ares-test.h

@ -224,8 +224,11 @@ CHECK_INCLUDE_FILES (sys/stat.h HAVE_SYS_STAT_H)
CHECK_INCLUDE_FILES (sys/time.h HAVE_SYS_TIME_H)
CHECK_INCLUDE_FILES (sys/uio.h HAVE_SYS_UIO_H)
CHECK_INCLUDE_FILES (sys/random.h HAVE_SYS_RANDOM_H)
CHECK_INCLUDE_FILES (sys/event.h HAVE_SYS_EVENT_H)
CHECK_INCLUDE_FILES (sys/epoll.h HAVE_SYS_EPOLL_H)
CHECK_INCLUDE_FILES (ifaddrs.h HAVE_IFADDRS_H)
CHECK_INCLUDE_FILES (time.h HAVE_TIME_H)
CHECK_INCLUDE_FILES (poll.h HAVE_POLL_H)
CHECK_INCLUDE_FILES (dlfcn.h HAVE_DLFCN_H)
CHECK_INCLUDE_FILES (unistd.h HAVE_UNISTD_H)
# On OpenBSD, you must include sys/types.h before netinet/tcp.h
@ -243,8 +246,14 @@ CHECK_INCLUDE_FILES ("winsock2.h;windows.h" HAVE_WINSOCK2_H)
CHECK_INCLUDE_FILES ("winsock2.h;ws2tcpip.h;windows.h" HAVE_WS2TCPIP_H)
CHECK_INCLUDE_FILES ("winsock2.h;iphlpapi.h;windows.h" HAVE_IPHLPAPI_H)
CHECK_INCLUDE_FILES ("winsock2.h;netioapi.h;windows.h" HAVE_NETIOAPI_H)
CHECK_INCLUDE_FILES ("winsock2.h;mswsock.h;windows.h" HAVE_MSWSOCK_H)
CHECK_INCLUDE_FILES ("winsock.h;windows.h" HAVE_WINSOCK_H)
CHECK_INCLUDE_FILES (windows.h HAVE_WINDOWS_H)
CHECK_INCLUDE_FILES ("windows.h;winternl.h" HAVE_WINTERNL_H)
CHECK_INCLUDE_FILES ("windows.h;ntdef.h" HAVE_NTDEF_H)
CHECK_INCLUDE_FILES ("windows.h;ntdef.h;ntstatus.h" HAVE_NTSTATUS_H)
ENDIF ()
# Set system-specific compiler flags
@ -324,7 +333,10 @@ CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_TIME_H sys/time.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_STAT_H sys/stat.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_UIO_H sys/uio.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_RANDOM_H sys/random.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_EVENT_H sys/event.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_EPOLL_H sys/epoll.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_TIME_H time.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_POLL_H poll.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_FCNTL_H fcntl.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_UNISTD_H unistd.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_WINSOCK2_H winsock2.h)
@ -431,6 +443,12 @@ CHECK_SYMBOL_EXISTS (writev "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_WRITEV)
CHECK_SYMBOL_EXISTS (arc4random_buf "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_ARC4RANDOM_BUF)
CHECK_SYMBOL_EXISTS (stat "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_STAT)
CHECK_SYMBOL_EXISTS (getifaddrs "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETIFADDRS)
CHECK_SYMBOL_EXISTS (poll "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_POLL)
CHECK_SYMBOL_EXISTS (pipe "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_PIPE)
CHECK_SYMBOL_EXISTS (pipe2 "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_PIPE2)
CHECK_SYMBOL_EXISTS (kqueue "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_KQUEUE)
CHECK_SYMBOL_EXISTS (epoll_create1 "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_EPOLL)
# On Android, the system headers may define __system_property_get(), but excluded
# from libc. We need to perform a link test instead of a header/symbol test.

@ -299,7 +299,11 @@ if test "$ac_cv_native_windows" = "yes" ; then
ws2tcpip.h \
iphlpapi.h \
netioapi.h \
ws2ipdef.h,
ws2ipdef.h \
winternl.h \
ntdef.h \
ntstatus.h \
mswsock.h,
[], [], [-])
dnl Windows builds require linking to iphlpapi
@ -424,6 +428,8 @@ AC_CHECK_HEADERS(
sys/param.h \
sys/uio.h \
sys/random.h \
sys/event.h \
sys/epoll.h \
assert.h \
iphlpapi.h \
netioapi.h \
@ -438,6 +444,7 @@ AC_CHECK_HEADERS(
strings.h \
stdbool.h \
time.h \
poll.h \
limits.h \
arpa/nameser.h \
arpa/nameser_compat.h \
@ -517,6 +524,12 @@ cares_all_includes="
#ifdef HAVE_SYS_RANDOM_H
# include <sys/random.h>
#endif
#ifdef HAVE_SYS_EVENT_H
# include <sys/event.h>
#endif
#ifdef HAVE_SYS_EPOLL_H
# include <sys/epoll.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
@ -526,6 +539,9 @@ cares_all_includes="
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_POLL_H
# include <poll.h>
#endif
#ifdef HAVE_NET_IF_H
# include <net/if.h>
#endif
@ -634,6 +650,11 @@ AC_CHECK_DECL(clock_gettime, [AC_DEFINE([HAVE_CLOCK_GETTIME], 1, [Define t
AC_CHECK_DECL(if_indextoname, [AC_DEFINE([HAVE_IF_INDEXTONAME], 1, [Define to 1 if you have `if_indextoname`] )], [], $cares_all_includes)
AC_CHECK_DECL(if_nametoindex, [AC_DEFINE([HAVE_IF_NAMETOINDEX], 1, [Define to 1 if you have `if_nametoindex`] )], [], $cares_all_includes)
AC_CHECK_DECL(getifaddrs, [AC_DEFINE([HAVE_GETIFADDRS], 1, [Define to 1 if you have `getifaddrs`] )], [], $cares_all_includes)
AC_CHECK_DECL(poll, [AC_DEFINE([HAVE_POLL], 1, [Define to 1 if you have `poll`] )], [], $cares_all_includes)
AC_CHECK_DECL(pipe, [AC_DEFINE([HAVE_PIPE], 1, [Define to 1 if you have `pipe`] )], [], $cares_all_includes)
AC_CHECK_DECL(pipe2, [AC_DEFINE([HAVE_PIPE2], 1, [Define to 1 if you have `pipe2`] )], [], $cares_all_includes)
AC_CHECK_DECL(kqueue, [AC_DEFINE([HAVE_KQUEUE], 1, [Define to 1 if you have `kqueue`] )], [], $cares_all_includes)
AC_CHECK_DECL(epoll_create1, [AC_DEFINE([HAVE_EPOLL], 1, [Define to 1 if you have `epoll_{create1,ctl,wait}`])], [], $cares_all_includes)
AC_CHECK_DECL(ConvertInterfaceIndexToLuid, [AC_DEFINE([HAVE_CONVERTINTERFACEINDEXTOLUID], 1, [Define to 1 if you have `ConvertInterfaceIndexToLuid`])], [], $cares_all_includes)
AC_CHECK_DECL(ConvertInterfaceLuidToNameA, [AC_DEFINE([HAVE_CONVERTINTERFACELUIDTONAMEA], 1, [Define to 1 if you have `ConvertInterfaceLuidToNameA`])], [], $cares_all_includes)
AC_CHECK_DECL(__system_property_get, [AC_DEFINE([HAVE___SYSTEM_PROPERTY_GET], 1, [Define to 1 if you have `__system_property_get`] )], [], $cares_all_includes)

@ -4,7 +4,7 @@
.\"
.TH ARES_FDS 3 "23 July 1998"
.SH NAME
ares_fds \- return file descriptors to select on. Deprecated.
ares_fds \- return file descriptors to select on (deprecated)
.SH SYNOPSIS
.nf
#include <ares.h>
@ -19,7 +19,8 @@ See the \fBNOTES\fP section on issues with this function and alternatives.
The \fBares_fds(3)\fP function retrieves the set of file descriptors which the
calling application should \fBselect(2)\fP on for reading and writing for the
processing of name service queries pending on the name service channel
identified by \fIchannel\fP.
identified by \fIchannel\fP. Should not be used with \fBARES_OPT_EVENT_THREAD\fP
is passed to \fIares_init_options(3)\fP.
File descriptors will be set in the file descriptor sets pointed to by
\fIread_fds\fP and \fIwrite_fds\fP as appropriate. File descriptors already
@ -45,11 +46,13 @@ c-ares does not attempt to detect this condition to prevent crashes due to both
implementation-defined behavior in the OS as well as integrator-controllable
tunables which may impact the limits.
It is recommended to use socket state callbacks (\fBARES_OPT_SOCK_STATE_CB\fP)
registered via \fBares_init_options(3)\fP, and use more modern methods to
check for socket readable/writable state such as \fBpoll(2)\fP, \fBepoll(2)\fP,
or \fBkqueue(2)\fP.
It is recommended to use \fBARES_OPT_EVENT_THREAD\fP passed to
\fIares_init_options(3)\fP, or socket state callbacks
(\fBARES_OPT_SOCK_STATE_CB\fP) registered via \fIares_init_options(3)\fP and use
more modern methods to check for socket readable/writable state such as
\fIpoll(2)\fP, \fIepoll(2)\fP, or \fIkqueue(2)\fP.
.SH SEE ALSO
.BR ares_init_options (3),
.BR ares_timeout (3),
.BR ares_process (3)
.SH AUTHOR

@ -1,18 +1,5 @@
.\"
.\" Copyright 1998 by the Massachusetts Institute of Technology.
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_GETADDRINFO 3 "4 November 2018"
@ -91,11 +78,15 @@ Completion or failure of the query may happen immediately, or may happen
during a later call to \fIares_process(3)\fP, \fIares_destroy(3)\fP or
\fIares_cancel(3)\fP.
.PP
If this is called from a thread other than which the main program event loop is
running, care needs to be taken to ensure any file descriptor lists are updated
immediately within the eventloop. When the associated callback is called,
it is called with a channel lock so care must be taken to ensure any processing
is minimal to prevent DNS channel stalls.
When the associated callback is called, it is called with a channel lock so care
must be taken to ensure any processing is minimal to prevent DNS channel stalls.
The callback may be triggered from a different thread than the one which
called \fIares_getaddrinfo(3)\fP.
For integrators running their own event loops and not using \fBARES_OPT_EVENT_THREAD\fP,
care needs to be taken to ensure any file descriptor lists are updated immediately
within the eventloop when notified.
.PP
The callback argument
.I arg

@ -1,18 +1,5 @@
.\"
.\" Copyright 1998 by the Massachusetts Institute of Technology.
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_GETHOSTBYADDR 3 "24 July 1998"
@ -99,11 +86,15 @@ did not complete successfully,
will be
.BR NULL .
.PP
If this is called from a thread other than which the main program event loop is
running, care needs to be taken to ensure any file descriptor lists are updated
immediately within the eventloop. When the associated callback is called,
it is called with a channel lock so care must be taken to ensure any processing
is minimal to prevent DNS channel stalls.
When the associated callback is called, it is called with a channel lock so care
must be taken to ensure any processing is minimal to prevent DNS channel stalls.
The callback may be triggered from a different thread than the one which
called \fIares_gethostbyaddr(3)\fP.
For integrators running their own event loops and not using \fBARES_OPT_EVENT_THREAD\fP,
care needs to be taken to ensure any file descriptor lists are updated immediately
within the eventloop when notified.
.SH SEE ALSO
.BR ares_process (3),
.BR ares_gethostbyname (3)

@ -1,18 +1,5 @@
.\"
.\" Copyright 1998 by the Massachusetts Institute of Technology.
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_GETHOSTBYNAME 3 "25 July 1998"
@ -107,11 +94,15 @@ did not complete successfully,
will be
.BR NULL .
.PP
If this is called from a thread other than which the main program event loop is
running, care needs to be taken to ensure any file descriptor lists are updated
immediately within the eventloop. When the associated callback is called,
it is called with a channel lock so care must be taken to ensure any processing
is minimal to prevent DNS channel stalls.
When the associated callback is called, it is called with a channel lock so care
must be taken to ensure any processing is minimal to prevent DNS channel stalls.
The callback may be triggered from a different thread than the one which
called \fIares_gethostbyname(3)\fP.
For integrators running their own event loops and not using \fBARES_OPT_EVENT_THREAD\fP,
care needs to be taken to ensure any file descriptor lists are updated immediately
within the eventloop when notified.
.SH SEE ALSO
.BR ares_process (3),
.BR ares_gethostbyaddr (3)

@ -1,18 +1,5 @@
.\"
.\" Copyright 2005 by Dominick Meglio.
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_GETNAMEINFO 3 "1 May 2009"
@ -86,11 +73,15 @@ failed, the ares library will invoke \fIcallback\fP. Completion or failure of
the query may happen immediately, or may happen during a later call to
\fIares_process(3)\fP, \fIares_destroy(3)\fP or \fIares_cancel(3)\fP.
.PP
If this is called from a thread other than which the main program event loop is
running, care needs to be taken to ensure any file descriptor lists are updated
immediately within the eventloop. When the associated callback is called,
it is called with a channel lock so care must be taken to ensure any processing
is minimal to prevent DNS channel stalls.
When the associated callback is called, it is called with a channel lock so care
must be taken to ensure any processing is minimal to prevent DNS channel stalls.
The callback may be triggered from a different thread than the one which
called \fIares_getnameinfo(3)\fP.
For integrators running their own event loops and not using \fBARES_OPT_EVENT_THREAD\fP,
care needs to be taken to ensure any file descriptor lists are updated immediately
within the eventloop when notified.
.PP
The callback argument
.I arg

@ -1,18 +1,5 @@
.\"
.\" Copyright 1998 by Daniel Stenberg
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_GETSOCK 3 "11 March 2010"
@ -56,9 +43,11 @@ This function was added in c-ares 1.3.1 and deprecated in c-ares 1.20.0 due to
the implementation of \fBARES_OPT_MAX_UDP_QUERIES\fP which makes it likely to
exceed 16 open file descriptors.
It is recommended to use socket state callbacks (\fBARES_OPT_SOCK_STATE_CB\fP)
registered via \fBares_init_options(3)\fP.
It is recommended to use \fBARES_OPT_EVENT_THREAD\fP passed to
\fIares_init_options(3)\fP or to use socket state callbacks
(\fBARES_OPT_SOCK_STATE_CB\fP) registered via \fBares_init_options(3)\fP.
.SH SEE ALSO
.BR ares_init_options (3),
.BR ares_timeout (3),
.BR ares_fds (3),
.BR ares_process (3)

@ -35,6 +35,7 @@ struct ares_options {
int udp_max_queries;
int maxtimeout; /* in milliseconds */
unsigned int qcache_max_ttl; /* in seconds */
ares_evsys_t evsys;
};
int ares_init_options(ares_channel_t **\fIchannelptr\fP,
@ -217,6 +218,8 @@ The value of
will be passed as the
.I data
argument.
Cannot be used with \fBARES_OPT_EVENT_THREAD\fP.
.TP 18
.B ARES_OPT_SORTLIST
.B struct apattern *\fIsortlist\fP;
@ -286,6 +289,29 @@ message. This must be a non-zero value otherwise the cache will be disabled.
Choose a reasonable value for your application such as 300 (5 minutes) or
3600 (1 hour).
.br
.TP 18
.B ARES_OPT_EVENT_THREAD
.B ares_evsys_t \fIevsys\fP;
.br
Enable the built-in event thread (Recommended). Introduced in c-ares 1.26.0.
Set the \fIevsys\fP parameter to \fBARES_EVSYS_DEFAULT\fP (0). Other values are
reserved for testing and should not be used by integrators.
This option cannot be used with the \fBARES_OPT_SOCK_STATE_CB\fP option, nor the
\fIares_set_socket_functions(3)\fP or
\fIares_set_socket_configure_callback(3)\fP functions.
When enabled, the integrator is no longer responsible for notifying c-ares of
any events on the file descriptors, so \fIares_process(3)\fP nor
\fIares_process_fd(3)\fP should ever be called when this option is enabled.
Use \fIares_threadsafety(3)\fP to determine if this option is available to be
used.
Returns \fBARES_ENOTIMP\fP if this option is passed but not available, and
\fBARES_ESERVFAIL\fP if there is a critical failure during initialization of
the event thread.
.br
.PP
The \fIoptmask\fP parameter also includes options without a corresponding
field in the

@ -1,18 +1,5 @@
.\"
.\" Copyright 1998 by the Massachusetts Institute of Technology.
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_SEND 3 "25 July 1998"
@ -44,16 +31,17 @@ to the DNS protocol. When the query is complete or has failed, the
ares library will invoke
.IR callback .
Completion or failure of the query may happen immediately, or may
happen during a later call to
.BR ares_process (3)
or
.BR ares_destroy (3).
happen later as network events are processed.
.PP
If this is called from a thread other than which the main program event loop is
running, care needs to be taken to ensure any file descriptor lists are updated
immediately within the eventloop. When the associated callback is called,
it is called with a channel lock so care must be taken to ensure any processing
is minimal to prevent DNS channel stalls.
When the associated callback is called, it is called with a channel lock so care
must be taken to ensure any processing is minimal to prevent DNS channel stalls.
The callback may be triggered from a different thread than the one which
called \fIares_send(3)\fP.
For integrators running their own event loops and not using \fBARES_OPT_EVENT_THREAD\fP,
care needs to be taken to ensure any file descriptor lists are updated immediately
within the eventloop when notified.
.PP
The callback argument
.I arg

@ -18,8 +18,10 @@ void ares_set_socket_configure_callback(ares_channel_t *\fIchannel\fP,
.fi
.SH DESCRIPTION
.PP
This function sets a \fIcallback\fP in the given ares channel handle. This
callback function will be invoked after the socket has been created, but
This function sets a \fIcallback\fP in the given ares channel handle. Cannot be
used when \fBARES_OPT_EVENT_THREAD\fP is passed to \fIares_init_options(3)\fP.
This callback function will be invoked after the socket has been created, but
before it has been connected to the remote server, which is an ideal time
to configure various socket options. The callback must return ARES_SUCCESS
if things are fine, or return -1 to signal an error. A returned error will

@ -23,6 +23,8 @@ void ares_set_socket_functions(ares_channel_t *\fIchannel\fP,
.SH DESCRIPTION
.PP
This function sets a set of callback \fIfunctions\fP in the given ares channel handle.
Cannot be used when \fBARES_OPT_EVENT_THREAD\fP is passed to \fIares_init_options(3)\fP.
These callback functions will be invoked to create/destroy socket objects and perform
io, instead of the normal system calls. A client application can override normal network
operation fully through this functionality, and provide its own transport layer. You

@ -17,8 +17,10 @@ thread safety enabled or not.
As of c-ares 1.23.0, this simply means that every public function which
references an \fIares_channel_t\fP object will lock the channel on entry and
release the lock on exit of the function. This will prevent concurrent
thread access to the channel, thus ensuring no corruption can occur. Future
versions will likely implement more threading-specific features.
thread access to the channel, thus ensuring no corruption can occur.
As of c-ares 1.26.0, this also indicates if \fBARES_OPT_EVENT_THREAD\fP can
be passed to \fIares_init_options(3)\fP.
.SH RETURN VALUES
\fIares_threadsafety(3)\fP can return any of the following values:

@ -170,6 +170,22 @@ typedef enum {
ARES_TRUE = 1
} ares_bool_t;
/*! Values for ARES_OPT_EVENT_THREAD */
typedef enum {
/*! Default (best choice) event system */
ARES_EVSYS_DEFAULT = 0,
/*! Win32 IOCP/AFD_POLL event system */
ARES_EVSYS_WIN32 = 1,
/*! Linux epoll */
ARES_EVSYS_EPOLL = 2,
/*! BSD/MacOS kqueue */
ARES_EVSYS_KQUEUE = 3,
/*! POSIX poll() */
ARES_EVSYS_POLL = 4,
/*! last fallback on Unix-like systems, select() */
ARES_EVSYS_SELECT = 5
} ares_evsys_t;
/* Flag values */
#define ARES_FLAG_USEVC (1 << 0)
#define ARES_FLAG_PRIMARY (1 << 1)
@ -204,6 +220,7 @@ typedef enum {
#define ARES_OPT_UDP_MAX_QUERIES (1 << 19)
#define ARES_OPT_MAXTIMEOUTMS (1 << 20)
#define ARES_OPT_QUERY_CACHE (1 << 21)
#define ARES_OPT_EVENT_THREAD (1 << 22)
/* Nameinfo flag values */
#define ARES_NI_NOFQDN (1 << 0)
@ -316,6 +333,7 @@ struct ares_options {
int udp_max_queries;
int maxtimeout; /* in milliseconds */
unsigned int qcache_max_ttl; /* Maximum TTL for query cache, 0=disabled */
ares_evsys_t evsys;
};
struct hostent;

@ -28,6 +28,13 @@ CSOURCES = ares__addrinfo2hostent.c \
ares_dns_parse.c \
ares_dns_record.c \
ares_dns_write.c \
ares_event_epoll.c \
ares_event_kqueue.c \
ares_event_poll.c \
ares_event_select.c \
ares_event_thread.c \
ares_event_wake_pipe.c \
ares_event_win32.c \
ares_expand_name.c \
ares_expand_string.c \
ares_fds.c \
@ -85,9 +92,12 @@ HHEADERS = ares__buf.h \
ares__iface_ips.h \
ares__llist.h \
ares__slist.h \
ares__threads.h \
ares_android.h \
ares_data.h \
ares_dns_private.h \
ares_event.h \
ares_event_win32.h \
ares_getenv.h \
ares_inet_net_pton.h \
ares_ipv6.h \

@ -135,6 +135,35 @@ fail:
return NULL;
}
const void **ares__htable_all_buckets(const ares__htable_t *htable, size_t *num)
{
const void **out = NULL;
size_t cnt = 0;
size_t i;
if (htable == NULL || num == NULL)
return NULL;
*num = 0;
out = ares_malloc_zero(sizeof(*out) * htable->num_keys);
if (out == NULL)
return NULL;
for (i=0; i<htable->size; i++) {
ares__llist_node_t *node;
for (node = ares__llist_node_first(htable->buckets[i]); node != NULL;
node = ares__llist_node_next(node)) {
out[cnt++] = ares__llist_node_val(node);
}
}
*num = cnt;
return out;
}
/*! Grabs the Hashtable index from the key and length. The h index is
* the hash of the function reduced to the size of the bucket list.
* We are doing "hash & (size - 1)" since we are guaranteeing a power of

@ -110,6 +110,18 @@ ares__htable_t *ares__htable_create(ares__htable_hashfunc_t hash_func,
*/
size_t ares__htable_num_keys(const ares__htable_t *htable);
/*! Retrieve an array of buckets from the hashtable. This is mainly used as
* a helper for retrieving an array of keys.
*
* \param[in] htable Initialized hashtable
* \param[out] num Count of returned buckets
* \return Array of pointers to the buckets. These are internal pointers
* to data within the hashtable, so if the key is removed, there
* will be a dangling pointer. It is expected wrappers will make
* such values safe by duplicating them.
*/
const void **ares__htable_all_buckets(const ares__htable_t *htable, size_t *num);
/*! Insert bucket into hashtable
*
* \param[in] htable Initialized hashtable.

@ -112,6 +112,39 @@ fail:
return NULL;
}
ares_socket_t *ares__htable_asvp_keys(const ares__htable_asvp_t *htable, size_t *num)
{
const void **buckets = NULL;
size_t cnt = 0;
ares_socket_t *out = NULL;
size_t i;
if (htable == NULL || num == NULL)
return NULL;
*num = 0;
buckets = ares__htable_all_buckets(htable->hash, &cnt);
if (buckets == NULL || cnt == 0) {
return NULL;
}
out = ares_malloc_zero(sizeof(*out) * cnt);
if (out == NULL) {
ares_free(buckets);
return NULL;
}
for (i=0; i<cnt; i++) {
out[i] = ((const ares__htable_asvp_bucket_t *)buckets[i])->key;
}
ares_free(buckets);
*num = cnt;
return out;
}
ares_bool_t ares__htable_asvp_insert(ares__htable_asvp_t *htable,
ares_socket_t key, void *val)
{

@ -68,6 +68,15 @@ void ares__htable_asvp_destroy(ares__htable_asvp_t *htable);
ares__htable_asvp_t *
ares__htable_asvp_create(ares__htable_asvp_val_free_t val_free);
/*! Retrieve an array of keys from the hashtable.
*
* \param[in] htable Initialized hashtable
* \param[out] num_keys Count of returned keys
* \return Array of keys in the hashtable. Must be free'd with ares_free().
*/
ares_socket_t *ares__htable_asvp_keys(const ares__htable_asvp_t *htable, size_t *num);
/*! Insert key/value into hash table
*
* \param[in] htable Initialized hash table

@ -450,6 +450,8 @@ ares_ssize_t ares__socket_write(ares_channel_t *channel, ares_socket_t s,
void ares_set_socket_callback(ares_channel_t *channel,
ares_sock_create_callback cb, void *data)
{
if (channel == NULL)
return;
channel->sock_create_cb = cb;
channel->sock_create_cb_data = data;
}
@ -458,6 +460,8 @@ void ares_set_socket_configure_callback(ares_channel_t *channel,
ares_sock_config_callback cb,
void *data)
{
if (channel == NULL || channel->optmask & ARES_OPT_EVENT_THREAD)
return;
channel->sock_config_cb = cb;
channel->sock_config_cb_data = data;
}
@ -466,6 +470,8 @@ void ares_set_socket_functions(ares_channel_t *channel,
const struct ares_socket_functions *funcs,
void *data)
{
if (channel == NULL || channel->optmask & ARES_OPT_EVENT_THREAD)
return;
channel->sock_funcs = funcs;
channel->sock_func_cb_data = data;
}

@ -34,7 +34,7 @@ struct ares__thread_mutex {
CRITICAL_SECTION mutex;
};
static ares__thread_mutex_t *ares__thread_mutex_create(void)
ares__thread_mutex_t *ares__thread_mutex_create(void)
{
ares__thread_mutex_t *mut = ares_malloc_zero(sizeof(*mut));
if (mut == NULL) {
@ -45,7 +45,7 @@ static ares__thread_mutex_t *ares__thread_mutex_create(void)
return mut;
}
static void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
{
if (mut == NULL) {
return;
@ -54,7 +54,7 @@ static void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
ares_free(mut);
}
static void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
{
if (mut == NULL) {
return;
@ -62,7 +62,7 @@ static void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
EnterCriticalSection(&mut->mutex);
}
static void ares__thread_mutex_unlock(ares__thread_mutex_t *mut)
void ares__thread_mutex_unlock(ares__thread_mutex_t *mut)
{
if (mut == NULL) {
return;
@ -70,14 +70,80 @@ static void ares__thread_mutex_unlock(ares__thread_mutex_t *mut)
LeaveCriticalSection(&mut->mutex);
}
# else
struct ares__thread {
HANDLE thread;
DWORD id;
void *(*func)(void *arg);
void *arg;
void *rv;
};
/* Wrap for pthread compatibility */
static DWORD WINAPI ares__thread_func(LPVOID lpParameter)
{
ares__thread_t *thread = lpParameter;
thread->rv = thread->func(thread->arg);
return 0;
}
ares_status_t ares__thread_create(ares__thread_t **thread,
ares__thread_func_t func, void *arg)
{
ares__thread_t *thr = NULL;
if (func == NULL || thread == NULL) {
return ARES_EFORMERR;
}
thr = ares_malloc_zero(sizeof(*thr));
if (thr == NULL) {
return ARES_ENOMEM;
}
thr->func = func;
thr->arg = arg;
thr->thread = CreateThread(NULL, 0, ares__thread_func, thr, 0, &thr->id);
if (thr->thread == NULL) {
ares_free(thr);
return ARES_ESERVFAIL;
}
*thread = thr;
return ARES_SUCCESS;
}
ares_status_t ares__thread_join(ares__thread_t *thread, void **rv)
{
ares_status_t status = ARES_SUCCESS;
if (thread == NULL) {
return ARES_EFORMERR;
}
if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) {
status = ARES_ENOTFOUND;
} else {
CloseHandle(thread->thread);
}
if (status == ARES_SUCCESS && rv != NULL) {
*rv = thread->rv;
}
ares_free(thread);
return status;
}
# else /* !WIN32 == PTHREAD */
# include <pthread.h>
struct ares__thread_mutex {
pthread_mutex_t mutex;
};
static ares__thread_mutex_t *ares__thread_mutex_create(void)
ares__thread_mutex_t *ares__thread_mutex_create(void)
{
pthread_mutexattr_t attr;
ares__thread_mutex_t *mut = ares_malloc_zero(sizeof(*mut));
@ -107,7 +173,7 @@ fail:
return NULL;
}
static void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
{
if (mut == NULL) {
return;
@ -116,7 +182,7 @@ static void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
ares_free(mut);
}
static void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
{
if (mut == NULL) {
return;
@ -124,66 +190,104 @@ static void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
pthread_mutex_lock(&mut->mutex);
}
static void ares__thread_mutex_unlock(ares__thread_mutex_t *mut)
void ares__thread_mutex_unlock(ares__thread_mutex_t *mut)
{
if (mut == NULL) {
return;
}
pthread_mutex_unlock(&mut->mutex);
}
# endif
ares_status_t ares__channel_threading_init(ares_channel_t *channel)
struct ares__thread {
pthread_t thread;
};
ares_status_t ares__thread_create(ares__thread_t **thread,
ares__thread_func_t func, void *arg)
{
channel->lock = ares__thread_mutex_create();
if (channel->lock == NULL) {
ares__thread_t *thr = NULL;
if (func == NULL || thread == NULL) {
return ARES_EFORMERR;
}
thr = ares_malloc_zero(sizeof(*thr));
if (thr == NULL) {
return ARES_ENOMEM;
}
if (pthread_create(&thr->thread, NULL, func, arg) != 0) {
ares_free(thr);
return ARES_ESERVFAIL;
}
*thread = thr;
return ARES_SUCCESS;
}
void ares__channel_threading_destroy(ares_channel_t *channel)
ares_status_t ares__thread_join(ares__thread_t *thread, void **rv)
{
ares__thread_mutex_destroy(channel->lock);
channel->lock = NULL;
void *ret = NULL;
ares_status_t status = ARES_SUCCESS;
if (thread == NULL) {
return ARES_EFORMERR;
}
if (pthread_join(thread->thread, &ret) != 0) {
status = ARES_ENOTFOUND;
}
ares_free(thread);
if (status == ARES_SUCCESS && rv != NULL) {
*rv = ret;
}
return status;
}
void ares__channel_lock(ares_channel_t *channel)
# endif
ares_bool_t ares_threadsafety(void)
{
ares__thread_mutex_lock(channel->lock);
return ARES_TRUE;
}
void ares__channel_unlock(ares_channel_t *channel)
#else /* !CARES_THREADS */
/* NoOp */
ares__thread_mutex_t *ares__thread_mutex_create(void)
{
ares__thread_mutex_unlock(channel->lock);
return NULL;
}
ares_bool_t ares_threadsafety(void)
void ares__thread_mutex_destroy(ares__thread_mutex_t *mut)
{
return ARES_TRUE;
(void)mut;
}
#else
/* NoOp */
ares_status_t ares__channel_threading_init(ares_channel_t *channel)
void ares__thread_mutex_lock(ares__thread_mutex_t *mut)
{
(void)channel;
return ARES_SUCCESS;
(void)mut;
}
void ares__channel_threading_destroy(ares_channel_t *channel)
void ares__thread_mutex_unlock(ares__thread_mutex_t *mut)
{
(void)channel;
(void)mut;
}
void ares__channel_lock(ares_channel_t *channel)
ares_status_t ares__thread_create(ares__thread_t **thread,
ares__thread_func_t func, void *arg)
{
(void)channel;
(void)thread;
(void)func;
(void)arg;
return ARES_ENOTIMP;
}
void ares__channel_unlock(ares_channel_t *channel)
ares_status_t ares__thread_join(ares__thread_t *thread, void **rv)
{
(void)channel;
(void)thread;
(void)rv;
return ARES_ENOTIMP;
}
ares_bool_t ares_threadsafety(void)
@ -191,3 +295,33 @@ ares_bool_t ares_threadsafety(void)
return ARES_FALSE;
}
#endif
ares_status_t ares__channel_threading_init(ares_channel_t *channel)
{
if (!ares_threadsafety()) {
return ARES_ENOTIMP;
}
channel->lock = ares__thread_mutex_create();
if (channel->lock == NULL) {
return ARES_ENOMEM;
}
return ARES_SUCCESS;
}
void ares__channel_threading_destroy(ares_channel_t *channel)
{
ares__thread_mutex_destroy(channel->lock);
channel->lock = NULL;
}
void ares__channel_lock(ares_channel_t *channel)
{
ares__thread_mutex_lock(channel->lock);
}
void ares__channel_unlock(ares_channel_t *channel)
{
ares__thread_mutex_unlock(channel->lock);
}

@ -0,0 +1,45 @@
/* MIT License
*
* Copyright (c) 2023 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
*/
#ifndef __ARES__THREADS_H
#define __ARES__THREADS_H
struct ares__thread_mutex;
typedef struct ares__thread_mutex ares__thread_mutex_t;
ares__thread_mutex_t *ares__thread_mutex_create(void);
void ares__thread_mutex_destroy(ares__thread_mutex_t *mut);
void ares__thread_mutex_lock(ares__thread_mutex_t *mut);
void ares__thread_mutex_unlock(ares__thread_mutex_t *mut);
struct ares__thread;
typedef struct ares__thread ares__thread_t;
typedef void *(*ares__thread_func_t)(void *arg);
ares_status_t ares__thread_create(ares__thread_t **thread,
ares__thread_func_t func, void *arg);
ares_status_t ares__thread_join(ares__thread_t *thread, void **rv);
#endif

@ -76,6 +76,24 @@
/* Define to 1 if you have the <errno.h> header file. */
#cmakedefine HAVE_ERRNO_H
/* Define to 1 if you have the <poll.h> header file. */
#cmakedefine HAVE_POLL_H
/* Define to 1 if you have the poll function. */
#cmakedefine HAVE_POLL
/* Define to 1 if you have the pipe function. */
#cmakedefine HAVE_PIPE
/* Define to 1 if you have the pipe2 function. */
#cmakedefine HAVE_PIPE2
/* Define to 1 if you have the kqueue function. */
#cmakedefine HAVE_KQUEUE
/* Define to 1 if you have the epoll{_create,ctl,wait} functions. */
#cmakedefine HAVE_EPOLL
/* Define to 1 if you have the fcntl function. */
#cmakedefine HAVE_FCNTL
@ -293,6 +311,12 @@
/* Define to 1 if you have the <sys/random.h> header file. */
#cmakedefine HAVE_SYS_RANDOM_H
/* Define to 1 if you have the <sys/event.h> header file. */
#cmakedefine HAVE_SYS_EVENT_H
/* Define to 1 if you have the <sys/epoll.h> header file. */
#cmakedefine HAVE_SYS_EPOLL_H
/* Define to 1 if you have the <sys/select.h> header file. */
#cmakedefine HAVE_SYS_SELECT_H
@ -329,6 +353,18 @@
/* Define to 1 if you have the winsock.h header file. */
#cmakedefine HAVE_WINSOCK_H
/* Define to 1 if you have the mswsock.h header file. */
#cmakedefine HAVE_MSWSOCK_H
/* Define to 1 if you have the winternl.h header file. */
#cmakedefine HAVE_WINTERNL_H
/* Define to 1 if you have the ntstatus.h header file. */
#cmakedefine HAVE_NTSTATUS_H
/* Define to 1 if you have the ntdef.h header file. */
#cmakedefine HAVE_NTDEF_H
/* Define to 1 if you have the writev function. */
#cmakedefine HAVE_WRITEV

@ -75,6 +75,11 @@ void ares_destroy(ares_channel_t *channel)
/* No more callbacks will be triggered after this point, unlock */
ares__channel_unlock(channel);
/* Shut down the event thread */
if (channel->optmask & ARES_OPT_EVENT_THREAD) {
ares_event_thread_destroy(channel);
}
if (channel->domains) {
for (i = 0; i < channel->ndomains; i++) {
ares_free(channel->domains[i]);

@ -0,0 +1,174 @@
/* MIT License
*
* Copyright (c) 2023 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
*/
#ifndef __ARES__EVENT_H
#define __ARES__EVENT_H
#include "ares_setup.h"
struct ares_event;
typedef struct ares_event ares_event_t;
typedef enum {
ARES_EVENT_FLAG_NONE = 0,
ARES_EVENT_FLAG_READ = 1 << 0,
ARES_EVENT_FLAG_WRITE = 1 << 1,
ARES_EVENT_FLAG_OTHER = 1 << 2
} ares_event_flags_t;
typedef void (*ares_event_cb_t)(ares_event_thread_t *e, ares_socket_t fd,
void *data, ares_event_flags_t flags);
typedef void (*ares_event_free_data_t)(void *data);
typedef void (*ares_event_signal_cb_t)(const ares_event_t *event);
struct ares_event {
/*! Registered event thread this event is bound to */
ares_event_thread_t *e;
/*! Flags to monitor. OTHER is only allowed if the socket is ARES_SOCKET_BAD.
*/
ares_event_flags_t flags;
/*! Callback to be called when event is triggered */
ares_event_cb_t cb;
/*! Socket to monitor, allowed to be ARES_SOCKET_BAD if not monitoring a
* socket. */
ares_socket_t fd;
/*! Data associated with event handle that will be passed to the callback.
* Typically OS/event subsystem specific data.
* Optional, may be NULL. */
/*! Data to be passed to callback. Optional, may be NULL. */
void *data;
/*! When cleaning up the registered event (either when removed or during
* shutdown), this function will be called to clean up the user-supplied
* data. Optional, May be NULL. */
ares_event_free_data_t free_data_cb;
/*! Callback to call to trigger an event. */
ares_event_signal_cb_t signal_cb;
};
typedef struct {
const char *name;
ares_bool_t (*init)(ares_event_thread_t *e);
void (*destroy)(ares_event_thread_t *e);
ares_bool_t (*event_add)(ares_event_t *event);
void (*event_del)(ares_event_t *event);
void (*event_mod)(ares_event_t *event, ares_event_flags_t new_flags);
size_t (*wait)(ares_event_thread_t *e, unsigned long timeout_ms);
} ares_event_sys_t;
struct ares_event_thread {
/*! Whether the event thread should be online or not. Checked on every wake
* event before sleeping. */
ares_bool_t isup;
/*! Handle to the thread for joining during shutdown */
ares__thread_t *thread;
/*! Lock to protect the data contained within the event thread itself */
ares__thread_mutex_t *mutex;
/*! Reference to the ares channel, for being able to call things like
* ares_timeout() and ares_process_fd(). */
ares_channel_t *channel;
/*! Not-yet-processed event handle updates. These will get enqueued by a
* thread other than the event thread itself. The event thread will then
* be woken then process these updates itself */
ares__llist_t *ev_updates;
/*! Registered event handles. */
ares__htable_asvp_t *ev_handles;
/*! Pointer to the event handle which is used to signal and wake the event
* thread itself. This is needed to be able to do things like update the
* file descriptors being waited on and to wake the event subsystem during
* shutdown */
ares_event_t *ev_signal;
/* Event subsystem callbacks */
const ares_event_sys_t *ev_sys;
/* Event subsystem private data */
void *ev_sys_data;
};
/*! Queue an update for the event handle.
*
* Will search by the fd passed if not ARES_SOCKET_BAD to find a match and
* perform an update or delete (depending on flags). Otherwise will add.
* Do not use the event handle returned if its not guaranteed to be an add
* operation.
*
* \param[out] event Event handle. Optional, can be NULL. This handle
* will be invalidate quickly if the result of the
* operation is not an ADD.
* \param[in] e pointer to event thread handle
* \param[in] flags flags for the event handle. Use
* ARES_EVENT_FLAG_NONE if removing a socket from
* queue (not valid if socket is ARES_SOCKET_BAD).
* Non-socket events cannot be removed, and must have
* ARES_EVENT_FLAG_OTHER set.
* \param[in] cb Callback to call when
* event is triggered. Required. Not allowed to be
* changed, ignored on modification.
* \param[in] fd File descriptor/socket to monitor. May
* be ARES_SOCKET_BAD if not monitoring file
* descriptor.
* \param[in] data Optional. Caller-supplied data to be passed to
* callback. Only allowed on initial add, cannot be
* modified later, ignored on modification.
* \param[in] free_data_cb Optional. Callback to clean up caller-supplied
* data. Only allowed on initial add, cannot be
* modified later, ignored on modification.
* \param[in] signal_cb Optional. Callback to call to trigger an event.
* \return ARES_SUCCESS on success
*/
ares_status_t ares_event_update(ares_event_t **event, ares_event_thread_t *e,
ares_event_flags_t flags, ares_event_cb_t cb,
ares_socket_t fd, void *data,
ares_event_free_data_t free_data_cb,
ares_event_signal_cb_t signal_cb);
#ifdef HAVE_PIPE
ares_event_t *ares_pipeevent_create(ares_event_thread_t *e);
#endif
#ifdef HAVE_POLL
extern const ares_event_sys_t ares_evsys_poll;
#endif
#ifdef HAVE_KQUEUE
extern const ares_event_sys_t ares_evsys_kqueue;
#endif
#ifdef HAVE_EPOLL
extern const ares_event_sys_t ares_evsys_epoll;
#endif
#ifdef _WIN32
extern const ares_event_sys_t ares_evsys_win32;
#endif
/* All systems have select(), but not all have a way to wake, so we require
* pipe() to wake the select() */
#ifdef HAVE_PIPE
extern const ares_event_sys_t ares_evsys_select;
#endif
#endif

@ -0,0 +1,198 @@
/* 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"
#ifdef HAVE_SYS_EPOLL_H
# include <sys/epoll.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_EPOLL
typedef struct {
int epoll_fd;
} ares_evsys_epoll_t;
static void ares_evsys_epoll_destroy(ares_event_thread_t *e)
{
ares_evsys_epoll_t *ep = NULL;
if (e == NULL) {
return;
}
ep = e->ev_sys_data;
if (ep == NULL) {
return;
}
if (ep->epoll_fd != -1) {
close(ep->epoll_fd);
}
ares_free(ep);
e->ev_sys_data = NULL;
}
static ares_bool_t ares_evsys_epoll_init(ares_event_thread_t *e)
{
ares_evsys_epoll_t *ep = NULL;
ep = ares_malloc_zero(sizeof(*ep));
if (ep == NULL) {
return ARES_FALSE;
}
e->ev_sys_data = ep;
ep->epoll_fd = epoll_create1(0);
if (ep->epoll_fd == -1) {
ares_evsys_epoll_destroy(e);
return ARES_FALSE;
}
# ifdef FD_CLOEXEC
fcntl(ep->epoll_fd, F_SETFD, FD_CLOEXEC);
# endif
e->ev_signal = ares_pipeevent_create(e);
if (e->ev_signal == NULL) {
ares_evsys_epoll_destroy(e);
return ARES_FALSE;
}
return ARES_TRUE;
}
static ares_bool_t ares_evsys_epoll_event_add(ares_event_t *event)
{
ares_event_thread_t *e = event->e;
ares_evsys_epoll_t *ep = e->ev_sys_data;
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
epev.data.fd = event->fd;
epev.events = EPOLLRDHUP | EPOLLERR | EPOLLHUP;
if (event->flags & ARES_EVENT_FLAG_READ) {
epev.events |= EPOLLIN;
}
if (event->flags & ARES_EVENT_FLAG_WRITE) {
epev.events |= EPOLLOUT;
}
if (epoll_ctl(ep->epoll_fd, EPOLL_CTL_ADD, event->fd, &epev) != 0) {
return ARES_FALSE;
}
return ARES_TRUE;
}
static void ares_evsys_epoll_event_del(ares_event_t *event)
{
ares_event_thread_t *e = event->e;
ares_evsys_epoll_t *ep = e->ev_sys_data;
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
epev.data.fd = event->fd;
epoll_ctl(ep->epoll_fd, EPOLL_CTL_DEL, event->fd, &epev);
}
static void ares_evsys_epoll_event_mod(ares_event_t *event,
ares_event_flags_t new_flags)
{
ares_event_thread_t *e = event->e;
ares_evsys_epoll_t *ep = e->ev_sys_data;
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
epev.data.fd = event->fd;
epev.events = EPOLLRDHUP | EPOLLERR | EPOLLHUP;
if (new_flags & ARES_EVENT_FLAG_READ) {
epev.events |= EPOLLIN;
}
if (new_flags & ARES_EVENT_FLAG_WRITE) {
epev.events |= EPOLLOUT;
}
epoll_ctl(ep->epoll_fd, EPOLL_CTL_MOD, event->fd, &epev);
}
static size_t ares_evsys_epoll_wait(ares_event_thread_t *e,
unsigned long timeout_ms)
{
struct epoll_event events[8];
size_t nevents = sizeof(events) / sizeof(*events);
ares_evsys_epoll_t *ep = e->ev_sys_data;
int rv;
size_t i;
size_t cnt = 0;
memset(events, 0, sizeof(events));
rv = epoll_wait(ep->epoll_fd, events, (int)nevents,
(timeout_ms == 0) ? -1 : (int)timeout_ms);
if (rv < 0) {
return 0;
}
nevents = (size_t)rv;
for (i = 0; i < nevents; i++) {
ares_event_t *ev;
ares_event_flags_t flags = 0;
ev = ares__htable_asvp_get_direct(e->ev_handles,
(ares_socket_t)events[i].data.fd);
if (ev == NULL || ev->cb == NULL) {
continue;
}
cnt++;
if (events[i].events & (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
flags |= ARES_EVENT_FLAG_READ;
}
if (events[i].events & EPOLLOUT) {
flags |= ARES_EVENT_FLAG_WRITE;
}
ev->cb(e, ev->fd, ev->data, flags);
}
return cnt;
}
const ares_event_sys_t ares_evsys_epoll = { "epoll",
ares_evsys_epoll_init,
ares_evsys_epoll_destroy,
ares_evsys_epoll_event_add,
ares_evsys_epoll_event_del,
ares_evsys_epoll_event_mod,
ares_evsys_epoll_wait };
#endif

@ -0,0 +1,249 @@
/* 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"
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_EVENT_H
# include <sys/event.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_KQUEUE
typedef struct {
int kqueue_fd;
struct kevent *changelist;
size_t nchanges;
size_t nchanges_alloc;
} ares_evsys_kqueue_t;
static void ares_evsys_kqueue_destroy(ares_event_thread_t *e)
{
ares_evsys_kqueue_t *kq = NULL;
if (e == NULL) {
return;
}
kq = e->ev_sys_data;
if (kq == NULL) {
return;
}
if (kq->kqueue_fd != -1) {
close(kq->kqueue_fd);
}
ares_free(kq->changelist);
ares_free(kq);
e->ev_sys_data = NULL;
}
static ares_bool_t ares_evsys_kqueue_init(ares_event_thread_t *e)
{
ares_evsys_kqueue_t *kq = NULL;
kq = ares_malloc_zero(sizeof(*kq));
if (kq == NULL) {
return ARES_FALSE;
}
e->ev_sys_data = kq;
kq->kqueue_fd = kqueue();
if (kq->kqueue_fd == -1) {
ares_evsys_kqueue_destroy(e);
return ARES_FALSE;
}
# ifdef FD_CLOEXEC
fcntl(kq->kqueue_fd, F_SETFD, FD_CLOEXEC);
# endif
kq->nchanges_alloc = 8;
kq->changelist =
ares_malloc_zero(sizeof(*kq->changelist) * kq->nchanges_alloc);
if (kq->changelist == NULL) {
ares_evsys_kqueue_destroy(e);
return ARES_FALSE;
}
e->ev_signal = ares_pipeevent_create(e);
if (e->ev_signal == NULL) {
ares_evsys_kqueue_destroy(e);
return ARES_FALSE;
}
return ARES_TRUE;
}
static void ares_evsys_kqueue_enqueue(ares_evsys_kqueue_t *kq, int fd,
int16_t filter, uint16_t flags)
{
size_t idx;
if (kq == NULL) {
return;
}
idx = kq->nchanges;
kq->nchanges++;
if (kq->nchanges > kq->nchanges_alloc) {
kq->nchanges_alloc <<= 1;
kq->changelist = ares_realloc_zero(kq->changelist, kq->nchanges_alloc >> 1,
kq->nchanges_alloc);
}
EV_SET(&kq->changelist[idx], fd, filter, flags, 0, 0, 0);
}
static void ares_evsys_kqueue_event_process(ares_event_t *event,
ares_event_flags_t old_flags,
ares_event_flags_t new_flags)
{
ares_event_thread_t *e = event->e;
ares_evsys_kqueue_t *kq;
if (e == NULL) {
return;
}
kq = e->ev_sys_data;
if (kq == NULL) {
return;
}
if (new_flags & ARES_EVENT_FLAG_READ && !(old_flags & ARES_EVENT_FLAG_READ)) {
ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_READ, EV_ADD | EV_ENABLE);
}
if (!(new_flags & ARES_EVENT_FLAG_READ) && old_flags & ARES_EVENT_FLAG_READ) {
ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_READ, EV_DELETE);
}
if (new_flags & ARES_EVENT_FLAG_WRITE &&
!(old_flags & ARES_EVENT_FLAG_WRITE)) {
ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE);
}
if (!(new_flags & ARES_EVENT_FLAG_WRITE) &&
old_flags & ARES_EVENT_FLAG_WRITE) {
ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_WRITE, EV_DELETE);
}
}
static ares_bool_t ares_evsys_kqueue_event_add(ares_event_t *event)
{
ares_evsys_kqueue_event_process(event, 0, event->flags);
return ARES_TRUE;
}
static void ares_evsys_kqueue_event_del(ares_event_t *event)
{
ares_evsys_kqueue_event_process(event, event->flags, 0);
}
static void ares_evsys_kqueue_event_mod(ares_event_t *event,
ares_event_flags_t new_flags)
{
ares_evsys_kqueue_event_process(event, event->flags, new_flags);
}
static size_t ares_evsys_kqueue_wait(ares_event_thread_t *e,
unsigned long timeout_ms)
{
struct kevent events[8];
size_t nevents = sizeof(events) / sizeof(*events);
ares_evsys_kqueue_t *kq = e->ev_sys_data;
int rv;
size_t i;
struct timespec ts;
struct timespec *timeout = NULL;
size_t cnt = 0;
if (timeout_ms != 0) {
ts.tv_sec = timeout_ms / 1000;
ts.tv_nsec = (timeout_ms % 1000) * 1000 * 1000;
timeout = &ts;
}
memset(events, 0, sizeof(events));
rv = kevent(kq->kqueue_fd, kq->changelist, (int)kq->nchanges, events,
(int)nevents, timeout);
if (rv < 0) {
return 0;
}
/* Changelist was consumed */
kq->nchanges = 0;
nevents = (size_t)rv;
for (i = 0; i < nevents; i++) {
ares_event_t *ev;
ares_event_flags_t flags = 0;
ev = ares__htable_asvp_get_direct(e->ev_handles,
(ares_socket_t)events[i].ident);
if (ev == NULL || ev->cb == NULL) {
continue;
}
cnt++;
if (events[i].filter == EVFILT_READ ||
events[i].flags & (EV_EOF | EV_ERROR)) {
flags |= ARES_EVENT_FLAG_READ;
} else {
flags |= ARES_EVENT_FLAG_WRITE;
}
ev->cb(e, ev->fd, ev->data, flags);
}
return cnt;
}
const ares_event_sys_t ares_evsys_kqueue = { "kqueue",
ares_evsys_kqueue_init,
ares_evsys_kqueue_destroy,
ares_evsys_kqueue_event_add,
ares_evsys_kqueue_event_del,
ares_evsys_kqueue_event_mod,
ares_evsys_kqueue_wait };
#endif

@ -0,0 +1,137 @@
/* 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"
#ifdef HAVE_POLL_H
# include <poll.h>
#endif
#if defined(HAVE_POLL)
static ares_bool_t ares_evsys_poll_init(ares_event_thread_t *e)
{
e->ev_signal = ares_pipeevent_create(e);
if (e->ev_signal == NULL) {
return ARES_FALSE;
}
return ARES_TRUE;
}
static void ares_evsys_poll_destroy(ares_event_thread_t *e)
{
(void)e;
}
static ares_bool_t ares_evsys_poll_event_add(ares_event_t *event)
{
(void)event;
return ARES_TRUE;
}
static void ares_evsys_poll_event_del(ares_event_t *event)
{
(void)event;
}
static void ares_evsys_poll_event_mod(ares_event_t *event,
ares_event_flags_t new_flags)
{
(void)event;
(void)new_flags;
}
static size_t ares_evsys_poll_wait(ares_event_thread_t *e,
unsigned long timeout_ms)
{
size_t num_fds = 0;
ares_socket_t *fdlist = ares__htable_asvp_keys(e->ev_handles, &num_fds);
struct pollfd *pollfd = NULL;
int rv;
size_t cnt = 0;
size_t i;
if (num_fds) {
pollfd = ares_malloc_zero(sizeof(*pollfd) * num_fds);
for (i = 0; i < num_fds; i++) {
ares_event_t *ev = ares__htable_asvp_get_direct(e->ev_handles, fdlist[i]);
pollfd[i].fd = ev->fd;
if (ev->flags & ARES_EVENT_FLAG_READ) {
pollfd[i].events |= POLLIN;
}
if (ev->flags & ARES_EVENT_FLAG_WRITE) {
pollfd[i].events |= POLLOUT;
}
}
}
ares_free(fdlist);
rv = poll(pollfd, (nfds_t)num_fds, (timeout_ms == 0) ? -1 : (int)timeout_ms);
if (rv <= 0) {
goto done;
}
for (i = 0; i < num_fds; i++) {
ares_event_t *ev;
ares_event_flags_t flags = 0;
if (pollfd[i].revents == 0) {
continue;
}
cnt++;
ev = ares__htable_asvp_get_direct(e->ev_handles, pollfd[i].fd);
if (ev == NULL || ev->cb == NULL) {
continue;
}
if (pollfd[i].revents & (POLLERR | POLLHUP | POLLIN)) {
flags |= ARES_EVENT_FLAG_READ;
}
if (pollfd[i].revents & POLLOUT) {
flags |= ARES_EVENT_FLAG_WRITE;
}
ev->cb(e, pollfd[i].fd, ev->data, flags);
}
done:
ares_free(pollfd);
return cnt;
}
const ares_event_sys_t ares_evsys_poll = { "poll",
ares_evsys_poll_init,
ares_evsys_poll_destroy, /* NoOp */
ares_evsys_poll_event_add, /* NoOp */
ares_evsys_poll_event_del, /* NoOp */
ares_evsys_poll_event_mod, /* NoOp */
ares_evsys_poll_wait };
#endif

@ -0,0 +1,150 @@
/* 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"
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
/* All systems have select(), but not all have a way to wake, so we require
* pipe() to wake the select() */
#if defined(HAVE_PIPE)
static ares_bool_t ares_evsys_select_init(ares_event_thread_t *e)
{
e->ev_signal = ares_pipeevent_create(e);
if (e->ev_signal == NULL) {
return ARES_FALSE;
}
return ARES_TRUE;
}
static void ares_evsys_select_destroy(ares_event_thread_t *e)
{
(void)e;
}
static ares_bool_t ares_evsys_select_event_add(ares_event_t *event)
{
(void)event;
return ARES_TRUE;
}
static void ares_evsys_select_event_del(ares_event_t *event)
{
(void)event;
}
static void ares_evsys_select_event_mod(ares_event_t *event,
ares_event_flags_t new_flags)
{
(void)event;
(void)new_flags;
}
static size_t ares_evsys_select_wait(ares_event_thread_t *e,
unsigned long timeout_ms)
{
size_t num_fds = 0;
ares_socket_t *fdlist = ares__htable_asvp_keys(e->ev_handles, &num_fds);
int rv;
size_t cnt = 0;
size_t i;
fd_set read_fds;
fd_set write_fds;
int nfds = 0;
struct timeval tv;
struct timeval *tout = NULL;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
for (i = 0; i < num_fds; i++) {
ares_event_t *ev = ares__htable_asvp_get_direct(e->ev_handles, fdlist[i]);
if (ev->flags & ARES_EVENT_FLAG_READ) {
FD_SET(ev->fd, &read_fds);
}
if (ev->flags & ARES_EVENT_FLAG_WRITE) {
FD_SET(ev->fd, &write_fds);
}
if (ev->fd + 1 > nfds) {
nfds = ev->fd + 1;
}
}
if (timeout_ms) {
tv.tv_sec = (int)(timeout_ms / 1000);
tv.tv_usec = (int)((timeout_ms % 1000) * 1000);
tout = &tv;
}
rv = select(nfds, &read_fds, &write_fds, NULL, tout);
if (rv > 0) {
for (i = 0; i < num_fds; i++) {
ares_event_t *ev;
ares_event_flags_t flags = 0;
ev = ares__htable_asvp_get_direct(e->ev_handles, fdlist[i]);
if (ev == NULL || ev->cb == NULL) {
continue;
}
if (FD_ISSET(fdlist[i], &read_fds)) {
flags |= ARES_EVENT_FLAG_READ;
}
if (FD_ISSET(fdlist[i], &write_fds)) {
flags |= ARES_EVENT_FLAG_WRITE;
}
if (flags == 0) {
continue;
}
cnt++;
ev->cb(e, fdlist[i], ev->data, flags);
}
}
ares_free(fdlist);
return cnt;
}
const ares_event_sys_t ares_evsys_select = {
"select",
ares_evsys_select_init,
ares_evsys_select_destroy, /* NoOp */
ares_evsys_select_event_add, /* NoOp */
ares_evsys_select_event_del, /* NoOp */
ares_evsys_select_event_mod, /* NoOp */
ares_evsys_select_wait
};
#endif

@ -0,0 +1,447 @@
/* 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"
static void ares_event_destroy_cb(void *arg)
{
ares_event_t *event = arg;
if (event == NULL) {
return;
}
/* Unregister from the event thread if it was registered with one */
if (event->e) {
ares_event_thread_t *e = event->e;
e->ev_sys->event_del(event);
event->e = NULL;
}
if (event->free_data_cb && event->data) {
event->free_data_cb(event->data);
}
ares_free(event);
}
/* See if a pending update already exists. We don't want to enqueue multiple
* updates for the same event handle. Right now this is O(n) based on number
* of updates already enqueued. In the future, it might make sense to make
* this O(1) with a hashtable. */
static ares_event_t *ares_event_update_find(ares_event_thread_t *e,
ares_socket_t fd, void *data)
{
ares__llist_node_t *node;
for (node = ares__llist_node_first(e->ev_updates); node != NULL;
node = ares__llist_node_next(node)) {
ares_event_t *ev = ares__llist_node_val(node);
if (fd != ARES_SOCKET_BAD && fd == ev->fd) {
return ev;
}
if (fd == ARES_SOCKET_BAD && ev->fd == ARES_SOCKET_BAD &&
data == ev->data) {
return ev;
}
}
return NULL;
}
ares_status_t ares_event_update(ares_event_t **event, ares_event_thread_t *e,
ares_event_flags_t flags, ares_event_cb_t cb,
ares_socket_t fd, void *data,
ares_event_free_data_t free_data_cb,
ares_event_signal_cb_t signal_cb)
{
ares_event_t *ev = NULL;
if (e == NULL || cb == NULL) {
return ARES_EFORMERR;
}
if (event != NULL) {
*event = NULL;
}
/* Validate flags */
if (fd == ARES_SOCKET_BAD) {
if (flags & (ARES_EVENT_FLAG_READ | ARES_EVENT_FLAG_WRITE)) {
return ARES_EFORMERR;
}
if (!(flags & ARES_EVENT_FLAG_OTHER)) {
return ARES_EFORMERR;
}
} else {
if (flags & ARES_EVENT_FLAG_OTHER) {
return ARES_EFORMERR;
}
}
/* That's all the validation we can really do */
/* See if we have a queued update already */
ev = ares_event_update_find(e, fd, data);
if (ev == NULL) {
/* Allocate a new one */
ev = ares_malloc_zero(sizeof(*ev));
if (ev == NULL) {
return ARES_ENOMEM;
}
if (ares__llist_insert_last(e->ev_updates, ev) == NULL) {
ares_free(ev);
return ARES_ENOMEM;
}
}
ev->flags = flags;
ev->fd = fd;
if (ev->cb == NULL) {
ev->cb = cb;
}
if (ev->data == NULL) {
ev->data = data;
}
if (ev->free_data_cb == NULL) {
ev->free_data_cb = free_data_cb;
}
if (ev->signal_cb == NULL) {
ev->signal_cb = signal_cb;
}
if (event != NULL) {
*event = ev;
}
return ARES_SUCCESS;
}
static void ares_event_signal(const ares_event_t *event)
{
if (event == NULL || event->signal_cb == NULL) {
return;
}
event->signal_cb(event);
}
static void ares_event_thread_wake(ares_event_thread_t *e)
{
if (e == NULL) {
return;
}
ares_event_signal(e->ev_signal);
}
static void ares_event_thread_process_fd(ares_event_thread_t *e,
ares_socket_t fd, void *data,
ares_event_flags_t flags)
{
(void)data;
ares_process_fd(e->channel,
(flags & ARES_EVENT_FLAG_READ) ? fd : ARES_SOCKET_BAD,
(flags & ARES_EVENT_FLAG_WRITE) ? fd : ARES_SOCKET_BAD);
}
static void ares_event_thread_sockstate_cb(void *data, ares_socket_t socket_fd,
int readable, int writable)
{
ares_event_thread_t *e = data;
ares_event_flags_t flags = ARES_EVENT_FLAG_NONE;
if (readable) {
flags |= ARES_EVENT_FLAG_READ;
}
if (writable) {
flags |= ARES_EVENT_FLAG_WRITE;
}
/* Update channel fd */
ares__thread_mutex_lock(e->mutex);
ares_event_update(NULL, e, flags, ares_event_thread_process_fd, socket_fd,
NULL, NULL, NULL);
/* Wake the event thread so it properly enqueues any updates */
ares_event_thread_wake(e);
ares__thread_mutex_unlock(e->mutex);
}
static void ares_event_process_updates(ares_event_thread_t *e)
{
ares__llist_node_t *node;
/* Iterate across all updates and apply to internal list, removing from update
* list */
while ((node = ares__llist_node_first(e->ev_updates)) != NULL) {
ares_event_t *newev = ares__llist_node_claim(node);
ares_event_t *oldev =
ares__htable_asvp_get_direct(e->ev_handles, newev->fd);
/* Adding new */
if (oldev == NULL) {
newev->e = e;
/* Don't try to add a new event if all flags are cleared, that's basically
* someone trying to delete something already deleted. Also if it fails
* to add, cleanup. */
if (newev->flags == ARES_EVENT_FLAG_NONE ||
!e->ev_sys->event_add(newev)) {
newev->e = NULL;
ares_event_destroy_cb(newev);
} else {
ares__htable_asvp_insert(e->ev_handles, newev->fd, newev);
}
continue;
}
/* Removal request */
if (newev->flags == ARES_EVENT_FLAG_NONE) {
/* the callback for the removal will call e->ev_sys->event_del(e, event)
*/
ares__htable_asvp_remove(e->ev_handles, newev->fd);
ares_free(newev);
continue;
}
/* Modify request -- only flags can be changed */
e->ev_sys->event_mod(oldev, newev->flags);
oldev->flags = newev->flags;
ares_free(newev);
}
}
static void *ares_event_thread(void *arg)
{
ares_event_thread_t *e = arg;
ares__thread_mutex_lock(e->mutex);
while (e->isup) {
struct timeval tv;
struct timeval *tvout;
unsigned long timeout_ms = 0; /* 0 = unlimited */
tvout = ares_timeout(e->channel, NULL, &tv);
if (tvout != NULL) {
timeout_ms =
(unsigned long)((tvout->tv_sec * 1000) + (tvout->tv_usec / 1000) + 1);
}
ares_event_process_updates(e);
/* Don't hold a mutex while waiting on events */
ares__thread_mutex_unlock(e->mutex);
e->ev_sys->wait(e, timeout_ms);
/* Each iteration should do timeout processing */
if (e->isup) {
ares_process_fd(e->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
}
/* Relock before we loop again */
ares__thread_mutex_lock(e->mutex);
}
ares__thread_mutex_unlock(e->mutex);
return NULL;
}
static void ares_event_thread_destroy_int(ares_event_thread_t *e)
{
ares__llist_node_t *node;
/* Wake thread and tell it to shutdown if it exists */
ares__thread_mutex_lock(e->mutex);
if (e->isup) {
e->isup = ARES_FALSE;
ares_event_thread_wake(e);
}
ares__thread_mutex_unlock(e->mutex);
/* Wait for thread to shutdown */
if (e->thread) {
ares__thread_join(e->thread, NULL);
e->thread = NULL;
}
/* Manually free any updates that weren't processed */
while ((node = ares__llist_node_first(e->ev_updates)) != NULL) {
ares_event_destroy_cb(ares__llist_node_claim(node));
}
ares__llist_destroy(e->ev_updates);
e->ev_updates = NULL;
ares__htable_asvp_destroy(e->ev_handles);
e->ev_handles = NULL;
if (e->ev_sys->destroy) {
e->ev_sys->destroy(e);
}
ares__thread_mutex_destroy(e->mutex);
e->mutex = NULL;
ares_free(e);
}
void ares_event_thread_destroy(ares_channel_t *channel)
{
ares_event_thread_t *e = channel->sock_state_cb_data;
if (e == NULL) {
return;
}
ares_event_thread_destroy_int(e);
}
static const ares_event_sys_t *ares_event_fetch_sys(ares_evsys_t evsys)
{
switch (evsys) {
case ARES_EVSYS_WIN32:
#if defined(_WIN32)
return &ares_evsys_win32;
#else
return NULL;
#endif
case ARES_EVSYS_EPOLL:
#if defined(HAVE_EPOLL)
return &ares_evsys_epoll;
#else
return NULL;
#endif
case ARES_EVSYS_KQUEUE:
#if defined(HAVE_KQUEUE)
return &ares_evsys_kqueue;
#else
return NULL;
#endif
case ARES_EVSYS_POLL:
#if defined(HAVE_POLL)
return &ares_evsys_poll;
#else
return NULL;
#endif
case ARES_EVSYS_SELECT:
#if defined(HAVE_PIPE)
return &ares_evsys_select;
#else
return NULL;
#endif
case ARES_EVSYS_DEFAULT:
default:
#if defined(_WIN32)
return &ares_evsys_win32;
#elif defined(HAVE_KQUEUE)
return &ares_evsys_kqueue;
#elif defined(HAVE_EPOLL)
return &ares_evsys_epoll;
#elif defined(HAVE_POLL)
return &ares_evsys_poll;
#elif defined(HAVE_PIPE)
return &ares_evsys_select;
#else
break;
#endif
}
return NULL;
}
ares_status_t ares_event_thread_init(ares_channel_t *channel)
{
ares_event_thread_t *e;
e = ares_malloc_zero(sizeof(*e));
if (e == NULL) {
return ARES_ENOMEM;
}
e->mutex = ares__thread_mutex_create();
if (e->mutex == NULL) {
ares_event_thread_destroy_int(e);
return ARES_ENOMEM;
}
e->ev_updates = ares__llist_create(NULL);
if (e->ev_updates == NULL) {
ares_event_thread_destroy_int(e);
return ARES_ENOMEM;
}
e->ev_handles = ares__htable_asvp_create(ares_event_destroy_cb);
if (e->ev_handles == NULL) {
ares_event_thread_destroy_int(e);
return ARES_ENOMEM;
}
e->channel = channel;
e->isup = ARES_TRUE;
e->ev_sys = ares_event_fetch_sys(channel->evsys);
if (e->ev_sys == NULL) {
ares_event_thread_destroy_int(e);
return ARES_ENOTIMP;
}
channel->sock_state_cb = ares_event_thread_sockstate_cb;
channel->sock_state_cb_data = e;
if (!e->ev_sys->init(e)) {
ares_event_thread_destroy_int(e);
channel->sock_state_cb = NULL;
channel->sock_state_cb_data = NULL;
return ARES_ESERVFAIL;
}
/* Before starting the thread, process any possible events the initialization
* might have enqueued as we may actually depend on these being valid
* immediately upon return, which may mean before the thread is fully spawned
* and processed the list itself. We don't want any sort of race conditions
* (like the event system wake handle itself). */
ares_event_process_updates(e);
/* Start thread */
if (ares__thread_create(&e->thread, ares_event_thread, e) != ARES_SUCCESS) {
ares_event_thread_destroy_int(e);
channel->sock_state_cb = NULL;
channel->sock_state_cb_data = NULL;
return ARES_ESERVFAIL;
}
return ARES_SUCCESS;
}

@ -0,0 +1,166 @@
/* 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"
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_PIPE
typedef struct {
int filedes[2];
} ares_pipeevent_t;
static void ares_pipeevent_destroy(ares_pipeevent_t *p)
{
if (p->filedes[0] != -1) {
close(p->filedes[0]);
}
if (p->filedes[1] != -1) {
close(p->filedes[1]);
}
ares_free(p);
}
static void ares_pipeevent_destroy_cb(void *arg)
{
ares_pipeevent_destroy(arg);
}
static ares_pipeevent_t *ares_pipeevent_init(void)
{
ares_pipeevent_t *p = ares_malloc_zero(sizeof(*p));
if (p == NULL) {
return NULL;
}
p->filedes[0] = -1;
p->filedes[1] = -1;
# ifdef HAVE_PIPE2
if (pipe2(p->filedes, O_NONBLOCK | O_CLOEXEC) != 0) {
ares_pipeevent_destroy(p);
return NULL;
}
# else
if (pipe(p->filedes) != 0) {
ares_pipeevent_destroy(p);
return NULL;
}
# ifdef O_NONBLOCK
{
int val;
val = fcntl(p->filedes[0], F_GETFL, 0);
if (val >= 0) {
val |= O_NONBLOCK;
}
fcntl(p->filedes[0], F_SETFL, val);
val = fcntl(p->filedes[1], F_GETFL, 0);
if (val >= 0) {
val |= O_NONBLOCK;
}
fcntl(p->filedes[1], F_SETFL, val);
}
# endif
# ifdef O_CLOEXEC
fcntl(p->filedes[0], F_SETFD, O_CLOEXEC);
fcntl(p->filedes[1], F_SETFD, O_CLOEXEC);
# endif
# endif
# ifdef F_SETNOSIGPIPE
fcntl(p->filedes[0], F_SETNOSIGPIPE, 1);
fcntl(p->filedes[1], F_SETNOSIGPIPE, 1);
# endif
return p;
}
static void ares_pipeevent_signal(const ares_event_t *e)
{
ares_pipeevent_t *p;
if (e == NULL || e->data == NULL) {
return;
}
p = e->data;
write(p->filedes[1], "1", 1);
}
static void ares_pipeevent_cb(ares_event_thread_t *e, ares_socket_t fd,
void *data, ares_event_flags_t flags)
{
unsigned char buf[32];
ares_pipeevent_t *p = NULL;
(void)e;
(void)fd;
(void)flags;
if (data == NULL) {
return;
}
p = data;
while (read(p->filedes[0], buf, sizeof(buf)) == sizeof(buf)) {
/* Do nothing */
}
}
ares_event_t *ares_pipeevent_create(ares_event_thread_t *e)
{
ares_event_t *event = NULL;
ares_pipeevent_t *p = NULL;
ares_status_t status;
p = ares_pipeevent_init();
if (p == NULL) {
return NULL;
}
status = ares_event_update(&event, e, ARES_EVENT_FLAG_READ, ares_pipeevent_cb,
p->filedes[0], p, ares_pipeevent_destroy_cb,
ares_pipeevent_signal);
if (status != ARES_SUCCESS) {
ares_pipeevent_destroy(p);
return NULL;
}
return event;
}
#endif

@ -0,0 +1,601 @@
/* 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

@ -0,0 +1,119 @@
/* 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
*/
#ifndef __ARES_EVENT_WIN32_H
#define __ARES_EVENT_WIN32_H
#ifdef _WIN32
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# ifdef HAVE_WS2TCPIP_H
# include <ws2tcpip.h>
# endif
# ifdef HAVE_MSWSOCK_H
# include <mswsock.h>
# endif
# ifdef HAVE_WINDOWS_H
# include <windows.h>
# endif
/* From winternl.h */
/* If WDK is not installed and not using MinGW, provide the needed definitions
*/
typedef LONG NTSTATUS;
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef VOID(NTAPI *PIO_APC_ROUTINE)(PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG Reserved);
/* From ntstatus.h */
# define STATUS_SUCCESS ((NTSTATUS)0x00000000)
# ifndef STATUS_PENDING
# define STATUS_PENDING ((NTSTATUS)0x00000103L)
# endif
# define STATUS_CANCELLED ((NTSTATUS)0xC0000120L)
# define STATUS_NOT_FOUND ((NTSTATUS)0xC0000225L)
/* Not sure what headers might have these */
# define IOCTL_AFD_POLL 0x00012024
# define AFD_POLL_RECEIVE 0x0001
# define AFD_POLL_RECEIVE_EXPEDITED 0x0002
# define AFD_POLL_SEND 0x0004
# define AFD_POLL_DISCONNECT 0x0008
# define AFD_POLL_ABORT 0x0010
# define AFD_POLL_LOCAL_CLOSE 0x0020
# define AFD_POLL_ACCEPT 0x0080
# define AFD_POLL_CONNECT_FAIL 0x0100
typedef struct _AFD_POLL_HANDLE_INFO {
HANDLE Handle;
ULONG Events;
NTSTATUS Status;
} AFD_POLL_HANDLE_INFO, *PAFD_POLL_HANDLE_INFO;
typedef struct _AFD_POLL_INFO {
LARGE_INTEGER Timeout;
ULONG NumberOfHandles;
ULONG Exclusive;
AFD_POLL_HANDLE_INFO Handles[1];
} AFD_POLL_INFO, *PAFD_POLL_INFO;
/* Prototypes for dynamically loaded functions from ntdll.dll */
typedef NTSTATUS(NTAPI *NtCancelIoFileEx_t)(HANDLE FileHandle,
PIO_STATUS_BLOCK IoRequestToCancel,
PIO_STATUS_BLOCK IoStatusBlock);
typedef NTSTATUS(NTAPI *NtDeviceIoControlFile_t)(
HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer,
ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);
/* On UWP/Windows Store, these definitions aren't there for some reason */
# ifndef SIO_BSP_HANDLE_POLL
# define SIO_BSP_HANDLE_POLL 0x4800001D
# endif
# ifndef SIO_BASE_HANDLE
# define SIO_BASE_HANDLE 0x48000022
# endif
# ifndef HANDLE_FLAG_INHERIT
# define HANDLE_FLAG_INHERIT 0x00000001
# endif
#endif /* _WIN32 */
#endif /* __ARES_EVENT_WIN32_H */

@ -389,6 +389,14 @@ int ares_init_options(ares_channel_t **channelptr,
ares_strerror(status)));
}
/* Initialize the event thread */
if (channel->optmask & ARES_OPT_EVENT_THREAD) {
status = ares_event_thread_init(channel);
if (status != ARES_SUCCESS) {
goto done;
}
}
done:
if (status != ARES_SUCCESS) {
ares_destroy(channel);

@ -225,6 +225,10 @@ int ares_save_options(ares_channel_t *channel, struct ares_options *options,
options->qcache_max_ttl = channel->qcache_max_ttl;
}
if (channel->optmask & ARES_OPT_EVENT_THREAD) {
options->evsys = channel->evsys;
}
*optmask = (int)channel->optmask;
return ARES_SUCCESS;
@ -267,6 +271,17 @@ ares_status_t ares__init_by_options(ares_channel_t *channel,
}
/* Easy stuff. */
/* Event Thread requires threading support and is incompatible with socket
* state callbacks */
if (optmask & ARES_OPT_EVENT_THREAD) {
if (!ares_threadsafety())
return ARES_ENOTIMP;
if (optmask & ARES_OPT_SOCK_STATE_CB)
return ARES_EFORMERR;
channel->evsys = options->evsys;
}
if (optmask & ARES_OPT_FLAGS) {
channel->flags = (unsigned int)options->flags;
}

@ -117,6 +117,7 @@ typedef struct ares_rand_state ares_rand_state;
#include "ares__buf.h"
#include "ares_dns_private.h"
#include "ares__iface_ips.h"
#include "ares__threads.h"
#ifndef HAVE_GETENV
# include "ares_getenv.h"
@ -231,9 +232,6 @@ typedef struct ares__qcache ares__qcache_t;
struct ares_hosts_file;
typedef struct ares_hosts_file ares_hosts_file_t;
struct ares__thread_mutex;
typedef struct ares__thread_mutex ares__thread_mutex_t;
struct ares_channeldata {
/* Configuration data */
unsigned int flags;
@ -253,6 +251,7 @@ struct ares_channeldata {
char *lookups;
size_t ednspsz;
unsigned int qcache_max_ttl;
ares_evsys_t evsys;
unsigned int optmask;
/* For binding to local devices and/or IP addresses. Leave
@ -581,6 +580,13 @@ void ares__channel_threading_destroy(ares_channel_t *channel);
void ares__channel_lock(ares_channel_t *channel);
void ares__channel_unlock(ares_channel_t *channel);
struct ares_event_thread;
typedef struct ares_event_thread ares_event_thread_t;
void ares_event_thread_destroy(ares_channel_t *channel);
ares_status_t ares_event_thread_init(ares_channel_t *channel);
#ifdef _MSC_VER
typedef __int64 ares_int64_t;
typedef unsigned __int64 ares_uint64_t;

@ -837,7 +837,6 @@ static size_t ares__calc_query_timeout(const struct query *query)
/* For each trip through the entire server list, we want to double the
* retry from the last retry */
rounds = (query->try_count / num_servers);
if (rounds > 0) {
timeplus <<= rounds;
}
@ -1002,7 +1001,6 @@ ares_status_t ares__send_query(struct query *query, struct timeval *now)
}
timeplus = ares__calc_query_timeout(query);
/* Keep track of queries bucketed by timeout, so we can process
* timeout events quickly.
*/

3
test/Makefile.inc vendored

@ -21,12 +21,13 @@ TESTSOURCES = ares-test-main.cc \
ares-test-live.cc \
ares-test-mock.cc \
ares-test-mock-ai.cc \
ares-test-mock-et.cc \
ares-test-internal.cc \
dns-proto.cc \
dns-proto-test.cc
TESTHEADERS = ares-test.h \
dns-proto.h \
dns-proto.h \
ares-test-ai.h
FUZZSOURCES = ares-test-fuzz.c \

@ -39,9 +39,13 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[ii], "-4") == 0) {
ares::test::families = ares::test::ipv4_family;
ares::test::families_modes = ares::test::ipv4_family_both_modes;
ares::test::evsys_families = ares::test::all_evsys_ipv4_family;
ares::test::evsys_families_modes = ares::test::all_evsys_ipv4_family_both_modes;
} else if (strcmp(argv[ii], "-6") == 0) {
ares::test::families = ares::test::ipv6_family;
ares::test::families_modes = ares::test::ipv6_family_both_modes;
ares::test::evsys_families = ares::test::all_evsys_ipv6_family;
ares::test::evsys_families_modes = ares::test::all_evsys_ipv6_family_both_modes;
} else {
gtest_argv.push_back(argv[ii]);
}

@ -748,28 +748,28 @@ TEST_P(MockChannelTestAI, FamilyV4ServiceName) {
}
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockChannelTestAI,
::testing::ValuesIn(ares::test::families_modes));
::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockUDPChannelTestAI,
::testing::ValuesIn(ares::test::families));
::testing::ValuesIn(ares::test::families), PrintFamily);
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockTCPChannelTestAI,
::testing::ValuesIn(ares::test::families));
::testing::ValuesIn(ares::test::families), PrintFamily);
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockExtraOptsTestAI,
::testing::ValuesIn(ares::test::families_modes));
::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockExtraOptsNDots5TestAI,
::testing::ValuesIn(ares::test::families_modes));
::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockNoCheckRespChannelTestAI,
::testing::ValuesIn(ares::test::families_modes));
::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamiliesAI, MockEDNSChannelTestAI,
::testing::ValuesIn(ares::test::families_modes));
::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(TransportModesAI, NoRotateMultiMockTestAI,
::testing::ValuesIn(ares::test::families_modes));
::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
} // namespace test

File diff suppressed because it is too large Load Diff

@ -1442,23 +1442,41 @@ TEST_P(NoRotateMultiMockTest, ServerNoResponseFailover) {
EXPECT_EQ("{'www.example.com' aliases=[] addrs=[2.3.4.5]}", ss4.str());
}
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockChannelTest, ::testing::ValuesIn(ares::test::families_modes));
std::string PrintFamilyMode(const testing::TestParamInfo<std::pair<int, bool>> &info)
{
std::string name;
name += af_tostr(std::get<0>(info.param));
name += "_";
name += mode_tostr(std::get<1>(info.param));
return name;
}
std::string PrintFamily(const testing::TestParamInfo<int> &info)
{
std::string name;
name += af_tostr(info.param);
return name;
}
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockChannelTest, ::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockUDPChannelTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockUDPChannelTest, ::testing::ValuesIn(ares::test::families), PrintFamily);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockUDPMaxQueriesTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockUDPMaxQueriesTest, ::testing::ValuesIn(ares::test::families), PrintFamily);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, CacheQueriesTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, CacheQueriesTest, ::testing::ValuesIn(ares::test::families), PrintFamily);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockTCPChannelTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockTCPChannelTest, ::testing::ValuesIn(ares::test::families), PrintFamily);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockExtraOptsTest, ::testing::ValuesIn(ares::test::families_modes));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockExtraOptsTest, ::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockNoCheckRespChannelTest, ::testing::ValuesIn(ares::test::families_modes));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockNoCheckRespChannelTest, ::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockEDNSChannelTest, ::testing::ValuesIn(ares::test::families_modes));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockEDNSChannelTest, ::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
INSTANTIATE_TEST_SUITE_P(TransportModes, NoRotateMultiMockTest, ::testing::ValuesIn(ares::test::families_modes));
INSTANTIATE_TEST_SUITE_P(TransportModes, NoRotateMultiMockTest, ::testing::ValuesIn(ares::test::families_modes), PrintFamilyMode);
} // namespace test
} // namespace ares

256
test/ares-test.cc vendored

@ -92,12 +92,161 @@ const std::vector<std::pair<int, bool>> ipv6_family_both_modes = {
std::make_pair<int, bool>(AF_INET6, true)
};
const std::vector<std::tuple<ares_evsys_t, int, bool>> all_evsys_ipv4_family_both_modes = {
#ifdef _WIN32
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET, true),
#endif
#ifdef HAVE_KQUEUE
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET, true),
#endif
#ifdef HAVE_EPOLL
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET, true),
#endif
#ifdef HAVE_POLL
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET, true),
#endif
#ifdef HAVE_PIPE
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET, true),
#endif
};
const std::vector<std::tuple<ares_evsys_t, int, bool>> all_evsys_ipv6_family_both_modes = {
#ifdef _WIN32
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET6, true),
#endif
#ifdef HAVE_KQUEUE
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET6, true),
#endif
#ifdef HAVE_EPOLL
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET6, true),
#endif
#ifdef HAVE_POLL
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET6, true),
#endif
#ifdef HAVE_PIPE
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET6, true),
#endif
};
const std::vector<std::tuple<ares_evsys_t, int, bool>> all_evsys_both_families_both_modes = {
#ifdef _WIN32
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET, true),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_WIN32, AF_INET6, true),
#endif
#ifdef HAVE_KQUEUE
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET, true),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_KQUEUE, AF_INET6, true),
#endif
#ifdef HAVE_EPOLL
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET, true),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_EPOLL, AF_INET6, true),
#endif
#ifdef HAVE_POLL
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET, true),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_POLL, AF_INET6, true),
#endif
#ifdef HAVE_PIPE
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET, true),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET6, false),
std::make_tuple<ares_evsys_t, int, bool>(ARES_EVSYS_SELECT, AF_INET6, true),
#endif
};
std::vector<std::tuple<ares_evsys_t, int, bool>> evsys_families_modes = all_evsys_both_families_both_modes;
const std::vector<std::tuple<ares_evsys_t, int>> all_evsys_ipv4_family = {
#ifdef _WIN32
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_WIN32, AF_INET),
#endif
#ifdef HAVE_KQUEUE
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_KQUEUE, AF_INET),
#endif
#ifdef HAVE_EPOLL
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_EPOLL, AF_INET),
#endif
#ifdef HAVE_POLL
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_POLL, AF_INET),
#endif
#ifdef HAVE_PIPE
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_SELECT, AF_INET),
#endif
};
const std::vector<std::tuple<ares_evsys_t, int>> all_evsys_ipv6_family = {
#ifdef _WIN32
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_WIN32, AF_INET6),
#endif
#ifdef HAVE_KQUEUE
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_KQUEUE, AF_INET6),
#endif
#ifdef HAVE_EPOLL
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_EPOLL, AF_INET6),
#endif
#ifdef HAVE_POLL
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_POLL, AF_INET6),
#endif
#ifdef HAVE_PIPE
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_SELECT, AF_INET6),
#endif
};
const std::vector<std::tuple<ares_evsys_t, int>> all_evsys_both_families = {
#ifdef _WIN32
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_WIN32, AF_INET),
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_WIN32, AF_INET6),
#endif
#ifdef HAVE_KQUEUE
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_KQUEUE, AF_INET),
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_KQUEUE, AF_INET6),
#endif
#ifdef HAVE_EPOLL
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_EPOLL, AF_INET),
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_EPOLL, AF_INET6),
#endif
#ifdef HAVE_POLL
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_POLL, AF_INET),
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_POLL, AF_INET6),
#endif
#ifdef HAVE_PIPE
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_SELECT, AF_INET),
std::make_tuple<ares_evsys_t, int>(ARES_EVSYS_SELECT, AF_INET6),
#endif
};
std::vector<std::tuple<ares_evsys_t, int>> evsys_families = all_evsys_both_families;
// Which parameters to use in tests
std::vector<int> families = both_families;
std::vector<std::pair<int, bool>> families_modes = both_families_both_modes;
unsigned long long LibraryTest::fails_ = 0;
std::map<size_t, int> LibraryTest::size_fails_;
std::mutex LibraryTest::lock_;
void ProcessWork(ares_channel_t *channel,
std::function<std::set<ares_socket_t>()> get_extrafds,
@ -188,33 +337,132 @@ void ProcessWork(ares_channel_t *channel,
}
}
void ProcessWorkEventThread(ares_channel_t *channel,
std::function<std::set<ares_socket_t>()> get_extrafds,
std::function<void(ares_socket_t)> process_extra,
unsigned int cancel_ms) {
int nfds=0, count;
fd_set readers;
size_t retry_cnt = 1;
#ifndef CARES_SYMBOL_HIDING
struct timeval tv_begin = ares__tvnow();
struct timeval tv_cancel = tv_begin;
if (cancel_ms) {
if (verbose) std::cerr << "ares_cancel will be called after " << cancel_ms << "ms" << std::endl;
tv_cancel.tv_sec += (cancel_ms / 1000);
tv_cancel.tv_usec += ((cancel_ms % 1000) * 1000);
}
#else
if (cancel_ms) {
std::cerr << "library built with symbol hiding, can't test with cancel support" << std::endl;
return;
}
#endif
while (true) {
#ifndef CARES_SYMBOL_HIDING
struct timeval tv_now = ares__tvnow();
struct timeval tv_remaining;
#endif
struct timeval tv;
/* c-ares is using its own event thread, so we only need to monitor the
* extrafds passed in */
FD_ZERO(&readers);
std::set<ares_socket_t> extrafds = get_extrafds();
for (ares_socket_t extrafd : extrafds) {
FD_SET(extrafd, &readers);
if (extrafd >= (ares_socket_t)nfds) {
nfds = (int)extrafd + 1;
}
}
/* If ares_timeout returns NULL, it means there are no requests in queue,
* so we can break out, but lets loop one additional time just incase we
* have some weird multithreading issue where a result hasn't yet been
* delivered. This is really just an odd case, its not "normal" to try
* to determine if an event has been delivered by solely monitoring the
* channel, really we should know how many callbacks we expect and how
* many we get, but that's not easy to do in a test framework. */
if (ares_timeout(channel, NULL, &tv) == NULL) {
if (retry_cnt == 0)
return;
retry_cnt--;
} else {
retry_cnt = 1;
}
#ifndef CARES_SYMBOL_HIDING
if (cancel_ms) {
unsigned int remaining_ms;
ares__timeval_remaining(&tv_remaining,
&tv_now,
&tv_cancel);
remaining_ms = (unsigned int)((tv_remaining.tv_sec * 1000) + (tv_remaining.tv_usec / 1000));
if (remaining_ms == 0) {
if (verbose) std::cerr << "Issuing ares_cancel()" << std::endl;
ares_cancel(channel);
cancel_ms = 0; /* Disable issuing cancel again */
}
}
#endif
/* We just always wait 50ms then recheck. Not doing any complex signalling. */
tv.tv_sec = 0;
tv.tv_usec = 50000;
count = select(nfds, &readers, nullptr, nullptr, &tv);
if (count < 0) {
fprintf(stderr, "select() failed, errno %d\n", errno);
return;
}
// Let the provided callback process any activity on the extra FD.
for (ares_socket_t extrafd : extrafds) {
if (FD_ISSET(extrafd, &readers)) {
process_extra(extrafd);
}
}
}
}
// static
void LibraryTest::SetAllocFail(int nth) {
lock_.lock();
assert(nth > 0);
assert(nth <= (int)(8 * sizeof(fails_)));
fails_ |= (1LL << (nth - 1));
lock_.unlock();
}
// static
void LibraryTest::SetAllocSizeFail(size_t size) {
lock_.lock();
size_fails_[size]++;
lock_.unlock();
}
// static
void LibraryTest::ClearFails() {
lock_.lock();
fails_ = 0;
size_fails_.clear();
lock_.unlock();
}
// static
bool LibraryTest::ShouldAllocFail(size_t size) {
lock_.lock();
bool fail = (fails_ & 0x01);
fails_ >>= 1;
if (size_fails_[size] > 0) {
size_fails_[size]--;
fail = true;
}
lock_.unlock();
return fail;
}
@ -647,6 +895,14 @@ void MockChannelOptsTest::Process(unsigned int cancel_ms) {
cancel_ms);
}
void MockEventThreadOptsTest::Process(unsigned int cancel_ms) {
using namespace std::placeholders;
ProcessWorkEventThread(channel_,
std::bind(&MockEventThreadOptsTest::fds, this),
std::bind(&MockEventThreadOptsTest::ProcessFD, this, _1),
cancel_ms);
}
std::ostream& operator<<(std::ostream& os, const HostResult& result) {
os << '{';
if (result.done_) {

81
test/ares-test.h vendored

@ -49,6 +49,7 @@
#include <memory>
#include <set>
#include <string>
#include <mutex>
#include <utility>
#include <vector>
@ -68,9 +69,20 @@ extern const std::vector<std::pair<int, bool>> both_families_both_modes;
extern const std::vector<std::pair<int, bool>> ipv4_family_both_modes;
extern const std::vector<std::pair<int, bool>> ipv6_family_both_modes;
extern const std::vector<std::tuple<ares_evsys_t, int, bool>> all_evsys_ipv4_family_both_modes;
extern const std::vector<std::tuple<ares_evsys_t, int, bool>> all_evsys_ipv6_family_both_modes;
extern const std::vector<std::tuple<ares_evsys_t, int, bool>> all_evsys_both_families_both_modes;
extern const std::vector<std::tuple<ares_evsys_t, int>> all_evsys_ipv4_family;
extern const std::vector<std::tuple<ares_evsys_t, int>> all_evsys_ipv6_family;
extern const std::vector<std::tuple<ares_evsys_t, int>> all_evsys_both_families;
// Which parameters to use in tests
extern std::vector<int> families;
extern std::vector<std::pair<int, bool>> families_modes;
extern std::vector<int> families;
extern std::vector<std::tuple<ares_evsys_t, int>> evsys_families;
extern std::vector<std::pair<int, bool>> families_modes;
extern std::vector<std::tuple<ares_evsys_t, int, bool>> evsys_families_modes;
// Process all pending work on ares-owned file descriptors, plus
// optionally the given set-of-FDs + work function.
@ -78,8 +90,19 @@ void ProcessWork(ares_channel_t *cha
std::function<std::set<ares_socket_t>()> get_extrafds,
std::function<void(ares_socket_t)> process_extra,
unsigned int cancel_ms = 0);
void ProcessWorkEventThread(ares_channel_t *channel,
std::function<std::set<ares_socket_t>()> get_extrafds,
std::function<void(ares_socket_t)> process_extra,
unsigned int cancel_ms);
std::set<ares_socket_t> NoExtraFDs();
const char *af_tostr(int af);
const char *mode_tostr(bool mode);
std::string PrintFamilyMode(const testing::TestParamInfo<std::pair<int, bool>> &info);
std::string PrintFamily(const testing::TestParamInfo<int> &info);
// Test fixture that ensures library initialization, and allows
// memory allocations to be failed.
class LibraryTest : public ::testing::Test {
@ -113,6 +136,7 @@ private:
static bool ShouldAllocFail(size_t size);
static unsigned long long fails_;
static std::map<size_t, int> size_fails_;
static std::mutex lock_;
};
// Test fixture that uses a default channel.
@ -324,6 +348,59 @@ public:
}
};
class MockEventThreadOptsTest : public MockChannelOptsTest {
public:
MockEventThreadOptsTest(int count, ares_evsys_t evsys, int family, bool force_tcp,
struct ares_options *givenopts, int optmask)
: MockChannelOptsTest(count, family, force_tcp, FillOptionsET(&evopts_, givenopts, evsys), optmask | ARES_OPT_EVENT_THREAD)
{
}
void Process(unsigned int cancel_ms = 0);
static struct ares_options *FillOptionsET(struct ares_options *opts, struct ares_options *givenopts, ares_evsys_t evsys) {
if (givenopts) {
memcpy(opts, givenopts, sizeof(*opts));
} else {
memset(opts, 0, sizeof(*opts));
}
opts->evsys = evsys;
return opts;
}
private:
struct ares_options evopts_;
};
class MockEventThreadTest
: public MockEventThreadOptsTest,
public ::testing::WithParamInterface<std::tuple<ares_evsys_t, int, bool>> {
public:
MockEventThreadTest()
: MockEventThreadOptsTest(1, std::get<0>(GetParam()), std::get<1>(GetParam()), std::get<2>(GetParam()), nullptr, 0)
{
}
};
class MockUDPEventThreadTest : public MockEventThreadOptsTest,
public ::testing::WithParamInterface<std::tuple<ares_evsys_t,int>> {
public:
MockUDPEventThreadTest() : MockEventThreadOptsTest(1, std::get<0>(GetParam()), std::get<1>(GetParam()), false, nullptr, 0)
{
}
};
class MockTCPEventThreadTest : public MockEventThreadOptsTest,
public ::testing::WithParamInterface<std::tuple<ares_evsys_t,int>> {
public:
MockTCPEventThreadTest() : MockEventThreadOptsTest(1, std::get<0>(GetParam()), std::get<1>(GetParam()), true, nullptr, 0)
{
}
};
// gMock action to set the reply for a mock server.
ACTION_P2(SetReplyData, mockserver, data)
{

Loading…
Cancel
Save