Commit 04d99916 authored by louiz’'s avatar louiz’

Add a level of inheritance above SocketHandler

SocketHandler has been renamed to TCPSocketHandler
SocketHandler is now a simple interface with a few methods, used only by
Poller.

This way we can inherite from the new SocketHandler class, to handle other
types of sockets, and still make them manageable by the poller without any
change in the Poller class.
parent b0ae8c66
......@@ -20,7 +20,7 @@ using namespace std::string_literals;
using namespace std::chrono_literals;
IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& username, Bridge* bridge):
SocketHandler(poller),
TCPSocketHandler(poller),
hostname(hostname),
username(username),
current_nick(username),
......
......@@ -5,7 +5,7 @@
#include <irc/irc_channel.hpp>
#include <irc/iid.hpp>
#include <network/socket_handler.hpp>
#include <network/tcp_socket_handler.hpp>
#include <unordered_map>
#include <memory>
......@@ -21,7 +21,7 @@ class Bridge;
* Represent one IRC client, i.e. an endpoint connected to a single IRC
* server, through a TCP socket, receiving and sending commands to it.
*/
class IrcClient: public SocketHandler
class IrcClient: public TCPSocketHandler
{
public:
explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& username, Bridge* bridge);
......
#include <network/tcp_socket_handler.hpp>
#include <xmpp/xmpp_component.hpp>
#include <utils/timed_events.hpp>
#include <network/poller.hpp>
......
#ifndef SOCKET_HANDLER_INCLUDED
# define SOCKET_HANDLER_INCLUDED
#include <logger/logger.hpp>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <utility>
#include <memory>
#include <string>
#include <list>
#include "config.h"
#ifdef BOTAN_FOUND
# include <botan/botan.h>
# include <botan/tls_client.h>
/**
* A very simple credential manager that accepts any certificate.
*/
class Permissive_Credentials_Manager: public Botan::Credentials_Manager
{
public:
void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector<Botan::X509_Certificate>&)
{ // TODO: Offer the admin to disallow connection on untrusted
// certificates
log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname);
}
};
#endif // BOTAN_FOUND
#ifndef SOCKET_HANDLER_INTERFACE_HPP
# define SOCKET_HANDLER_INTERFACE_HPP
typedef int socket_t;
class Poller;
/**
* An interface, with a series of callbacks that should be implemented in
* subclasses that deal with a socket. These callbacks are called on various events
* (read/write/timeout, etc) when they are notified to a poller
* (select/poll/epoll etc)
*/
class SocketHandler
{
protected:
~SocketHandler() {}
public:
explicit SocketHandler(std::shared_ptr<Poller> poller);
/**
* Connect to the remote server, and call on_connected() if this
* succeeds. If tls is true, we set use_tls to true and will also call
* start_tls() when the connection succeeds.
*/
void connect(const std::string& address, const std::string& port, const bool tls);
void connect();
/**
* 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();
/**
* Write as much data from out_buf as possible, in the socket.
*/
void on_send();
/**
* 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();
/**
* Returns the socket that should be handled by the poller.
*/
socket_t get_socket() const;
/**
* Close the connection, remove us from the poller
*/
void close();
/**
* Called by a TimedEvent, when the connection did not succeed or fail
* after a given time.
*/
void on_connection_timeout();
/**
* Called when the connection is successful.
*/
virtual void on_connected() = 0;
/**
* Called when the connection fails. Not when it is closed later, just at
* the connect() call.
*/
virtual void on_connection_failed(const std::string& reason) = 0;
/**
* Called when we detect a disconnection from the remote host.
*/
virtual void on_connection_close() = 0;
/**
* Handle/consume (some of) the data received so far. The data to handle
* may be in the in_buf buffer, or somewhere else, depending on what
* get_receive_buffer() returned. If some data is used from in_buf, it
* should be truncated, only the unused data should be left untouched.
*
* The size argument is the size of the last chunk of data that was added to the buffer.
*/
virtual void parse_in_buffer(const size_t size) = 0;
bool is_connected() const;
bool is_connecting() const;
private:
/**
* Initialize the socket with the parameters contained in the given
* addrinfo structure.
*/
void init_socket(const struct addrinfo* rp);
/**
* Reads from the socket into the provided buffer. If an error occurs
* (read returns <= 0), the handling of the error is done here (close the
* connection, log a message, etc).
*
* Returns the value returned by ::recv(), so the buffer should not be
* used if it’s not positive.
*/
ssize_t do_recv(void* recv_buf, const size_t buf_size);
/**
* Reads data from the socket and calls parse_in_buffer with it.
*/
void plain_recv();
/**
* Mark the given data as ready to be sent, as-is, on the socket, as soon
* as we can.
*/
void raw_send(std::string&& data);
#ifdef BOTAN_FOUND
/**
* Create the TLS::Client object, with all the callbacks etc. This must be
* called only when we know we are able to send TLS-encrypted data over
* the socket.
*/
void start_tls();
/**
* An additional step to pass the data into our tls object to decrypt it
* before passing it to parse_in_buffer.
*/
void tls_recv();
/**
* Pass the data to the tls object in order to encrypt it. The tls object
* will then call raw_send as a callback whenever data as been encrypted
* and can be sent on the socket.
*/
void tls_send(std::string&& data);
/**
* Called by the tls object that some data has been decrypt. We call
* parse_in_buffer() to handle that unencrypted data.
*/
void tls_data_cb(const Botan::byte* data, size_t size);
/**
* Called by the tls object to indicate that some data has been encrypted
* and is now ready to be sent on the socket as is.
*/
void tls_output_fn(const Botan::byte* data, size_t size);
/**
* Called by the tls object to indicate that a TLS alert has been
* received. We don’t use it, we just log some message, at the moment.
*/
void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t);
/**
* Called by the tls object at the end of the TLS handshake. We don't do
* anything here appart from logging the TLS session information.
*/
bool tls_handshake_cb(const Botan::TLS::Session& session);
/**
* Called whenever the tls session goes from inactive to active. This
* means that the handshake has just been successfully done, and we can
* now proceed to send any available data into our tls object.
*/
void on_tls_activated();
#endif // BOTAN_FOUND
/**
* The handled socket.
*/
socket_t socket;
/**
* Where data is added, when we want to send something to the client.
*/
std::list<std::string> out_buf;
/**
* Keep the details of the addrinfo the triggered a EINPROGRESS error when
* connect()ing to it, to reuse it directly when connect() is called
* again.
*/
struct addrinfo addrinfo;
struct sockaddr ai_addr;
socklen_t ai_addrlen;
protected:
/**
* A pointer to the poller that manages us, because we need to communicate
* with it, sometimes (for example to tell it that he now needs to watch
* write events for us). Do not ever try to delete it.
*
* And a raw pointer because we are not owning it, it is owning us
* (actually it is sharing our ownership with a Bridge).
*/
std::shared_ptr<Poller> poller;
/**
* Where data read from the socket is added until we can extract a full
* and meaningful “message” from it.
*
* TODO: something more efficient than a string.
*/
std::string in_buf;
/**
* Whether we are using TLS on this connection or not.
*/
bool use_tls;
/**
* Provide a buffer in which data can be directly received. This can be
* used to avoid copying data into in_buf before using it. If no buffer
* needs to be provided, nullptr is returned (the default implementation
* does that), in that case our internal in_buf will be used to save the
* data until it can be used by parse_in_buffer().
*/
virtual void* get_receive_buffer(const size_t size) const;
/**
* Hostname we are connected/connecting to
*/
std::string address;
/**
* Port we are connected/connecting to
*/
std::string port;
bool connected;
bool connecting;
SocketHandler() {}
virtual ~SocketHandler() {}
virtual socket_t get_socket() const = 0;
virtual void on_recv() = 0;
virtual void on_send() = 0;
virtual void connect() = 0;
virtual bool is_connected() const = 0;
private:
SocketHandler(const SocketHandler&) = delete;
SocketHandler(SocketHandler&&) = delete;
SocketHandler& operator=(const SocketHandler&) = delete;
SocketHandler& operator=(SocketHandler&&) = delete;
#ifdef BOTAN_FOUND
/**
* Botan stuff to manipulate a TLS session.
*/
static Botan::AutoSeeded_RNG rng;
static Permissive_Credentials_Manager credential_manager;
static Botan::TLS::Policy policy;
static Botan::TLS::Session_Manager_In_Memory session_manager;
/**
* We use a unique_ptr because we may not want to create the object at
* all. The Botan::TLS::Client object generates a handshake message as
* soon and calls the output_fn callback with it as soon as it is
* created. Therefore, we do not want to create it if do not intend to do
* send any TLS-encrypted message. We create the object only when needed
* (for example after we have negociated a TLS session using a STARTTLS
* message, or stuf like that).
*
* See start_tls for the method where this object is created.
*/
std::unique_ptr<Botan::TLS::Client> tls;
/**
* An additional buffer to keep data that the user wants to send, but
* cannot because the handshake is not done.
*/
std::string pre_buf;
#endif // BOTAN_FOUND
};
#endif // SOCKET_HANDLER_INCLUDED
#endif // SOCKET_HANDLER_INTERFACE_HPP
#include <network/socket_handler.hpp>
#include <network/tcp_socket_handler.hpp>
#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
......@@ -21,10 +21,10 @@
#ifdef BOTAN_FOUND
# include <botan/hex.h>
Botan::AutoSeeded_RNG SocketHandler::rng;
Permissive_Credentials_Manager SocketHandler::credential_manager;
Botan::TLS::Policy SocketHandler::policy;
Botan::TLS::Session_Manager_In_Memory SocketHandler::session_manager(SocketHandler::rng);
Botan::AutoSeeded_RNG TCPSocketHandler::rng;
Permissive_Credentials_Manager TCPSocketHandler::credential_manager;
Botan::TLS::Policy TCPSocketHandler::policy;
Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng);
#endif
......@@ -37,7 +37,7 @@ using namespace std::chrono_literals;
namespace ph = std::placeholders;
SocketHandler::SocketHandler(std::shared_ptr<Poller> poller):
TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
socket(-1),
poller(poller),
use_tls(false),
......@@ -45,7 +45,7 @@ SocketHandler::SocketHandler(std::shared_ptr<Poller> poller):
connecting(false)
{}
void SocketHandler::init_socket(const struct addrinfo* rp)
void TCPSocketHandler::init_socket(const struct addrinfo* rp)
{
if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
throw std::runtime_error("Could not create socket: "s + strerror(errno));
......@@ -61,7 +61,7 @@ void SocketHandler::init_socket(const struct addrinfo* rp)
throw std::runtime_error("Could not initialize socket: "s + strerror(errno));
}
void SocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
{
this->address = address;
this->port = port;
......@@ -146,7 +146,7 @@ void SocketHandler::connect(const std::string& address, const std::string& port,
// it to have failed
TimedEventsManager::instance().add_event(
TimedEvent(std::chrono::steady_clock::now() + 5s,
std::bind(&SocketHandler::on_connection_timeout, this),
std::bind(&TCPSocketHandler::on_connection_timeout, this),
"connection_timeout"s + std::to_string(this->socket)));
return ;
}
......@@ -158,18 +158,18 @@ void SocketHandler::connect(const std::string& address, const std::string& port,
return ;
}
void SocketHandler::on_connection_timeout()
void TCPSocketHandler::on_connection_timeout()
{
this->close();
this->on_connection_failed("connection timed out");
}
void SocketHandler::connect()
void TCPSocketHandler::connect()
{
this->connect(this->address, this->port, this->use_tls);
}
void SocketHandler::on_recv()
void TCPSocketHandler::on_recv()
{
#ifdef BOTAN_FOUND
if (this->use_tls)
......@@ -179,7 +179,7 @@ void SocketHandler::on_recv()
this->plain_recv();
}
void SocketHandler::plain_recv()
void TCPSocketHandler::plain_recv()
{
static constexpr size_t buf_size = 4096;
char buf[buf_size];
......@@ -203,7 +203,7 @@ void SocketHandler::plain_recv()
}
}
ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_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)
......@@ -228,7 +228,7 @@ ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_size)
return size;
}
void SocketHandler::on_send()
void TCPSocketHandler::on_send()
{
struct iovec msg_iov[UIO_FASTIOV] = {};
struct msghdr msg{nullptr, 0,
......@@ -274,7 +274,7 @@ void SocketHandler::on_send()
}
}
void SocketHandler::close()
void TCPSocketHandler::close()
{
TimedEventsManager::instance().cancel("connection_timeout"s +
std::to_string(this->socket));
......@@ -292,12 +292,12 @@ void SocketHandler::close()
this->port.clear();
}
socket_t SocketHandler::get_socket() const
socket_t TCPSocketHandler::get_socket() const
{
return this->socket;
}
void SocketHandler::send_data(std::string&& data)
void TCPSocketHandler::send_data(std::string&& data)
{
#ifdef BOTAN_FOUND
if (this->use_tls)
......@@ -307,7 +307,7 @@ void SocketHandler::send_data(std::string&& data)
this->raw_send(std::move(data));
}
void SocketHandler::raw_send(std::string&& data)
void TCPSocketHandler::raw_send(std::string&& data)
{
if (data.empty())
return ;
......@@ -316,41 +316,41 @@ void SocketHandler::raw_send(std::string&& data)
this->poller->watch_send_events(this);
}
void SocketHandler::send_pending_data()
void TCPSocketHandler::send_pending_data()
{
if (this->connected && !this->out_buf.empty())
this->poller->watch_send_events(this);
}
bool SocketHandler::is_connected() const
bool TCPSocketHandler::is_connected() const
{
return this->connected;
}
bool SocketHandler::is_connecting() const
bool TCPSocketHandler::is_connecting() const
{
return this->connecting;
}
void* SocketHandler::get_receive_buffer(const size_t) const
void* TCPSocketHandler::get_receive_buffer(const size_t) const
{
return nullptr;
}
#ifdef BOTAN_FOUND
void SocketHandler::start_tls()
void TCPSocketHandler::start_tls()
{
Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port));
this->tls = std::make_unique<Botan::TLS::Client>(
std::bind(&SocketHandler::tls_output_fn, this, ph::_1, ph::_2),
std::bind(&SocketHandler::tls_data_cb, this, ph::_1, ph::_2),
std::bind(&SocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3),
std::bind(&SocketHandler::tls_handshake_cb, this, ph::_1),
std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2),
std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2),
std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3),
std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1),
session_manager, credential_manager, policy,
rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version());
}
void SocketHandler::tls_recv()
void TCPSocketHandler::tls_recv()
{
static constexpr size_t buf_size = 4096;
char recv_buf[buf_size];
......@@ -366,7 +366,7 @@ void SocketHandler::tls_recv()
}
}
void SocketHandler::tls_send(std::string&& data)
void TCPSocketHandler::tls_send(std::string&& data)
{
if (this->tls->is_active())
{
......@@ -387,7 +387,7 @@ void SocketHandler::tls_send(std::string&& data)
this->pre_buf += data;
}
void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size)
void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size)
{
this->in_buf += std::string(reinterpret_cast<const char*>(data),
size);
......@@ -395,17 +395,17 @@ void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size)
this->parse_in_buffer(size);
}
void SocketHandler::tls_output_fn(const Botan::byte* data, size_t size)
void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size)
{
this->raw_send(std::string(reinterpret_cast<const char*>(data), size));
}
void SocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t)
void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t)
{
log_debug("tls_alert: " << alert.type_string());
}
bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session)
bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session)
{
log_debug("Handshake with " << session.server_info().hostname() << " complete."
<< " Version: " << session.version().to_string()
......@@ -417,7 +417,7 @@ bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session)
return true;
}
void SocketHandler::on_tls_activated()
void TCPSocketHandler::on_tls_activated()
{
this->send_data("");
}
......
#ifndef SOCKET_HANDLER_INCLUDED
# define SOCKET_HANDLER_INCLUDED
#include <network/socket_handler.hpp>
#include <logger/logger.hpp>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <utility>
#include <memory>
#include <string>
#include <list>
#include "config.h"
#ifdef BOTAN_FOUND
# include <botan/botan.h>
# include <botan/tls_client.h>
/**
* A very simple credential manager that accepts any certificate.
*/
class Permissive_Credentials_Manager: public Botan::Credentials_Manager
{
public:
void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector<Botan::X509_Certificate>&)
{ // TODO: Offer the admin to disallow connection on untrusted
// certificates
log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname);
}
};
#endif // BOTAN_FOUND
class Poller;
/**
* An interface, with a series of callbacks that should be implemented in
* subclasses that deal with a socket. These callbacks are called on various events
* (read/write/timeout, etc) when they are notified to a poller
* (select/poll/epoll etc)
*/
class TCPSocketHandler: SocketHandler
{
protected:
~TCPSocketHandler() {}
public:
explicit TCPSocketHandler(std::shared_ptr<Poller> poller);
/**
* Connect to the remote server, and call on_connected() if this
* succeeds. If tls is true, we set use_tls to true and will also call
* start_tls() when the connection succeeds.
*/
void connect(const std::string& address, const std::string& port, const bool tls);
void connect();
/**
* 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();
/**
* Write as much data from out_buf as possible, in the socket.
*/
void on_send();
/**
* 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();
/**
* Returns the socket that should be handled by the poller.
*/
socket_t get_socket() const;
/**
* Close the connection, remove us from the poller
*/
void close();
/**
* Called by a TimedEvent, when the connection did not succeed or fail
* after a given time.
*/