diff --git a/include/ConnectionInfo.hpp b/include/ConnectionInfo.hpp index 5eac213..529e2bf 100644 --- a/include/ConnectionInfo.hpp +++ b/include/ConnectionInfo.hpp @@ -7,7 +7,7 @@ class ConnectionInfo { - friend class TCPConnection; + friend class TCPConnectionBase; friend class UDPConnection; friend bool operator<(const ConnectionInfo&, const ConnectionInfo&); diff --git a/include/NetworkService.hpp b/include/NetworkService.hpp index 9778310..dba2b37 100644 --- a/include/NetworkService.hpp +++ b/include/NetworkService.hpp @@ -40,8 +40,8 @@ class NetworkService { bool started_server_ = false; - void OnConnectionOpen(std::shared_ptr connection) const; - void OnConnectionClose(std::shared_ptr connection) const; + void OnConnectionOpen(ConnectionInfo connectioninfo) const; + void OnConnectionClose(ConnectionInfo connectioninfo) const; void OnReceive(ConnectionInfo connectioninfo, char data[], size_t bytes_received) const; void OnSend(ConnectionInfo connectioninfo, char data[], size_t data_size, size_t bytes_sent) const; diff --git a/include/SSLOptions.hpp b/include/SSLOptions.hpp new file mode 100644 index 0000000..3df2195 --- /dev/null +++ b/include/SSLOptions.hpp @@ -0,0 +1,45 @@ + +#if !defined(_SSLOPTIONS_H) +#define _SSLOPTIONS_H + +#include + +#include + +class SSLOptions { + + friend class TCPServerSSL; + +public: + + /* Set the file containing the certificate chain in PEM format. + */ + void setCertificateChainFile(std::string& filename) { + this->options_set_ = true; + this->certificate_chain_file_ = filename; + } + + /* Set the file containing the private key in PEM format. + */ + void setPrivateKeyFile(std::string& filename) { + this->options_set_ = true; + this->private_key_file_ = filename; + } + + /* Returns true if any SSL options are set, meaning SSL will be used. + */ + bool options_set() { + return this->options_set_; + } + + +private: + + bool options_set_ = false; + + std::string certificate_chain_file_ = ""; + std::string private_key_file_ = ""; + +}; + +#endif // _SSLOPTIONS_H diff --git a/include/ServiceOptions.hpp b/include/ServiceOptions.hpp index 297312f..78be9f4 100644 --- a/include/ServiceOptions.hpp +++ b/include/ServiceOptions.hpp @@ -3,6 +3,7 @@ #define _SERVICEOPTIONS_H #include +#include class ServiceOptions { public: @@ -13,6 +14,8 @@ class ServiceOptions { int threads_ = 1; int32_t server_port_ = -1; TransportProtocol transport_protocol_ = TransportProtocol::ip_default; + SSLOptions ssl_options; + }; #endif //_SERVICEOPTIONS_H diff --git a/include/TCPConnection.hpp b/include/TCPConnection.hpp index 93167aa..c2f1661 100644 --- a/include/TCPConnection.hpp +++ b/include/TCPConnection.hpp @@ -2,27 +2,17 @@ #if !defined(_TCPCONNECTION_H) #define _TCPCONNECTION_H -#include -#include +#include -#include -#include - -class ConnectionInfo; - -class TCPConnection : public Connection { +class TCPConnection : public TCPConnectionBase { friend class TCPServer; public: - ~TCPConnection(); - boost::asio::ip::tcp::socket socket_; - - bool is_open(); - void close(); + bool is_open() override; void send_nonblocking(char data[], size_t bytes_to_send); void send_nonblocking_buffer(char data[], size_t bytes_to_send); @@ -36,25 +26,12 @@ class TCPConnection : public Connection { protected: TCPConnection(boost::asio::io_service& io_service, std::shared_ptr server_ptr); - TCPConnection(TCPConnection&); - - void close_socket(); - - ConnectionInfo constructConnectionInfo() override; - bool endpoint_less_than(Connection*) const override; - bool endpoint_equals(Connection*) const override; + bool endpoint_less_than(Connection*) const; + bool endpoint_equals(Connection*) const; private: - const static int max_buf_length = 1024; - char data_[max_buf_length]; - - /* Used for locking the connection, make sure the connection cannot be closed - * by another thread while handlers are active */ - std::recursive_mutex connection_mtx_; - }; - -#endif // _TCPCONNECTION_H +#endif // _TCPCONNECTION_H \ No newline at end of file diff --git a/include/TCPConnectionBase.hpp b/include/TCPConnectionBase.hpp new file mode 100644 index 0000000..f85807b --- /dev/null +++ b/include/TCPConnectionBase.hpp @@ -0,0 +1,53 @@ + +#if !defined(_TCPCONNECTIONBASE_H) +#define _TCPCONNECTIONBASE_H + +#include +#include + +#include +#include + +class ConnectionInfo; + +class TCPConnectionBase : public Connection { + + friend class TCPServer; + +public: + + ~TCPConnectionBase(); + + void close(); + + virtual void send_nonblocking(char data[], size_t bytes_to_send) = 0; + virtual void send_nonblocking_buffer(char data[], size_t bytes_to_send) = 0; + + virtual void start_read() = 0; + virtual void handle_read(std::shared_ptr connection, const boost::system::error_code& error, size_t bytes_transferred) = 0; + + virtual void start_write() = 0; + virtual void handle_write(std::shared_ptr connection, DynamicArray, const boost::system::error_code& error, size_t bytes_transferred) = 0; + +protected: + + TCPConnectionBase(boost::asio::io_service& io_service, std::shared_ptr server_ptr); + TCPConnectionBase(TCPConnectionBase&); + + void close_socket(); + + ConnectionInfo constructConnectionInfo() override; + + const static int max_buf_length = 1024; + char data_[max_buf_length]; + + /* Used for locking the connection, make sure the connection cannot be closed + * by another thread while handlers are active */ + std::recursive_mutex connection_mtx_; + +private: + +}; + + +#endif // _TCPCONNECTIONBASE_H diff --git a/include/TCPConnectionSSL.hpp b/include/TCPConnectionSSL.hpp new file mode 100644 index 0000000..eaf97f0 --- /dev/null +++ b/include/TCPConnectionSSL.hpp @@ -0,0 +1,40 @@ + +#if !defined(_TCPCONNECTIONSSL_H) +#define _TCPCONNECTIONSSL_H + +#include + +#include + +class TCPConnectionSSL : public TCPConnectionBase { + + friend class TCPServerSSL; + +public: + + void start_handshake(); + void handle_handshake(const boost::system::error_code& error); + + bool is_open() override; + + void send_nonblocking(char data[], size_t bytes_to_send); + void send_nonblocking_buffer(char data[], size_t bytes_to_send); + + void start_read(); + void handle_read(std::shared_ptr connection, const boost::system::error_code& error, size_t bytes_transferred); + + void start_write(); + void handle_write(std::shared_ptr connection, DynamicArray, const boost::system::error_code& error, size_t bytes_transferred); + +protected: + + TCPConnectionSSL(boost::asio::io_service& io_service, boost::asio::ssl::context& ssl_context, std::shared_ptr server_ptr); + + bool endpoint_less_than(Connection*) const; + bool endpoint_equals(Connection*) const; + + boost::asio::ssl::stream ssl_socket_; + +}; + +#endif // _TCPCONNECTIONSSL_H diff --git a/include/TCPServer.hpp b/include/TCPServer.hpp index b1e5290..827cea9 100644 --- a/include/TCPServer.hpp +++ b/include/TCPServer.hpp @@ -31,14 +31,11 @@ class TCPServer : public Server { std::vector> connections; - void start_accept(); - void handle_accept(std::shared_ptr connection, const boost::system::error_code& error); + virtual void start_accept(); + virtual void handle_accept(std::shared_ptr connection, const boost::system::error_code& error); void OnStart(); - void OnStop(); - - void register_new_connection(std::shared_ptr connection); - + void OnStop(); private: diff --git a/include/TCPServerSSL.hpp b/include/TCPServerSSL.hpp new file mode 100644 index 0000000..d78640b --- /dev/null +++ b/include/TCPServerSSL.hpp @@ -0,0 +1,30 @@ + +#if !defined(_TCPSERVERSSL_H) +#define _TCPSERVERSSL_H + +#include + +#include + +class SSLOptions; + +class TCPServerSSL : public TCPServer { + +public: + + TCPServerSSL(int threads, uint16_t server_port, NetworkService* callback_service, TransportProtocol tp, SSLOptions ssl_options); + +protected: + + void start_accept(); + void handle_accept(std::shared_ptr connection, const boost::system::error_code& error); + +private: + + boost::asio::ssl::context ssl_context_; + +}; + + + +#endif // _TCPSERVERSSL_H diff --git a/src/Connection.cpp b/src/Connection.cpp index e4f61f2..415a161 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -13,11 +13,11 @@ Connection::~Connection() { } void Connection::OnConnectionOpen() { - this->server_ptr_->callback_service_->OnConnectionOpen(this->this_shared_ptr_); + this->server_ptr_->callback_service_->OnConnectionOpen(this->constructConnectionInfo()); } void Connection::OnConnectionClose() { - this->server_ptr_->callback_service_->OnConnectionClose(this->this_shared_ptr_); + this->server_ptr_->callback_service_->OnConnectionClose(this->constructConnectionInfo()); } void Connection::OnReceive(char data[], size_t bytes_received) { diff --git a/src/NetworkService.cpp b/src/NetworkService.cpp index 33fdaf5..485077d 100644 --- a/src/NetworkService.cpp +++ b/src/NetworkService.cpp @@ -35,9 +35,18 @@ NetworkService::NetworkService(ServiceOptions options) { case TransportProtocol::ipv6_tcp: { - std::shared_ptr new_server(new TCPServer(options.threads_, server_port, this, options.transport_protocol_)); - new_server->this_shared_ptr_ = new_server; - this->underlying_server_ = new_server; + + // Check whether SSL should be used. + if (options.ssl_options.options_set) { + std::shared_ptr new_server(TCPServerSSL(options.threads_, server_port, this, options.transport_protocol_, options.ssl_options)); + new_server->this_shared_ptr_ = new_server; + this->underlying_server_ = new_server; + } + else { + std::shared_ptr new_server(new TCPServer(options.threads_, server_port, this, options.transport_protocol_)); + new_server->this_shared_ptr_ = new_server; + this->underlying_server_ = new_server; + } } break; @@ -45,6 +54,11 @@ NetworkService::NetworkService(ServiceOptions options) { case TransportProtocol::ipv6_udp: { + // SSL over UDP is not supported. + if (options.ssl_options.options_set()) { + throw new std::invalid_argument("Argument mismatch, SSL options are given, but TransportProtocol specifies UDP. SSL over UDP is not supported."); + } + std::shared_ptr new_server(new UDPServer(options.threads_, server_port, this, options.transport_protocol_)); new_server->this_shared_ptr_ = new_server; this->underlying_server_ = new_server; @@ -96,18 +110,18 @@ void NetworkService::stop() { this->started_server_ = false; } -void NetworkService::OnConnectionOpen(std::shared_ptr connection) const { +void NetworkService::OnConnectionOpen(ConnectionInfo connectioninfo) const { for (Handler* h : handlers_) { - h->OnConnectionOpen(connection); + h->OnConnectionOpen(connectioninfo); } } -void NetworkService::OnConnectionClose(std::shared_ptr connection) const { +void NetworkService::OnConnectionClose(ConnectionInfo connectioninfo) const { for (Handler* h : handlers_) { - h->OnConnectionClose(connection); + h->OnConnectionClose(connectioninfo); } } diff --git a/src/TCPConnection.cpp b/src/TCPConnection.cpp index 82ac696..dcae2df 100644 --- a/src/TCPConnection.cpp +++ b/src/TCPConnection.cpp @@ -1,22 +1,9 @@ #include - -#include -#include -#include -#include - -#include +#include TCPConnection::TCPConnection(boost::asio::io_service& io_service, std::shared_ptr server_ptr) -: Connection(server_ptr), socket_(io_service) -{ -} - -TCPConnection::~TCPConnection() { - /* The destruction will be called when all shared pointers are destroyed. - * Which means this object will not be used again. - */ +: TCPConnectionBase(io_service, server_ptr), socket_(io_service) { } bool TCPConnection::is_open() { @@ -35,12 +22,12 @@ void TCPConnection::send_nonblocking(char data[], size_t bytes_to_send) { DynamicArray arr(bytes_to_send); memcpy(arr.data(), data, bytes_to_send); - this->socket_.async_write_some(boost::asio::buffer(arr.data(), arr.size()), std::bind(&TCPConnection::handle_write, this, this->this_shared_ptr_, std::move(arr), std::placeholders::_1, std::placeholders::_2)); + this->socket_.async_write_some(boost::asio::buffer(arr.data(), arr.size()), std::bind(&TCPConnectionBase::handle_write, this, this->this_shared_ptr_, std::move(arr), std::placeholders::_1, std::placeholders::_2)); } } void TCPConnection::send_nonblocking_buffer(char data[], size_t bytes_to_send) { - + { std::unique_lock lock(this->connection_mtx_); @@ -50,32 +37,15 @@ void TCPConnection::send_nonblocking_buffer(char data[], size_t bytes_to_send) { DynamicArray arr(data, bytes_to_send); - this->socket_.async_write_some(boost::asio::buffer(arr.data(), arr.size()), std::bind(&TCPConnection::handle_write, this, this->this_shared_ptr_, arr, std::placeholders::_1, std::placeholders::_2)); - } - - -} - -void TCPConnection::close() { - - { - std::unique_lock lock(this->connection_mtx_); - - this->close_socket(); + this->socket_.async_write_some(boost::asio::buffer(arr.data(), arr.size()), std::bind(&TCPConnectionBase::handle_write, this, this->this_shared_ptr_, arr, std::placeholders::_1, std::placeholders::_2)); } - -} - -void TCPConnection::close_socket() { - this->OnConnectionClose(); - this->this_shared_ptr_.reset(); } void TCPConnection::start_read() { /* Start an aysnc read. The data received is stored in the data_ member variable. Which is passed to any handlers in the handle_read function and subsequent calls. */ - this->socket_.async_read_some(boost::asio::buffer(this->data_, this->max_buf_length), std::bind(&TCPConnection::handle_read, this, this->this_shared_ptr_, std::placeholders::_1, std::placeholders::_2)); + this->socket_.async_read_some(boost::asio::buffer(this->data_, this->max_buf_length), std::bind(&TCPConnectionBase::handle_read, this, this->this_shared_ptr_, std::placeholders::_1, std::placeholders::_2)); } @@ -108,7 +78,7 @@ void TCPConnection::start_write() { void TCPConnection::handle_write(std::shared_ptr connection, DynamicArray arr, const boost::system::error_code& error, size_t bytes_transferred) { - + { std::unique_lock lock(this->connection_mtx_); @@ -122,21 +92,15 @@ void TCPConnection::handle_write(std::shared_ptr connection, Dynamic // The DynamicArray used to hold the reference to the written data is deleted here. } -ConnectionInfo TCPConnection::constructConnectionInfo() { - - ConnectionInfo result(this_shared_ptr_); - return result; -} - bool TCPConnection::endpoint_less_than(Connection* connection) const { - assert(dynamic_cast(connection) != nullptr); // Check that comparisons are not made between TCP and UDP connections. + assert(dynamic_cast(connection) != nullptr); // Check that comparisons are not made between TCP, TCP with SSL and UDP connections. TCPConnection* tcpcon = static_cast(connection); return this->socket_.remote_endpoint() < tcpcon->socket_.remote_endpoint(); } bool TCPConnection::endpoint_equals(Connection* connection) const { - assert(dynamic_cast(connection) != nullptr); // Check that comparisons are not made between TCP and UDP connectinons. + assert(dynamic_cast(connection) != nullptr); // Check that comparisons are not made between TCP, TCP with SSL and UDP connectinons. TCPConnection* tcpcon = static_cast(connection); return this->socket_.remote_endpoint() == tcpcon->socket_.remote_endpoint(); diff --git a/src/TCPConnectionBase.cpp b/src/TCPConnectionBase.cpp new file mode 100644 index 0000000..68a520f --- /dev/null +++ b/src/TCPConnectionBase.cpp @@ -0,0 +1,41 @@ + +#include + +#include +#include +#include +#include + +#include + +TCPConnectionBase::TCPConnectionBase(boost::asio::io_service& io_service, std::shared_ptr server_ptr) +: Connection(server_ptr) +{ +} + +TCPConnectionBase::~TCPConnectionBase() { + /* The destruction will be called when all shared pointers are destroyed. + * Which means this object will not be used again. + */ +} + + + +void TCPConnectionBase::close() { + { + std::unique_lock lock(this->connection_mtx_); + + this->close_socket(); + } +} + +void TCPConnectionBase::close_socket() { + this->OnConnectionClose(); + this->this_shared_ptr_.reset(); +} + +ConnectionInfo TCPConnectionBase::constructConnectionInfo() { + + ConnectionInfo result(this_shared_ptr_); + return result; +} diff --git a/src/TCPConnectionSSL.cpp b/src/TCPConnectionSSL.cpp new file mode 100644 index 0000000..5ac575f --- /dev/null +++ b/src/TCPConnectionSSL.cpp @@ -0,0 +1,132 @@ + +#include + +TCPConnectionSSL::TCPConnectionSSL(boost::asio::io_service& io_service, boost::asio::ssl::context& ssl_context, std::shared_ptr server_ptr) +: TCPConnectionBase(io_service, server_ptr), ssl_socket_(io_service, ssl_context) { +} + +void TCPConnectionSSL::start_handshake() { + this->ssl_socket_.async_handshake(boost::asio::ssl::stream_base::server, + std::bind(&TCPConnectionSSL::handle_handshake, this, std::placeholders::_1)); +} + +void TCPConnectionSSL::handle_handshake(const boost::system::error_code& error) { + { + std::unique_lock lock(this->connection_mtx_); + + if (!this->is_open()) { + return; + } + + if (error != boost::system::errc::success) { + this->close_socket(); + return; + } + + /* Handshake has completed without any (security) errors. + * Start reading data. + */ + this->OnConnectionOpen(); + this->start_read(); + } +} + +bool TCPConnectionSSL::is_open() { + return this->ssl_socket_.lowest_layer().is_open(); +} + +void TCPConnectionSSL::send_nonblocking(char data[], size_t bytes_to_send) { + + { + std::unique_lock lock(this->connection_mtx_); + + if (!this->is_open()) { + return; + } + + DynamicArray arr(bytes_to_send); + memcpy(arr.data(), data, bytes_to_send); + + this->ssl_socket_.async_write_some(boost::asio::buffer(arr.data(), arr.size()), std::bind(&TCPConnectionBase::handle_write, this, this->this_shared_ptr_, std::move(arr), std::placeholders::_1, std::placeholders::_2)); + } +} + +void TCPConnectionSSL::send_nonblocking_buffer(char data[], size_t bytes_to_send) { + + { + std::unique_lock lock(this->connection_mtx_); + + if (!this->is_open()) { + return; + } + + DynamicArray arr(data, bytes_to_send); + + this->ssl_socket_.async_write_some(boost::asio::buffer(arr.data(), arr.size()), std::bind(&TCPConnectionBase::handle_write, this, this->this_shared_ptr_, arr, std::placeholders::_1, std::placeholders::_2)); + } +} + +void TCPConnectionSSL::start_read() { + + /* Start an aysnc read. The data received is stored in the data_ member variable. + Which is passed to any handlers in the handle_read function and subsequent calls. */ + this->ssl_socket_.async_read_some(boost::asio::buffer(this->data_, this->max_buf_length), std::bind(&TCPConnectionBase::handle_read, this, this->this_shared_ptr_, std::placeholders::_1, std::placeholders::_2)); + +} + +void TCPConnectionSSL::handle_read(std::shared_ptr connection, const boost::system::error_code& error, size_t bytes_transferred) { + + { + std::unique_lock lock(this->connection_mtx_); + + if (!this->is_open()) { + return; + } + + if (error != boost::system::errc::success) { + this->close_socket(); + return; + } + + // Call all handers before reading more data. + this->OnReceive(this->data_, bytes_transferred); + + this->start_read(); + + } + +} + +void TCPConnectionSSL::start_write() { + +} + +void TCPConnectionSSL::handle_write(std::shared_ptr connection, DynamicArray arr, const boost::system::error_code& error, size_t bytes_transferred) { + + + { + std::unique_lock lock(this->connection_mtx_); + + if (error == boost::system::errc::success) { + /* If sending went without any errors, it means message has actually been sent. + Call any handlers */ + this->OnSend(arr.data(), arr.size(), bytes_transferred); + } + } + + // The DynamicArray used to hold the reference to the written data is deleted here. +} + +bool TCPConnectionSSL::endpoint_less_than(Connection* connection) const { + assert(dynamic_cast(connection) != nullptr); // Check that comparisons are not made between TCP, TCP with SSL and UDP connections. + + TCPConnectionSSL* tcpcon = static_cast(connection); + return this->ssl_socket_.lowest_layer().remote_endpoint() < tcpcon->ssl_socket_.lowest_layer().remote_endpoint(); +} + +bool TCPConnectionSSL::endpoint_equals(Connection* connection) const { + assert(dynamic_cast(connection) != nullptr); // Check that comparisons are not made between TCP, TCP with SSL and UDP connectinons. + + TCPConnectionSSL* tcpcon = static_cast(connection); + return this->ssl_socket_.lowest_layer().remote_endpoint() == tcpcon->ssl_socket_.lowest_layer().remote_endpoint(); +} diff --git a/src/TCPServer.cpp b/src/TCPServer.cpp index e969c39..f46da9f 100644 --- a/src/TCPServer.cpp +++ b/src/TCPServer.cpp @@ -93,9 +93,3 @@ void TCPServer::OnStop() { this->tcp_acceptor_.close(); } - -/* Keeping a list of connections is not needed, connections are kept alive until closed. -*/ -void TCPServer::register_new_connection(std::shared_ptr connection) { - -} diff --git a/src/TCPServerSSL.cpp b/src/TCPServerSSL.cpp new file mode 100644 index 0000000..1b66b6f --- /dev/null +++ b/src/TCPServerSSL.cpp @@ -0,0 +1,58 @@ + +#include + +#include +#include + +TCPServerSSL::TCPServerSSL(int threads, uint16_t server_port, NetworkService* callback_service, TransportProtocol tp, SSLOptions ssl_options) +: TCPServer(threads, server_port, callback_service, tp), ssl_context_(this->boost_io_service_, boost::asio::ssl::context::sslv23_server) { + + this->ssl_context_.set_options(boost::asio::ssl::context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::single_dh_use); + + if (!ssl_options.certificate_chain_file_.empty) this->ssl_context_.use_certificate_chain_file(ssl_options.certificate_chain_file_); + if (!ssl_options.private_key_file_.empty) this->ssl_context_.use_private_key_file(ssl_options.private_key_file_, boost::asio::ssl::context::file_format::pem); + +} + +void TCPServerSSL::start_accept() { + + // Fill in the connections smart pointer to itself. + std::shared_ptr connection(new TCPConnectionSSL(this->boost_io_service_, this->ssl_context_, this->this_shared_ptr_)); + connection->this_shared_ptr_ = connection; + + // Async call to accept new connection. + this->tcp_acceptor_.async_accept(connection->ssl_socket_.lowest_layer(), + std::bind(&TCPServerSSL::handle_accept, this, connection, + std::placeholders::_1) + ); + +} + +void TCPServerSSL::handle_accept(std::shared_ptr connection, const boost::system::error_code& error) { + + { + std::unique_lock lock(this->server_mtx_); + + if (this->boost_io_service_.stopped()) { + this->stop_server(); + } + + if (error == boost::asio::error::operation_aborted) { + /* Operation is aborted due to server stopping or other reasons, + * just return doing nothing. */ + return; + } else if (error) { + /* Another error, mark the connection as closed. */ + connection->close(); + } + else + { + connection->start_handshake(); + } + + this->start_accept(); + + } +}