summaryrefslogtreecommitdiff
path: root/louloulibs/network
diff options
context:
space:
mode:
authorFlorent Le Coz <louiz@louiz.org>2015-05-28 23:42:52 +0200
committerFlorent Le Coz <louiz@louiz.org>2015-05-28 23:46:24 +0200
commite1a7114c8daa10589c830ce972cf461c3540111b (patch)
tree3b9fc79a881b252248d30c8e797bed13f131e90b /louloulibs/network
parent897b281e67dc82700db9fd9c2dedc5e01e5871ee (diff)
downloadbiboumi-e1a7114c8daa10589c830ce972cf461c3540111b.tar.gz
biboumi-e1a7114c8daa10589c830ce972cf461c3540111b.tar.bz2
biboumi-e1a7114c8daa10589c830ce972cf461c3540111b.tar.xz
biboumi-e1a7114c8daa10589c830ce972cf461c3540111b.zip
louloulibs is directly included, instead of being a submodule
Because this is a nightmare to manage
Diffstat (limited to 'louloulibs/network')
m---------louloulibs0
-rw-r--r--louloulibs/network/dns_handler.cpp112
-rw-r--r--louloulibs/network/dns_handler.hpp62
-rw-r--r--louloulibs/network/dns_socket_handler.cpp45
-rw-r--r--louloulibs/network/dns_socket_handler.hpp46
-rw-r--r--louloulibs/network/poller.cpp225
-rw-r--r--louloulibs/network/poller.hpp95
-rw-r--r--louloulibs/network/socket_handler.hpp45
-rw-r--r--louloulibs/network/tcp_socket_handler.cpp590
-rw-r--r--louloulibs/network/tcp_socket_handler.hpp293
10 files changed, 1513 insertions, 0 deletions
diff --git a/louloulibs b/louloulibs
deleted file mode 160000
-Subproject 0f3c1183e2bf0941ae2bffd3f31577bce4f3001
diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp
new file mode 100644
index 0000000..ec53683
--- /dev/null
+++ b/louloulibs/network/dns_handler.cpp
@@ -0,0 +1,112 @@
+#include <louloulibs.h>
+#ifdef CARES_FOUND
+
+#include <network/dns_socket_handler.hpp>
+#include <network/tcp_socket_handler.hpp>
+#include <network/dns_handler.hpp>
+#include <network/poller.hpp>
+
+#include <algorithm>
+#include <stdexcept>
+
+DNSHandler DNSHandler::instance;
+
+using namespace std::string_literals;
+
+void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent)
+{
+ TCPSocketHandler* socket_handler = static_cast<TCPSocketHandler*>(arg);
+ socket_handler->on_hostname4_resolved(status, hostent);
+}
+
+void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent)
+{
+ TCPSocketHandler* socket_handler = static_cast<TCPSocketHandler*>(arg);
+ socket_handler->on_hostname6_resolved(status, hostent);
+}
+
+DNSHandler::DNSHandler()
+{
+ 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));
+ if ((ares_error = ::ares_init(&this->channel)) != 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;
+}
+
+void DNSHandler::destroy()
+{
+ this->socket_handlers.clear();
+ ::ares_destroy(this->channel);
+ ::ares_library_cleanup();
+}
+
+void DNSHandler::gethostbyname(const std::string& name,
+ TCPSocketHandler* socket_handler, int family)
+{
+ socket_handler->free_cares_addrinfo();
+ if (family == AF_INET)
+ ::ares_gethostbyname(this->channel, name.data(), family,
+ &::on_hostname4_resolved, socket_handler);
+ else
+ ::ares_gethostbyname(this->channel, name.data(), family,
+ &::on_hostname6_resolved, socket_handler);
+}
+
+void DNSHandler::watch_dns_sockets(std::shared_ptr<Poller>& poller)
+{
+ 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_front(std::make_unique<DNSSocketHandler>(poller, i));
+ it = this->socket_handlers.begin();
+ }
+ poller->add_socket_handler(it->get());
+ if (write)
+ poller->watch_send_events(it->get());
+ }
+ }
+}
+
+#endif /* CARES_FOUND */
diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp
new file mode 100644
index 0000000..a515f52
--- /dev/null
+++ b/louloulibs/network/dns_handler.hpp
@@ -0,0 +1,62 @@
+#ifndef DNS_HANDLER_HPP_INCLUDED
+#define DNS_HANDLER_HPP_INCLUDED
+
+#include <louloulibs.h>
+#ifdef CARES_FOUND
+
+class TCPSocketHandler;
+class Poller;
+class DNSSocketHandler;
+
+# include <ares.h>
+# include <memory>
+# include <string>
+# include <list>
+
+void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent);
+void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent);
+
+/**
+ * Class managing DNS resolution. It should only be statically instanciated
+ * once in SocketHandler. It manages ares channel and calls various
+ * functions of that library.
+ */
+
+class DNSHandler
+{
+public:
+ DNSHandler();
+ ~DNSHandler() = default;
+ void gethostbyname(const std::string& name, TCPSocketHandler* 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();
+ ares_channel& get_channel();
+
+ static DNSHandler instance;
+
+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
+ */
+ std::list<std::unique_ptr<DNSSocketHandler>> socket_handlers;
+ ares_channel channel;
+
+ DNSHandler(const DNSHandler&) = delete;
+ DNSHandler(DNSHandler&&) = delete;
+ DNSHandler& operator=(const DNSHandler&) = delete;
+ DNSHandler& operator=(DNSHandler&&) = delete;
+};
+
+#endif /* CARES_FOUND */
+#endif /* DNS_HANDLER_HPP_INCLUDED */
diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp
new file mode 100644
index 0000000..124c9b2
--- /dev/null
+++ b/louloulibs/network/dns_socket_handler.cpp
@@ -0,0 +1,45 @@
+#include <louloulibs.h>
+#ifdef CARES_FOUND
+
+#include <network/dns_socket_handler.hpp>
+#include <network/dns_handler.hpp>
+#include <network/poller.hpp>
+
+#include <ares.h>
+
+DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller> poller,
+ const socket_t socket):
+ SocketHandler(poller, socket)
+{
+}
+
+DNSSocketHandler::~DNSSocketHandler()
+{
+}
+
+void DNSSocketHandler::connect()
+{
+}
+
+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->poller->remove_socket_handler(this->socket);
+ ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD);
+}
+
+void DNSSocketHandler::on_send()
+{
+ // always stop watching send and read events. We will re-watch them if the
+ // next call to ares_fds tell us to
+ this->poller->remove_socket_handler(this->socket);
+ ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket);
+}
+
+bool DNSSocketHandler::is_connected() const
+{
+ return true;
+}
+
+#endif /* CARES_FOUND */
diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp
new file mode 100644
index 0000000..ad119e1
--- /dev/null
+++ b/louloulibs/network/dns_socket_handler.hpp
@@ -0,0 +1,46 @@
+#ifndef DNS_SOCKET_HANDLER_HPP
+# define DNS_SOCKET_HANDLER_HPP
+
+#include <louloulibs.h>
+#ifdef CARES_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)
+ */
+
+class DNSSocketHandler: public SocketHandler
+{
+public:
+ explicit DNSSocketHandler(std::shared_ptr<Poller> poller, const socket_t socket);
+ ~DNSSocketHandler();
+ /**
+ * 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;
+ /**
+ * Do nothing, because we are always considered to be connected, since the
+ * connection is done by c-ares and not by us.
+ */
+ void connect() override final;
+ /**
+ * Always true, see the comment for connect()
+ */
+ bool is_connected() const override final;
+
+private:
+ DNSSocketHandler(const DNSSocketHandler&) = delete;
+ DNSSocketHandler(DNSSocketHandler&&) = delete;
+ DNSSocketHandler& operator=(const DNSSocketHandler&) = delete;
+ DNSSocketHandler& operator=(DNSSocketHandler&&) = delete;
+};
+
+#endif // CARES_FOUND
+#endif // DNS_SOCKET_HANDLER_HPP
diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp
new file mode 100644
index 0000000..329e1c8
--- /dev/null
+++ b/louloulibs/network/poller.cpp
@@ -0,0 +1,225 @@
+#include <network/poller.hpp>
+#include <logger/logger.hpp>
+#include <utils/timed_events.hpp>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+
+Poller::Poller()
+{
+#if POLLER == POLL
+ this->nfds = 0;
+#elif POLLER == EPOLL
+ this->epfd = ::epoll_create1(0);
+ if (this->epfd == -1)
+ {
+ log_error("epoll failed: " << strerror(errno));
+ throw std::runtime_error("Could not create epoll instance");
+ }
+#endif
+}
+
+Poller::~Poller()
+{
+}
+
+void Poller::add_socket_handler(SocketHandler* socket_handler)
+{
+ // Don't do anything if the socket is already managed
+ const auto it = this->socket_handlers.find(socket_handler->get_socket());
+ if (it != this->socket_handlers.end())
+ return ;
+
+ this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler);
+
+ // We always watch all sockets for receive events
+#if POLLER == POLL
+ this->fds[this->nfds].fd = socket_handler->get_socket();
+ this->fds[this->nfds].events = POLLIN;
+ this->nfds++;
+#endif
+#if POLLER == EPOLL
+ struct epoll_event event = {EPOLLIN, {socket_handler}};
+ const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event);
+ if (res == -1)
+ {
+ log_error("epoll_ctl failed: " << strerror(errno));
+ throw std::runtime_error("Could not add socket to epoll");
+ }
+#endif
+}
+
+void Poller::remove_socket_handler(const socket_t socket)
+{
+ const auto it = this->socket_handlers.find(socket);
+ if (it == this->socket_handlers.end())
+ throw std::runtime_error("Trying to remove a SocketHandler that is not managed");
+ this->socket_handlers.erase(it);
+
+#if POLLER == POLL
+ for (size_t i = 0; i < this->nfds; i++)
+ {
+ if (this->fds[i].fd == socket)
+ {
+ // Move all subsequent pollfd by one on the left, erasing the
+ // value of the one we remove
+ for (size_t j = i; j < this->nfds - 1; ++j)
+ {
+ this->fds[j].fd = this->fds[j+1].fd;
+ this->fds[j].events= this->fds[j+1].events;
+ }
+ this->nfds--;
+ }
+ }
+#elif POLLER == EPOLL
+ const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr);
+ if (res == -1)
+ {
+ log_error("epoll_ctl failed: " << strerror(errno));
+ throw std::runtime_error("Could not remove socket from epoll");
+ }
+#endif
+}
+
+void Poller::watch_send_events(SocketHandler* socket_handler)
+{
+#if POLLER == POLL
+ for (size_t i = 0; i <= this->nfds; ++i)
+ {
+ if (this->fds[i].fd == socket_handler->get_socket())
+ {
+ this->fds[i].events = POLLIN|POLLOUT;
+ return;
+ }
+ }
+ throw std::runtime_error("Cannot watch a non-registered socket for send events");
+#elif POLLER == EPOLL
+ struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}};
+ const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event);
+ if (res == -1)
+ {
+ log_error("epoll_ctl failed: " << strerror(errno));
+ throw std::runtime_error("Could not modify socket flags in epoll");
+ }
+#endif
+}
+
+void Poller::stop_watching_send_events(SocketHandler* socket_handler)
+{
+#if POLLER == POLL
+ for (size_t i = 0; i <= this->nfds; ++i)
+ {
+ if (this->fds[i].fd == socket_handler->get_socket())
+ {
+ this->fds[i].events = POLLIN;
+ return;
+ }
+ }
+ throw std::runtime_error("Cannot watch a non-registered socket for send events");
+#elif POLLER == EPOLL
+ struct epoll_event event = {EPOLLIN, {socket_handler}};
+ const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event);
+ if (res == -1)
+ {
+ log_error("epoll_ctl failed: " << strerror(errno));
+ throw std::runtime_error("Could not modify socket flags in epoll");
+ }
+#endif
+}
+
+int Poller::poll(const std::chrono::milliseconds& timeout)
+{
+ if (this->socket_handlers.empty() && timeout == utils::no_timeout)
+ return -1;
+#if POLLER == POLL
+ // Convert our nice timeout into this ugly struct
+ struct timespec timeout_ts;
+ struct timespec* timeout_tsp;
+ if (timeout > 0s)
+ {
+ auto seconds = std::chrono::duration_cast<std::chrono::seconds>(timeout);
+ timeout_ts.tv_sec = seconds.count();
+ timeout_ts.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(timeout - seconds).count();
+ timeout_tsp = &timeout_ts;
+ }
+ else
+ timeout_tsp = nullptr;
+
+ // Unblock all signals, only during the ppoll call
+ sigset_t empty_signal_set;
+ sigemptyset(&empty_signal_set);
+ int nb_events = ::ppoll(this->fds, this->nfds, timeout_tsp,
+ &empty_signal_set);
+ if (nb_events < 0)
+ {
+ if (errno == EINTR)
+ return true;
+ log_error("poll failed: " << strerror(errno));
+ throw std::runtime_error("Poll failed");
+ }
+ // We cannot possibly have more ready events than the number of fds we are
+ // watching
+ assert(static_cast<unsigned int>(nb_events) <= this->nfds);
+ for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i)
+ {
+ if (this->fds[i].revents == 0)
+ continue;
+ else if (this->fds[i].revents & POLLIN)
+ {
+ auto socket_handler = this->socket_handlers.at(this->fds[i].fd);
+ socket_handler->on_recv();
+ nb_events--;
+ }
+ else if (this->fds[i].revents & POLLOUT)
+ {
+ auto socket_handler = this->socket_handlers.at(this->fds[i].fd);
+ if (socket_handler->is_connected())
+ socket_handler->on_send();
+ else
+ socket_handler->connect();
+ nb_events--;
+ }
+ }
+ return 1;
+#elif POLLER == EPOLL
+ static const size_t max_events = 12;
+ struct epoll_event revents[max_events];
+ // Unblock all signals, only during the epoll_pwait call
+ sigset_t empty_signal_set;
+ sigemptyset(&empty_signal_set);
+ const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, timeout.count(),
+ &empty_signal_set);
+ if (nb_events == -1)
+ {
+ if (errno == EINTR)
+ return 0;
+ log_error("epoll wait: " << strerror(errno));
+ throw std::runtime_error("Epoll_wait failed");
+ }
+ for (int i = 0; i < nb_events; ++i)
+ {
+ auto socket_handler = static_cast<SocketHandler*>(revents[i].data.ptr);
+ if (revents[i].events & EPOLLIN)
+ socket_handler->on_recv();
+ else if (revents[i].events & EPOLLOUT)
+ {
+ if (socket_handler->is_connected())
+ socket_handler->on_send();
+ else
+ socket_handler->connect();
+ }
+ }
+ return nb_events;
+#endif
+}
+
+size_t Poller::size() const
+{
+ return this->socket_handlers.size();
+}
diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp
new file mode 100644
index 0000000..de0cb48
--- /dev/null
+++ b/louloulibs/network/poller.hpp
@@ -0,0 +1,95 @@
+#ifndef POLLER_INCLUDED
+# define POLLER_INCLUDED
+
+#include <network/socket_handler.hpp>
+
+#include <unordered_map>
+#include <memory>
+#include <chrono>
+
+#define POLL 1
+#define EPOLL 2
+#define KQUEUE 3
+#include <louloulibs.h>
+#ifndef POLLER
+ #define POLLER POLL
+#endif
+
+#if POLLER == POLL
+ #include <poll.h>
+ #define MAX_POLL_FD_NUMBER 4096
+#elif POLLER == EPOLL
+ #include <sys/epoll.h>
+#else
+ #error Invalid POLLER value
+#endif
+
+/**
+ * We pass some SocketHandlers to this Poller, which uses
+ * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers,
+ * and call the callbacks when event occurs.
+ *
+ * TODO: support these pollers:
+ * - kqueue(2)
+ */
+
+class Poller
+{
+public:
+ explicit Poller();
+ ~Poller();
+ /**
+ * Add a SocketHandler to be monitored by this Poller. All receive events
+ * are always automatically watched.
+ */
+ void add_socket_handler(SocketHandler* socket_handler);
+ /**
+ * Remove (and stop managing) a SocketHandler, designated by the given socket_t.
+ */
+ void remove_socket_handler(const socket_t socket);
+ /**
+ * Signal the poller that he needs to watch for send events for the given
+ * SocketHandler.
+ */
+ void watch_send_events(SocketHandler* socket_handler);
+ /**
+ * Signal the poller that he needs to stop watching for send events for
+ * this SocketHandler.
+ */
+ void stop_watching_send_events(SocketHandler* socket_handler);
+ /**
+ * Wait for all watched events, and call the SocketHandlers' callbacks
+ * when one is ready. Returns if nothing happened before the provided
+ * timeout. If the timeout is 0, it waits forever. If there is no
+ * watched event, returns -1 immediately, ignoring the timeout value.
+ * Otherwise, returns the number of event handled. If 0 is returned this
+ * means that we were interrupted by a signal, or the timeout occured.
+ */
+ int poll(const std::chrono::milliseconds& timeout);
+ /**
+ * Returns the number of SocketHandlers managed by the poller.
+ */
+ size_t size() const;
+
+private:
+ /**
+ * A "list" of all the SocketHandlers that we manage, indexed by socket,
+ * because that's what is returned by select/poll/etc when an event
+ * occures.
+ */
+ std::unordered_map<socket_t, SocketHandler*> socket_handlers;
+
+#if POLLER == POLL
+ struct pollfd fds[MAX_POLL_FD_NUMBER];
+ nfds_t nfds;
+#elif POLLER == EPOLL
+ int epfd;
+#endif
+
+ Poller(const Poller&) = delete;
+ Poller(Poller&&) = delete;
+ Poller& operator=(const Poller&) = delete;
+ Poller& operator=(Poller&&) = delete;
+};
+
+#endif // POLLER_INCLUDED
diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp
new file mode 100644
index 0000000..d01ac5d
--- /dev/null
+++ b/louloulibs/network/socket_handler.hpp
@@ -0,0 +1,45 @@
+#ifndef SOCKET_HANDLER_HPP
+# define SOCKET_HANDLER_HPP
+
+#include <louloulibs.h>
+#include <memory>
+
+class Poller;
+
+typedef int socket_t;
+
+class SocketHandler
+{
+public:
+ explicit SocketHandler(std::shared_ptr<Poller> poller, const socket_t socket):
+ poller(poller),
+ socket(socket)
+ {}
+ virtual ~SocketHandler() {}
+ virtual void on_recv() = 0;
+ virtual void on_send() = 0;
+ virtual void connect() = 0;
+ virtual bool is_connected() const = 0;
+
+ socket_t get_socket() const
+ { return this->socket; }
+
+protected:
+ /**
+ * A pointer to the poller that manages us, because we need to communicate
+ * with it.
+ */
+ std::shared_ptr<Poller> poller;
+ /**
+ * The handled socket.
+ */
+ socket_t socket;
+
+private:
+ SocketHandler(const SocketHandler&) = delete;
+ SocketHandler(SocketHandler&&) = delete;
+ SocketHandler& operator=(const SocketHandler&) = delete;
+ SocketHandler& operator=(SocketHandler&&) = delete;
+};
+
+#endif // SOCKET_HANDLER_HPP
diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp
new file mode 100644
index 0000000..f647b86
--- /dev/null
+++ b/louloulibs/network/tcp_socket_handler.cpp
@@ -0,0 +1,590 @@
+#include <network/tcp_socket_handler.hpp>
+#include <network/dns_handler.hpp>
+
+#include <utils/timed_events.hpp>
+#include <utils/scopeguard.hpp>
+#include <network/poller.hpp>
+
+#include <logger/logger.hpp>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <stdexcept>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <cstring>
+#include <fcntl.h>
+
+#include <iostream>
+
+#ifdef BOTAN_FOUND
+# include <botan/hex.h>
+
+Botan::AutoSeeded_RNG TCPSocketHandler::rng;
+Permissive_Credentials_Manager TCPSocketHandler::credential_manager;
+Botan::TLS::Policy TCPSocketHandler::policy;
+Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng);
+
+#endif
+
+#ifndef UIO_FASTIOV
+# define UIO_FASTIOV 8
+#endif
+
+using namespace std::string_literals;
+using namespace std::chrono_literals;
+
+namespace ph = std::placeholders;
+
+TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
+ SocketHandler(poller, -1),
+ use_tls(false),
+ connected(false),
+ connecting(false)
+#ifdef CARES_FOUND
+ ,resolving(false),
+ resolved(false),
+ resolved4(false),
+ resolved6(false),
+ cares_addrinfo(nullptr),
+ cares_error()
+#endif
+{}
+
+TCPSocketHandler::~TCPSocketHandler()
+{
+#ifdef CARES_FOUND
+ this->free_cares_addrinfo();
+#endif
+}
+
+void TCPSocketHandler::init_socket(const struct addrinfo* rp)
+{
+ if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
+ throw std::runtime_error("Could not create socket: "s + strerror(errno));
+ int optval = 1;
+ if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1)
+ log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno));
+ // Set the socket on non-blocking mode. This is useful to receive a EAGAIN
+ // error when connect() would block, to not block the whole process if a
+ // remote is not responsive.
+ const int existing_flags = ::fcntl(this->socket, F_GETFL, 0);
+ if ((existing_flags == -1) ||
+ (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1))
+ throw std::runtime_error("Could not initialize socket: "s + strerror(errno));
+}
+
+void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
+{
+ this->address = address;
+ this->port = port;
+ this->use_tls = tls;
+
+ utils::ScopeGuard sg;
+
+ struct addrinfo* addr_res;
+
+ if (!this->connecting)
+ {
+ // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if
+ // this is the first call of this function.
+#ifdef CARES_FOUND
+ if (!this->resolved)
+ {
+ log_info("Trying to connect to " << address << ":" << port);
+ // Start the asynchronous process of resolving the hostname. Once
+ // the addresses have been found and `resolved` has been set to true
+ // (but connecting will still be false), TCPSocketHandler::connect()
+ // needs to be called, again.
+ this->resolving = true;
+ DNSHandler::instance.gethostbyname(address, this, AF_INET6);
+ DNSHandler::instance.gethostbyname(address, this, AF_INET);
+ return;
+ }
+ else
+ {
+ // The c-ares resolved the hostname and the available addresses
+ // where saved in the cares_addrinfo linked list. Now, just use
+ // this list to try to connect.
+ addr_res = this->cares_addrinfo;
+ if (!addr_res)
+ {
+ this->close();
+ this->on_connection_failed(this->cares_error);
+ return ;
+ }
+ }
+#else
+ log_info("Trying to connect to " << address << ":" << port);
+ 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;
+
+ const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res);
+
+ if (res != 0)
+ {
+ log_warning("getaddrinfo failed: "s + gai_strerror(res));
+ this->close();
+ this->on_connection_failed(gai_strerror(res));
+ return ;
+ }
+ // Make sure the alloced structure is always freed at the end of the
+ // function
+ sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); });
+#endif
+ }
+ else
+ { // This function is called again, use the saved addrinfo structure,
+ // instead of re-doing the whole getaddrinfo process.
+ addr_res = &this->addrinfo;
+ }
+
+ for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next)
+ {
+ if (!this->connecting)
+ {
+ try {
+ this->init_socket(rp);
+ }
+ catch (const std::runtime_error& error) {
+ log_error("Failed to init socket: " << error.what());
+ break;
+ }
+ }
+ if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0
+ || errno == EISCONN)
+ {
+ log_info("Connection success.");
+ TimedEventsManager::instance().cancel("connection_timeout"s +
+ std::to_string(this->socket));
+ this->poller->add_socket_handler(this);
+ this->connected = true;
+ this->connecting = false;
+#ifdef BOTAN_FOUND
+ if (this->use_tls)
+ this->start_tls();
+#endif
+ this->on_connected();
+ return ;
+ }
+ else if (errno == EINPROGRESS || errno == EALREADY)
+ { // retry this process later, when the socket
+ // is ready to be written on.
+ this->connecting = true;
+ this->poller->add_socket_handler(this);
+ this->poller->watch_send_events(this);
+ // Save the addrinfo structure, to use it on the next call
+ this->ai_addrlen = rp->ai_addrlen;
+ memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen);
+ memcpy(&this->addrinfo, rp, sizeof(struct addrinfo));
+ this->addrinfo.ai_addr = reinterpret_cast<struct sockaddr*>(&this->ai_addr);
+ this->addrinfo.ai_next = nullptr;
+ // If the connection has not succeeded or failed in 5s, we consider
+ // it to have failed
+ TimedEventsManager::instance().add_event(
+ TimedEvent(std::chrono::steady_clock::now() + 5s,
+ std::bind(&TCPSocketHandler::on_connection_timeout, this),
+ "connection_timeout"s + std::to_string(this->socket)));
+ return ;
+ }
+ log_info("Connection failed:" << strerror(errno));
+ }
+ log_error("All connection attempts failed.");
+ this->close();
+ this->on_connection_failed(strerror(errno));
+ return ;
+}
+
+void TCPSocketHandler::on_connection_timeout()
+{
+ this->close();
+ this->on_connection_failed("connection timed out");
+}
+
+void TCPSocketHandler::connect()
+{
+ this->connect(this->address, this->port, this->use_tls);
+}
+
+void TCPSocketHandler::on_recv()
+{
+#ifdef BOTAN_FOUND
+ if (this->use_tls)
+ this->tls_recv();
+ else
+#endif
+ this->plain_recv();
+}
+
+void TCPSocketHandler::plain_recv()
+{
+ static constexpr size_t buf_size = 4096;
+ char buf[buf_size];
+ void* recv_buf = this->get_receive_buffer(buf_size);
+
+ if (recv_buf == nullptr)
+ recv_buf = buf;
+
+ const ssize_t size = this->do_recv(recv_buf, buf_size);
+
+ if (size > 0)
+ {
+ if (buf == recv_buf)
+ {
+ // data needs to be placed in the in_buf string, because no buffer
+ // was provided to receive that data directly. The in_buf buffer
+ // will be handled in parse_in_buffer()
+ this->in_buf += std::string(buf, size);
+ }
+ this->parse_in_buffer(size);
+ }
+}
+
+ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size)
+{
+ ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0);
+ if (0 == size)
+ {
+ this->on_connection_close("");
+ this->close();
+ }
+ else if (-1 == size)
+ {
+ log_warning("Error while reading from socket: " << strerror(errno));
+ // Remember if we were connecting, or already connected when this
+ // happened, because close() sets this->connecting to false
+ const auto were_connecting = this->connecting;
+ this->close();
+ if (were_connecting)
+ this->on_connection_failed(strerror(errno));
+ else
+ this->on_connection_close(strerror(errno));
+ }
+ return size;
+}
+
+void TCPSocketHandler::on_send()
+{
+ struct iovec msg_iov[UIO_FASTIOV] = {};
+ struct msghdr msg{nullptr, 0,
+ msg_iov,
+ 0, nullptr, 0, 0};
+ for (std::string& s: this->out_buf)
+ {
+ // unconsting the content of s is ok, sendmsg will never modify it
+ msg_iov[msg.msg_iovlen].iov_base = const_cast<char*>(s.data());
+ msg_iov[msg.msg_iovlen].iov_len = s.size();
+ if (++msg.msg_iovlen == UIO_FASTIOV)
+ break;
+ }
+ ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL);
+ if (res < 0)
+ {
+ log_error("sendmsg failed: " << strerror(errno));
+ this->on_connection_close(strerror(errno));
+ this->close();
+ }
+ else
+ {
+ // remove all the strings that were successfully sent.
+ for (auto it = this->out_buf.begin();
+ it != this->out_buf.end();)
+ {
+ if (static_cast<size_t>(res) >= (*it).size())
+ {
+ res -= (*it).size();
+ it = this->out_buf.erase(it);
+ }
+ else
+ {
+ // If one string has partially been sent, we use substr to
+ // crop it
+ if (res > 0)
+ (*it) = (*it).substr(res, std::string::npos);
+ break;
+ }
+ }
+ if (this->out_buf.empty())
+ this->poller->stop_watching_send_events(this);
+ }
+}
+
+void TCPSocketHandler::close()
+{
+ TimedEventsManager::instance().cancel("connection_timeout"s +
+ std::to_string(this->socket));
+ if (this->connected || this->connecting)
+ this->poller->remove_socket_handler(this->get_socket());
+ if (this->socket != -1)
+ {
+ ::close(this->socket);
+ this->socket = -1;
+ }
+ this->connected = false;
+ this->connecting = false;
+#ifdef CARES_FOUND
+ this->resolving = false;
+ this->resolved = false;
+ this->resolved4 = false;
+ this->resolved6 = false;
+ this->free_cares_addrinfo();
+ this->cares_error.clear();
+#endif
+ this->in_buf.clear();
+ this->out_buf.clear();
+ this->port.clear();
+}
+
+void TCPSocketHandler::send_data(std::string&& data)
+{
+#ifdef BOTAN_FOUND
+ if (this->use_tls)
+ this->tls_send(std::move(data));
+ else
+#endif
+ this->raw_send(std::move(data));
+}
+
+void TCPSocketHandler::raw_send(std::string&& data)
+{
+ if (data.empty())
+ return ;
+ this->out_buf.emplace_back(std::move(data));
+ if (this->connected)
+ this->poller->watch_send_events(this);
+}
+
+void TCPSocketHandler::send_pending_data()
+{
+ if (this->connected && !this->out_buf.empty())
+ this->poller->watch_send_events(this);
+}
+
+bool TCPSocketHandler::is_connected() const
+{
+ return this->connected;
+}
+
+bool TCPSocketHandler::is_connecting() const
+{
+#ifdef CARES_FOUND
+ return this->connecting || this->resolving;
+#else
+ return this->connecting;
+#endif
+}
+
+void* TCPSocketHandler::get_receive_buffer(const size_t) const
+{
+ return nullptr;
+}
+
+#ifdef BOTAN_FOUND
+void TCPSocketHandler::start_tls()
+{
+ Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port));
+ this->tls = std::make_unique<Botan::TLS::Client>(
+ std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2),
+ std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2),
+ std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3),
+ std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1),
+ session_manager, credential_manager, policy,
+ rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version());
+}
+
+void TCPSocketHandler::tls_recv()
+{
+ static constexpr size_t buf_size = 4096;
+ char recv_buf[buf_size];
+
+ const ssize_t size = this->do_recv(recv_buf, buf_size);
+ if (size > 0)
+ {
+ const bool was_active = this->tls->is_active();
+ this->tls->received_data(reinterpret_cast<const Botan::byte*>(recv_buf),
+ static_cast<size_t>(size));
+ if (!was_active && this->tls->is_active())
+ this->on_tls_activated();
+ }
+}
+
+void TCPSocketHandler::tls_send(std::string&& data)
+{
+ if (this->tls->is_active())
+ {
+ const bool was_active = this->tls->is_active();
+ if (!this->pre_buf.empty())
+ {
+ this->tls->send(reinterpret_cast<const Botan::byte*>(this->pre_buf.data()),
+ this->pre_buf.size());
+ this->pre_buf = "";
+ }
+ if (!data.empty())
+ this->tls->send(reinterpret_cast<const Botan::byte*>(data.data()),
+ data.size());
+ if (!was_active && this->tls->is_active())
+ this->on_tls_activated();
+ }
+ else
+ this->pre_buf += data;
+}
+
+void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size)
+{
+ this->in_buf += std::string(reinterpret_cast<const char*>(data),
+ size);
+ if (!this->in_buf.empty())
+ this->parse_in_buffer(size);
+}
+
+void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size)
+{
+ this->raw_send(std::string(reinterpret_cast<const char*>(data), size));
+}
+
+void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t)
+{
+ log_debug("tls_alert: " << alert.type_string());
+}
+
+bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session)
+{
+ log_debug("Handshake with " << session.server_info().hostname() << " complete."
+ << " Version: " << session.version().to_string()
+ << " using " << session.ciphersuite().to_string());
+ if (!session.session_id().empty())
+ log_debug("Session ID " << Botan::hex_encode(session.session_id()));
+ if (!session.session_ticket().empty())
+ log_debug("Session ticket " << Botan::hex_encode(session.session_ticket()));
+ return true;
+}
+
+void TCPSocketHandler::on_tls_activated()
+{
+ this->send_data("");
+}
+
+void Permissive_Credentials_Manager::verify_certificate_chain(const std::string& type,
+ const std::string& purported_hostname,
+ const std::vector<Botan::X509_Certificate>&)
+{ // TODO: Offer the admin to disallow connection on untrusted
+ // certificates
+ log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname);
+}
+
+#endif // BOTAN_FOUND
+
+#ifdef CARES_FOUND
+
+void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent)
+{
+ this->resolved4 = true;
+ if (status == ARES_SUCCESS)
+ this->fill_ares_addrinfo4(hostent);
+ else
+ this->cares_error = ::ares_strerror(status);
+
+ if (this->resolved4 && this->resolved6)
+ {
+ this->resolved = true;
+ this->resolving = false;
+ this->connect();
+ }
+}
+
+void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent)
+{
+ this->resolved6 = true;
+ if (status == ARES_SUCCESS)
+ this->fill_ares_addrinfo6(hostent);
+ else
+ this->cares_error = ::ares_strerror(status);
+
+ if (this->resolved4 && this->resolved6)
+ {
+ this->resolved = true;
+ this->resolving = false;
+ this->connect();
+ }
+}
+
+void TCPSocketHandler::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* addr = new struct sockaddr_in;
+ addr->sin_family = hostent->h_addrtype;
+ addr->sin_port = htons(strtoul(this->port.data(), nullptr, 10));
+ addr->sin_addr.s_addr = (*address)->s_addr;
+
+ current->ai_addr = reinterpret_cast<struct sockaddr*>(addr);
+ current->ai_next = nullptr;
+ current->ai_canonname = nullptr;
+
+ current->ai_next = prev;
+ this->cares_addrinfo = current;
+ prev = current;
+ ++address;
+ }
+}
+
+void TCPSocketHandler::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* addr = new struct sockaddr_in6;
+ addr->sin6_family = hostent->h_addrtype;
+ addr->sin6_port = htons(strtoul(this->port.data(), nullptr, 10));
+ ::memcpy(addr->sin6_addr.s6_addr, (*address)->s6_addr, 16);
+ addr->sin6_flowinfo = 0;
+ addr->sin6_scope_id = 0;
+
+ current->ai_addr = reinterpret_cast<struct sockaddr*>(addr);
+ current->ai_next = nullptr;
+ current->ai_canonname = nullptr;
+
+ current->ai_next = prev;
+ this->cares_addrinfo = current;
+ prev = current;
+ ++address;
+ }
+}
+
+void TCPSocketHandler::free_cares_addrinfo()
+{
+ while (this->cares_addrinfo)
+ {
+ delete this->cares_addrinfo->ai_addr;
+ auto next = this->cares_addrinfo->ai_next;
+ delete this->cares_addrinfo;
+ this->cares_addrinfo = next;
+ }
+}
+
+#endif // CARES_FOUND
diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp
new file mode 100644
index 0000000..2b5cd49
--- /dev/null
+++ b/louloulibs/network/tcp_socket_handler.hpp
@@ -0,0 +1,293 @@
+#ifndef SOCKET_HANDLER_INCLUDED
+# define SOCKET_HANDLER_INCLUDED
+
+#include <network/socket_handler.hpp>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <memory>
+#include <string>
+#include <list>
+
+#include "louloulibs.h"
+
+#ifdef CARES_FOUND
+# include <ares.h>
+#endif
+
+#ifdef BOTAN_FOUND
+# include <botan/botan.h>
+# include <botan/tls_client.h>
+
+/**
+ * A very simple credential manager that accepts any certificate.
+ */
+class Permissive_Credentials_Manager: public Botan::Credentials_Manager
+{
+public:
+ void verify_certificate_chain(const std::string& type,
+ const std::string& purported_hostname,
+ const std::vector<Botan::X509_Certificate>&);
+};
+#endif // BOTAN_FOUND
+
+/**
+ * An interface, with a series of callbacks that should be implemented in
+ * subclasses that deal with a socket. These callbacks are called on various events
+ * (read/write/timeout, etc) when they are notified to a poller
+ * (select/poll/epoll etc)
+ */
+class TCPSocketHandler: public SocketHandler
+{
+protected:
+ ~TCPSocketHandler();
+
+public:
+ explicit TCPSocketHandler(std::shared_ptr<Poller> poller);
+ /**
+ * Connect to the remote server, and call on_connected() if this
+ * succeeds. If tls is true, we set use_tls to true and will also call
+ * start_tls() when the connection succeeds.
+ */
+ void connect(const std::string& address, const std::string& port, const bool tls);
+ void connect() override final;
+ /**
+ * Reads raw data from the socket. And pass it to parse_in_buffer()
+ * If we are using TLS on this connection, we call tls_recv()
+ */
+ void on_recv() override final;
+ /**
+ * Write as much data from out_buf as possible, in the socket.
+ */
+ void on_send() override final;
+ /**
+ * Add the given data to out_buf and tell our poller that we want to be
+ * notified when a send event is ready.
+ *
+ * This can be overriden if we want to modify the data before sending
+ * it. For example if we want to encrypt it.
+ */
+ void send_data(std::string&& data);
+ /**
+ * Watch the socket for send events, if our out buffer is not empty.
+ */
+ void send_pending_data();
+ /**
+ * Close the connection, remove us from the poller
+ */
+ void close();
+ /**
+ * Called by a TimedEvent, when the connection did not succeed or fail
+ * after a given time.
+ */
+ void on_connection_timeout();
+ /**
+ * Called when the connection is successful.
+ */
+ virtual void on_connected() = 0;
+ /**
+ * Called when the connection fails. Not when it is closed later, just at
+ * the connect() call.
+ */
+ virtual void on_connection_failed(const std::string& reason) = 0;
+ /**
+ * Called when we detect a disconnection from the remote host.
+ */
+ virtual void on_connection_close(const std::string& error) = 0;
+ /**
+ * Handle/consume (some of) the data received so far. The data to handle
+ * may be in the in_buf buffer, or somewhere else, depending on what
+ * get_receive_buffer() returned. If some data is used from in_buf, it
+ * should be truncated, only the unused data should be left untouched.
+ *
+ * The size argument is the size of the last chunk of data that was added to the buffer.
+ */
+ virtual void parse_in_buffer(const size_t size) = 0;
+ bool is_connected() const override final;
+ bool is_connecting() const;
+
+#ifdef CARES_FOUND
+ void on_hostname4_resolved(int status, struct hostent* hostent);
+ void on_hostname6_resolved(int status, struct hostent* hostent);
+
+ void free_cares_addrinfo();
+
+ void fill_ares_addrinfo4(const struct hostent* hostent);
+ void fill_ares_addrinfo6(const struct hostent* hostent);
+#endif
+
+private:
+ /**
+ * Initialize the socket with the parameters contained in the given
+ * addrinfo structure.
+ */
+ void init_socket(const struct addrinfo* rp);
+ /**
+ * Reads from the socket into the provided buffer. If an error occurs
+ * (read returns <= 0), the handling of the error is done here (close the
+ * connection, log a message, etc).
+ *
+ * Returns the value returned by ::recv(), so the buffer should not be
+ * used if it’s not positive.
+ */
+ ssize_t do_recv(void* recv_buf, const size_t buf_size);
+ /**
+ * Reads data from the socket and calls parse_in_buffer with it.
+ */
+ void plain_recv();
+ /**
+ * Mark the given data as ready to be sent, as-is, on the socket, as soon
+ * as we can.
+ */
+ void raw_send(std::string&& data);
+
+#ifdef BOTAN_FOUND
+ /**
+ * Create the TLS::Client object, with all the callbacks etc. This must be
+ * called only when we know we are able to send TLS-encrypted data over
+ * the socket.
+ */
+ void start_tls();
+ /**
+ * An additional step to pass the data into our tls object to decrypt it
+ * before passing it to parse_in_buffer.
+ */
+ void tls_recv();
+ /**
+ * Pass the data to the tls object in order to encrypt it. The tls object
+ * will then call raw_send as a callback whenever data as been encrypted
+ * and can be sent on the socket.
+ */
+ void tls_send(std::string&& data);
+ /**
+ * Called by the tls object that some data has been decrypt. We call
+ * parse_in_buffer() to handle that unencrypted data.
+ */
+ void tls_data_cb(const Botan::byte* data, size_t size);
+ /**
+ * Called by the tls object to indicate that some data has been encrypted
+ * and is now ready to be sent on the socket as is.
+ */
+ void tls_output_fn(const Botan::byte* data, size_t size);
+ /**
+ * Called by the tls object to indicate that a TLS alert has been
+ * received. We don’t use it, we just log some message, at the moment.
+ */
+ void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t);
+ /**
+ * Called by the tls object at the end of the TLS handshake. We don't do
+ * anything here appart from logging the TLS session information.
+ */
+ bool tls_handshake_cb(const Botan::TLS::Session& session);
+ /**
+ * Called whenever the tls session goes from inactive to active. This
+ * means that the handshake has just been successfully done, and we can
+ * now proceed to send any available data into our tls object.
+ */
+ void on_tls_activated();
+#endif // BOTAN_FOUND
+ /**
+ * Where data is added, when we want to send something to the client.
+ */
+ std::list<std::string> out_buf;
+ /**
+ * Keep the details of the addrinfo that triggered a EINPROGRESS error when
+ * connect()ing to it, to reuse it directly when connect() is called
+ * again.
+ */
+ struct addrinfo addrinfo;
+ struct sockaddr_in6 ai_addr;
+ socklen_t ai_addrlen;
+
+protected:
+ /**
+ * Where data read from the socket is added until we can extract a full
+ * and meaningful “message” from it.
+ *
+ * TODO: something more efficient than a string.
+ */
+ std::string in_buf;
+ /**
+ * Whether we are using TLS on this connection or not.
+ */
+ bool use_tls;
+ /**
+ * Provide a buffer in which data can be directly received. This can be
+ * used to avoid copying data into in_buf before using it. If no buffer
+ * needs to be provided, nullptr is returned (the default implementation
+ * does that), in that case our internal in_buf will be used to save the
+ * data until it can be used by parse_in_buffer().
+ */
+ virtual void* get_receive_buffer(const size_t size) const;
+ /**
+ * Hostname we are connected/connecting to
+ */
+ std::string address;
+ /**
+ * Port we are connected/connecting to
+ */
+ std::string port;
+
+ bool connected;
+ bool connecting;
+
+#ifdef CARES_FOUND
+ bool resolving;
+ /**
+ * Whether or not the DNS resolution was successfully done
+ */
+ bool resolved;
+ bool resolved4;
+ bool resolved6;
+ /**
+ * When using c-ares to resolve the host asynchronously, we need the
+ * c-ares callback 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 next call of connect() will then try all these values
+ * (exactly like we do with the result of getaddrinfo) and save the one
+ * that worked (or returned EINPROGRESS) in the other struct addrinfo (see
+ * the members addrinfo, ai_addrlen, and ai_addr).
+ */
+ struct addrinfo* cares_addrinfo;
+ std::string cares_error;
+#endif // CARES_FOUND
+
+private:
+ TCPSocketHandler(const TCPSocketHandler&) = delete;
+ TCPSocketHandler(TCPSocketHandler&&) = delete;
+ TCPSocketHandler& operator=(const TCPSocketHandler&) = delete;
+ TCPSocketHandler& operator=(TCPSocketHandler&&) = delete;
+
+#ifdef BOTAN_FOUND
+ /**
+ * Botan stuff to manipulate a TLS session.
+ */
+ static Botan::AutoSeeded_RNG rng;
+ static Permissive_Credentials_Manager credential_manager;
+ static Botan::TLS::Policy policy;
+ static Botan::TLS::Session_Manager_In_Memory session_manager;
+ /**
+ * We use a unique_ptr because we may not want to create the object at
+ * all. The Botan::TLS::Client object generates a handshake message as
+ * soon and calls the output_fn callback with it as soon as it is
+ * created. Therefore, we do not want to create it if do not intend to do
+ * send any TLS-encrypted message. We create the object only when needed
+ * (for example after we have negociated a TLS session using a STARTTLS
+ * message, or stuf like that).
+ *
+ * See start_tls for the method where this object is created.
+ */
+ std::unique_ptr<Botan::TLS::Client> tls;
+ /**
+ * An additional buffer to keep data that the user wants to send, but
+ * cannot because the handshake is not done.
+ */
+ std::string pre_buf;
+#endif // BOTAN_FOUND
+};
+
+#endif // SOCKET_HANDLER_INCLUDED