~singpolyma/biboumi

5b56007828f20c763df3f36ceed809188880663e — louiz’ 7 years ago eecb953
Use udns instead of c-ares

fix #3226
M .gitlab-ci.yml => .gitlab-ci.yml +10 -10
@@ 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

M CHANGELOG.rst => CHANGELOG.rst +2 -0
@@ 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
========================

M CMakeLists.txt => CMakeLists.txt +2 -2
@@ 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()

#

M INSTALL.rst => INSTALL.rst +2 -2
@@ 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

M docker/biboumi-test/debian/Dockerfile => docker/biboumi-test/debian/Dockerfile +1 -1
@@ 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

M docker/biboumi-test/fedora/Dockerfile => docker/biboumi-test/fedora/Dockerfile +1 -1
@@ 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

M louloulibs/CMakeLists.txt => louloulibs/CMakeLists.txt +10 -10
@@ 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()

#

D louloulibs/cmake/Modules/FindCARES.cmake => louloulibs/cmake/Modules/FindCARES.cmake +0 -37
@@ 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)

A louloulibs/cmake/Modules/FindUDNS.cmake => louloulibs/cmake/Modules/FindUDNS.cmake +37 -0
@@ 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)

M louloulibs/louloulibs.h.cmake => louloulibs/louloulibs.h.cmake +1 -1
@@ 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

M louloulibs/network/dns_handler.cpp => louloulibs/network/dns_handler.cpp +21 -104
@@ 1,5 1,5 @@
#include <louloulibs.h>
#ifdef CARES_FOUND
#ifdef UDNS_FOUND

#include <network/dns_socket_handler.hpp>
#include <network/dns_handler.hpp>


@@ 7,123 7,40 @@

#include <utils/timed_events.hpp>

#include <algorithm>
#include <udns.h>

DNSHandler DNSHandler::instance;
#include <cstring>

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<DNSSocketHandler> DNSHandler::socket_handler{};

void DNSHandler::destroy()
DNSHandler::DNSHandler(std::shared_ptr<Poller> 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<DNSSocketHandler>(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>& 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<DNSSocketHandler>(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 */

M louloulibs/network/dns_handler.hpp => louloulibs/network/dns_handler.hpp +12 -34
@@ 1,59 1,37 @@
#pragma once

#include <louloulibs.h>
#ifdef CARES_FOUND
#ifdef UDNS_FOUND

class TCPSocketHandler;
class Poller;
class DNSSocketHandler;

# include <ares.h>
# include <memory>
# include <string>
# include <vector>
#include <network/dns_socket_handler.hpp>

/**
 * 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 <string>
#include <vector>
#include <memory>

class DNSHandler
{
private:
  DNSHandler();
public:
  DNSHandler(std::shared_ptr<Poller> 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>& 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<std::unique_ptr<DNSSocketHandler>> socket_handlers;
  ares_channel channel;
  static std::unique_ptr<DNSSocketHandler> socket_handler;
};

#endif /* CARES_FOUND */
#endif /* UDNS_FOUND */

M louloulibs/network/dns_socket_handler.cpp => louloulibs/network/dns_socket_handler.cpp +15 -17
@@ 1,34 1,27 @@
#include <louloulibs.h>
#ifdef CARES_FOUND
#ifdef UDNS_FOUND

#include <network/dns_socket_handler.hpp>
#include <network/dns_handler.hpp>
#include <network/poller.hpp>

#include <ares.h>
#include <udns.h>

DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller> 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 */

M louloulibs/network/dns_socket_handler.hpp => louloulibs/network/dns_socket_handler.hpp +9 -20
@@ 1,44 1,33 @@
#pragma once

#include <louloulibs.h>
#ifdef CARES_FOUND
#ifdef UDNS_FOUND

#include <network/socket_handler.hpp>
#include <ares.h>

/**
 * 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> poller, DNSHandler& handler, const socket_t socket);
  ~DNSSocketHandler() = default;
  explicit DNSSocketHandler(std::shared_ptr<Poller> 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

M louloulibs/network/resolver.cpp => louloulibs/network/resolver.cpp +169 -111
@@ 1,19 1,32 @@
#include <network/dns_handler.hpp>
#include <utils/timed_events.hpp>
#include <network/resolver.hpp>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <udns.h>

#include <fstream>
#include <cstdlib>
#include <sstream>
#include <chrono>
#include <map>

using namespace std::string_literals;

static std::map<int, std::string> 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<Resolver*>(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<Resolver*>(data);
    resolver->on_hostname6_resolved(result);
  };

  auto hostname4_resolved = [](dns_ctx*, dns_rr_a4* result, void* data)
  {
    Resolver* resolver = static_cast<Resolver*>(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<std::string> Resolver::look_in_etc_hosts(const std::string &hostname)
{
  std::ifstream hosts("/etc/hosts");
  std::string line;

  std::vector<std::string> results;
  while (std::getline(hosts, line))
    {
      Resolver* resolver = static_cast<Resolver*>(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<struct in_addr**>(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<struct sockaddr*>(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<struct in6_addr**>(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<struct sockaddr*>(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)
{

M louloulibs/network/resolver.hpp => louloulibs/network/resolver.hpp +17 -28
@@ 1,38 1,31 @@
#pragma once


#include "louloulibs.h"

#include <functional>
#include <vector>
#include <memory>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <udns.h>

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<void(const char*)>;
  using SuccessCallbackType = std::function<void(const struct addrinfo*)>;



@@ 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<std::string> 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<struct addrinfo, AddrinfoDeleter> addr;

  ErrorCallbackType error_cb;

M louloulibs/network/tcp_client_socket_handler.cpp => louloulibs/network/tcp_client_socket_handler.cpp +3 -3
@@ 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)

M src/main.cpp => src/main.cpp +8 -11
@@ 6,7 6,7 @@
#include <utils/xdg.hpp>
#include <utils/reload.hpp>

#ifdef CARES_FOUND
#ifdef UDNS_FOUND
# include <network/dns_handler.hpp>
#endif



@@ 129,15 129,16 @@ int main(int ac, char** av)

  auto p = std::make_shared<Poller>();

#ifdef UDNS_FOUND
  DNSHandler dns_handler(p);
#endif

  auto xmpp_component =
    std::make_shared<BiboumiComponent>(p, hostname, password);
  xmpp_component->start();

  IdentdServer identd(*xmpp_component, p, static_cast<uint16_t>(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.");