diff options
Diffstat (limited to 'louloulibs/network')
-rw-r--r-- | louloulibs/network/credentials_manager.cpp | 140 | ||||
-rw-r--r-- | louloulibs/network/credentials_manager.hpp | 55 | ||||
-rw-r--r-- | louloulibs/network/dns_handler.cpp | 46 | ||||
-rw-r--r-- | louloulibs/network/dns_handler.hpp | 37 | ||||
-rw-r--r-- | louloulibs/network/dns_socket_handler.cpp | 43 | ||||
-rw-r--r-- | louloulibs/network/dns_socket_handler.hpp | 33 | ||||
-rw-r--r-- | louloulibs/network/poller.cpp | 234 | ||||
-rw-r--r-- | louloulibs/network/poller.hpp | 98 | ||||
-rw-r--r-- | louloulibs/network/resolver.cpp | 281 | ||||
-rw-r--r-- | louloulibs/network/resolver.hpp | 122 | ||||
-rw-r--r-- | louloulibs/network/socket_handler.hpp | 42 | ||||
-rw-r--r-- | louloulibs/network/tcp_client_socket_handler.cpp | 261 | ||||
-rw-r--r-- | louloulibs/network/tcp_client_socket_handler.hpp | 82 | ||||
-rw-r--r-- | louloulibs/network/tcp_server_socket.hpp | 70 | ||||
-rw-r--r-- | louloulibs/network/tcp_socket_handler.cpp | 358 | ||||
-rw-r--r-- | louloulibs/network/tcp_socket_handler.hpp | 251 |
16 files changed, 0 insertions, 2153 deletions
diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp deleted file mode 100644 index 289307b..0000000 --- a/louloulibs/network/credentials_manager.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "louloulibs.h" - -#ifdef BOTAN_FOUND -#include <network/tcp_socket_handler.hpp> -#include <network/credentials_manager.hpp> -#include <logger/logger.hpp> -#include <botan/tls_exceptn.h> -#include <config/config.hpp> - -#ifdef USE_DATABASE -# include <database/database.hpp> -#endif - -/** - * TODO find a standard way to find that out. - */ -static const std::vector<std::string> default_cert_files = { - "/etc/ssl/certs/ca-bundle.crt", - "/etc/pki/tls/certs/ca-bundle.crt", - "/etc/ssl/certs/ca-certificates.crt", - "/etc/ca-certificates/extracted/tls-ca-bundle.pem" -}; - -Botan::Certificate_Store_In_Memory BasicCredentialsManager::certificate_store; -bool BasicCredentialsManager::certs_loaded = false; - -BasicCredentialsManager::BasicCredentialsManager(const TCPSocketHandler* const socket_handler): - Botan::Credentials_Manager(), - socket_handler(socket_handler), - trusted_fingerprint{} -{ - BasicCredentialsManager::load_certs(); -} - -void BasicCredentialsManager::set_trusted_fingerprint(const std::string& fingerprint) -{ - this->trusted_fingerprint = fingerprint; -} - -const std::string& BasicCredentialsManager::get_trusted_fingerprint() const -{ - return this->trusted_fingerprint; -} - -void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs, - const std::string& hostname, const std::string& trusted_fingerprint, - std::exception_ptr exc) -{ - - if (!trusted_fingerprint.empty() && !certs.empty() && - trusted_fingerprint == certs[0].fingerprint() && - certs[0].matches_dns_name(hostname)) - // We trust the certificate, based on the trusted fingerprint and - // the fact that the hostname matches - return; - - if (exc) - std::rethrow_exception(exc); -} - -#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) -void BasicCredentialsManager::verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector<Botan::X509_Certificate>& certs) -{ - log_debug("Checking remote certificate (", type, ") for hostname ", purported_hostname); - try - { - Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); - log_debug("Certificate is valid"); - } - catch (const std::exception& tls_exception) - { - log_warning("TLS certificate check failed: ", tls_exception.what()); - std::exception_ptr exception_ptr{}; - if (this->socket_handler->abort_on_invalid_cert()) - exception_ptr = std::current_exception(); - - check_tls_certificate(certs, purported_hostname, this->trusted_fingerprint, exception_ptr); - } -} -#endif - -bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector<std::string>& paths) -{ - for (const auto& path: paths) - { - try - { - Botan::DataSource_Stream bundle(path); - log_debug("Using ca bundle: ", path); - while (!bundle.end_of_data() && bundle.check_available(27)) - { - // TODO: remove this work-around for Botan 1.11.29 - // https://github.com/randombit/botan/issues/438#issuecomment-192866796 - // Note that every certificate that fails to be transcoded into latin-1 - // will be ignored. As a result, some TLS connection may be refused - // because the certificate is signed by an issuer that was ignored. - try { - Botan::X509_Certificate cert(bundle); - BasicCredentialsManager::certificate_store.add_certificate(std::move(cert)); - } catch (const Botan::Decoding_Error& error) { - continue; - } - } - // Only use the first file that can successfully be read. - return true; - } - catch (const Botan::Stream_IO_Error& e) - { - log_debug(e.what()); - } - } - return false; -} - -void BasicCredentialsManager::load_certs() -{ - // Only load the certificates the first time - if (BasicCredentialsManager::certs_loaded) - return; - const std::string conf_path = Config::get("ca_file", ""); - std::vector<std::string> paths; - if (conf_path.empty()) - paths = default_cert_files; - else - paths.push_back(conf_path); - - if (BasicCredentialsManager::try_to_open_one_ca_bundle(paths)) - BasicCredentialsManager::certs_loaded = true; - else - log_warning("The CA could not be loaded, TLS negociation will probably fail."); -} - -std::vector<Botan::Certificate_Store*> BasicCredentialsManager::trusted_certificate_authorities(const std::string&, const std::string&) -{ - return {&this->certificate_store}; -} - -#endif diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp deleted file mode 100644 index 29ee024..0000000 --- a/louloulibs/network/credentials_manager.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "louloulibs.h" - -#ifdef BOTAN_FOUND - -#include <botan/botan.h> -#include <botan/tls_client.h> - -class TCPSocketHandler; - -/** - * If the given cert isn’t valid, based on the given hostname - * and fingerprint, then throws the exception if it’s non-empty. - * - * Must be called after the standard (from Botan) way of - * checking the certificate, if we want to also accept certificates based - * on a trusted fingerprint. - */ -void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs, - const std::string& hostname, const std::string& trusted_fingerprint, - std::exception_ptr exc); - -class BasicCredentialsManager: public Botan::Credentials_Manager -{ -public: - BasicCredentialsManager(const TCPSocketHandler* const socket_handler); - - BasicCredentialsManager(BasicCredentialsManager&&) = delete; - BasicCredentialsManager(const BasicCredentialsManager&) = delete; - BasicCredentialsManager& operator=(const BasicCredentialsManager&) = delete; - BasicCredentialsManager& operator=(BasicCredentialsManager&&) = delete; - -#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) - void verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector<Botan::X509_Certificate>&) override final; -#endif - std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string& type, - const std::string& context) override final; - void set_trusted_fingerprint(const std::string& fingerprint); - const std::string& get_trusted_fingerprint() const; - -private: - const TCPSocketHandler* const socket_handler; - - static bool try_to_open_one_ca_bundle(const std::vector<std::string>& paths); - static void load_certs(); - static Botan::Certificate_Store_In_Memory certificate_store; - static bool certs_loaded; - std::string trusted_fingerprint; -}; - -#endif //BOTAN_FOUND - diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp deleted file mode 100644 index 641c087..0000000 --- a/louloulibs/network/dns_handler.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include <louloulibs.h> -#ifdef UDNS_FOUND - -#include <network/dns_socket_handler.hpp> -#include <network/dns_handler.hpp> -#include <network/poller.hpp> - -#include <utils/timed_events.hpp> - -#include <udns.h> -#include <cerrno> -#include <cstring> - -class Resolver; - -using namespace std::string_literals; - -std::unique_ptr<DNSSocketHandler> DNSHandler::socket_handler{}; - -DNSHandler::DNSHandler(std::shared_ptr<Poller>& poller) -{ - 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::destroy() -{ - DNSHandler::socket_handler.reset(nullptr); - dns_close(nullptr); -} - -void DNSHandler::watch() -{ - DNSHandler::socket_handler->watch(); -} - -void DNSHandler::unwatch() -{ - DNSHandler::socket_handler->unwatch(); -} - -#endif /* UDNS_FOUND */ diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp deleted file mode 100644 index 416f85f..0000000 --- a/louloulibs/network/dns_handler.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include <louloulibs.h> -#ifdef UDNS_FOUND - -class Poller; - -#include <network/dns_socket_handler.hpp> - -#include <string> -#include <vector> -#include <memory> - -class DNSHandler -{ -public: - explicit 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 destroy(); - - static void watch(); - static void unwatch(); - -private: - /** - * Manager for the socket returned by udns, that we need to watch with the poller - */ - static std::unique_ptr<DNSSocketHandler> socket_handler; -}; - -#endif /* UDNS_FOUND */ diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp deleted file mode 100644 index 84e5625..0000000 --- a/louloulibs/network/dns_socket_handler.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include <louloulibs.h> -#ifdef UDNS_FOUND - -#include <network/dns_socket_handler.hpp> -#include <network/dns_handler.hpp> -#include <network/poller.hpp> - -#include <udns.h> - -DNSSocketHandler::DNSSocketHandler(std::shared_ptr<Poller>& poller, - const socket_t socket): - SocketHandler(poller, socket) -{ - poller->add_socket_handler(this); -} - -DNSSocketHandler::~DNSSocketHandler() -{ - this->unwatch(); -} - -void DNSSocketHandler::on_recv() -{ - dns_ioevent(nullptr, 0); -} - -bool DNSSocketHandler::is_connected() const -{ - return true; -} - -void DNSSocketHandler::unwatch() -{ - if (this->poller->is_managing_socket(this->socket)) - this->poller->remove_socket_handler(this->socket); -} - -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 deleted file mode 100644 index fc5f41f..0000000 --- a/louloulibs/network/dns_socket_handler.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include <louloulibs.h> -#ifdef UDNS_FOUND - -#include <network/socket_handler.hpp> - -/** - * 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 DNSSocketHandler: public SocketHandler -{ -public: - 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; - - void on_recv() override final; - - /** - * Always true, see the comment for connect() - */ - bool is_connected() const override final; - - void watch(); - void unwatch(); -}; - -#endif // UDNS_FOUND diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp deleted file mode 100644 index 9f5bcfb..0000000 --- a/louloulibs/network/poller.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#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 <unistd.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() -{ -#if POLLER == EPOLL - if (this->epfd > 0) - ::close(this->epfd); -#endif -} - -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) - { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - if (this->fds[i].revents == 0) - continue; - else if (this->fds[i].revents & POLLIN && socket_handler->is_connected()) - { - socket_handler->on_recv(); - nb_events--; - } - else if (this->fds[i].revents & POLLOUT && socket_handler->is_connected()) - { - socket_handler->on_send(); - nb_events--; - } - else if (this->fds[i].revents & POLLOUT || - this->fds[i].revents & POLLIN) - { - 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->is_connected()) - socket_handler->on_recv(); - else if (revents[i].events & EPOLLOUT && socket_handler->is_connected()) - socket_handler->on_send(); - else if (revents[i].events & EPOLLOUT) - socket_handler->connect(); - } - return nb_events; -#endif -} - -size_t Poller::size() const -{ - return this->socket_handlers.size(); -} - -bool Poller::is_managing_socket(const socket_t socket) const -{ - return (this->socket_handlers.find(socket) != this->socket_handlers.end()); -} diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp deleted file mode 100644 index e39e438..0000000 --- a/louloulibs/network/poller.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - - -#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(); - Poller(const Poller&) = delete; - Poller(Poller&&) = delete; - Poller& operator=(const Poller&) = delete; - Poller& operator=(Poller&&) = delete; - /** - * 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; - /** - * Whether the given socket is managed by the poller - */ - bool is_managing_socket(const socket_t socket) 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 -}; - - diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp deleted file mode 100644 index db7fb32..0000000 --- a/louloulibs/network/resolver.cpp +++ /dev/null @@ -1,281 +0,0 @@ -#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> -#ifdef UDNS_FOUND -# include <udns.h> -#endif - -#include <fstream> -#include <cstdlib> -#include <sstream> -#include <chrono> -#include <map> - -using namespace std::string_literals; - -#ifdef UDNS_FOUND -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"} -}; -#endif - -Resolver::Resolver(): -#ifdef UDNS_FOUND - resolved4(false), - resolved6(false), - resolving(false), - port{}, -#endif - resolved(false), - error_msg{} -{ -} - -void Resolver::resolve(const std::string& hostname, const std::string& port, - SuccessCallbackType success_cb, ErrorCallbackType error_cb) -{ - this->error_cb = error_cb; - this->success_cb = success_cb; -#ifdef UDNS_FOUND - this->port = port; -#endif - - this->start_resolving(hostname, port); -} - -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; - this->resolved4 = false; - this->resolved6 = false; - - this->error_msg.clear(); - this->addr.reset(nullptr); - - // 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) - { - 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); - resolver->after_resolved(); - std::free(result); - }; - - auto hostname4_resolved = [](dns_ctx*, dns_rr_a4* result, void* data) - { - Resolver* resolver = static_cast<Resolver*>(data); - resolver->on_hostname4_resolved(result); - resolver->after_resolved(); - std::free(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)) - { - 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(dns_rr_a4 *result) -{ - this->resolved4 = true; - - 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 - { - const auto error = dns_error_messages.find(status); - if (error != end(dns_error_messages)) - this->error_msg = error->second; - } -} - -void Resolver::on_hostname6_resolved(dns_rr_a6 *result) -{ - this->resolved6 = true; - - const auto status = dns_status(nullptr); - - if (status >= 0 && result) - { - char buf[INET6_ADDRSTRLEN]; - 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); - } - } -} - -void Resolver::after_resolved() -{ - if (dns_active(nullptr) == 0) - DNSHandler::unwatch(); - - if (this->resolved6 && this->resolved4) - this->on_resolved(); -} - -void Resolver::on_resolved() -{ - this->resolved = true; - this->resolving = false; - if (!this->addr) - { - if (this->error_cb) - this->error_cb(this->error_msg.data()); - } - else - { - if (this->success_cb) - this->success_cb(this->addr.get()); - } -} - -#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); - - const auto res = this->call_getaddrinfo(hostname.data(), port.data(), 0); - - this->resolved = true; - - if (res != 0) - { - this->error_msg = gai_strerror(res); - if (this->error_cb) - this->error_cb(this->error_msg.data()); - } - else - { - if (this->success_cb) - this->success_cb(this->addr.get()); - } -} -#endif // ifdef UDNS_FOUND - -std::string addr_to_string(const struct addrinfo* rp) -{ - char buf[INET6_ADDRSTRLEN]; - if (rp->ai_family == AF_INET) - return ::inet_ntop(rp->ai_family, - &reinterpret_cast<sockaddr_in*>(rp->ai_addr)->sin_addr, - buf, sizeof(buf)); - else if (rp->ai_family == AF_INET6) - return ::inet_ntop(rp->ai_family, - &reinterpret_cast<sockaddr_in6*>(rp->ai_addr)->sin6_addr, - buf, sizeof(buf)); - return {}; -} diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp deleted file mode 100644 index a560819..0000000 --- a/louloulibs/network/resolver.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -#include "louloulibs.h" - -#include <functional> -#include <vector> -#include <memory> -#include <string> - -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#ifdef UDNS_FOUND -# include <udns.h> -#endif - -class AddrinfoDeleter -{ - public: - void operator()(struct addrinfo* addr) - { - freeaddrinfo(addr); - } -}; - - -class Resolver -{ -public: - - using ErrorCallbackType = std::function<void(const char*)>; - using SuccessCallbackType = std::function<void(const struct addrinfo*)>; - - Resolver(); - ~Resolver() = default; - Resolver(const Resolver&) = delete; - Resolver(Resolver&&) = delete; - Resolver& operator=(const Resolver&) = delete; - Resolver& operator=(Resolver&&) = delete; - - bool is_resolving() const - { -#ifdef UDNS_FOUND - return this->resolving; -#else - return false; -#endif - } - - bool is_resolved() const - { - return this->resolved; - } - - const auto& get_result() const - { - return this->addr; - } - std::string get_error_message() const - { - return this->error_msg; - } - - void clear() - { -#ifdef UDNS_FOUND - this->resolved6 = false; - this->resolved4 = false; - this->resolving = false; - this->port.clear(); -#endif - this->resolved = false; - this->addr.reset(); - this->error_msg.clear(); - } - - void resolve(const std::string& hostname, const std::string& port, - SuccessCallbackType success_cb, ErrorCallbackType error_cb); - -private: - void start_resolving(const std::string& hostname, const std::string& port); - 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); - /** - * Called after one record (4 or 6) has been resolved. - */ - void after_resolved(); - - void start_timer(); - - void on_resolved(); - - bool resolved4; - bool resolved6; - - bool resolving; - - std::string port; - -#endif - /** - * Tells if we finished the resolution process. It doesn't indicate if it - * was successful (it is true even if the result is an error). - */ - bool resolved; - std::string error_msg; - - std::unique_ptr<struct addrinfo, AddrinfoDeleter> addr; - - ErrorCallbackType error_cb; - SuccessCallbackType success_cb; -}; - -std::string addr_to_string(const struct addrinfo* rp); diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp deleted file mode 100644 index 6a7220e..0000000 --- a/louloulibs/network/socket_handler.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include <louloulibs.h> -#include <memory> - -class Poller; - -using socket_t = int; - -class SocketHandler -{ -public: - explicit SocketHandler(std::shared_ptr<Poller>& poller, const socket_t socket): - poller(poller), - socket(socket) - {} - virtual ~SocketHandler() = default; - SocketHandler(const SocketHandler&) = delete; - SocketHandler(SocketHandler&&) = delete; - SocketHandler& operator=(const SocketHandler&) = delete; - SocketHandler& operator=(SocketHandler&&) = delete; - - virtual void on_recv() {} - virtual void on_send() {} - virtual void connect() {} - 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; -}; - diff --git a/louloulibs/network/tcp_client_socket_handler.cpp b/louloulibs/network/tcp_client_socket_handler.cpp deleted file mode 100644 index 4628703..0000000 --- a/louloulibs/network/tcp_client_socket_handler.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include <network/tcp_client_socket_handler.hpp> -#include <utils/timed_events.hpp> -#include <utils/scopeguard.hpp> -#include <network/poller.hpp> - -#include <logger/logger.hpp> - -#include <cstring> -#include <unistd.h> -#include <fcntl.h> - -using namespace std::string_literals; - -TCPClientSocketHandler::TCPClientSocketHandler(std::shared_ptr<Poller>& poller): - TCPSocketHandler(poller), - hostname_resolution_failed(false), - connected(false), - connecting(false) -{} - -TCPClientSocketHandler::~TCPClientSocketHandler() -{ - this->close(); -} - -void TCPClientSocketHandler::init_socket(const struct addrinfo* rp) -{ - if (this->socket != -1) - ::close(this->socket); - if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) - throw std::runtime_error("Could not create socket: "s + std::strerror(errno)); - // Bind the socket to a specific address, if specified - if (!this->bind_addr.empty()) - { - // Convert the address from string format to a sockaddr that can be - // used in bind() - struct addrinfo* result; - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_NUMERICHOST; - hints.ai_family = AF_UNSPEC; - int err = ::getaddrinfo(this->bind_addr.data(), nullptr, &hints, &result); - if (err != 0 || !result) - log_error("Failed to bind socket to ", this->bind_addr, ": ", - gai_strerror(err)); - else - { - utils::ScopeGuard sg([result](){ freeaddrinfo(result); }); - struct addrinfo* rp; - for (rp = result; rp; rp = rp->ai_next) - { - if ((::bind(this->socket, - reinterpret_cast<const struct sockaddr*>(rp->ai_addr), - rp->ai_addrlen)) == 0) - break; - } - if (!rp) - log_error("Failed to bind socket to ", this->bind_addr, ": ", - strerror(errno)); - else - log_info("Socket successfully bound to ", this->bind_addr); - } - } - 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 + std::strerror(errno)); -} - -void TCPClientSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) -{ - this->address = address; - this->port = port; - this->use_tls = tls; - - struct addrinfo* addr_res; - - if (!this->connecting) - { - // Get the addrinfo from getaddrinfo (or using udns), only if - // this is the first call of this function. - if (!this->resolver.is_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), TCPClientSocketHandler::connect() - // needs to be called, again. - this->resolver.resolve(address, port, - [this](const struct addrinfo*) - { - log_debug("Resolution success, calling connect() again"); - this->connect(); - }, - [this](const char*) - { - log_debug("Resolution failed, calling connect() again"); - this->connect(); - }); - return; - } - else - { - // 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) - { - this->hostname_resolution_failed = true; - const auto msg = this->resolver.get_error_message(); - this->close(); - this->on_connection_failed(msg); - return ; - } - } - } - 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; - } - } - - this->display_resolved_ip(rp); - - 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(this->address, this->port); -#endif - this->connection_date = std::chrono::system_clock::now(); - - // Get our local TCP port and store it - this->local_port = static_cast<uint16_t>(-1); - if (rp->ai_family == AF_INET6) - { - struct sockaddr_in6 a; - socklen_t l = sizeof(a); - if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1) - this->local_port = ntohs(a.sin6_port); - } - else if (rp->ai_family == AF_INET) - { - struct sockaddr_in a; - socklen_t l = sizeof(a); - if (::getsockname(this->socket, (struct sockaddr*)&a, &l) != -1) - this->local_port = ntohs(a.sin_port); - } - - log_debug("Local port: ", this->local_port, ", and remote port: ", this->port); - - 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(&TCPClientSocketHandler::on_connection_timeout, this), - "connection_timeout"s + std::to_string(this->socket))); - return ; - } - log_info("Connection failed:", std::strerror(errno)); - } - log_error("All connection attempts failed."); - this->close(); - this->on_connection_failed(std::strerror(errno)); - return ; -} - -void TCPClientSocketHandler::on_connection_timeout() -{ - this->close(); - this->on_connection_failed("connection timed out"); -} - -void TCPClientSocketHandler::connect() -{ - this->connect(this->address, this->port, this->use_tls); -} - -void TCPClientSocketHandler::close() -{ - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - - TCPSocketHandler::close(); - - this->connected = false; - this->connecting = false; - this->port.clear(); - this->resolver.clear(); -} - -void TCPClientSocketHandler::display_resolved_ip(struct addrinfo* rp) const -{ - if (rp->ai_family == AF_INET) - log_debug("Trying IPv4 address ", addr_to_string(rp)); - else if (rp->ai_family == AF_INET6) - log_debug("Trying IPv6 address ", addr_to_string(rp)); -} - -bool TCPClientSocketHandler::is_connected() const -{ - return this->connected; -} - -bool TCPClientSocketHandler::is_connecting() const -{ - return this->connecting || this->resolver.is_resolving(); -} - -std::string TCPClientSocketHandler::get_port() const -{ - return this->port; -} - -bool TCPClientSocketHandler::match_port_pairt(const uint16_t local, const uint16_t remote) const -{ - const uint16_t remote_port = static_cast<uint16_t>(std::stoi(this->port)); - return this->is_connected() && local == this->local_port && remote == remote_port; -} diff --git a/louloulibs/network/tcp_client_socket_handler.hpp b/louloulibs/network/tcp_client_socket_handler.hpp deleted file mode 100644 index 74caca9..0000000 --- a/louloulibs/network/tcp_client_socket_handler.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include <network/tcp_socket_handler.hpp> - -class TCPClientSocketHandler: public TCPSocketHandler -{ - public: - TCPClientSocketHandler(std::shared_ptr<Poller>& poller); - ~TCPClientSocketHandler(); - /** - * 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; - /** - * 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; - bool is_connected() const override; - bool is_connecting() const override; - - std::string get_port() const; - - void close() override final; - std::chrono::system_clock::time_point connection_date; - - /** - * Whether or not this connection is using the two given TCP ports. - */ - bool match_port_pairt(const uint16_t local, const uint16_t remote) const; - - protected: - bool hostname_resolution_failed; - /** - * Address to bind the socket to, before calling connect(). - * If empty, it’s equivalent to binding to INADDR_ANY. - */ - std::string bind_addr; - /** - * Display the resolved IP, just for information purpose. - */ - void display_resolved_ip(struct addrinfo* rp) const; - private: - /** - * Initialize the socket with the parameters contained in the given - * addrinfo structure. - */ - void init_socket(const struct addrinfo* rp); - /** - * DNS resolver - */ - Resolver resolver; - /** - * Keep the details of the addrinfo returned by the resolver 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{}; - - /** - * Hostname we are connected/connecting to - */ - std::string address; - /** - * Port we are connected/connecting to - */ - std::string port; - - uint16_t local_port{}; - - bool connected; - bool connecting; -}; diff --git a/louloulibs/network/tcp_server_socket.hpp b/louloulibs/network/tcp_server_socket.hpp deleted file mode 100644 index c511962..0000000 --- a/louloulibs/network/tcp_server_socket.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include <network/socket_handler.hpp> -#include <network/poller.hpp> -#include <logger/logger.hpp> - -#include <string> - -#include <arpa/inet.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/ip.h> - -#include <cstring> -#include <cassert> - -template <typename RemoteSocketType> -class TcpSocketServer: public SocketHandler -{ - public: - TcpSocketServer(std::shared_ptr<Poller>& poller, const uint16_t port): - SocketHandler(poller, -1) - { - if ((this->socket = ::socket(AF_INET6, SOCK_STREAM, 0)) == -1) - throw std::runtime_error(std::string{"Could not create socket: "} + std::strerror(errno)); - - int opt = 1; - if (::setsockopt(this->socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) - throw std::runtime_error(std::string{"Failed to set socket option: "} + std::strerror(errno)); - - struct sockaddr_in6 addr{}; - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(port); - addr.sin6_addr = IN6ADDR_ANY_INIT; - if ((::bind(this->socket, (const struct sockaddr*)&addr, sizeof(addr))) == -1) - { // If we can’t listen on this port, we just give up, but this is not fatal. - log_warning("Failed to bind on port ", std::to_string(port), ": ", std::strerror(errno)); - return; - } - - if ((::listen(this->socket, 10)) == -1) - throw std::runtime_error("listen() failed"); - - this->accept(); - } - ~TcpSocketServer() = default; - - void on_recv() override - { - // Accept a RemoteSocketType - int socket = ::accept(this->socket, nullptr, nullptr); - - auto client = std::make_unique<RemoteSocketType>(poller, socket, *this); - this->poller->add_socket_handler(client.get()); - this->sockets.push_back(std::move(client)); - } - - protected: - std::vector<std::unique_ptr<RemoteSocketType>> sockets; - - private: - void accept() - { - this->poller->add_socket_handler(this); - } - bool is_connected() const override - { - return true; - } -}; diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp deleted file mode 100644 index 7eebae0..0000000 --- a/louloulibs/network/tcp_socket_handler.cpp +++ /dev/null @@ -1,358 +0,0 @@ -#include <network/tcp_socket_handler.hpp> -#include <network/dns_handler.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 <cstring> - -#ifdef BOTAN_FOUND -# include <botan/hex.h> -# include <botan/tls_exceptn.h> - -namespace -{ - Botan::AutoSeeded_RNG& get_rng() - { - static Botan::AutoSeeded_RNG rng{}; - return rng; - } - BiboumiTLSPolicy& get_policy() - { - static BiboumiTLSPolicy policy{}; - return policy; - } - Botan::TLS::Session_Manager_In_Memory& get_session_manager() - { - static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()}; - return session_manager; - } -} -#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) -#ifdef BOTAN_FOUND - ,credential_manager(this) -#endif -{} - -TCPSocketHandler::~TCPSocketHandler() -{ - if (this->poller->is_managing_socket(this->get_socket())) - this->poller->remove_socket_handler(this->get_socket()); - if (this->socket != -1) - { - ::close(this->socket); - this->socket = -1; - } -} - -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) - { - if (this->is_connecting()) - log_warning("Error connecting: ", strerror(errno)); - else - 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->is_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 (const 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(); - msg.msg_iovlen++; - 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. - auto it = this->out_buf.begin(); - while (it != this->out_buf.end()) - { - if (static_cast<size_t>(res) >= it->size()) - { - res -= it->size(); - ++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; - } - } - this->out_buf.erase(this->out_buf.begin(), it); - if (this->out_buf.empty()) - this->poller->stop_watching_send_events(this); - } -} - -void TCPSocketHandler::close() -{ - if (this->is_connected() || this->is_connecting()) - this->poller->remove_socket_handler(this->get_socket()); - if (this->socket != -1) - { - ::close(this->socket); - this->socket = -1; - } - this->in_buf.clear(); - this->out_buf.clear(); -} - -void TCPSocketHandler::send_data(std::string&& data) -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - try { - this->tls_send(std::move(data)); - } catch (const Botan::TLS::TLS_Exception& e) { - this->on_connection_close("TLS error: "s + e.what()); - this->close(); - return ; - } - 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->is_connected()) - this->poller->watch_send_events(this); -} - -void TCPSocketHandler::send_pending_data() -{ - if (this->is_connected() && !this->out_buf.empty()) - this->poller->watch_send_events(this); -} - -bool TCPSocketHandler::is_using_tls() const -{ - return this->use_tls; -} - -void* TCPSocketHandler::get_receive_buffer(const size_t) const -{ - return nullptr; -} - -void TCPSocketHandler::consume_in_buffer(const std::size_t size) -{ - this->in_buf = this->in_buf.substr(size, std::string::npos); -} - -#ifdef BOTAN_FOUND -void TCPSocketHandler::start_tls(const std::string& address, const std::string& port) -{ - Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port)); - this->tls = std::make_unique<Botan::TLS::Client>( -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) - *this, -# else - [this](const Botan::byte* data, size_t size) { this->tls_emit_data(data, size); }, - [this](const Botan::byte* data, size_t size) { this->tls_record_received(0, data, size); }, - [this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); }, - [this](const Botan::TLS::Session& session) { return this->tls_session_established(session); }, -# endif - get_session_manager(), this->credential_manager, get_policy(), - get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version()); -} - -void TCPSocketHandler::tls_recv() -{ - static constexpr size_t buf_size = 4096; - Botan::byte 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(); - try { - this->tls->received_data(recv_buf, static_cast<size_t>(size)); - } catch (const Botan::TLS::TLS_Exception& e) { - // May happen if the server sends malformed TLS data (buggy server, - // or more probably we are just connected to a server that sends - // plain-text) - this->on_connection_close("TLS error: "s + e.what()); - this->close(); - return ; - } - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } -} - -void TCPSocketHandler::tls_send(std::string&& data) -{ - // We may not be connected yet, or the tls session has - // not yet been negociated - if (this->tls && this->tls->is_active()) - { - const bool was_active = this->tls->is_active(); - if (!this->pre_buf.empty()) - { - this->tls->send(this->pre_buf.data(), this->pre_buf.size()); - this->pre_buf.clear(); - } - 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.insert(this->pre_buf.end(), - std::make_move_iterator(data.begin()), - std::make_move_iterator(data.end())); -} - -void TCPSocketHandler::tls_record_received(uint64_t, 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_emit_data(const Botan::byte *data, size_t size) -{ - this->raw_send(std::string(reinterpret_cast<const char*>(data), size)); -} - -void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert) -{ - log_debug("tls_alert: ", alert.type_string()); -} - -bool TCPSocketHandler::tls_session_established(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; -} - -#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34) -void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain, - const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses, - const std::vector<Botan::Certificate_Store*>& trusted_roots, - Botan::Usage_Type usage, const std::string& hostname, - const Botan::TLS::Policy& policy) -{ - log_debug("Checking remote certificate for hostname ", hostname); - try - { - Botan::TLS::Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy); - log_debug("Certificate is valid"); - } - catch (const std::exception& tls_exception) - { - log_warning("TLS certificate check failed: ", tls_exception.what()); - std::exception_ptr exception_ptr{}; - if (this->abort_on_invalid_cert()) - exception_ptr = std::current_exception(); - - check_tls_certificate(cert_chain, hostname, this->credential_manager.get_trusted_fingerprint(), exception_ptr); - } -} -#endif - -void TCPSocketHandler::on_tls_activated() -{ - this->send_data({}); -} - -#endif // BOTAN_FOUND diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp deleted file mode 100644 index 3ee2f47..0000000 --- a/louloulibs/network/tcp_socket_handler.hpp +++ /dev/null @@ -1,251 +0,0 @@ -#pragma once - -#include "louloulibs.h" - -#include <network/socket_handler.hpp> -#include <network/resolver.hpp> - -#include <network/credentials_manager.hpp> - -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <netdb.h> - -#include <chrono> -#include <vector> -#include <memory> -#include <string> -#include <list> - -#ifdef BOTAN_FOUND - -# include <botan/types.h> -# include <botan/botan.h> -# include <botan/tls_session_manager.h> - -class BiboumiTLSPolicy: public Botan::TLS::Policy -{ -public: -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,33) - bool use_ecc_point_compression() const override - { - return true; - } - bool require_cert_revocation_info() const override - { - return false; - } -# endif -}; - -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) -# define BOTAN_TLS_CALLBACKS_OVERRIDE override final -# else -# define BOTAN_TLS_CALLBACKS_OVERRIDE -# endif -#endif - -/** - * Does all the read/write, buffering etc. With optional tls. - * But doesn’t do any connect() or accept() or anything else. - */ -class TCPSocketHandler: public SocketHandler -#ifdef BOTAN_FOUND -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) - ,public Botan::TLS::Callbacks -# endif -#endif -{ -protected: - ~TCPSocketHandler(); -public: - explicit TCPSocketHandler(std::shared_ptr<Poller>& poller); - TCPSocketHandler(const TCPSocketHandler&) = delete; - TCPSocketHandler(TCPSocketHandler&&) = delete; - TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; - TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; - - /** - * 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 - */ - virtual void close(); - /** - * 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. - * - * The function should call consume_in_buffer, with the size that was consumed by the - * “parsing”, and thus to be removed from the input buffer. - */ - virtual void parse_in_buffer(const size_t size) = 0; -#ifdef BOTAN_FOUND - /** - * Tell whether the credential manager should cancel the connection when the - * certificate is invalid. - */ - virtual bool abort_on_invalid_cert() const - { - return true; - } -#endif - bool is_using_tls() const; - -private: - /** - * 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); - - protected: - virtual bool is_connecting() const = 0; -#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(const std::string& address, const std::string& port); - private: - /** - * 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_record_received(uint64_t rec_no, const Botan::byte* data, size_t size) BOTAN_TLS_CALLBACKS_OVERRIDE; - /** - * 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_emit_data(const Botan::byte* data, size_t size) BOTAN_TLS_CALLBACKS_OVERRIDE; - /** - * 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(Botan::TLS::Alert alert) BOTAN_TLS_CALLBACKS_OVERRIDE; - /** - * 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_session_established(const Botan::TLS::Session& session) BOTAN_TLS_CALLBACKS_OVERRIDE; - -#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34) - void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain, - const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses, - const std::vector<Botan::Certificate_Store*>& trusted_roots, - Botan::Usage_Type usage, - const std::string& hostname, - const Botan::TLS::Policy& policy) BOTAN_TLS_CALLBACKS_OVERRIDE; -#endif - /** - * 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::vector<std::string> out_buf; -protected: - /** - * Whether we are using TLS on this connection or not. - */ - bool use_tls; - /** - * 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; - /** - * Remove the given “size” first bytes from our in_buf. - */ - void consume_in_buffer(const std::size_t size); - /** - * 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; - /** - * Called when we detect a disconnection from the remote host. - */ - virtual void on_connection_close(const std::string&) {} - virtual void on_connection_failed(const std::string&) {} - -#ifdef BOTAN_FOUND -protected: - BasicCredentialsManager credential_manager; -private: - /** - * 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 and - * calls the output_fn callback with it as soon as it is created. - * Therefore, we do not want to create it if we do not intend to 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::vector<Botan::byte> pre_buf; -#endif // BOTAN_FOUND -}; |