From f77e52665278c21353fea96c2de3d430c8ee3063 Mon Sep 17 00:00:00 2001 From: psychon Date: Tue, 30 Jun 2009 09:56:01 +0000 Subject: [PATCH] Add c-ares support Every socket which is based on CZNCSock will now use c-ares for its name resolving. This is possible thanks to CSocket's Csock::GetAddrInfo() which lets one override the DNS lookup. This can be disabled with --disable-c-ares. If IPv6 is enabled and Csocket didn't specify which kind of lookup (ipv4/ipv6) it wants, we first do an ipv4 lookup. If that lookup doesn't yield any useful result, we try again with an ipv6 lookup. If one wants to force ipv6 usage on a domain which also resolves to an ipv4 address, he has to set an ipv6 vhost. git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@1551 726aef4b-f618-498e-8847-2d620e286838 --- Csocket.h | 2 +- Socket.cpp | 184 +++++++++++++++++++++++++++++++++++++++++- Socket.h | 33 +++++++- configure | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++- configure.in | 13 ++- 5 files changed, 441 insertions(+), 11 deletions(-) diff --git a/Csocket.h b/Csocket.h index eaf7fc02..3d24865d 100644 --- a/Csocket.h +++ b/Csocket.h @@ -1826,7 +1826,7 @@ protected: { return( select( nfds, readfds, writefds, exceptfds, timeout ) ); } -private: +protected: /** * fills a map of socks to a message for check * map is empty if none are ready, check GetErrno() for the error, if not SUCCESS Select() failed diff --git a/Socket.cpp b/Socket.cpp index 9a0a711e..ed8903bc 100644 --- a/Socket.cpp +++ b/Socket.cpp @@ -7,11 +7,187 @@ */ #include "Socket.h" +#ifdef HAVE_ARES +#include +#endif -CSockManager::CSockManager() : TSocketManager() -{ +#ifdef HAVE_ARES +struct DNSLookup { + bool bSocketDead; + bool bLookupDone; + // When both of the above are true, this struct can be freed + + // Query + CString sHost; + CSSockAddr::EAFRequire family; + + // Result + int ares_status; + CSSockAddr addr; +}; + +static ares_channel& GetAres() { + static ares_channel m_ares; + return m_ares; +} +#endif + +CZNCSock::~CZNCSock() { +#ifdef HAVE_ARES + if (m_dns_lookup) { + m_dns_lookup->bSocketDead = true; + if (m_dns_lookup->bLookupDone) + delete m_dns_lookup; + m_dns_lookup = NULL; + } +#endif } -CSockManager::~CSockManager() -{ +CSockManager::CSockManager() : TSocketManager() { +#ifdef HAVE_ARES + int i = ares_init(&GetAres()); + if (i != ARES_SUCCESS) { + CUtils::PrintError("Could not initialize c-ares: " + CString(ares_strerror(i))); + exit(0); + } + DEBUG("Successfully initialized c-ares"); +#endif } + +CSockManager::~CSockManager() { +#ifdef HAVE_ARES + ares_destroy(GetAres()); +#endif +} + +#ifdef HAVE_ARES +int CSockManager::Select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ + int ret; + + // We assume that nfds is already the max. number of sockets allowed by + // the OS, so we don't need to update it here. + ares_fds(GetAres(), readfds, writefds); + ares_timeout(GetAres(), timeout, timeout); + + ret = ::select(nfds, readfds, writefds, exceptfds, timeout); + + ares_process(GetAres(), readfds, writefds); + + return ret; +} + +void CZNCSock::ares_callback(void *lookup, int status, int timeout, struct hostent *h) { + struct DNSLookup *p = (struct DNSLookup *) lookup; + p->bLookupDone = true; + + DEBUG("DNS lookup done for " << p->sHost); + if (p->bSocketDead) { + delete p; + return; + } + + p->ares_status = status; + if (!h) { +#ifdef HAVE_IPV6 + if (p->family == CSSockAddr::RAF_ANY) { + // Try an AAAA lookup + p->family = CSSockAddr::RAF_INET6; + DEBUG("Retrying with AAAA"); + p->bLookupDone = false; + ares_gethostbyname(GetAres(), p->sHost.c_str(), p->family, + ares_callback, p); + return; + } +#endif + if (status == ARES_SUCCESS) + p->ares_status = ARES_ENOTIMP; + return; + } + + if (h->h_addrtype == AF_INET && h->h_length == sizeof(in_addr)) { + memcpy(p->addr.GetAddr(), h->h_addr_list[0], sizeof(in_addr)); + p->addr.SetIPv6(false); + } +#ifdef HAVE_IPV6 + else if (h->h_addrtype == AF_INET6 && h->h_length == sizeof(in6_addr)) { + memcpy(p->addr.GetAddr6(), h->h_addr_list[0], sizeof(in6_addr)); + p->addr.SetIPv6(true); + } +#endif + else + DEBUG(__PRETTY_FUNCTION__ << ": Got unknown address with length " << h->h_length); +} + +int CZNCSock::GetAddrInfo(const CS_STRING &sHostname, CSSockAddr &csSockAddr) { + // If this is an ip address, no lookup is necessary +#ifdef HAVE_IPV6 + if (inet_pton(AF_INET6, sHostname.c_str(), csSockAddr.GetAddr6()) > 0) { + csSockAddr.SetIPv6(true); + SetIPv6(true); + return 0; + } +#endif + if (inet_pton(AF_INET, sHostname.c_str(), csSockAddr.GetAddr()) > 0) { + csSockAddr.SetIPv6(false); + SetIPv6(false); + return 0; + } + + if (!m_dns_lookup) { + DEBUG("New dns lookup for " << sHostname); + + m_dns_lookup = new struct DNSLookup; + m_dns_lookup->bSocketDead = false; + m_dns_lookup->bLookupDone = false; + m_dns_lookup->sHost = sHostname; + m_dns_lookup->ares_status = 0; + m_dns_lookup->family = csSockAddr.GetAFRequire(); + + CSSockAddr::EAFRequire family = csSockAddr.GetAFRequire(); + if (family == CSSockAddr::RAF_ANY) { + // post-1.6.0 c-ares (=current CVS versions) support + // lookups with AF_UNSPEC which means it first tries an + // AAAA lookup and then fails back to an A lookup. + // Older versions (= any version out there) just + // generate an "address family not supported" error + // message if you feed them, so we can't use this nice + // feature for now. + family = CSSockAddr::RAF_INET; + } + ares_gethostbyname(GetAres(), sHostname.c_str(), family, + ares_callback, m_dns_lookup); + } + + if (m_dns_lookup->sHost != sHostname) + // This *cannot* happen + DEBUG(__PRETTY_FUNCTION__ << ": Query target for an active DNS query changed!"); + + if (!m_dns_lookup->bLookupDone) { + DEBUG("waiting for dns on [" << sHostname << "] to finish..."); + return EAGAIN; + } + + if (m_dns_lookup->ares_status != ARES_SUCCESS) { + DEBUG("Error while looking up [" << sHostname << "]: " + << ares_strerror(m_dns_lookup->ares_status)); + delete m_dns_lookup; + m_dns_lookup = NULL; + return ETIMEDOUT; + } + +#ifdef HAVE_IPV6 + memcpy(csSockAddr.GetAddr6(), m_dns_lookup->addr.GetAddr6(), sizeof(in6_addr)); +#endif + memcpy(csSockAddr.GetAddr(), m_dns_lookup->addr.GetAddr(), sizeof(in_addr)); + csSockAddr.SetIPv6(m_dns_lookup->addr.GetIPv6()); + SetIPv6(csSockAddr.GetIPv6()); + + DEBUG("GetAddrInfo() done for " << sHostname.c_str()); + + delete m_dns_lookup; + m_dns_lookup = NULL; + + return 0; +} +#endif diff --git a/Socket.h b/Socket.h index cda94fd2..65ebc883 100644 --- a/Socket.h +++ b/Socket.h @@ -11,11 +11,32 @@ #include "Csocket.h" +#ifdef HAVE_ARES +struct DNSLookup; +#endif + class CZNCSock : public Csock { public: - CZNCSock(int timeout = 60) : Csock(timeout) {} - CZNCSock(const CString& sHost, u_short port, int timeout = 60) : Csock(sHost, port, timeout) {} - ~CZNCSock() {} + CZNCSock(int timeout = 60) : Csock(timeout) { +#ifdef HAVE_ARES + m_dns_lookup = NULL; +#endif + } + CZNCSock(const CString& sHost, u_short port, int timeout = 60) : Csock(sHost, port, timeout) { +#ifdef HAVE_ARES + m_dns_lookup = NULL; +#endif + } + + ~CZNCSock(); + +#ifdef HAVE_ARES + static void ares_callback(void *lookup, int status, int timeout, struct hostent *h); + virtual int GetAddrInfo(const CS_STRING & sHostname, CSSockAddr & csSockAddr); + +private: + struct DNSLookup *m_dns_lookup; +#endif }; class CSockManager : public TSocketManager { @@ -79,6 +100,12 @@ public: } private: protected: +#ifdef HAVE_ARES + int Select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); + +private: + using TSocketManager::Select; +#endif /* HAVE_ARES */ }; #endif /* SOCKET_H */ diff --git a/configure b/configure index 87d3df50..5ec0422d 100755 --- a/configure +++ b/configure @@ -612,6 +612,9 @@ LIBZNCDIR LIBZNC MODFLAGS PERL +c_ares_LIBS +c_ares_CFLAGS +PKG_CONFIG host_os host_vendor host_cpu @@ -675,6 +678,7 @@ enable_openssl enable_perl enable_sasl enable_optimization +enable_c_ares with_module_prefix with_module_data_prefix ' @@ -686,7 +690,10 @@ CXXFLAGS LDFLAGS LIBS CPPFLAGS -CCC' +CCC +PKG_CONFIG +c_ares_CFLAGS +c_ares_LIBS' # Initialize some variables set by options. @@ -1320,6 +1327,7 @@ Optional Features: --enable-sasl enable sasl --disable-optimization Disable some compiler optimizations to decrease memory usage while compiling + --disable-c-ares disable c-ares usage Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1337,6 +1345,10 @@ Some influential environment variables: LIBS libraries to pass to the linker, e.g. -l CPPFLAGS C/C++/Objective C preprocessor flags, e.g. -I if you have headers in a nonstandard directory + PKG_CONFIG path to pkg-config utility + c_ares_CFLAGS + C compiler flags for c_ares, overriding pkg-config + c_ares_LIBS linker flags for c_ares, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -2634,6 +2646,13 @@ else OPTIMIZE="yes" fi +# Check whether --enable-c-ares was given. +if test "${enable_c_ares+set}" = set; then + enableval=$enable_c_ares; ARES="$enableval" +else + ARES="yes" +fi + if test "$DEBUG" != "no"; then appendCXX -ggdb -D_DEBUG @@ -2819,6 +2838,202 @@ if test "x$ac_cv_func_lstat" = x""yes; then fi +if test "x$ARES" = "xyes"; then + + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_path_PKG_CONFIG+set}" = set; then + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:$LINENO: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_PKG_CONFIG"; then + ac_pt_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_path_ac_pt_PKG_CONFIG+set}" = set; then + $as_echo_n "(cached) " >&6 +else + case $ac_pt_PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG +if test -n "$ac_pt_PKG_CONFIG"; then + { $as_echo "$as_me:$LINENO: result: $ac_pt_PKG_CONFIG" >&5 +$as_echo "$ac_pt_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:$LINENO: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_pt_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +fi + +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=0.9.0 + { $as_echo "$as_me:$LINENO: checking pkg-config is at least version $_pkg_min_version" >&5 +$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + PKG_CONFIG="" + fi + +fi + +pkg_failed=no +{ $as_echo "$as_me:$LINENO: checking for c_ares" >&5 +$as_echo_n "checking for c_ares... " >&6; } + +if test -n "$PKG_CONFIG"; then + if test -n "$c_ares_CFLAGS"; then + pkg_cv_c_ares_CFLAGS="$c_ares_CFLAGS" + else + if test -n "$PKG_CONFIG" && \ + { ($as_echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libcares\"") >&5 + ($PKG_CONFIG --exists --print-errors "libcares") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + pkg_cv_c_ares_CFLAGS=`$PKG_CONFIG --cflags "libcares" 2>/dev/null` +else + pkg_failed=yes +fi + fi +else + pkg_failed=untried +fi +if test -n "$PKG_CONFIG"; then + if test -n "$c_ares_LIBS"; then + pkg_cv_c_ares_LIBS="$c_ares_LIBS" + else + if test -n "$PKG_CONFIG" && \ + { ($as_echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libcares\"") >&5 + ($PKG_CONFIG --exists --print-errors "libcares") 2>&5 + ac_status=$? + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then + pkg_cv_c_ares_LIBS=`$PKG_CONFIG --libs "libcares" 2>/dev/null` +else + pkg_failed=yes +fi + fi +else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + c_ares_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "libcares"` + else + c_ares_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "libcares"` + fi + # Put the nasty error message in config.log where it belongs + echo "$c_ares_PKG_ERRORS" >&5 + + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:$LINENO: error: c-ares not found. Try --disable-c-ares." >&5 +$as_echo "$as_me: error: c-ares not found. Try --disable-c-ares." >&2;} + { (exit 1); exit 1; }; } +elif test $pkg_failed = untried; then + { { $as_echo "$as_me:$LINENO: error: c-ares not found. Try --disable-c-ares." >&5 +$as_echo "$as_me: error: c-ares not found. Try --disable-c-ares." >&2;} + { (exit 1); exit 1; }; } +else + c_ares_CFLAGS=$pkg_cv_c_ares_CFLAGS + c_ares_LIBS=$pkg_cv_c_ares_LIBS + { $as_echo "$as_me:$LINENO: result: yes" >&5 +$as_echo "yes" >&6; } + : +fi + appendCXX $c_ares_CFLAGS -DHAVE_ARES + appendLib $c_ares_LIBS +fi + if test -z "$NOSSL"; then if test -n "$OPENSSL"; then appendLib -L${OPENSSL}/lib @@ -4584,7 +4799,8 @@ if test x"$NOSSL" = "x1" ; then else echo "openssl: yes" fi -echo modules: $MODULES +echo "c-ares: $ARES" +echo "modules: $MODULES" if test x"$PERL" = "x" ; then echo "perl: no" else diff --git a/configure.in b/configure.in index 1d4f582d..a931ba42 100644 --- a/configure.in +++ b/configure.in @@ -90,6 +90,10 @@ AC_ARG_ENABLE( [optimization], decrease memory usage while compiling]), [OPTIMIZE="$enableval"], [OPTIMIZE="yes"]) +AC_ARG_ENABLE( [c-ares], + AS_HELP_STRING([--disable-c-ares], [disable c-ares usage]), + [ARES="$enableval"], + [ARES="yes"]) if test "$DEBUG" != "no"; then appendCXX -ggdb -D_DEBUG @@ -112,6 +116,12 @@ fi AC_CHECK_LIB( gnugetopt, getopt_long,) AC_CHECK_FUNC([lstat], [appendCXX -DHAVE_LSTAT]) +if test "x$ARES" = "xyes"; then + PKG_CHECK_MODULES([c_ares], [libcares],, AC_MSG_ERROR([c-ares not found. Try --disable-c-ares.])) + appendCXX $c_ares_CFLAGS -DHAVE_ARES + appendLib $c_ares_LIBS +fi + if test -z "$NOSSL"; then if test -n "$OPENSSL"; then appendLib -L${OPENSSL}/lib @@ -254,7 +264,8 @@ if test x"$NOSSL" = "x1" ; then else echo "openssl: yes" fi -echo modules: $MODULES +echo "c-ares: $ARES" +echo "modules: $MODULES" if test x"$PERL" = "x" ; then echo "perl: no" else