Refactoring louloulibs and cmake
Use OBJECT libraries Remove the louloulibs directory Write FOUND variables in the cache
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#include "biboumi.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
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "biboumi.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
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <biboumi.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 */
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <biboumi.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 */
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <biboumi.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 */
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <biboumi.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
|
||||
@@ -0,0 +1,234 @@
|
||||
#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());
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <network/socket_handler.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#define POLL 1
|
||||
#define EPOLL 2
|
||||
#define KQUEUE 3
|
||||
#include <biboumi.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
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
#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 {};
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include "biboumi.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);
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <biboumi.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;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#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;
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
#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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,358 @@
|
||||
#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
|
||||
@@ -0,0 +1,251 @@
|
||||
#pragma once
|
||||
|
||||
#include "biboumi.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
|
||||
};
|
||||
Reference in New Issue
Block a user