diff --git a/include/bitcoin/network/config/endpoint.hpp b/include/bitcoin/network/config/endpoint.hpp index 9f38efb08..8359eb2f4 100644 --- a/include/bitcoin/network/config/endpoint.hpp +++ b/include/bitcoin/network/config/endpoint.hpp @@ -39,6 +39,10 @@ class BCT_API endpoint using system::config::endpoint::endpoint; + /// Properties. + /// ----------------------------------------------------------------------- + bool is_address() const NOEXCEPT; + /// Operators. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/network/error.hpp b/include/bitcoin/network/error.hpp index a1893bdbf..0cc468e57 100644 --- a/include/bitcoin/network/error.hpp +++ b/include/bitcoin/network/error.hpp @@ -126,6 +126,23 @@ enum error_t : uint8_t subscriber_stopped, desubscribed, + // socks5 + socks_method, + socks_username, + socks_password, + socks_server_name, + socks_authentication, + socks_failure, + socks_disallowed, + socks_net_unreachable, + socks_host_unreachable, + socks_connection_refused, + socks_connection_expired, + socks_unsupported_command, + socks_unsupported_address, + socks_unassigned_failure, + socks_response_invalid, + ////// http 4xx client error bad_request, ////unauthorized, diff --git a/include/bitcoin/network/net.hpp b/include/bitcoin/network/net.hpp index fcff631af..b8a70d9e4 100644 --- a/include/bitcoin/network/net.hpp +++ b/include/bitcoin/network/net.hpp @@ -217,10 +217,12 @@ class BCT_API net friend class session; /// I/O factories. - virtual acceptor::ptr create_acceptor(size_t maximum) NOEXCEPT; + virtual acceptor::ptr create_acceptor() NOEXCEPT; + virtual connector::ptr create_seed_connector() NOEXCEPT; + virtual connector::ptr create_manual_connector() NOEXCEPT; virtual connectors_ptr create_connectors(size_t count) NOEXCEPT; - virtual connector::ptr create_connector(size_t maximum) NOEXCEPT; - virtual connector::ptr create_connector() NOEXCEPT; + virtual connector::ptr create_connector(const settings::socks5& socks, + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT; /// Sequences. virtual void do_start(const result_handler& handler) NOEXCEPT; diff --git a/include/bitcoin/network/net/connector.hpp b/include/bitcoin/network/net/connector.hpp index fb7c7a939..051526f92 100644 --- a/include/bitcoin/network/net/connector.hpp +++ b/include/bitcoin/network/net/connector.hpp @@ -86,11 +86,15 @@ class BCT_API connector virtual void start(const std::string& hostname, uint16_t port, const config::address& host, socket_handler&& handler) NOEXCEPT; - virtual void handle_connected(const code& ec, const finish_ptr& finish, - socket::ptr socket) NOEXCEPT; + /// Handlers, overridable for proxied connector. + virtual void handle_connect(const code& ec, const finish_ptr& finish, + const socket::ptr& socket) NOEXCEPT; virtual void handle_timer(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT; + /// Override to inform socket construction. + virtual bool proxied() const NOEXCEPT; + /// Running in the strand. bool stranded() NOEXCEPT; @@ -111,8 +115,6 @@ class BCT_API connector const socket::ptr& socket) NOEXCEPT; void do_handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT; - void handle_connect(code ec, const finish_ptr& finish, - const socket::ptr& socket) NOEXCEPT; }; typedef std_vector connectors; diff --git a/include/bitcoin/network/net/connector_socks.hpp b/include/bitcoin/network/net/connector_socks.hpp index 52dcb4559..a42af6703 100644 --- a/include/bitcoin/network/net/connector_socks.hpp +++ b/include/bitcoin/network/net/connector_socks.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace libbitcoin { namespace network { @@ -46,18 +47,18 @@ class BCT_API connector_socks /// Resolves socks5 endpoint and stores address as member for each connect. connector_socks(const logger& log, asio::strand& strand, - asio::io_context& service, const config::endpoint& socks5_proxy, - const steady_clock::duration& timeout, size_t maximum_request, - std::atomic_bool& suspended) NOEXCEPT; + asio::io_context& service, const steady_clock::duration& timeout, + size_t maximum_request, std::atomic_bool& suspended, + const settings::socks5& socks) NOEXCEPT; protected: - void start(const std::string& hostname, uint16_t port, - const config::address& host, socket_handler&& handler) NOEXCEPT override; + static code socks_response(uint8_t value) NOEXCEPT; /// Connector overrides. - void handle_connected(const code& ec, const finish_ptr& finish, - socket::ptr socket) NOEXCEPT override; - void handle_timer(const code& ec, const finish_ptr& finish, + bool proxied() const NOEXCEPT override; + void start(const std::string& hostname, uint16_t port, + const config::address& host, socket_handler&& handler) NOEXCEPT override; + void handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT override; private: @@ -67,24 +68,47 @@ class BCT_API connector_socks using data_cptr = std::shared_ptr>; // socks5 handshake - void do_socks(const code& ec, const socket::ptr& socket) NOEXCEPT; + void do_socks_greeting_write(const code& ec, const finish_ptr& finish, + const socket::ptr& socket) NOEXCEPT; void handle_socks_greeting_write(const code& ec, size_t size, - const socket::ptr& socket, const data_cptr<3>& greeting) NOEXCEPT; + const finish_ptr& finish, const socket::ptr& socket, + const data_cptr<3>& greeting) NOEXCEPT; + void handle_socks_method_read(const code& ec, size_t size, - const socket::ptr& socket, const data_ptr<2>& response) NOEXCEPT; + const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<2>& response) NOEXCEPT; + void handle_socks_authentication_write(const code& ec, + size_t size, const finish_ptr& finish, const socket::ptr& socket, + const system::chunk_ptr& authenticator) NOEXCEPT; + void handle_socks_authentication_read(const code& ec, + size_t size, const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<2>& response) NOEXCEPT; + + void do_socks_connect_write(const finish_ptr& finish, + const socket::ptr& socket) NOEXCEPT; void handle_socks_connect_write(const code& ec, size_t size, - const socket::ptr& socket, const system::chunk_ptr& request) NOEXCEPT; + const finish_ptr& finish, const socket::ptr& socket, + const system::chunk_ptr& request) NOEXCEPT; + void handle_socks_response_read(const code& ec, size_t size, - const socket::ptr& socket, const data_ptr<4>& response) NOEXCEPT; + const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<4>& response) NOEXCEPT; void handle_socks_length_read(const code& ec, size_t size, - const socket::ptr& socket, const data_ptr<1>& host_length) NOEXCEPT; + const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<1>& host_length) NOEXCEPT; void handle_socks_address_read(const code& ec, size_t size, - const socket::ptr& socket, const system::chunk_ptr& address) NOEXCEPT; - void do_socks_finish(const code& ec, const socket::ptr& socket) NOEXCEPT; - void socks_finish(const code& ec, const socket::ptr& socket) NOEXCEPT; + const finish_ptr& finish, const socket::ptr& socket, + const system::chunk_ptr& address) NOEXCEPT; + + void do_socks_finish(const code& ec, const finish_ptr& finish, + const socket::ptr& socket) NOEXCEPT; + void socks_finish(const code& ec, const finish_ptr& finish, + const socket::ptr& socket) NOEXCEPT; - // This is protected by strand. - const config::endpoint socks5_; + // These are thread safe. + const settings::socks5& socks5_; + const uint8_t method_; + const bool proxied_; }; typedef std_vector socks_connectors; diff --git a/include/bitcoin/network/net/socket.hpp b/include/bitcoin/network/net/socket.hpp index 2b5159300..0b8545ddc 100644 --- a/include/bitcoin/network/net/socket.hpp +++ b/include/bitcoin/network/net/socket.hpp @@ -49,7 +49,8 @@ class BCT_API socket /// Use only for outgoing connections (retains outgoing address). socket(const logger& log, asio::io_context& service, - size_t maximum_request, const config::address& address) NOEXCEPT; + size_t maximum_request, const config::address& address, + bool proxied=false) NOEXCEPT; /// Asserts/logs stopped. virtual ~socket() NOEXCEPT; @@ -86,6 +87,7 @@ class BCT_API socket result_handler&& handler) NOEXCEPT; /// Create an outbound connection, handler posted to socket strand. + /// Authority will be set to the connected endpoint unless proxied is set. virtual void connect(const asio::endpoints& range, result_handler&& handler) NOEXCEPT; @@ -140,9 +142,10 @@ class BCT_API socket /// Properties. /// ----------------------------------------------------------------------- - /// Get the authority (incoming) of the remote endpoint. + /// Get the authority (outgoing/incoming) of the remote endpoint. virtual const config::authority& authority() const NOEXCEPT; + /// TODO: this can be set to the binding for incoming sockets. /// Get the address (outgoing) of the remote endpoint. virtual const config::address& address() const NOEXCEPT; @@ -285,6 +288,7 @@ class BCT_API socket protected: // These are thread safe. + const bool proxied_; const size_t maximum_; asio::strand strand_; asio::io_context& service_; diff --git a/include/bitcoin/network/sessions/session.hpp b/include/bitcoin/network/sessions/session.hpp index 75b53caf2..541366620 100644 --- a/include/bitcoin/network/sessions/session.hpp +++ b/include/bitcoin/network/sessions/session.hpp @@ -186,16 +186,16 @@ class BCT_API session /// Factories. /// ----------------------------------------------------------------------- - /// Call to create channel acceptor. - virtual acceptor::ptr create_acceptor(size_t maximum) NOEXCEPT; + /// Create a channel acceptor (inbound). + virtual acceptor::ptr create_acceptor() NOEXCEPT; - /// Call to create channel connector for seed connections. - virtual connector::ptr create_connector() NOEXCEPT; + /// Create a seed channel connector. + virtual connector::ptr create_seed_connector() NOEXCEPT; - /// Call to create channel connector for manual/outbound connections. - virtual connector::ptr create_connector(size_t maximum) NOEXCEPT; + /// Create a manual channel connector. + virtual connector::ptr create_manual_connector() NOEXCEPT; - /// Call to create a set of channel connectors. + /// Create a batch of outbound channel connectors. virtual connectors_ptr create_connectors(size_t count) NOEXCEPT; /// Create a channel from the started socket. diff --git a/include/bitcoin/network/settings.hpp b/include/bitcoin/network/settings.hpp index 3e5dc5178..f66765ddc 100644 --- a/include/bitcoin/network/settings.hpp +++ b/include/bitcoin/network/settings.hpp @@ -41,10 +41,10 @@ constexpr uint32_t maximum_request_default /// Common network configuration settings, properties not thread safe. struct BCT_API settings { - struct socks5_client + struct socks5 { - DEFAULT_COPY_MOVE_DESTRUCT(socks5_client); - socks5_client() NOEXCEPT; + DEFAULT_COPY_MOVE_DESTRUCT(socks5); + socks5() NOEXCEPT; /// Proxy credentials are stored and passed in cleartext. std::string username{}; @@ -57,7 +57,7 @@ struct BCT_API settings virtual bool proxied() const NOEXCEPT; /// False if both username and password are empty. - virtual bool secured() const NOEXCEPT; + virtual bool authenticated() const NOEXCEPT; }; struct tcp_server @@ -111,11 +111,11 @@ struct BCT_API settings }; struct peer_manual - : public tcp_server, public socks5_client + : public tcp_server, public socks5 { // The friends field must be initialized after peers is set. peer_manual(system::chain::selection) NOEXCEPT - : tcp_server("manual"), socks5_client() + : tcp_server("manual"), socks5() { } @@ -130,10 +130,10 @@ struct BCT_API settings }; struct peer_outbound - : public tcp_server, public socks5_client + : public tcp_server, public socks5 { peer_outbound(system::chain::selection context) NOEXCEPT - : tcp_server("outbound"), socks5_client() + : tcp_server("outbound"), socks5() { connections = 10; diff --git a/src/config/endpoint.cpp b/src/config/endpoint.cpp index ffb34b80b..1900311c0 100644 --- a/src/config/endpoint.cpp +++ b/src/config/endpoint.cpp @@ -27,6 +27,12 @@ namespace libbitcoin { namespace network { namespace config { +bool endpoint::is_address() const NOEXCEPT +{ + // Serialize to address, cast to bool, true if not default (address). + return to_address(); +} + // protected address endpoint::to_address() const NOEXCEPT { diff --git a/src/error.cpp b/src/error.cpp index 156bfe543..fff2305fc 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -99,6 +99,23 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { subscriber_stopped, "subscriber stopped" }, { desubscribed, "subscriber desubscribed" }, + // socks5 + { socks_method, "socks method not supported" }, + { socks_username, "socks username too long" }, + { socks_password, "socks password too long" }, + { socks_server_name, "socks server name too long" }, + { socks_authentication, "socks authentication failed" }, + { socks_failure, "socks failure" }, + { socks_disallowed, "socks connection disallowed" }, + { socks_net_unreachable, "socks network unreachable" }, + { socks_host_unreachable, "socks host unreachable" }, + { socks_connection_refused, "socks connection refused" }, + { socks_connection_expired, "socks connection expired" }, + { socks_unsupported_command, "socks unsupported command" }, + { socks_unsupported_address, "socks unsupported address" }, + { socks_unassigned_failure, "socks unassigned failure" }, + { socks_response_invalid, "socks response invalid" }, + ////// http 4xx client error { bad_request, "bad request" }, ////{ unauthorized, "unauthorized" }, diff --git a/src/net.cpp b/src/net.cpp index de1a250f5..15b6bbce3 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -69,38 +69,61 @@ net::~net() NOEXCEPT // ---------------------------------------------------------------------------- // inbound/server -acceptor::ptr net::create_acceptor(size_t maximum) NOEXCEPT +acceptor::ptr net::create_acceptor() NOEXCEPT { + const auto& settings = network_settings(); + const auto maximum = settings.inbound.maximum_request; + return emplace_shared(log, strand(), service(), maximum, accept_suspended_); } -// outbound (batch) -connectors_ptr net::create_connectors(size_t count) NOEXCEPT +// outbound (general) +connector::ptr net::create_connector(const settings::socks5& socks, + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT { - const auto connects = to_shared(); - connects->reserve(count); + if (socks.proxied()) + return emplace_shared(log, strand(), service(), + timeout, maximum, connect_suspended_, socks); - const auto maximum = network_settings().outbound.maximum_request; - for (size_t connect{}; connect < count; ++connect) - connects->push_back(create_connector(maximum)); + // Above can handle both proxy and non-proxy, but this is more efficient. + return emplace_shared(log, strand(), service(), + timeout, maximum, connect_suspended_); +} - return connects; +// outbound (seed) +connector::ptr net::create_seed_connector() NOEXCEPT +{ + const auto& settings = network_settings(); + + return create_connector(settings.outbound, + settings.outbound.seeding_timeout(), + settings.outbound.maximum_request); } -// manual/outbound -connector::ptr net::create_connector(size_t maximum) NOEXCEPT +// outbound (manual) +connector::ptr net::create_manual_connector() NOEXCEPT { - return emplace_shared(log, strand(), service(), - network_settings().connect_timeout(), maximum, connect_suspended_); + const auto& settings = network_settings(); + + return create_connector(settings.manual, + settings.connect_timeout(), + settings.manual.maximum_request); } -// seed -connector::ptr net::create_connector() NOEXCEPT +// outbound (batch) +connectors_ptr net::create_connectors(size_t count) NOEXCEPT { - return emplace_shared(log, strand(), service(), - network_settings().outbound.seeding_timeout(), - network_settings().outbound.maximum_request, connect_suspended_); + const auto& settings = network_settings(); + const auto timeout = settings.connect_timeout(); + const auto connects = to_shared(); + connects->reserve(count); + + for (size_t connect{}; connect < count; ++connect) + connects->push_back(create_connector(settings.outbound, timeout, + settings.outbound.maximum_request)); + + return connects; } // Start sequence. diff --git a/src/net/connector.cpp b/src/net/connector.cpp index 776560e38..3be9d8ecc 100644 --- a/src/net/connector.cpp +++ b/src/net/connector.cpp @@ -77,6 +77,12 @@ void connector::stop() NOEXCEPT // Properties. // ---------------------------------------------------------------------------- +// protected/virtual +bool connector::proxied() const NOEXCEPT +{ + return false; +} + // protected bool connector::stranded() NOEXCEPT { @@ -100,8 +106,6 @@ void connector::connect(const authority& host, std::move(handler)); } -// TODO: this is getting a zero port for seeds (and maybe manual). -// TODO: that results in the connection being interpreted as inbound. // This used by seed, manual, and socks5 (endpoint from config). void connector::connect(const endpoint& host, socket_handler&& handler) NOEXCEPT @@ -133,7 +137,7 @@ void connector::start(const std::string& hostname, uint16_t port, // Create a socket and shared finish context. const auto finish = std::make_shared(false); const auto socket = std::make_shared(log, service_, - maximum_, host); + maximum_, host, proxied()); // Posts handle_timer to strand. timer_->start( @@ -195,48 +199,48 @@ void connector::do_handle_connect(const code& ec, const finish_ptr& finish, shared_from_this(), ec, finish, socket)); } -// private -void connector::handle_connect(code ec, const finish_ptr& finish, +// protected/virtual +void connector::handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(stranded()); // Timer stopped the socket, it wins (with timeout/failure). if (socket->stopped()) - ec = error::operation_canceled; - else if (suspended_.load()) - ec = error::service_suspended; - - handle_connected(ec, finish, socket); -} - -// protected/virtual -void connector::handle_connected(const code& ec, const finish_ptr& finish, - socket::ptr socket) NOEXCEPT -{ - BC_ASSERT(stranded()); + { + racer_.finish(error::operation_canceled, nullptr); + return; + } - if (ec) + if (suspended_.load()) { socket->stop(); - socket.reset(); + timer_->stop(); + racer_.finish(error::service_suspended, nullptr); + return; } - else + + // Failure in connect, connector wins (with connect failure). + if (ec) { - *finish = true; + socket->stop(); + timer_->stop(); + racer_.finish(ec, nullptr); + return; } + // Successful connect (error::success), inform and cancel timer. + *finish = true; timer_->stop(); - racer_.finish(ec, socket); + racer_.finish(error::success, socket); } -// private +// protected/virtual void connector::handle_timer(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(stranded()); - // Successful connect, connector wins (error::success). if (*finish) { racer_.finish(error::operation_canceled, nullptr); @@ -247,6 +251,7 @@ void connector::handle_timer(const code& ec, const finish_ptr& finish, // Stopped socket returned with failure code for option of host recovery. if (ec) { + // Socket stop is thread safe, dispatching to its own strand. socket->stop(); resolver_.cancel(); racer_.finish(ec, socket); diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 285f15878..3f5f4f61b 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace libbitcoin { namespace network { @@ -35,55 +36,11 @@ namespace network { BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) -BC_PUSH_WARNING(NO_ARRAY_INDEXING) using namespace system; using namespace std::placeholders; -connector_socks::connector_socks(const logger& log, asio::strand& strand, - asio::io_context& service, const config::endpoint& socks5_proxy, - const steady_clock::duration& timeout, size_t maximum_request, - std::atomic_bool& suspended) NOEXCEPT - : connector(log, strand, service, timeout, maximum_request, suspended), - socks5_(socks5_proxy), - tracker(log) -{ -} - -// protected/override -void connector_socks::start(const std::string&, uint16_t, - const config::address& host, socket_handler&& handler) NOEXCEPT -{ - // hostname and port are redundant with host for outgoing connections. - connector::start(socks5_.host(), socks5_.port(), host, std::move(handler)); -} - -// protected/override -void connector_socks::handle_connected(const code& ec, const finish_ptr&, - socket::ptr socket) NOEXCEPT -{ - BC_ASSERT(stranded()); - - // Perform socks5 handshake on the socket strand. - do_socks(ec, socket); -} - -// protected/override -void connector_socks::handle_timer(const code& ec, const finish_ptr&, - const socket::ptr& socket) NOEXCEPT -{ - BC_ASSERT(stranded()); - - // Connector strand cannot access the socket at this point. - // Post to the socks connector strand to protect the socket. - boost::asio::post(socket->strand(), - std::bind(&connector_socks::do_socks_finish, - shared_from_base(), ec, socket)); -} - -// socks5 handshake (private) -// ---------------------------------------------------------------------------- -// datatracker.ietf.org/doc/html/rfc1928 +constexpr auto port_size = sizeof(uint16_t); enum socks : uint8_t { @@ -102,12 +59,17 @@ enum socks : uint8_t connection_expired = 0x06, unsupported_command = 0x07, unsupported_address = 0x08, + unassigned = 0xff, // method (authentication) type method_none = 0xff, method_clear = 0x00, method_gssapi = 0x01, - method_password = 0x02, + method_basic = 0x02, + + // method basic (subnegotiation) + method_basic_version = 0x01, + method_basic_success = 0x00, // command type command_connect = 0x01, @@ -120,14 +82,85 @@ enum socks : uint8_t address_ipv6 = 0x04 }; -void connector_socks::do_socks(const code& ec, +// static +code connector_socks::socks_response(uint8_t value) NOEXCEPT +{ + switch (value) + { + case socks::success: return error::success; + case socks::failure: return error::socks_failure; + case socks::disallowed: return error::socks_disallowed; + case socks::net_unreachable: return error::socks_net_unreachable; + case socks::host_unreachable: return error::socks_host_unreachable; + case socks::connection_refused: return error::socks_connection_refused; + case socks::connection_expired: return error::socks_connection_expired; + case socks::unsupported_command: return error::socks_unsupported_command; + case socks::unsupported_address: return error::socks_unsupported_address; + default: return error::socks_unassigned_failure; + }; +} + +// Caller can avoid proxied_ condition by using connector when not proxied. +connector_socks::connector_socks(const logger& log, asio::strand& strand, + asio::io_context& service, const steady_clock::duration& timeout, + size_t maximum_request, std::atomic_bool& suspended, + const settings::socks5& socks) NOEXCEPT + : connector(log, strand, service, timeout, maximum_request, suspended), + socks5_(socks), + proxied_(socks.proxied()), + method_(socks.authenticated() ? socks::method_basic : socks::method_clear), + tracker(log) +{ +} + +// protected/override +bool connector_socks::proxied() const NOEXCEPT +{ + return proxied_; +} + +// protected/override +void connector_socks::start(const std::string& hostname, uint16_t port, + const config::address& host, socket_handler&& handler) NOEXCEPT +{ + if (proxied()) + { + const auto& sox = socks5_.socks; + connector::start(sox.host(), sox.port(), host, std::move(handler)); + return; + } + + connector::start(hostname, port, host, std::move(handler)); +} + +// protected/override +void connector_socks::handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(stranded()); + if (proxied()) + { + do_socks_greeting_write(ec, finish, socket); + return; + } + + connector::handle_connect(ec, finish, socket); +} + +// socks5 handshake (private) +// ---------------------------------------------------------------------------- +// datatracker.ietf.org/doc/html/rfc1928 (socks5) +// datatracker.ietf.org/doc/html/rfc1929 (basic authentication) + +void connector_socks::do_socks_greeting_write(const code& ec, + const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT +{ + BC_ASSERT(stranded()); + if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - socks_finish(result, socket); + socks_finish(result, finish, socket); return; } @@ -138,32 +171,33 @@ void connector_socks::do_socks(const code& ec, // +----+----------+----------+ const auto greeting = to_shared>( { - socks::version, - 1_u8, - socks::method_clear + socks::version, 1_u8, method_ }); // Start of socket strand sequence. + // All socket operations are dispatched to its own strand, so this write + // will be posted before invocation. This makes socket calls thread safe. socket->write({ greeting->data(), greeting->size() }, std::bind(&connector_socks::handle_socks_greeting_write, shared_from_base(), - _1, _2, socket, greeting)); + _1, _2, finish, socket, greeting)); } void connector_socks::handle_socks_greeting_write(const code& ec, size_t size, - const socket::ptr& socket, const data_cptr<3>& greeting) NOEXCEPT + const finish_ptr& finish, const socket::ptr& socket, + const data_cptr<3>& greeting) NOEXCEPT { BC_ASSERT(socket->stranded()); if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - do_socks_finish(result, socket); + do_socks_finish(result, finish, socket); return; } if (size != sizeof(*greeting)) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::operation_failed, finish, socket); return; } @@ -172,32 +206,158 @@ void connector_socks::handle_socks_greeting_write(const code& ec, size_t size, socket->read({ response->data(), response->size() }, std::bind(&connector_socks::handle_socks_method_read, shared_from_base(), - _1, _2, socket, response)); + _1, _2, finish, socket, response)); } void connector_socks::handle_socks_method_read(const code& ec, size_t size, - const socket::ptr& socket, const data_ptr<2>& response) NOEXCEPT + const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<2>& response) NOEXCEPT { BC_ASSERT(socket->stranded()); if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - do_socks_finish(result, socket); + do_socks_finish(result, finish, socket); return; } - const auto& in = *response; - // +----+--------+ // |VER | METHOD | // +----+--------+ // | 1 | 1 | // +----+--------+ if (size != sizeof(*response) || - in[0] != socks::version || - in[1] != socks::method_clear) + response->at(0) != socks::version || + response->at(1) != method_) + { + do_socks_finish(error::socks_method, finish, socket); + return; + } + + // Bypass authentication. + if (method_ == socks::method_clear) { - do_socks_finish(error::connect_failed, socket); + do_socks_connect_write(finish, socket); + return; + } + + const auto& username = socks5_.username; + const auto username_length = username.size(); + + // Socks5 limits valid username length to one byte. + if (is_limited(username_length)) + { + do_socks_finish(error::socks_username, finish, socket); + return; + } + + const auto& password = socks5_.password; + const auto password_length = password.size(); + + // Socks5 limits valid password length to one byte. + if (is_limited(password_length)) + { + do_socks_finish(error::socks_password, finish, socket); + return; + } + + // +----+------+----------+------+----------+ + // |VER | ULEN | UNAME | PLEN | PASSWD | + // +----+------+----------+------+----------+ + // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + // +----+------+----------+------+----------+ + const auto length = 1u + 1u + username_length + 1u + password_length; + const auto authenticator = emplace_shared(length); + auto it = authenticator->begin(); + *it++ = socks::method_basic_version; + *it++ = narrow_cast(username_length); + it = std::copy(username.begin(), username.end(), it); + *it++ = narrow_cast(password_length); + it = std::copy(password.begin(), password.end(), it); + + socket->write({ authenticator->data(), authenticator->size() }, + std::bind(&connector_socks::handle_socks_authentication_write, + shared_from_base(), + _1, _2, finish, socket, authenticator)); +} + +void connector_socks::handle_socks_authentication_write(const code& ec, + size_t size, const finish_ptr& finish, const socket::ptr& socket, + const chunk_ptr& authenticator) NOEXCEPT +{ + BC_ASSERT(socket->stranded()); + + if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) + { + do_socks_finish(result, finish, socket); + return; + } + + if (size != authenticator->size()) + { + do_socks_finish(error::operation_failed, finish, socket); + return; + } + + const auto auth_res = emplace_shared>(); + + socket->read({ auth_res->data(), auth_res->size() }, + std::bind(&connector_socks::handle_socks_authentication_read, + shared_from_base(), + _1, _2, finish, socket, auth_res)); +} + +void connector_socks::handle_socks_authentication_read(const code& ec, + size_t size, const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<2>& response) NOEXCEPT +{ + BC_ASSERT(socket->stranded()); + + if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) + { + do_socks_finish(result, finish, socket); + return; + } + + // +----+--------+ + // |VER | STATUS | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + if (size != sizeof(*response) || + response->at(0) != socks::method_basic_version || + response->at(1) != socks::method_basic_success) + { + do_socks_finish(error::socks_authentication, finish, socket); + return; + } + + do_socks_connect_write(finish, socket); +} + +// A DNS name (e.g. manual endpont) works when not proxied, as the name is +// passed through resolution below. However when the connection is proxied, +// the proxy name goes through resolution, but the host name does not. As a +// result proxied manual connections are currently limited to numeric IP +// addresses (authorities), despite being parsed as names (endpoints). BUT, +// this is easily resolvable by allowing the proxy to perform the secondary +// name resolution (preferred anyway). That would work here, except that host +// is presently passed via socket->address(), which results in a default +// address (due to failed lexical conversion from name to address in socket). +// This also prevents seeds from resolving via a proxy. +void connector_socks::do_socks_connect_write(const finish_ptr& finish, + const socket::ptr& socket) NOEXCEPT +{ + BC_ASSERT(socket->stranded()); + + const auto port = to_big_endian(socket->address().port()); + const auto host = socket->address().to_host(); + const auto host_length = host.length(); + + // Socks5 limits valid host lengths to one byte. + if (is_limited(host_length)) + { + do_socks_finish(error::socks_server_name, finish, socket); return; } @@ -206,40 +366,38 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, // +----+-----+-------+------+----------+----------+ // | 1 | 1 | X'00' | 1 | Variable | 2 | // +----+-----+-------+------+----------+----------+ - const auto host = socket->address().to_host(); - const auto port = to_big_endian(socket->address().port()); - const auto length = 5u + host.length() + sizeof(uint16_t); + const auto length = 5u + host_length + port_size; const auto request = emplace_shared(length); - auto& out = *request; - out[0] = socks::version; - out[1] = socks::command_connect; - out[2] = socks::reserved; - out[3] = socks::address_fqdn; - out[4] = narrow_cast(host.size()); - auto it = std::next(request->begin(), 5u); + auto it = request->begin(); + *it++ = socks::version; + *it++ = socks::command_connect; + *it++ = socks::reserved; + *it++ = socks::address_fqdn; + *it++ = narrow_cast(host_length); it = std::copy(host.begin(), host.end(), it); it = std::copy(port.begin(), port.end(), it); socket->write({ request->data(), request->size() }, std::bind(&connector_socks::handle_socks_connect_write, shared_from_base(), - _1, _2, socket, request)); + _1, _2, finish, socket, request)); } void connector_socks::handle_socks_connect_write(const code& ec, size_t size, - const socket::ptr& socket, const system::chunk_ptr& request) NOEXCEPT + const finish_ptr& finish, const socket::ptr& socket, + const chunk_ptr& request) NOEXCEPT { BC_ASSERT(socket->stranded()); if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - do_socks_finish(result, socket); + do_socks_finish(result, finish, socket); return; } if (size != request->size()) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::operation_failed, finish, socket); return; } @@ -248,58 +406,63 @@ void connector_socks::handle_socks_connect_write(const code& ec, size_t size, socket->read({ response->data(), response->size() }, std::bind(&connector_socks::handle_socks_response_read, shared_from_base(), - _1, _2, socket, response)); + _1, _2, finish, socket, response)); } void connector_socks::handle_socks_response_read(const code& ec, size_t size, - const socket::ptr& socket, const data_ptr<4>& response) NOEXCEPT + const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<4>& response) NOEXCEPT { BC_ASSERT(socket->stranded()); if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - do_socks_finish(result, socket); + do_socks_finish(result, finish, socket); return; } - const auto& in = *response; - // +----+-----+-------+------+ // |VER | REP | RSV | ATYP | // +----+-----+-------+------+ // | 1 | 1 | X'00' | 1 | // +----+-----+-------+------+ if (size != sizeof(*response) || - in[0] != socks::version || - in[1] != socks::success || - in[2] != socks::reserved) + response->at(0) != socks::version || + response->at(2) != socks::reserved) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::socks_response_invalid, finish, socket); return; } - switch (in[3]) + // Map response code to error code. + if (const auto code = socks_response(response->at(1))) + { + do_socks_finish(code, finish, socket); + return; + } + + switch (response->at(3)) { case socks::address_ipv4: { // A version-4 IP address with length of 4 octets. - const auto address = emplace_shared(4_size); + const auto address = emplace_shared(4u + port_size); - socket->read({ address.get(), sizeof(uint8_t) }, + socket->read({ address->data(), address->size() }, std::bind(&connector_socks::handle_socks_address_read, shared_from_base(), - _1, _2, socket, address)); + _1, _2, finish, socket, address)); return; } case socks::address_ipv6: { // A version-6 IP address with length of 16 octets. - const auto address = emplace_shared(16_size); + const auto address = emplace_shared(16u + port_size); socket->read({ address->data(), address->size() }, std::bind(&connector_socks::handle_socks_address_read, shared_from_base(), - _1, _2, socket, address)); + _1, _2, finish, socket, address)); return; } case socks::address_fqdn: @@ -309,54 +472,57 @@ void connector_socks::handle_socks_response_read(const code& ec, size_t size, // of name that follow (and excludes two byte length of the port). const auto length = emplace_shared>(); - socket->read({ length.get(), sizeof(uint8_t) }, + socket->read({ length->data(), sizeof(uint8_t) }, std::bind(&connector_socks::handle_socks_length_read, shared_from_base(), - _1, _2, socket, length)); + _1, _2, finish, socket, length)); return; } default: { - do_socks_finish(error::operation_failed, socket); + // There are no other types defined. + do_socks_finish(error::socks_response_invalid, finish, socket); return; } } } void connector_socks::handle_socks_length_read(const code& ec, size_t size, - const socket::ptr& socket, const data_ptr<1>& host_length) NOEXCEPT + const finish_ptr& finish, const socket::ptr& socket, + const data_ptr<1>& host_length) NOEXCEPT { BC_ASSERT(socket->stranded()); if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - do_socks_finish(result, socket); + do_socks_finish(result, finish, socket); return; } if (size != sizeof(*host_length)) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::socks_response_invalid, finish, socket); return; } - const auto bytes = host_length->front() + sizeof(uint16_t); - const auto address = emplace_shared(bytes); + const auto length = host_length->front() + port_size; + const auto address = emplace_shared(length); socket->read({ address->data(), address->size() }, std::bind(&connector_socks::handle_socks_address_read, shared_from_base(), - _1, _2, socket, address)); + _1, _2, finish, socket, address)); } void connector_socks::handle_socks_address_read(const code& ec, size_t size, - const socket::ptr& socket, const chunk_ptr& address) NOEXCEPT + const finish_ptr& finish, const socket::ptr& socket, + const chunk_ptr& address) NOEXCEPT { BC_ASSERT(socket->stranded()); if (const auto result = (socket->stopped() ? error::channel_stopped : ec)) { - do_socks_finish(result, socket); + do_socks_finish(result, finish, socket); return; } @@ -367,47 +533,36 @@ void connector_socks::handle_socks_address_read(const code& ec, size_t size, // +----------+----------+ if (size != address->size()) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::socks_response_invalid, finish, socket); return; } - // address:port isn't used (could convert to config::address, add setter), - // but the outbound address_/authority_ members are set by connect(). - ////socket->set_address(address); - - do_socks_finish(error::success, socket); + // The address:port is the local binding by the socks5 server (unused). + // The outbound address_/authority_ members are set by connect(). + do_socks_finish(error::success, finish, socket); } -void connector_socks::do_socks_finish(const code& ec, +void connector_socks::do_socks_finish(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(socket->stranded()); - // Either socks operation error or operation_canceled from connector timer. - // The socket is stopped here in case this is invoked by the timer (race). - // That causes any above asynchronous operation to cancel and post handler. - if (ec) - socket->stop(); - // End of socket strand sequence. boost::asio::post(strand_, std::bind(&connector_socks::socks_finish, - shared_from_base(), ec, socket)); + shared_from_base(), ec, finish, socket)); } -void connector_socks::socks_finish(const code& ec, +void connector_socks::socks_finish(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(stranded()); - - // Stops the timer in all cases, and stops/resets the socket if ec set. - connector::handle_connected(ec, move_shared(false), socket); + connector::handle_connect(ec, finish, socket); } BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() -BC_POP_WARNING() } // namespace network } // namespace libbitcoin diff --git a/src/net/socket.cpp b/src/net/socket.cpp index 658cee21f..7035c3a10 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -56,8 +56,10 @@ socket::socket(const logger& log, asio::io_context& service, // authority_.port() nonzero implies outbound connection. socket::socket(const logger& log, asio::io_context& service, - size_t maximum_request, const config::address& address) NOEXCEPT - : maximum_(maximum_request), + size_t maximum_request, const config::address& address, + bool proxied) NOEXCEPT + : proxied_(proxied), + maximum_(maximum_request), strand_(service.get_executor()), service_(service), socket_(strand_), @@ -618,16 +620,24 @@ void socket::handle_accept(boost_code ec, handler(code); } -void socket::handle_connect(const boost_code& ec, - const asio::endpoint& peer, const result_handler& handler) NOEXCEPT +// The peer is what the socket connected to. For socks proxy this will be the +// proxy server's address:port. address_ is the value set at construct, and for +// outbound this is the intended peer. For inbound this is defaulted (and then +// set to socket_.remote_endpoint in handle_accept). For outbound this is the +// host value of type config::address passed to connector::start. The inbound() +// method depends upon this being defaulted (not set) for incoming connections +// and set for outgoing. +void socket::handle_connect(const boost_code& ec, const asio::endpoint& peer, + const result_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); - authority_ = peer; - - // Outgoing connection requires address_ for .inbound() resolution. - if (is_zero(address_.port())) - address_ = { peer }; + // For socks proxy, peer will be the server's local binding. In this case + // authority_ is set to address_ as passed on start(). When this is a fqdn + // it fails lexical parse and results in a default address (see also: + // do_socks_connect_write). + authority_ = proxied_ ? config::authority{ address_ } : + config::authority{ peer }; if (error::asio_is_canceled(ec)) { @@ -924,7 +934,7 @@ void socket::logx(const std::string& context, const boost_code& ec) const NOEXCEPT { LOGX("Socket " << context << " error (" << ec.value() << ") " - << ec.category().name() << ":" << ec.message()); + << ec.category().name() << " : " << ec.message()); } BC_POP_WARNING() diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index 6dfc61e9b..9ca82c7ec 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -343,27 +343,27 @@ void session::unsubscribe_close() NOEXCEPT // ---------------------------------------------------------------------------- // inbound/server -acceptor::ptr session::create_acceptor(size_t maximum) NOEXCEPT +acceptor::ptr session::create_acceptor() NOEXCEPT { - return network_.create_acceptor(maximum); + return network_.create_acceptor(); } -// outbound (batch) -connectors_ptr session::create_connectors(size_t count) NOEXCEPT +// outbound (seed) +connector::ptr session::create_seed_connector() NOEXCEPT { - return network_.create_connectors(count); + return network_.create_seed_connector(); } -// manual/outbound -connector::ptr session::create_connector(size_t maximum) NOEXCEPT +// outbound (manual) +connector::ptr session::create_manual_connector() NOEXCEPT { - return network_.create_connector(maximum); + return network_.create_manual_connector(); } -// seed -connector::ptr session::create_connector() NOEXCEPT +// outbound (batch) +connectors_ptr session::create_connectors(size_t count) NOEXCEPT { - return network_.create_connector(); + return network_.create_connectors(count); } // Properties. diff --git a/src/sessions/session_inbound.cpp b/src/sessions/session_inbound.cpp index 41fac245f..db1dd12bb 100644 --- a/src/sessions/session_inbound.cpp +++ b/src/sessions/session_inbound.cpp @@ -79,10 +79,9 @@ void session_inbound::handle_started(const code& ec, LOGN("Accepting " << network_settings().inbound.connections << " peers on " << network_settings().inbound.binds.size() << " bindings."); - const auto maximum = network_settings().inbound.maximum_request; for (const auto& bind: network_settings().inbound.binds) { - const auto acceptor = create_acceptor(maximum); + const auto acceptor = create_acceptor(); // Require that all acceptors at least start. if (const auto error_code = acceptor->start(bind)) diff --git a/src/sessions/session_manual.cpp b/src/sessions/session_manual.cpp index 6837c0ca2..00b65c664 100644 --- a/src/sessions/session_manual.cpp +++ b/src/sessions/session_manual.cpp @@ -85,8 +85,7 @@ void session_manual::connect(const config::endpoint& peer, BC_ASSERT(stranded()); // Create a persistent connector for the manual connection. - const auto connector = create_connector( - network_settings().manual.maximum_request); + const auto connector = create_manual_connector(); subscribe_stop([=](const code&) NOEXCEPT { @@ -133,7 +132,6 @@ void session_manual::handle_connect(const code& ec, const socket::ptr& socket, if (ec == error::service_suspended) { - ////LOGS("Suspended manual channel start [" << peer << "]."); defer(BIND(start_connect, _1, peer, connector, handler)); return; } diff --git a/src/sessions/session_outbound.cpp b/src/sessions/session_outbound.cpp index 856b76e5a..ecb70bb53 100644 --- a/src/sessions/session_outbound.cpp +++ b/src/sessions/session_outbound.cpp @@ -98,7 +98,7 @@ void session_outbound::handle_started(const code& ec, LOGN("Create " << peers << " connections " << batch << " at a time."); // There is currently no way to vary the number of connections at runtime. - for (size_t peer = 0; peer < peers; ++peer) + for (size_t peer{}; peer < peers; ++peer) start_connect(error::success); // This is the end of the start sequence (actually at connector->connect). @@ -215,16 +215,21 @@ void session_outbound::handle_connect(const code& ec, return; } - if (ec == error::service_suspended) - { - ////LOGS("Suspended outbound channel start."); - defer(BIND(start_connect, _1)); - return; - } - // There was an error connecting a channel, so try again after delay. if (ec) { + if (ec == error::connect_failed || + ec == error::operation_timeout || + ec == error::service_suspended || + ec == error::socks_failure) + { + LOGV("Failed to connect outbound address: " << ec.message()); + } + else + { + LOGS("Failed to connect outbound address: " << ec.message()); + } + // Avoid tight loop with delay timer. defer(BIND(start_connect, _1)); return; @@ -250,7 +255,7 @@ void session_outbound::handle_channel_start(const code&, BC_ASSERT(stranded()); ////LOGS("Outbound channel start [" << channel->authority() << "] " - //// "(" << key << ") " << ec.message()); + //// << ec.message()); } void session_outbound::attach_protocols( @@ -265,7 +270,7 @@ void session_outbound::handle_channel_stop(const code& ec, BC_ASSERT(stranded()); ////LOGS("Outbound channel stop [" << channel->authority() << "] " - //// "(" << key << ") " << ec.message()); + //// << ec.message()); reclaim(ec, channel); diff --git a/src/sessions/session_seed.cpp b/src/sessions/session_seed.cpp index 2e66f3e60..433f9ebc1 100644 --- a/src/sessions/session_seed.cpp +++ b/src/sessions/session_seed.cpp @@ -122,7 +122,7 @@ void session_seed::handle_started(const code& ec, for (const auto& seed: network_settings().outbound.seeds) { - const auto connector = create_connector(); + const auto connector = create_seed_connector(); subscribe_stop([=](const code&) NOEXCEPT { connector->stop(); diff --git a/src/sessions/session_server.cpp b/src/sessions/session_server.cpp index 4db8e15f5..936a074af 100644 --- a/src/sessions/session_server.cpp +++ b/src/sessions/session_server.cpp @@ -81,10 +81,9 @@ void session_server::handle_started(const code& ec, LOGN("Accepting " << options_.connections << " " << name_ << " connections on " << options_.binds.size() << " bindings."); - const auto maximum = options_.maximum_request; for (const auto& bind: options_.binds) { - const auto acceptor = create_acceptor(maximum); + const auto acceptor = create_acceptor(); // Require that all acceptors at least start. if (const auto error_code = acceptor->start(bind)) diff --git a/src/settings.cpp b/src/settings.cpp index ac138b1ae..f41868227 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -29,19 +29,19 @@ namespace network { using namespace system; using namespace messages::peer; -// socks5_client +// socks5 // ---------------------------------------------------------------------------- -settings::socks5_client::socks5_client() NOEXCEPT +settings::socks5::socks5() NOEXCEPT { } -bool settings::socks5_client::proxied() const NOEXCEPT +bool settings::socks5::proxied() const NOEXCEPT { return is_nonzero(socks.port()); } -bool settings::socks5_client::secured() const NOEXCEPT +bool settings::socks5::authenticated() const NOEXCEPT { return !(username.empty() && password.empty()); } diff --git a/test/error.cpp b/test/error.cpp index 0388a5a25..3e1093f08 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -265,8 +265,6 @@ BOOST_AUTO_TEST_CASE(error_t__code__oversubscribed__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "service oversubscribed"); } -// outgoing connection failures - BOOST_AUTO_TEST_CASE(error_t__code__address_blocked__true_expected_message) { constexpr auto value = error::address_blocked; @@ -276,6 +274,8 @@ BOOST_AUTO_TEST_CASE(error_t__code__address_blocked__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "address blocked by policy"); } +// outgoing connection failures + BOOST_AUTO_TEST_CASE(error_t__code__address_in_use__true_expected_message) { constexpr auto value = error::address_in_use; @@ -500,6 +500,143 @@ BOOST_AUTO_TEST_CASE(error_t__code__desubscribed__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "subscriber desubscribed"); } +// socks5 + +BOOST_AUTO_TEST_CASE(error_t__code__socks_method__true_expected_message) +{ + constexpr auto value = error::socks_method; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks method not supported"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_username__true_expected_message) +{ + constexpr auto value = error::socks_username; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks username too long"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_password__true_expected_message) +{ + constexpr auto value = error::socks_password; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks password too long"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_server_name__true_expected_message) +{ + constexpr auto value = error::socks_server_name; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks server name too long"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_authentication__true_expected_message) +{ + constexpr auto value = error::socks_authentication; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks authentication failed"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_failure__true_expected_message) +{ + constexpr auto value = error::socks_failure; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks failure"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_disallowed__true_expected_message) +{ + constexpr auto value = error::socks_disallowed; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks connection disallowed"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_net_unreachable__true_expected_message) +{ + constexpr auto value = error::socks_net_unreachable; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks network unreachable"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_host_unreachable__true_expected_message) +{ + constexpr auto value = error::socks_host_unreachable; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks host unreachable"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_connection_refused__true_expected_message) +{ + constexpr auto value = error::socks_connection_refused; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks connection refused"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_connection_expired__true_expected_message) +{ + constexpr auto value = error::socks_connection_expired; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks connection expired"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_unsupported_command__true_expected_message) +{ + constexpr auto value = error::socks_unsupported_command; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks unsupported command"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_unsupported_address__true_expected_message) +{ + constexpr auto value = error::socks_unsupported_address; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks unsupported address"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_unassigned_failure__true_expected_message) +{ + constexpr auto value = error::socks_unassigned_failure; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks unassigned failure"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__socks_response_invalid__true_expected_message) +{ + constexpr auto value = error::socks_response_invalid; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "socks response invalid"); +} + // http 4xx client error BOOST_AUTO_TEST_CASE(error_t__code__bad_request__true_expected_message) diff --git a/test/protocols/protocol.cpp b/test/protocols/protocol.cpp index 4c7d899ec..058f806fd 100644 --- a/test/protocols/protocol.cpp +++ b/test/protocols/protocol.cpp @@ -113,10 +113,9 @@ class mock_connector { public: mock_connector(const logger& log, asio::strand& strand, - asio::io_context& service, const settings& settings, + asio::io_context& service, const steady_clock::duration& timeout, size_t maximum) NOEXCEPT - : connector(log, strand, service, settings.connect_timeout(), maximum, - suspended_), + : connector(log, strand, service, timeout, maximum, suspended_), stopped_(false) { } @@ -155,17 +154,19 @@ class mock_net using net::net; // Create mock acceptor to inject mock channel. - acceptor::ptr create_acceptor(size_t maximum) NOEXCEPT override + acceptor::ptr create_acceptor() NOEXCEPT override { - return std::make_shared(log, strand(), service(), - maximum); + const auto maximum = network_settings().inbound.maximum_request; + return std::make_shared(log, strand(), service(), maximum); } // Create mock connector to inject mock channel. - connector::ptr create_connector(size_t maximum) NOEXCEPT override + connector::ptr create_connector(const settings::socks5& , + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { + // TODO: socks. return std::make_shared(log, strand(), service(), - network_settings(), maximum); + timeout, maximum); } }; diff --git a/test/sessions/session.cpp b/test/sessions/session.cpp index 89ce5bc5d..ab78e8b13 100644 --- a/test/sessions/session.cpp +++ b/test/sessions/session.cpp @@ -117,24 +117,24 @@ class mock_session return session_peer::stranded(); } - acceptor::ptr create_acceptor(size_t maximum) NOEXCEPT override + acceptor::ptr create_acceptor() NOEXCEPT override { - return session_peer::create_acceptor(maximum); + return session_peer::create_acceptor(); } - connectors_ptr create_connectors(size_t count) NOEXCEPT override + connector::ptr create_seed_connector() NOEXCEPT override { - return session_peer::create_connectors(count); + return session_peer::create_seed_connector(); } - connector::ptr create_connector(size_t maximum) NOEXCEPT override + connector::ptr create_manual_connector() NOEXCEPT override { - return session_peer::create_connector(maximum); + return session_peer::create_manual_connector(); } - connector::ptr create_connector() NOEXCEPT override + connectors_ptr create_connectors(size_t count) NOEXCEPT override { - return session_peer::create_connector(); + return session_peer::create_connectors(count); } ////size_t address_count() const NOEXCEPT override @@ -206,27 +206,22 @@ class mock_net public: using net::net; - acceptor::ptr create_acceptor(size_t maximum) NOEXCEPT override + acceptor::ptr create_acceptor() NOEXCEPT override { ++acceptors_; - return net::create_acceptor(maximum); + return net::create_acceptor(); } - size_t acceptors() const NOEXCEPT - { - return acceptors_; - } - - connector::ptr create_connector() NOEXCEPT override + connector::ptr create_connector(const settings::socks5& socks, + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { ++connectors_; - return net::create_connector(); + return net::create_connector(socks, timeout, maximum); } - connector::ptr create_connector(size_t maximum) NOEXCEPT override + size_t acceptors() const NOEXCEPT { - ++connectors_; - return net::create_connector(maximum); + return acceptors_; } size_t connectors() const NOEXCEPT @@ -395,41 +390,41 @@ BOOST_AUTO_TEST_CASE(session__create_acceptor__always__expected) settings set(selection::mainnet); mock_net net(set, log); mock_session session(net, 1); - BOOST_REQUIRE(session.create_acceptor(42)); + BOOST_REQUIRE(session.create_acceptor()); BOOST_REQUIRE_EQUAL(net.acceptors(), 1u); } -BOOST_AUTO_TEST_CASE(session__create_connectors__always__expected) +BOOST_AUTO_TEST_CASE(session__create_manual_connector__always__expected) { const logger log{}; settings set(selection::mainnet); mock_net net(set, log); mock_session session(net, 1); - constexpr auto expected = 42u; - const auto connectors = session.create_connectors(expected); - BOOST_REQUIRE(connectors); - BOOST_REQUIRE_EQUAL(connectors->size(), expected); - BOOST_REQUIRE_EQUAL(net.connectors(), expected); + BOOST_REQUIRE(session.create_manual_connector()); + BOOST_REQUIRE_EQUAL(net.connectors(), 1u); } -BOOST_AUTO_TEST_CASE(session__create_connector__always__expected) +BOOST_AUTO_TEST_CASE(session__create_seed_connector__always__expected) { const logger log{}; settings set(selection::mainnet); mock_net net(set, log); mock_session session(net, 1); - BOOST_REQUIRE(session.create_connector(42u)); + BOOST_REQUIRE(session.create_seed_connector()); BOOST_REQUIRE_EQUAL(net.connectors(), 1u); } -BOOST_AUTO_TEST_CASE(session__create_connector_seed__always__expected) +BOOST_AUTO_TEST_CASE(session__create_connectors__always__expected) { const logger log{}; settings set(selection::mainnet); mock_net net(set, log); mock_session session(net, 1); - BOOST_REQUIRE(session.create_connector()); - BOOST_REQUIRE_EQUAL(net.connectors(), 1u); + constexpr auto expected = 42u; + const auto connectors = session.create_connectors(expected); + BOOST_REQUIRE(connectors); + BOOST_REQUIRE_EQUAL(connectors->size(), expected); + BOOST_REQUIRE_EQUAL(net.connectors(), expected); } // utilities diff --git a/test/sessions/session_inbound.cpp b/test/sessions/session_inbound.cpp index 5ac2f62de..4553c49fd 100644 --- a/test/sessions/session_inbound.cpp +++ b/test/sessions/session_inbound.cpp @@ -321,8 +321,9 @@ class mock_net } // Create mock acceptor to inject mock channel. - acceptor::ptr create_acceptor(size_t maximum) NOEXCEPT override + acceptor::ptr create_acceptor() NOEXCEPT override { + const auto maximum = network_settings().inbound.maximum_request; return ((acceptor_ = std::make_shared(log, strand(), service(), maximum, suspended_))); } diff --git a/test/sessions/session_manual.cpp b/test/sessions/session_manual.cpp index 5de8d7bb8..71c5fb847 100644 --- a/test/sessions/session_manual.cpp +++ b/test/sessions/session_manual.cpp @@ -230,11 +230,12 @@ class mock_net } // Create mock connector to inject mock channel. - connector::ptr create_connector(size_t maximum) NOEXCEPT override + connector::ptr create_connector(const settings::socks5& , + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { + // TODO: socks. return ((connector_ = std::make_shared(log, strand(), - service(), network_settings().connect_timeout(), maximum, - suspended_))); + service(), timeout, maximum, suspended_))); } session_inbound::ptr attach_inbound_session() NOEXCEPT override diff --git a/test/sessions/session_outbound.cpp b/test/sessions/session_outbound.cpp index 4df5450c3..b26806dd5 100644 --- a/test/sessions/session_outbound.cpp +++ b/test/sessions/session_outbound.cpp @@ -236,11 +236,12 @@ class mock_net } // Create mock connector to inject mock channel. - connector::ptr create_connector(size_t maximum) NOEXCEPT override + connector::ptr create_connector(const settings::socks5& , + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { + // TODO: socks. return ((connector_ = std::make_shared(log, strand(), - service(), network_settings().connect_timeout(), maximum, - suspended_))); + service(), timeout, maximum, suspended_))); } session_inbound::ptr attach_inbound_session() NOEXCEPT override @@ -306,10 +307,10 @@ class mock_connector_stop_connect typedef std::shared_ptr ptr; mock_connector_stop_connect(const logger& log, asio::strand& strand, - asio::io_context& service, const settings& settings, size_t maximum, - mock_session_outbound::ptr session) NOEXCEPT - : mock_connector_connect_success(log, strand, service, - settings.connect_timeout(), maximum, suspended_), + asio::io_context& service, const steady_clock::duration& timeout, + size_t maximum, mock_session_outbound::ptr session) NOEXCEPT + : mock_connector_connect_success(log, strand, service, timeout, maximum, + suspended_), session_(session) { } @@ -350,13 +351,15 @@ class mock_net_stop_connect } // Create mock connector to inject mock channel. - connector::ptr create_connector(size_t maximum) NOEXCEPT override + connector::ptr create_connector(const settings::socks5& , + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { if (connector_) return connector_; + // TODO: socks. return ((connector_ = std::make_shared( - log, strand(), service(), network_settings(), maximum, session_))); + log, strand(), service(), timeout, maximum, session_))); } session_inbound::ptr attach_inbound_session() NOEXCEPT override diff --git a/test/sessions/session_seed.cpp b/test/sessions/session_seed.cpp index c50c88db1..4fd53b72c 100644 --- a/test/sessions/session_seed.cpp +++ b/test/sessions/session_seed.cpp @@ -228,11 +228,12 @@ class mock_net } // Create mock connector to inject mock channel. - connector::ptr create_connector() NOEXCEPT override + connector::ptr create_connector(const settings::socks5& , + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { + // TODO: socks. return ((connector_ = std::make_shared(log, strand(), - service(), network_settings().connect_timeout(), - network_settings().outbound.maximum_request, suspended_))); + service(), timeout, maximum, suspended_))); } session_inbound::ptr attach_inbound_session() NOEXCEPT override @@ -321,11 +322,10 @@ class mock_connector_stop_connect typedef std::shared_ptr ptr; mock_connector_stop_connect(const logger& log, asio::strand& strand, - asio::io_context& service, const settings& settings, - mock_session_seed::ptr session) NOEXCEPT - : mock_connector_connect_success(log, strand, service, - settings.outbound.seeding_timeout(), - settings.outbound.maximum_request, suspended_), + asio::io_context& service, const steady_clock::duration& timeout, + uint32_t maximum, mock_session_seed::ptr session) NOEXCEPT + : mock_connector_connect_success(log, strand, service, timeout, maximum, + suspended_), session_(session) { } @@ -366,13 +366,15 @@ class mock_net_stop_connect } // Create mock connector to inject mock channel. - connector::ptr create_connector() NOEXCEPT override + connector::ptr create_connector(const settings::socks5& , + const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT override { if (connector_) return connector_; + // TODO: socks. return ((connector_ = std::make_shared( - log, strand(), service(), network_settings(), session_))); + log, strand(), service(), timeout, maximum, session_))); } session_inbound::ptr attach_inbound_session() NOEXCEPT override diff --git a/test/settings.cpp b/test/settings.cpp index 5dbd056bf..ed9ca6f2a 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -307,11 +307,11 @@ BOOST_AUTO_TEST_CASE(settings__excluded__default__true) // ---------------------------------------------------------------------------- constexpr auto maximum_request = system::chain::max_block_weight; -BOOST_AUTO_TEST_CASE(settings__socks5_client__defaults__expected) +BOOST_AUTO_TEST_CASE(settings__socks5__defaults__expected) { - const settings::socks5_client instance{}; + const settings::socks5 instance{}; - BOOST_REQUIRE(!instance.secured()); + BOOST_REQUIRE(!instance.authenticated()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); BOOST_REQUIRE(!instance.proxied()); @@ -394,8 +394,8 @@ BOOST_AUTO_TEST_CASE(settings__peer_outbound__mainnet__expected) { const settings::peer_outbound instance{ system::chain::selection::mainnet }; - // socks5_client - BOOST_REQUIRE(!instance.secured()); + // socks5 + BOOST_REQUIRE(!instance.authenticated()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); BOOST_REQUIRE(!instance.proxied()); @@ -669,8 +669,8 @@ BOOST_AUTO_TEST_CASE(settings__peer_manual__mainnet__expected) { const settings::peer_manual instance{ system::chain::selection::mainnet }; - // socks5_client - BOOST_REQUIRE(!instance.secured()); + // socks5 + BOOST_REQUIRE(!instance.authenticated()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); BOOST_REQUIRE(!instance.proxied());