Move all the connect() logic from TCPSocketHandler into a subclass
This way, TCPSocketHandler only deal with the message sending/receiving, not the connect() or anything else. This will be used for implementing servers (because when a client is accepted, we don’t need all the connect() and dns resolution stuff).
This commit is contained in:
@@ -40,6 +40,11 @@ bool DNSSocketHandler::is_connected() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DNSSocketHandler::is_connecting() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void DNSSocketHandler::remove_from_poller()
|
void DNSSocketHandler::remove_from_poller()
|
||||||
{
|
{
|
||||||
if (this->poller->is_managing_socket(this->socket))
|
if (this->poller->is_managing_socket(this->socket))
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public:
|
|||||||
* Always true, see the comment for connect()
|
* Always true, see the comment for connect()
|
||||||
*/
|
*/
|
||||||
bool is_connected() const override final;
|
bool is_connected() const override final;
|
||||||
|
bool is_connecting() const override final;
|
||||||
void remove_from_poller();
|
void remove_from_poller();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public:
|
|||||||
virtual void on_send() = 0;
|
virtual void on_send() = 0;
|
||||||
virtual void connect() = 0;
|
virtual void connect() = 0;
|
||||||
virtual bool is_connected() const = 0;
|
virtual bool is_connected() const = 0;
|
||||||
|
virtual bool is_connecting() const = 0;
|
||||||
|
|
||||||
socket_t get_socket() const
|
socket_t get_socket() const
|
||||||
{ return this->socket; }
|
{ return this->socket; }
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
#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 <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 + 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;
|
||||||
|
int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &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;
|
||||||
|
int bind_error = 0;
|
||||||
|
for (rp = result; rp; rp = rp->ai_next)
|
||||||
|
{
|
||||||
|
if ((bind_error = ::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 + 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 ares_gethostbyname), 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 c-ares resolved the hostname and the available addresses
|
||||||
|
// where saved in the cares_addrinfo linked list. Now, just use
|
||||||
|
// this list to try to connect.
|
||||||
|
addr_res = this->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();
|
||||||
|
|
||||||
|
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:", strerror(errno));
|
||||||
|
}
|
||||||
|
log_error("All connection attempts failed.");
|
||||||
|
this->close();
|
||||||
|
this->on_connection_failed(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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
std::string get_port() const;
|
||||||
|
|
||||||
|
void close() override final;
|
||||||
|
std::chrono::system_clock::time_point connection_date;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
bool connected;
|
||||||
|
bool connecting;
|
||||||
|
};
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
#include <network/tcp_socket_handler.hpp>
|
#include <network/tcp_socket_handler.hpp>
|
||||||
#include <network/dns_handler.hpp>
|
#include <network/dns_handler.hpp>
|
||||||
|
|
||||||
#include <utils/timed_events.hpp>
|
|
||||||
#include <utils/scopeguard.hpp>
|
|
||||||
#include <network/poller.hpp>
|
#include <network/poller.hpp>
|
||||||
|
|
||||||
#include <logger/logger.hpp>
|
#include <logger/logger.hpp>
|
||||||
@@ -12,7 +10,6 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
#ifdef BOTAN_FOUND
|
#ifdef BOTAN_FOUND
|
||||||
# include <botan/hex.h>
|
# include <botan/hex.h>
|
||||||
@@ -35,10 +32,7 @@ namespace ph = std::placeholders;
|
|||||||
|
|
||||||
TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
|
TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
|
||||||
SocketHandler(poller, -1),
|
SocketHandler(poller, -1),
|
||||||
use_tls(false),
|
use_tls(false)
|
||||||
connected(false),
|
|
||||||
connecting(false),
|
|
||||||
hostname_resolution_failed(false)
|
|
||||||
#ifdef BOTAN_FOUND
|
#ifdef BOTAN_FOUND
|
||||||
,credential_manager(this)
|
,credential_manager(this)
|
||||||
#endif
|
#endif
|
||||||
@@ -46,181 +40,13 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
|
|||||||
|
|
||||||
TCPSocketHandler::~TCPSocketHandler()
|
TCPSocketHandler::~TCPSocketHandler()
|
||||||
{
|
{
|
||||||
this->close();
|
if (this->poller->is_managing_socket(this->get_socket()))
|
||||||
}
|
this->poller->remove_socket_handler(this->get_socket());
|
||||||
|
|
||||||
|
|
||||||
void TCPSocketHandler::init_socket(const struct addrinfo* rp)
|
|
||||||
{
|
|
||||||
if (this->socket != -1)
|
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 + 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
|
::close(this->socket);
|
||||||
// used in bind()
|
this->socket = -1;
|
||||||
struct addrinfo* result;
|
|
||||||
int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &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;
|
|
||||||
int bind_error = 0;
|
|
||||||
for (rp = result; rp; rp = rp->ai_next)
|
|
||||||
{
|
|
||||||
if ((bind_error = ::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 + strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
|
|
||||||
{
|
|
||||||
this->address = address;
|
|
||||||
this->port = port;
|
|
||||||
this->use_tls = tls;
|
|
||||||
|
|
||||||
struct addrinfo* addr_res;
|
|
||||||
|
|
||||||
if (!this->connecting)
|
|
||||||
{
|
|
||||||
// Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if
|
|
||||||
// this is the first call of this function.
|
|
||||||
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), TCPSocketHandler::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 c-ares resolved the hostname and the available addresses
|
|
||||||
// where saved in the cares_addrinfo linked list. Now, just use
|
|
||||||
// this list to try to connect.
|
|
||||||
addr_res = this->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();
|
|
||||||
#endif
|
|
||||||
this->connection_date = std::chrono::system_clock::now();
|
|
||||||
|
|
||||||
this->on_connected();
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
else if (errno == EINPROGRESS || errno == EALREADY)
|
|
||||||
{ // retry this process later, when the socket
|
|
||||||
// is ready to be written on.
|
|
||||||
this->connecting = true;
|
|
||||||
this->poller->add_socket_handler(this);
|
|
||||||
this->poller->watch_send_events(this);
|
|
||||||
// Save the addrinfo structure, to use it on the next call
|
|
||||||
this->ai_addrlen = rp->ai_addrlen;
|
|
||||||
memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen);
|
|
||||||
memcpy(&this->addrinfo, rp, sizeof(struct addrinfo));
|
|
||||||
this->addrinfo.ai_addr = reinterpret_cast<struct sockaddr*>(&this->ai_addr);
|
|
||||||
this->addrinfo.ai_next = nullptr;
|
|
||||||
// If the connection has not succeeded or failed in 5s, we consider
|
|
||||||
// it to have failed
|
|
||||||
TimedEventsManager::instance().add_event(
|
|
||||||
TimedEvent(std::chrono::steady_clock::now() + 5s,
|
|
||||||
std::bind(&TCPSocketHandler::on_connection_timeout, this),
|
|
||||||
"connection_timeout"s + std::to_string(this->socket)));
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
log_info("Connection failed:", strerror(errno));
|
|
||||||
}
|
|
||||||
log_error("All connection attempts failed.");
|
|
||||||
this->close();
|
|
||||||
this->on_connection_failed(strerror(errno));
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSocketHandler::on_connection_timeout()
|
|
||||||
{
|
|
||||||
this->close();
|
|
||||||
this->on_connection_failed("connection timed out");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSocketHandler::connect()
|
|
||||||
{
|
|
||||||
this->connect(this->address, this->port, this->use_tls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPSocketHandler::on_recv()
|
void TCPSocketHandler::on_recv()
|
||||||
@@ -267,13 +93,13 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size)
|
|||||||
}
|
}
|
||||||
else if (-1 == size)
|
else if (-1 == size)
|
||||||
{
|
{
|
||||||
if (this->connecting)
|
if (this->is_connecting())
|
||||||
log_warning("Error connecting: ", strerror(errno));
|
log_warning("Error connecting: ", strerror(errno));
|
||||||
else
|
else
|
||||||
log_warning("Error while reading from socket: ", strerror(errno));
|
log_warning("Error while reading from socket: ", strerror(errno));
|
||||||
// Remember if we were connecting, or already connected when this
|
// Remember if we were connecting, or already connected when this
|
||||||
// happened, because close() sets this->connecting to false
|
// happened, because close() sets this->connecting to false
|
||||||
const auto were_connecting = this->connecting;
|
const auto were_connecting = this->is_connecting();
|
||||||
this->close();
|
this->close();
|
||||||
if (were_connecting)
|
if (were_connecting)
|
||||||
this->on_connection_failed(strerror(errno));
|
this->on_connection_failed(strerror(errno));
|
||||||
@@ -333,29 +159,15 @@ void TCPSocketHandler::on_send()
|
|||||||
|
|
||||||
void TCPSocketHandler::close()
|
void TCPSocketHandler::close()
|
||||||
{
|
{
|
||||||
TimedEventsManager::instance().cancel("connection_timeout"s +
|
if (this->is_connected() || this->is_connecting())
|
||||||
std::to_string(this->socket));
|
|
||||||
if (this->connected || this->connecting)
|
|
||||||
this->poller->remove_socket_handler(this->get_socket());
|
this->poller->remove_socket_handler(this->get_socket());
|
||||||
if (this->socket != -1)
|
if (this->socket != -1)
|
||||||
{
|
{
|
||||||
::close(this->socket);
|
::close(this->socket);
|
||||||
this->socket = -1;
|
this->socket = -1;
|
||||||
}
|
}
|
||||||
this->connected = false;
|
|
||||||
this->connecting = false;
|
|
||||||
this->in_buf.clear();
|
this->in_buf.clear();
|
||||||
this->out_buf.clear();
|
this->out_buf.clear();
|
||||||
this->port.clear();
|
|
||||||
this->resolver.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSocketHandler::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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPSocketHandler::send_data(std::string&& data)
|
void TCPSocketHandler::send_data(std::string&& data)
|
||||||
@@ -379,45 +191,35 @@ void TCPSocketHandler::raw_send(std::string&& data)
|
|||||||
if (data.empty())
|
if (data.empty())
|
||||||
return ;
|
return ;
|
||||||
this->out_buf.emplace_back(std::move(data));
|
this->out_buf.emplace_back(std::move(data));
|
||||||
if (this->connected)
|
if (this->is_connected())
|
||||||
this->poller->watch_send_events(this);
|
this->poller->watch_send_events(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPSocketHandler::send_pending_data()
|
void TCPSocketHandler::send_pending_data()
|
||||||
{
|
{
|
||||||
if (this->connected && !this->out_buf.empty())
|
if (this->is_connected() && !this->out_buf.empty())
|
||||||
this->poller->watch_send_events(this);
|
this->poller->watch_send_events(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TCPSocketHandler::is_connected() const
|
|
||||||
{
|
|
||||||
return this->connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TCPSocketHandler::is_connecting() const
|
|
||||||
{
|
|
||||||
return this->connecting || this->resolver.is_resolving();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TCPSocketHandler::is_using_tls() const
|
bool TCPSocketHandler::is_using_tls() const
|
||||||
{
|
{
|
||||||
return this->use_tls;
|
return this->use_tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TCPSocketHandler::get_port() const
|
|
||||||
{
|
|
||||||
return this->port;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* TCPSocketHandler::get_receive_buffer(const size_t) const
|
void* TCPSocketHandler::get_receive_buffer(const size_t) const
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef BOTAN_FOUND
|
void TCPSocketHandler::consume_in_buffer(const std::size_t size)
|
||||||
void TCPSocketHandler::start_tls()
|
|
||||||
{
|
{
|
||||||
Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port));
|
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>(
|
this->tls = std::make_unique<Botan::TLS::Client>(
|
||||||
std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2),
|
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_data_cb, this, ph::_1, ph::_2),
|
||||||
|
|||||||
@@ -20,10 +20,8 @@
|
|||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface, with a series of callbacks that should be implemented in
|
* Does all the read/write, buffering etc. With optional tls.
|
||||||
* subclasses that deal with a socket. These callbacks are called on various events
|
* But doesn’t do any connect() or accept() or anything else.
|
||||||
* (read/write/timeout, etc) when they are notified to a poller
|
|
||||||
* (select/poll/epoll etc)
|
|
||||||
*/
|
*/
|
||||||
class TCPSocketHandler: public SocketHandler
|
class TCPSocketHandler: public SocketHandler
|
||||||
{
|
{
|
||||||
@@ -36,13 +34,6 @@ public:
|
|||||||
TCPSocketHandler& operator=(const TCPSocketHandler&) = delete;
|
TCPSocketHandler& operator=(const TCPSocketHandler&) = delete;
|
||||||
TCPSocketHandler& operator=(TCPSocketHandler&&) = delete;
|
TCPSocketHandler& operator=(TCPSocketHandler&&) = delete;
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to the remote server, and call on_connected() if this
|
|
||||||
* succeeds. If tls is true, we set use_tls to true and will also call
|
|
||||||
* start_tls() when the connection succeeds.
|
|
||||||
*/
|
|
||||||
void connect(const std::string& address, const std::string& port, const bool tls);
|
|
||||||
void connect() override final;
|
|
||||||
/**
|
/**
|
||||||
* Reads raw data from the socket. And pass it to parse_in_buffer()
|
* 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()
|
* If we are using TLS on this connection, we call tls_recv()
|
||||||
@@ -67,25 +58,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Close the connection, remove us from the poller
|
* Close the connection, remove us from the poller
|
||||||
*/
|
*/
|
||||||
void close();
|
virtual void close();
|
||||||
/**
|
|
||||||
* Called by a TimedEvent, when the connection did not succeed or fail
|
|
||||||
* after a given time.
|
|
||||||
*/
|
|
||||||
void on_connection_timeout();
|
|
||||||
/**
|
|
||||||
* Called when the connection is successful.
|
|
||||||
*/
|
|
||||||
virtual void on_connected() = 0;
|
|
||||||
/**
|
|
||||||
* Called when the connection fails. Not when it is closed later, just at
|
|
||||||
* the connect() call.
|
|
||||||
*/
|
|
||||||
virtual void on_connection_failed(const std::string& reason) = 0;
|
|
||||||
/**
|
|
||||||
* Called when we detect a disconnection from the remote host.
|
|
||||||
*/
|
|
||||||
virtual void on_connection_close(const std::string& error) = 0;
|
|
||||||
/**
|
/**
|
||||||
* Handle/consume (some of) the data received so far. The data to handle
|
* 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
|
* may be in the in_buf buffer, or somewhere else, depending on what
|
||||||
@@ -93,6 +66,9 @@ public:
|
|||||||
* should be truncated, only the unused data should be left untouched.
|
* 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 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;
|
virtual void parse_in_buffer(const size_t size) = 0;
|
||||||
#ifdef BOTAN_FOUND
|
#ifdef BOTAN_FOUND
|
||||||
@@ -105,18 +81,9 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
bool is_connected() const override final;
|
|
||||||
bool is_connecting() const;
|
|
||||||
bool is_using_tls() const;
|
bool is_using_tls() const;
|
||||||
std::string get_port() const;
|
|
||||||
std::chrono::system_clock::time_point connection_date;
|
|
||||||
|
|
||||||
private:
|
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
|
* 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
|
* (read returns <= 0), the handling of the error is done here (close the
|
||||||
@@ -137,12 +104,14 @@ private:
|
|||||||
void raw_send(std::string&& data);
|
void raw_send(std::string&& data);
|
||||||
|
|
||||||
#ifdef BOTAN_FOUND
|
#ifdef BOTAN_FOUND
|
||||||
|
protected:
|
||||||
/**
|
/**
|
||||||
* Create the TLS::Client object, with all the callbacks etc. This must be
|
* 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
|
* called only when we know we are able to send TLS-encrypted data over
|
||||||
* the socket.
|
* the socket.
|
||||||
*/
|
*/
|
||||||
void start_tls();
|
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
|
* An additional step to pass the data into our tls object to decrypt it
|
||||||
* before passing it to parse_in_buffer.
|
* before passing it to parse_in_buffer.
|
||||||
@@ -185,20 +154,11 @@ private:
|
|||||||
* Where data is added, when we want to send something to the client.
|
* Where data is added, when we want to send something to the client.
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> out_buf;
|
std::vector<std::string> out_buf;
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
protected:
|
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
|
* Where data read from the socket is added until we can extract a full
|
||||||
* and meaningful “message” from it.
|
* and meaningful “message” from it.
|
||||||
@@ -207,9 +167,9 @@ protected:
|
|||||||
*/
|
*/
|
||||||
std::string in_buf;
|
std::string in_buf;
|
||||||
/**
|
/**
|
||||||
* Whether we are using TLS on this connection or not.
|
* Remove the given “size” first bytes from our in_buf.
|
||||||
*/
|
*/
|
||||||
bool use_tls;
|
void consume_in_buffer(const std::size_t size);
|
||||||
/**
|
/**
|
||||||
* Provide a buffer in which data can be directly received. This can be
|
* 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
|
* used to avoid copying data into in_buf before using it. If no buffer
|
||||||
@@ -219,31 +179,12 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual void* get_receive_buffer(const size_t size) const;
|
virtual void* get_receive_buffer(const size_t size) const;
|
||||||
/**
|
/**
|
||||||
* Hostname we are connected/connecting to
|
* Called when we detect a disconnection from the remote host.
|
||||||
*/
|
*/
|
||||||
std::string address;
|
virtual void on_connection_close(const std::string& error) = 0;
|
||||||
/**
|
virtual void on_connection_failed(const std::string& error) = 0;
|
||||||
* Port we are connected/connecting to
|
|
||||||
*/
|
|
||||||
std::string port;
|
|
||||||
|
|
||||||
bool connected;
|
|
||||||
bool connecting;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
|
||||||
* Display the resolved IP, just for information purpose.
|
|
||||||
*/
|
|
||||||
void display_resolved_ip(struct addrinfo* rp) const;
|
|
||||||
|
|
||||||
#ifdef BOTAN_FOUND
|
#ifdef BOTAN_FOUND
|
||||||
/**
|
/**
|
||||||
* Botan stuff to manipulate a TLS session.
|
* Botan stuff to manipulate a TLS session.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ static std::set<std::string> kickable_errors{
|
|||||||
};
|
};
|
||||||
|
|
||||||
XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
|
XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
|
||||||
TCPSocketHandler(poller),
|
TCPClientSocketHandler(poller),
|
||||||
ever_auth(false),
|
ever_auth(false),
|
||||||
first_connection_try(true),
|
first_connection_try(true),
|
||||||
secret(secret),
|
secret(secret),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <xmpp/adhoc_commands_handler.hpp>
|
#include <xmpp/adhoc_commands_handler.hpp>
|
||||||
#include <network/tcp_socket_handler.hpp>
|
#include <network/tcp_client_socket_handler.hpp>
|
||||||
#include <xmpp/xmpp_parser.hpp>
|
#include <xmpp/xmpp_parser.hpp>
|
||||||
#include <xmpp/body.hpp>
|
#include <xmpp/body.hpp>
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
*
|
*
|
||||||
* TODO: implement XEP-0225: Component Connections
|
* TODO: implement XEP-0225: Component Connections
|
||||||
*/
|
*/
|
||||||
class XmppComponent: public TCPSocketHandler
|
class XmppComponent: public TCPClientSocketHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
|
explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname
|
|||||||
const std::string& nickname, const std::string& username,
|
const std::string& nickname, const std::string& username,
|
||||||
const std::string& realname, const std::string& user_hostname,
|
const std::string& realname, const std::string& user_hostname,
|
||||||
Bridge& bridge):
|
Bridge& bridge):
|
||||||
TCPSocketHandler(poller),
|
TCPClientSocketHandler(poller),
|
||||||
hostname(hostname),
|
hostname(hostname),
|
||||||
user_hostname(user_hostname),
|
user_hostname(user_hostname),
|
||||||
username(username),
|
username(username),
|
||||||
@@ -338,7 +338,7 @@ void IrcClient::parse_in_buffer(const size_t)
|
|||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
break ;
|
break ;
|
||||||
IrcMessage message(this->in_buf.substr(0, pos));
|
IrcMessage message(this->in_buf.substr(0, pos));
|
||||||
this->in_buf = this->in_buf.substr(pos + 2, std::string::npos);
|
this->consume_in_buffer(pos + 2);
|
||||||
log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);
|
log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);
|
||||||
|
|
||||||
// Call the standard callback (if any), associated with the command
|
// Call the standard callback (if any), associated with the command
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <irc/irc_channel.hpp>
|
#include <irc/irc_channel.hpp>
|
||||||
#include <irc/iid.hpp>
|
#include <irc/iid.hpp>
|
||||||
|
|
||||||
#include <network/tcp_socket_handler.hpp>
|
#include <network/tcp_client_socket_handler.hpp>
|
||||||
#include <network/resolver.hpp>
|
#include <network/resolver.hpp>
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -23,7 +23,7 @@ class Bridge;
|
|||||||
* Represent one IRC client, i.e. an endpoint connected to a single IRC
|
* Represent one IRC client, i.e. an endpoint connected to a single IRC
|
||||||
* server, through a TCP socket, receiving and sending commands to it.
|
* server, through a TCP socket, receiving and sending commands to it.
|
||||||
*/
|
*/
|
||||||
class IrcClient: public TCPSocketHandler
|
class IrcClient: public TCPClientSocketHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
|
explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
|
||||||
|
|||||||
Reference in New Issue
Block a user