From 5b56007828f20c763df3f36ceed809188880663e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 6 Jan 2017 22:58:18 +0100 Subject: Use udns instead of c-ares fix #3226 --- .gitlab-ci.yml | 20 +- CHANGELOG.rst | 2 + CMakeLists.txt | 4 +- INSTALL.rst | 4 +- docker/biboumi-test/debian/Dockerfile | 2 +- docker/biboumi-test/fedora/Dockerfile | 2 +- louloulibs/CMakeLists.txt | 20 +- louloulibs/cmake/Modules/FindCARES.cmake | 37 --- louloulibs/cmake/Modules/FindUDNS.cmake | 37 +++ louloulibs/louloulibs.h.cmake | 2 +- louloulibs/network/dns_handler.cpp | 125 ++-------- louloulibs/network/dns_handler.hpp | 46 +--- louloulibs/network/dns_socket_handler.cpp | 32 ++- louloulibs/network/dns_socket_handler.hpp | 29 +-- louloulibs/network/resolver.cpp | 280 ++++++++++++++--------- louloulibs/network/resolver.hpp | 45 ++-- louloulibs/network/tcp_client_socket_handler.cpp | 6 +- src/main.cpp | 19 +- 18 files changed, 320 insertions(+), 392 deletions(-) delete mode 100644 louloulibs/cmake/Modules/FindCARES.cmake create mode 100644 louloulibs/cmake/Modules/FindUDNS.cmake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1c62fa6..d03a47f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ variables: COMPILER: "g++" BUILD_TYPE: "Debug" BOTAN: "-DWITH_BOTAN=1" - CARES: "-DWITH_CARES=1" + UDNS: "-DWITH_UDNS=1" SYSTEMD: "-DWITH_SYSTEMD=1" LIBIDN: "-DWITH_LIBIDN=1" LITESQL: "-DWITH_LITESQL=1" @@ -20,8 +20,8 @@ variables: - docker image: docker.louiz.org/biboumi-test-fedora:latest script: - - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL}" - - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}" + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc || echo 1) - make check -j$(nproc || echo 1) @@ -32,7 +32,7 @@ build:1: build:2: variables: - CARES: "-DWITHOUT_CARES=1" + UDNS: "-DWITHOUT_UDNS=1" <<: *basic_build build:3: @@ -49,19 +49,19 @@ build:4: build:5: variables: LITESQL: "-DWITHOUT_LITESQL=1" - CARES: "-DWITHOUT_CARES=1" + UDNS: "-DWITHOUT_UDNS=1" <<: *basic_build build:6: variables: BOTAN: "-DWITHOUT_BOTAN=1" - CARES: "-DWITHOUT_CARES=1" + UDNS: "-DWITHOUT_UDNS=1" <<: *basic_build build:6: variables: LIBIDN: "-DWITHOUT_LIBIDN=1" - CARES: "-DWITHOUT_CARES=1" + UDNS: "-DWITHOUT_UDNS=1" <<: *basic_build build:rpm: @@ -72,7 +72,7 @@ build:rpm: - docker image: docker.louiz.org/biboumi-test-fedora:latest script: - - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make rpm -j$(nproc || echo 1) artifacts: paths: @@ -87,7 +87,7 @@ build:rpm: tags: - docker script: - - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc || echo 1) - make coverage_check -j$(nproc || echo 1) - make coverage_e2e -j$(nproc || echo 1) @@ -119,7 +119,7 @@ test:freebsd: SYSTEMD: "-DWITHOUT_SYSTEMD=1" stage: test script: - - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi - make check - make e2e diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dfc435b..69676cb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,8 @@ Version 5.0 =========== - An identd server has been added + - Use the udns library instead of c-ares, for asynchronous DNS resolution. + It’s still fully optional. Version 4.0 - 2016-11-09 ======================== diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e06947..7d934ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,8 +129,8 @@ endif() if(BOTAN_FOUND) include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) endif() -if(CARES_FOUND) - include_directories(${CARES_INCLUDE_DIRS}) +if(UDNS_FOUND) + include_directories(${UDNS_INCLUDE_DIRS}) endif() # diff --git a/INSTALL.rst b/INSTALL.rst index 1526d7e..5016d5f 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -36,7 +36,7 @@ libidn_ (optional, but recommended) Provides the stringprep functionality. Without it, JIDs for IRC users are not provided. -c-ares_ (optional, but recommended) +udns_ (optional, but recommended) Asynchronously resolve domain names. This offers better reactivity and performances when connecting to a big number of IRC servers at the same time. @@ -155,7 +155,7 @@ to use biboumi. .. _libuuid: http://sourceforge.net/projects/libuuid/ .. _libidn: http://www.gnu.org/software/libidn/ .. _libbotan: http://botan.randombit.net/ -.. _c-ares: http://c-ares.haxx.se/ +.. _udns: http://www.corpit.ru/mjt/udns.html .. _litesql: http://git.louiz.org/litesql .. _systemd: https://www.freedesktop.org/wiki/Software/systemd/ .. _biboumi.1.rst: doc/biboumi.1.rst diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index 406dc44..ee30e14 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -9,7 +9,7 @@ RUN apt update RUN apt install -y g++ RUN apt install -y clang RUN apt install -y valgrind -RUN apt install -y libc-ares-dev +RUN apt install -y libudns-dev RUN apt install -y libsqlite3-dev RUN apt install -y libuuid1 RUN apt install -y cmake diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 4d82c7d..e0d206b 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -9,7 +9,7 @@ RUN dnf update -y RUN dnf install -y gcc-c++ RUN dnf install -y clang RUN dnf install -y valgrind -RUN dnf install -y c-ares-devel +RUN dnf install -y udns-devel RUN dnf install -y sqlite-devel RUN dnf install -y libuuid-devel RUN dnf install -y cmake diff --git a/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt index 2251eed..f672833 100644 --- a/louloulibs/CMakeLists.txt +++ b/louloulibs/CMakeLists.txt @@ -33,10 +33,10 @@ elseif(NOT WITHOUT_BOTAN) find_package(BOTAN) endif() -if(WITH_CARES) - find_package(CARES REQUIRED) -elseif(NOT WITHOUT_CARES) - find_package(CARES) +if(WITH_UDNS) + find_package(UDNS REQUIRED) +elseif(NOT WITHOUT_UDNS) + find_package(UDNS) endif() # To be able to include the config.h file generated by cmake @@ -68,10 +68,10 @@ if(BOTAN_FOUND) set(BOTAN_INCLUDE_DIRS ${BOTAN_INCLUDE_DIRS} PARENT_SCOPE) endif() -if(CARES_FOUND) - include_directories(${CARES_INCLUDE_DIRS}) - set(CARES_FOUND ${CARES_FOUND} PARENT_SCOPE) - set(CARES_INCLUDE_DIRS ${CARES_INCLUDE_DIRS} PARENT_SCOPE) +if(UDNS_FOUND) + include_directories(${UDNS_INCLUDE_DIRS}) + set(UDNS_FOUND ${UDNS_FOUND} PARENT_SCOPE) + set(UDNS_INCLUDE_DIRS ${UDNS_INCLUDE_DIRS} PARENT_SCOPE) endif() set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") @@ -118,8 +118,8 @@ target_link_libraries(network logger) if(BOTAN_FOUND) target_link_libraries(network ${BOTAN_LIBRARIES}) endif() -if(CARES_FOUND) - target_link_libraries(network ${CARES_LIBRARIES}) +if(UDNS_FOUND) + target_link_libraries(network ${UDNS_LIBRARIES}) endif() # diff --git a/louloulibs/cmake/Modules/FindCARES.cmake b/louloulibs/cmake/Modules/FindCARES.cmake deleted file mode 100644 index c4c757a..0000000 --- a/louloulibs/cmake/Modules/FindCARES.cmake +++ /dev/null @@ -1,37 +0,0 @@ -# - Find c-ares -# Find the c-ares library, and more particularly the stringprep header. -# -# This module defines the following variables: -# CARES_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# CARES_INCLUDE_DIRS - The directory where to find the header file -# CARES_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# CARES_INCLUDE_DIR -# CARES_LIBRARY -# -# This file is in the public domain - -if(NOT CARES_FOUND) - find_path(CARES_INCLUDE_DIRS NAMES ares.h - DOC "The c-ares include directory") - - find_library(CARES_LIBRARIES NAMES cares - DOC "The c-ares library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set CARES_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(CARES REQUIRED_VARS CARES_LIBRARIES CARES_INCLUDE_DIRS) - - # Compatibility for all the ways of writing these variables - if(CARES_FOUND) - set(CARES_INCLUDE_DIR ${CARES_INCLUDE_DIRS}) - set(CARES_LIBRARY ${CARES_LIBRARIES}) - endif() -endif() - -mark_as_advanced(CARES_INCLUDE_DIRS CARES_LIBRARIES) diff --git a/louloulibs/cmake/Modules/FindUDNS.cmake b/louloulibs/cmake/Modules/FindUDNS.cmake new file mode 100644 index 0000000..1d32cd3 --- /dev/null +++ b/louloulibs/cmake/Modules/FindUDNS.cmake @@ -0,0 +1,37 @@ +# - Find udns +# Find the udns library +# +# This module defines the following variables: +# UDNS_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# UDNS_INCLUDE_DIRS - The directory where to find the header file +# UDNS_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# as the variables above. The user can thus choose his/her prefered way +# to write them. +# UDNS_INCLUDE_DIR +# UDNS_LIBRARY +# +# This file is in the public domain + +if(NOT UDNS_FOUND) + find_path(UDNS_INCLUDE_DIRS NAMES udns.h + DOC "The udns include directory") + + find_library(UDNS_LIBRARIES NAMES udns + DOC "The udns library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set UDNS_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(UDNS REQUIRED_VARS UDNS_LIBRARIES UDNS_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(UDNS_FOUND) + set(UDNS_INCLUDE_DIR ${UDNS_INCLUDE_DIRS}) + set(UDNS_LIBRARY ${UDNS_LIBRARIES}) + endif() +endif() + +mark_as_advanced(UDNS_INCLUDE_DIRS UDNS_LIBRARIES) diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake index 6131b70..ebb9b9a 100644 --- a/louloulibs/louloulibs.h.cmake +++ b/louloulibs/louloulibs.h.cmake @@ -4,7 +4,7 @@ #cmakedefine SYSTEMD_FOUND #cmakedefine POLLER ${POLLER} #cmakedefine BOTAN_FOUND -#cmakedefine CARES_FOUND +#cmakedefine UDNS_FOUND #cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" #cmakedefine PROJECT_NAME "${PROJECT_NAME}" #cmakedefine HAS_GET_TIME diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index b1807b5..ad0ad38 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -1,5 +1,5 @@ #include -#ifdef CARES_FOUND +#ifdef UDNS_FOUND #include #include @@ -7,123 +7,40 @@ #include -#include +#include -DNSHandler DNSHandler::instance; +#include + +class Resolver; using namespace std::string_literals; -DNSHandler::DNSHandler(): - socket_handlers{}, - channel{nullptr} -{ - int ares_error; - if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) - throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error)); - struct ares_options options = {}; - // The default timeout values are way too high - options.timeout = 1000; - options.tries = 3; - if ((ares_error = ::ares_init_options(&this->channel, - &options, - ARES_OPT_TIMEOUTMS|ARES_OPT_TRIES)) != ARES_SUCCESS) - throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error)); -} -ares_channel& DNSHandler::get_channel() -{ - return this->channel; -} +std::unique_ptr DNSHandler::socket_handler{}; -void DNSHandler::destroy() +DNSHandler::DNSHandler(std::shared_ptr poller) { - this->remove_all_sockets_from_poller(); - this->socket_handlers.clear(); - ::ares_destroy(this->channel); - ::ares_library_cleanup(); + dns_init(nullptr, 0); + const auto socket = dns_open(nullptr); + if (socket == -1) + throw std::runtime_error("Failed to initialize udns socket: "s + strerror(errno)); + + DNSHandler::socket_handler = std::make_unique(poller, socket); } -void DNSHandler::gethostbyname(const std::string& name, ares_host_callback callback, - void* data, int family) +void DNSHandler::destroy() { - ::ares_gethostbyname(this->channel, name.data(), family, - callback, data); + DNSHandler::socket_handler.reset(nullptr); + dns_close(nullptr); } -void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) +void DNSHandler::watch() { - fd_set readers; - fd_set writers; - - FD_ZERO(&readers); - FD_ZERO(&writers); - - int ndfs = ::ares_fds(this->channel, &readers, &writers); - // For each existing DNS socket, see if we are still supposed to watch it, - // if not then erase it - this->socket_handlers.erase( - std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), - [&readers](const auto& dns_socket) - { - return !FD_ISSET(dns_socket->get_socket(), &readers); - }), - this->socket_handlers.end()); - - for (auto i = 0; i < ndfs; ++i) - { - bool read = FD_ISSET(i, &readers); - bool write = FD_ISSET(i, &writers); - // Look for the DNSSocketHandler with this fd - auto it = std::find_if(this->socket_handlers.begin(), - this->socket_handlers.end(), - [i](const auto& socket_handler) - { - return i == socket_handler->get_socket(); - }); - if (!read && !write) // No need to read or write to it - { // If found, erase it and stop watching it because it is not - // needed anymore - if (it != this->socket_handlers.end()) - // The socket destructor removes it from the poller - this->socket_handlers.erase(it); - } - else // We need to write and/or read to it - { // If not found, create it because we need to watch it - if (it == this->socket_handlers.end()) - { - this->socket_handlers.emplace(this->socket_handlers.begin(), - std::make_unique(poller, *this, i)); - it = this->socket_handlers.begin(); - } - poller->add_socket_handler(it->get()); - if (write) - poller->watch_send_events(it->get()); - } - } - // Cancel previous timer, if any. - TimedEventsManager::instance().cancel("DNS timeout"); - struct timeval tv; - struct timeval* tvp; - tvp = ::ares_timeout(this->channel, NULL, &tv); - if (tvp) - { - auto future_time = std::chrono::steady_clock::now() + std::chrono::seconds(tvp->tv_sec) + \ - std::chrono::microseconds(tvp->tv_usec); - TimedEventsManager::instance().add_event(TimedEvent(std::move(future_time), - [this]() - { - for (auto& dns_socket_handler: this->socket_handlers) - dns_socket_handler->on_recv(); - }, - "DNS timeout")); - } + DNSHandler::socket_handler->watch(); } -void DNSHandler::remove_all_sockets_from_poller() +void DNSHandler::unwatch() { - for (const auto& socket_handler: this->socket_handlers) - { - socket_handler->remove_from_poller(); - } + DNSHandler::socket_handler->unwatch(); } -#endif /* CARES_FOUND */ +#endif /* UDNS_FOUND */ diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index 53a7799..0148156 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -1,59 +1,37 @@ #pragma once #include -#ifdef CARES_FOUND +#ifdef UDNS_FOUND -class TCPSocketHandler; class Poller; -class DNSSocketHandler; -# include -# include -# include -# include +#include -/** - * Class managing DNS resolution. It should only be statically instanciated - * once in SocketHandler. It manages ares channel and calls various - * functions of that library. - */ +#include +#include +#include class DNSHandler { -private: - DNSHandler(); public: + DNSHandler(std::shared_ptr poller); ~DNSHandler() = default; + DNSHandler(const DNSHandler&) = delete; DNSHandler(DNSHandler&&) = delete; DNSHandler& operator=(const DNSHandler&) = delete; DNSHandler& operator=(DNSHandler&&) = delete; - void gethostbyname(const std::string& name, ares_host_callback callback, - void* socket_handler, int family); - /** - * Call ares_fds to know what fd needs to be watched by the poller, create - * or destroy DNSSocketHandlers depending on the result. - */ - void watch_dns_sockets(std::shared_ptr& poller); - /** - * Destroy and stop watching all the DNS sockets. Then de-init the channel - * and library. - */ void destroy(); - void remove_all_sockets_from_poller(); - ares_channel& get_channel(); - static DNSHandler instance; + static void watch(); + static void unwatch(); private: /** - * The list of sockets that needs to be watched, according to the last - * call to ares_fds. DNSSocketHandlers are added to it or removed from it - * in the watch_dns_sockets() method + * Manager for the socket returned by udns, that we need to watch with the poller */ - std::vector> socket_handlers; - ares_channel channel; + static std::unique_ptr socket_handler; }; -#endif /* CARES_FOUND */ +#endif /* UDNS_FOUND */ diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp index a69f59b..ad744a9 100644 --- a/louloulibs/network/dns_socket_handler.cpp +++ b/louloulibs/network/dns_socket_handler.cpp @@ -1,34 +1,27 @@ #include -#ifdef CARES_FOUND +#ifdef UDNS_FOUND #include #include #include -#include +#include DNSSocketHandler::DNSSocketHandler(std::shared_ptr poller, - DNSHandler& handler, const socket_t socket): - SocketHandler(poller, socket), - handler(handler) + SocketHandler(poller, socket) { + poller->add_socket_handler(this); } -void DNSSocketHandler::on_recv() +DNSSocketHandler::~DNSSocketHandler() { - // always stop watching send and read events. We will re-watch them if the - // next call to ares_fds tell us to - this->handler.remove_all_sockets_from_poller(); - ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD); + this->unwatch(); } -void DNSSocketHandler::on_send() +void DNSSocketHandler::on_recv() { - // always stop watching send and read events. We will re-watch them if the - // next call to ares_fds tell us to - this->handler.remove_all_sockets_from_poller(); - ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket); + dns_ioevent(nullptr, 0); } bool DNSSocketHandler::is_connected() const @@ -36,10 +29,15 @@ bool DNSSocketHandler::is_connected() const return true; } -void DNSSocketHandler::remove_from_poller() +void DNSSocketHandler::unwatch() { if (this->poller->is_managing_socket(this->socket)) this->poller->remove_socket_handler(this->socket); } -#endif /* CARES_FOUND */ +void DNSSocketHandler::watch() +{ + this->poller->add_socket_handler(this); +} + +#endif /* UDNS_FOUND */ diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index e3fed0c..e12f145 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -1,44 +1,33 @@ #pragma once #include -#ifdef CARES_FOUND +#ifdef UDNS_FOUND #include -#include /** - * Manage a socket returned by ares_fds. We do not create, open or close the - * socket ourself: this is done by c-ares. We just call ares_process_fd() - * with the correct parameters, depending on what can be done on that socket - * (Poller reported it to be writable or readeable) + * Manage the UDP socket provided by udns, we do not create, open or close the + * socket ourself: this is done by udns. We only watch it for readability */ - -class DNSHandler; - class DNSSocketHandler: public SocketHandler { public: - explicit DNSSocketHandler(std::shared_ptr poller, DNSHandler& handler, const socket_t socket); - ~DNSSocketHandler() = default; + explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); + ~DNSSocketHandler(); DNSSocketHandler(const DNSSocketHandler&) = delete; DNSSocketHandler(DNSSocketHandler&&) = delete; DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; - /** - * Just call dns_process_fd, c-ares will do its work of send()ing or - * recv()ing the data it wants on that socket. - */ void on_recv() override final; - void on_send() override final; + /** * Always true, see the comment for connect() */ bool is_connected() const override final; - void remove_from_poller(); -private: - DNSHandler& handler; + void watch(); + void unwatch(); }; -#endif // CARES_FOUND +#endif // UDNS_FOUND diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index 3847d87..efb0cf0 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -1,19 +1,32 @@ #include +#include #include #include #include #include +#include +#include #include +#include +#include +#include using namespace std::string_literals; +static std::map dns_error_messages { + {DNS_E_TEMPFAIL, "Timeout while contacting DNS servers"}, + {DNS_E_PROTOCOL, "Misformatted DNS reply"}, + {DNS_E_NXDOMAIN, "Domain name not found"}, + {DNS_E_NOMEM, "Out of memory"}, + {DNS_E_BADQUERY, "Misformatted domain name"} +}; + Resolver::Resolver(): -#ifdef CARES_FOUND +#ifdef UDNS_FOUND resolved4(false), resolved6(false), resolving(false), - cares_addrinfo(nullptr), port{}, #endif resolved(false), @@ -26,15 +39,44 @@ void Resolver::resolve(const std::string& hostname, const std::string& port, { this->error_cb = error_cb; this->success_cb = success_cb; -#ifdef CARES_FOUND +#ifdef UDNS_FOUND this->port = port; #endif this->start_resolving(hostname, port); } -#ifdef CARES_FOUND -void Resolver::start_resolving(const std::string& hostname, const std::string&) +int Resolver::call_getaddrinfo(const char *name, const char* port, int flags) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = flags; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + struct addrinfo* addr_res = nullptr; + const int res = ::getaddrinfo(name, port, + &hints, &addr_res); + + if (res == 0 && addr_res) + { + if (!this->addr) + this->addr.reset(addr_res); + else + { // Append this result at the end of the linked list + struct addrinfo *rp = this->addr.get(); + while (rp->ai_next) + rp = rp->ai_next; + rp->ai_next = addr_res; + } + } + + return res; +} + +#ifdef UDNS_FOUND +void Resolver::start_resolving(const std::string& hostname, const std::string& port) { this->resolving = true; this->resolved = false; @@ -42,48 +84,139 @@ void Resolver::start_resolving(const std::string& hostname, const std::string&) this->resolved6 = false; this->error_msg.clear(); - this->cares_addrinfo = nullptr; + this->addr.reset(nullptr); - auto hostname4_resolved = [](void* arg, int status, int, - struct hostent* hostent) + // We first try to use it as an IP address directly. We tell getaddrinfo + // to NOT use any DNS resolution. + if (this->call_getaddrinfo(hostname.data(), port.data(), AI_NUMERICHOST) == 0) { - Resolver* resolver = static_cast(arg); - resolver->on_hostname4_resolved(status, hostent); - }; - auto hostname6_resolved = [](void* arg, int status, int, - struct hostent* hostent) + this->on_resolved(); + return; + } + + // Then we look into /etc/hosts to translate the given hostname + const auto hosts = this->look_in_etc_hosts(hostname); + if (!hosts.empty()) + { + for (const auto &host: hosts) + this->call_getaddrinfo(host.data(), port.data(), AI_NUMERICHOST); + this->on_resolved(); + return; + } + + // And finally, we try a DNS resolution + auto hostname6_resolved = [](dns_ctx*, dns_rr_a6* result, void* data) + { + Resolver* resolver = static_cast(data); + resolver->on_hostname6_resolved(result); + }; + + auto hostname4_resolved = [](dns_ctx*, dns_rr_a4* result, void* data) + { + Resolver* resolver = static_cast(data); + resolver->on_hostname4_resolved(result); + }; + + DNSHandler::watch(); + auto res = dns_submit_a4(nullptr, hostname.data(), 0, hostname4_resolved, this); + if (!res) + this->on_hostname4_resolved(nullptr); + res = dns_submit_a6(nullptr, hostname.data(), 0, hostname6_resolved, this); + if (!res) + this->on_hostname6_resolved(nullptr); + + this->start_timer(); +} + +void Resolver::start_timer() +{ + const auto timeout = dns_timeouts(nullptr, -1, 0); + if (timeout < 0) + return; + TimedEvent event(std::chrono::steady_clock::now() + std::chrono::seconds(timeout), [this]() { this->start_timer(); }, "DNS"); + TimedEventsManager::instance().add_event(std::move(event)); +} + +std::vector Resolver::look_in_etc_hosts(const std::string &hostname) +{ + std::ifstream hosts("/etc/hosts"); + std::string line; + + std::vector results; + while (std::getline(hosts, line)) { - Resolver* resolver = static_cast(arg); - resolver->on_hostname6_resolved(status, hostent); - }; - - DNSHandler::instance.gethostbyname(hostname, hostname6_resolved, - this, AF_INET6); - DNSHandler::instance.gethostbyname(hostname, hostname4_resolved, - this, AF_INET); + if (line.empty()) + continue; + + std::string ip; + std::istringstream line_stream(line); + line_stream >> ip; + if (ip.empty() || ip[0] == '#') + continue; + + std::string host; + while (line_stream >> host && !host.empty() && host[0] != '#') + { + if (hostname == host) + { + results.push_back(ip); + break; + } + } + } + return results; } -void Resolver::on_hostname4_resolved(int status, struct hostent* hostent) +void Resolver::on_hostname4_resolved(dns_rr_a4 *result) { + if (dns_active(nullptr) == 0) + DNSHandler::unwatch(); + this->resolved4 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo4(hostent); + + const auto status = dns_status(nullptr); + + if (status >= 0 && result) + { + char buf[INET6_ADDRSTRLEN]; + + for (auto i = 0; i < result->dnsa4_nrr; ++i) + { + inet_ntop(AF_INET, &result->dnsa4_addr[i], buf, sizeof(buf)); + this->call_getaddrinfo(buf, this->port.data(), AI_NUMERICHOST); + } + } else - this->error_msg = ::ares_strerror(status); + { + const auto error = dns_error_messages.find(status); + if (error != end(dns_error_messages)) + this->error_msg = error->second; + } - if (this->resolved4 && this->resolved6) + if (this->resolved6 && this->resolved4) this->on_resolved(); } -void Resolver::on_hostname6_resolved(int status, struct hostent* hostent) +void Resolver::on_hostname6_resolved(dns_rr_a6 *result) { + if (dns_active(nullptr) == 0) + DNSHandler::unwatch(); + this->resolved6 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo6(hostent); - else - this->error_msg = ::ares_strerror(status); + char buf[INET6_ADDRSTRLEN]; + + const auto status = dns_status(nullptr); - if (this->resolved4 && this->resolved6) + if (status >= 0 && result) + { + for (auto i = 0; i < result->dnsa6_nrr; ++i) + { + inet_ntop(AF_INET6, &result->dnsa6_addr[i], buf, sizeof(buf)); + this->call_getaddrinfo(buf, this->port.data(), AI_NUMERICHOST); + } + } + + if (this->resolved6 && this->resolved4) this->on_resolved(); } @@ -91,100 +224,26 @@ void Resolver::on_resolved() { this->resolved = true; this->resolving = false; - if (!this->cares_addrinfo) + if (!this->addr) { if (this->error_cb) this->error_cb(this->error_msg.data()); } else { - this->addr.reset(this->cares_addrinfo); if (this->success_cb) this->success_cb(this->addr.get()); } } -void Resolver::fill_ares_addrinfo4(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in_addr** address = reinterpret_cast(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - current->ai_protocol = 0; - current->ai_addrlen = sizeof(struct sockaddr_in); - - struct sockaddr_in* ai_addr = new struct sockaddr_in; - - ai_addr->sin_family = hostent->h_addrtype; - ai_addr->sin_port = htons(std::strtoul(this->port.data(), nullptr, 10)); - ai_addr->sin_addr.s_addr = (*address)->s_addr; - - current->ai_addr = reinterpret_cast(ai_addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void Resolver::fill_ares_addrinfo6(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - current->ai_protocol = 0; - current->ai_addrlen = sizeof(struct sockaddr_in6); - - struct sockaddr_in6* ai_addr = new struct sockaddr_in6; - ai_addr->sin6_family = hostent->h_addrtype; - ai_addr->sin6_port = htons(std::strtoul(this->port.data(), nullptr, 10)); - ::memcpy(ai_addr->sin6_addr.s6_addr, (*address)->s6_addr, sizeof(ai_addr->sin6_addr.s6_addr)); - ai_addr->sin6_flowinfo = 0; - ai_addr->sin6_scope_id = 0; - - current->ai_addr = reinterpret_cast(ai_addr); - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -#else // ifdef CARES_FOUND +#else // ifdef UDNS_FOUND void Resolver::start_resolving(const std::string& hostname, const std::string& port) { // If the resolution fails, the addr will be unset this->addr.reset(nullptr); - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - - struct addrinfo* addr_res = nullptr; - const int res = ::getaddrinfo(hostname.data(), port.data(), - &hints, &addr_res); + const auto res = this->call_getaddrinfo(hostname.data(), port.data(), 0); this->resolved = true; @@ -196,12 +255,11 @@ void Resolver::start_resolving(const std::string& hostname, const std::string& p } else { - this->addr.reset(addr_res); if (this->success_cb) this->success_cb(this->addr.get()); } } -#endif // ifdef CARES_FOUND +#endif // ifdef UDNS_FOUND std::string addr_to_string(const struct addrinfo* rp) { diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp index 7365f93..f516da5 100644 --- a/louloulibs/network/resolver.hpp +++ b/louloulibs/network/resolver.hpp @@ -1,38 +1,31 @@ #pragma once - #include "louloulibs.h" #include +#include #include #include #include #include #include +#include class AddrinfoDeleter { public: void operator()(struct addrinfo* addr) { -#ifdef CARES_FOUND - while (addr) - { - delete addr->ai_addr; - auto next = addr->ai_next; - delete addr; - addr = next; - } -#else freeaddrinfo(addr); -#endif } }; + class Resolver { public: + using ErrorCallbackType = std::function; using SuccessCallbackType = std::function; @@ -45,7 +38,7 @@ public: bool is_resolving() const { -#ifdef CARES_FOUND +#ifdef UDNS_FOUND return this->resolving; #else return false; @@ -68,11 +61,10 @@ public: void clear() { -#ifdef CARES_FOUND +#ifdef UDNS_FOUND this->resolved6 = false; this->resolved4 = false; this->resolving = false; - this->cares_addrinfo = nullptr; this->port.clear(); #endif this->resolved = false; @@ -85,12 +77,18 @@ public: private: void start_resolving(const std::string& hostname, const std::string& port); -#ifdef CARES_FOUND - void on_hostname4_resolved(int status, struct hostent* hostent); - void on_hostname6_resolved(int status, struct hostent* hostent); + std::vector look_in_etc_hosts(const std::string& hostname); + /** + * Call getaddrinfo() on the given hostname or IP, and append the result + * to our internal addrinfo list. Return getaddrinfo()’s return value. + */ + int call_getaddrinfo(const char* name, const char* port, int flags); + +#ifdef UDNS_FOUND + void on_hostname4_resolved(dns_rr_a4 *result); + void on_hostname6_resolved(dns_rr_a6 *result); - void fill_ares_addrinfo4(const struct hostent* hostent); - void fill_ares_addrinfo6(const struct hostent* hostent); + void start_timer(); void on_resolved(); @@ -99,14 +97,6 @@ private: bool resolving; - /** - * When using c-ares to resolve the host asynchronously, we need the - * c-ares callbacks to fill a structure (a struct addrinfo, for - * compatibility with getaddrinfo and the rest of the code that works when - * c-ares is not used) with all returned values (for example an IPv6 and - * an IPv4). The pointer is given to the unique_ptr to manage its lifetime. - */ - struct addrinfo* cares_addrinfo; std::string port; #endif @@ -117,7 +107,6 @@ private: bool resolved; std::string error_msg; - std::unique_ptr addr; ErrorCallbackType error_cb; diff --git a/louloulibs/network/tcp_client_socket_handler.cpp b/louloulibs/network/tcp_client_socket_handler.cpp index 7f21e3f..4e6445c 100644 --- a/louloulibs/network/tcp_client_socket_handler.cpp +++ b/louloulibs/network/tcp_client_socket_handler.cpp @@ -79,7 +79,7 @@ void TCPClientSocketHandler::connect(const std::string& address, const std::stri if (!this->connecting) { - // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if + // Get the addrinfo from getaddrinfo (or using udns), only if // this is the first call of this function. if (!this->resolver.is_resolved()) { @@ -103,8 +103,8 @@ void TCPClientSocketHandler::connect(const std::string& address, const std::stri } else { - // The c-ares resolved the hostname and the available addresses - // where saved in the cares_addrinfo linked list. Now, just use + // The DNS resolver resolved the hostname and the available addresses + // where saved in the addrinfo linked list. Now, just use // this list to try to connect. addr_res = this->resolver.get_result().get(); if (!addr_res) diff --git a/src/main.cpp b/src/main.cpp index 060372b..bc8e779 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,7 +6,7 @@ #include #include -#ifdef CARES_FOUND +#ifdef UDNS_FOUND # include #endif @@ -129,15 +129,16 @@ int main(int ac, char** av) auto p = std::make_shared(); +#ifdef UDNS_FOUND + DNSHandler dns_handler(p); +#endif + auto xmpp_component = std::make_shared(p, hostname, password); xmpp_component->start(); IdentdServer identd(*xmpp_component, p, static_cast(Config::get_int("identd_port", 113))); -#ifdef CARES_FOUND - DNSHandler::instance.watch_dns_sockets(p); -#endif auto timeout = TimedEventsManager::instance().get_timeout(); while (p->poll(timeout) != -1) { @@ -155,6 +156,9 @@ int main(int ac, char** av) exiting = true; stop.store(false); xmpp_component->shutdown(); +#ifdef UDNS_FOUND + dns_handler.destroy(); +#endif identd.shutdown(); // Cancel the timer for a potential reconnection TimedEventsManager::instance().cancel("XMPP reconnection"); @@ -200,18 +204,11 @@ int main(int ac, char** av) xmpp_component->close(); if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); -#ifdef CARES_FOUND - if (!exiting) - DNSHandler::instance.watch_dns_sockets(p); -#endif if (exiting) // If we are exiting, do not wait for any timed event timeout = utils::no_timeout; else timeout = TimedEventsManager::instance().get_timeout(); } -#ifdef CARES_FOUND - DNSHandler::instance.destroy(); -#endif if (!xmpp_component->ever_auth) return 1; // To signal that the process did not properly start log_info("All connections cleanly closed, have a nice day."); -- cgit v1.2.3