From 3641a51840613314d144364ee1ee698c5c1b77c4 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 03:18:39 -0500 Subject: [PATCH 01/19] Refactor connector factories for socks5 injection. --- include/bitcoin/network/net.hpp | 8 +- include/bitcoin/network/net/connector.hpp | 6 +- .../bitcoin/network/net/connector_socks.hpp | 43 +++-- include/bitcoin/network/sessions/session.hpp | 14 +- include/bitcoin/network/settings.hpp | 14 +- src/net.cpp | 54 ++++-- src/net/connector.cpp | 43 ++--- src/net/connector_socks.cpp | 170 ++++++++---------- src/net/socket.cpp | 2 +- src/sessions/session.cpp | 22 +-- src/sessions/session_inbound.cpp | 3 +- src/sessions/session_manual.cpp | 3 +- src/sessions/session_seed.cpp | 2 +- src/sessions/session_server.cpp | 3 +- src/settings.cpp | 8 +- test/protocols/protocol.cpp | 17 +- test/sessions/session.cpp | 59 +++--- test/sessions/session_inbound.cpp | 3 +- test/sessions/session_manual.cpp | 7 +- test/sessions/session_outbound.cpp | 21 ++- test/sessions/session_seed.cpp | 22 +-- test/settings.cpp | 8 +- 22 files changed, 272 insertions(+), 260 deletions(-) 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..0f50b46e7 100644 --- a/include/bitcoin/network/net/connector.hpp +++ b/include/bitcoin/network/net/connector.hpp @@ -86,8 +86,8 @@ 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; + 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; @@ -111,8 +111,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..d35e368c4 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,16 @@ 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; /// 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, + void handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT override; private: @@ -67,24 +66,34 @@ 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(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_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 bool proxied_; }; typedef std_vector socks_connectors; 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..c8aacb6d4 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{}; @@ -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/net.cpp b/src/net.cpp index de1a250f5..0621d5227 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -69,38 +69,56 @@ 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); + 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)); +// outbound (seed) +connector::ptr net::create_seed_connector() NOEXCEPT +{ + const auto& settings = network_settings(); - return connects; + 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..264fdbd41 100644 --- a/src/net/connector.cpp +++ b/src/net/connector.cpp @@ -195,48 +195,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 +247,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..6f8c28fd7 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 { @@ -41,44 +42,32 @@ 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 + 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_(socks5_proxy), + socks5_(socks), proxied_(socks.proxied()), tracker(log) { } // protected/override -void connector_socks::start(const std::string&, uint16_t, +void connector_socks::start(const std::string& hostname, uint16_t port, 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)); + // hostname and port are redundant with host. + // For proxy substitute for its connection and retain host for tunnel. + const auto& host_ = proxied_ ? socks5_.socks.host() : hostname; + const auto port_ = proxied_ ? socks5_.socks.port() : port; + connector::start(host_, 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&, +void connector_socks::handle_connect(const code& ec, const finish_ptr& finish, 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)); + do_socks(ec, finish, socket); } // socks5 handshake (private) @@ -120,14 +109,16 @@ enum socks : uint8_t address_ipv6 = 0x04 }; -void connector_socks::do_socks(const code& ec, +constexpr auto port_size = sizeof(uint16_t); + +void connector_socks::do_socks(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; } @@ -144,26 +135,29 @@ void connector_socks::do_socks(const code& ec, }); // 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::connect_failed, finish, socket); return; } @@ -172,32 +166,31 @@ 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) != socks::method_clear) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::connect_failed, finish, socket); return; } @@ -208,14 +201,13 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, // +----+-----+-------+------+----------+----------+ 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()); + request->at(0) = socks::version; + request->at(1) = socks::command_connect; + request->at(2) = socks::reserved; + request->at(3) = socks::address_fqdn; + request->at(4) = narrow_cast(host.size()); auto it = std::next(request->begin(), 5u); it = std::copy(host.begin(), host.end(), it); it = std::copy(port.begin(), port.end(), it); @@ -223,23 +215,24 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, 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 system::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::connect_failed, finish, socket); return; } @@ -248,58 +241,57 @@ 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(1) != socks::success || + response->at(2) != socks::reserved) { - do_socks_finish(error::connect_failed, socket); + do_socks_finish(error::connect_failed, finish, socket); return; } - switch (in[3]) + 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 +301,56 @@ 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); + do_socks_finish(error::operation_failed, 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::connect_failed, 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,41 +361,31 @@ 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::connect_failed, 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() diff --git a/src/net/socket.cpp b/src/net/socket.cpp index 658cee21f..01a6b2182 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -924,7 +924,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..b3cc282d9 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 { 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..cd9af9ff9 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::secured() const NOEXCEPT { return !(username.empty() && password.empty()); } 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..2fa0e31ba 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -307,9 +307,9 @@ 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.username.empty()); @@ -394,7 +394,7 @@ BOOST_AUTO_TEST_CASE(settings__peer_outbound__mainnet__expected) { const settings::peer_outbound instance{ system::chain::selection::mainnet }; - // socks5_client + // socks5 BOOST_REQUIRE(!instance.secured()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); @@ -669,7 +669,7 @@ BOOST_AUTO_TEST_CASE(settings__peer_manual__mainnet__expected) { const settings::peer_manual instance{ system::chain::selection::mainnet }; - // socks5_client + // socks5 BOOST_REQUIRE(!instance.secured()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); From ca757adb648bfd9fbd3db9f305ae94fcb9f4c715 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 14:37:01 -0500 Subject: [PATCH 02/19] Rename socks5::secured() to authenticated(). --- include/bitcoin/network/settings.hpp | 2 +- src/settings.cpp | 2 +- test/settings.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/bitcoin/network/settings.hpp b/include/bitcoin/network/settings.hpp index c8aacb6d4..f66765ddc 100644 --- a/include/bitcoin/network/settings.hpp +++ b/include/bitcoin/network/settings.hpp @@ -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 diff --git a/src/settings.cpp b/src/settings.cpp index cd9af9ff9..f41868227 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -41,7 +41,7 @@ bool settings::socks5::proxied() const NOEXCEPT return is_nonzero(socks.port()); } -bool settings::socks5::secured() const NOEXCEPT +bool settings::socks5::authenticated() const NOEXCEPT { return !(username.empty() && password.empty()); } diff --git a/test/settings.cpp b/test/settings.cpp index 2fa0e31ba..ed9ca6f2a 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -311,7 +311,7 @@ BOOST_AUTO_TEST_CASE(settings__socks5__defaults__expected) { 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()); @@ -395,7 +395,7 @@ BOOST_AUTO_TEST_CASE(settings__peer_outbound__mainnet__expected) const settings::peer_outbound instance{ system::chain::selection::mainnet }; // socks5 - BOOST_REQUIRE(!instance.secured()); + BOOST_REQUIRE(!instance.authenticated()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); BOOST_REQUIRE(!instance.proxied()); @@ -670,7 +670,7 @@ BOOST_AUTO_TEST_CASE(settings__peer_manual__mainnet__expected) const settings::peer_manual instance{ system::chain::selection::mainnet }; // socks5 - BOOST_REQUIRE(!instance.secured()); + BOOST_REQUIRE(!instance.authenticated()); BOOST_REQUIRE(instance.username.empty()); BOOST_REQUIRE(instance.password.empty()); BOOST_REQUIRE(!instance.proxied()); From c4f11f58e4722f77a8c45225eb0a9fd5d7ee88f3 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 15:53:29 -0500 Subject: [PATCH 03/19] Add ec authentication_failed. --- include/bitcoin/network/error.hpp | 1 + src/error.cpp | 1 + test/error.cpp | 13 +++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/network/error.hpp b/include/bitcoin/network/error.hpp index a1893bdbf..b6445ec56 100644 --- a/include/bitcoin/network/error.hpp +++ b/include/bitcoin/network/error.hpp @@ -96,6 +96,7 @@ enum error_t : uint8_t address_in_use, resolve_failed, connect_failed, + authentication_failed, // heading read failures invalid_heading, diff --git a/src/error.cpp b/src/error.cpp index 156bfe543..b6b12f4c0 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -69,6 +69,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { address_in_use, "address already in use" }, { resolve_failed, "resolving hostname failed" }, { connect_failed, "unable to reach remote host" }, + { authentication_failed, "unable to authenticate" }, // heading read failures { invalid_heading, "invalid message heading" }, diff --git a/test/error.cpp b/test/error.cpp index 0388a5a25..24bd41c39 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; @@ -303,6 +303,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__connect_failed__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "unable to reach remote host"); } +BOOST_AUTO_TEST_CASE(error_t__code__authentication_failed__true_expected_message) +{ + constexpr auto value = error::authentication_failed; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "unable to authenticate"); +} + // heading read failures BOOST_AUTO_TEST_CASE(error_t__code__invalid_heading__true_expected_message) From 4e5b8a039cc8aeb54571830dc846b4ac361d9256 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 15:58:58 -0500 Subject: [PATCH 04/19] Fix/optimize lack of proxy bypass when not configured. --- src/net.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 0621d5227..15b6bbce3 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -82,8 +82,13 @@ acceptor::ptr net::create_acceptor() NOEXCEPT connector::ptr net::create_connector(const settings::socks5& socks, const steady_clock::duration& timeout, uint32_t maximum) NOEXCEPT { - return emplace_shared(log, strand(), service(), - timeout, maximum, connect_suspended_, socks); + if (socks.proxied()) + return emplace_shared(log, strand(), service(), + timeout, maximum, connect_suspended_, socks); + + // Above can handle both proxy and non-proxy, but this is more efficient. + return emplace_shared(log, strand(), service(), + timeout, maximum, connect_suspended_); } // outbound (seed) From b190333ca3559ba8a28608b3096fa5f20d9feb4b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 15:59:11 -0500 Subject: [PATCH 05/19] Integrate socks5 basic (password) authentication. --- .../bitcoin/network/net/connector_socks.hpp | 15 +- src/net/connector_socks.cpp | 225 ++++++++++++++---- 2 files changed, 188 insertions(+), 52 deletions(-) diff --git a/include/bitcoin/network/net/connector_socks.hpp b/include/bitcoin/network/net/connector_socks.hpp index d35e368c4..aad18a24d 100644 --- a/include/bitcoin/network/net/connector_socks.hpp +++ b/include/bitcoin/network/net/connector_socks.hpp @@ -66,17 +66,28 @@ class BCT_API connector_socks using data_cptr = std::shared_ptr>; // socks5 handshake - void do_socks(const code& ec, const finish_ptr& finish, + 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 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 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 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 finish_ptr& finish, const socket::ptr& socket, const data_ptr<4>& response) NOEXCEPT; @@ -86,6 +97,7 @@ class BCT_API connector_socks void handle_socks_address_read(const code& ec, size_t size, 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, @@ -93,6 +105,7 @@ class BCT_API connector_socks // These are thread safe. const settings::socks5& socks5_; + const uint8_t method_; const bool proxied_; }; diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 6f8c28fd7..9db60c03c 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -36,43 +36,12 @@ 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 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()), - tracker(log) -{ -} - -// protected/override -void connector_socks::start(const std::string& hostname, uint16_t port, - const config::address& host, socket_handler&& handler) NOEXCEPT -{ - // hostname and port are redundant with host. - // For proxy substitute for its connection and retain host for tunnel. - const auto& host_ = proxied_ ? socks5_.socks.host() : hostname; - const auto port_ = proxied_ ? socks5_.socks.port() : port; - connector::start(host_, 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()); - do_socks(ec, finish, socket); -} - -// socks5 handshake (private) -// ---------------------------------------------------------------------------- -// datatracker.ietf.org/doc/html/rfc1928 +constexpr auto port_size = sizeof(uint16_t); +constexpr auto length_size = sizeof(uint8_t); enum socks : uint8_t { @@ -96,7 +65,11 @@ enum socks : uint8_t 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, @@ -109,13 +82,60 @@ enum socks : uint8_t address_ipv6 = 0x04 }; -constexpr auto port_size = sizeof(uint16_t); +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 +void connector_socks::start(const std::string& hostname, uint16_t port, + const config::address& host, socket_handler&& handler) NOEXCEPT +{ + // Caller can avoid this condition by using connector when not proxied. + if (!proxied_) + { + connector::start(hostname, port, host, std::move(handler)); + return; + } -void connector_socks::do_socks(const code& ec, const finish_ptr& finish, + // hostname and port are redundant with host. + connector::start(socks5_.socks.host(), socks5_.socks.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()); + // Caller can avoid this condition by using connector when not proxied. + if (!proxied_) + { + connector::handle_connect(ec, finish, socket); + return; + } + + do_socks_greeting_write(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, finish, socket); @@ -129,9 +149,7 @@ void connector_socks::do_socks(const code& ec, const finish_ptr& finish, // +----+----------+----------+ const auto greeting = to_shared>( { - socks::version, - 1_u8, - socks::method_clear + socks::version, 1_u8, method_ }); // Start of socket strand sequence. @@ -188,7 +206,115 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, // +----+--------+ if (size != sizeof(*response) || response->at(0) != socks::version || - response->at(1) != socks::method_clear) + response->at(1) != method_) + { + do_socks_finish(error::connect_failed, finish, socket); + return; + } + + if (method_ == socks::method_clear) + { + do_socks_connect_write(finish, socket); + return; + } + + const auto& username = socks5_.username; + const auto& password = socks5_.password; + const auto username_length = username.size(); + const auto password_length = password.size(); + + // Socks5 limits valid username and password lengths to one byte. + if (limit(username_length) || limit(password_length)) + { + do_socks_finish(error::connect_failed, 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::connect_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::authentication_failed, finish, socket); + return; + } + + do_socks_connect_write(finish, socket); +} + +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 (limit(host_length)) { do_socks_finish(error::connect_failed, finish, socket); return; @@ -199,16 +325,14 @@ 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() + port_size; + const auto length = 5u + host_length + port_size; const auto request = emplace_shared(length); - request->at(0) = socks::version; - request->at(1) = socks::command_connect; - request->at(2) = socks::reserved; - request->at(3) = socks::address_fqdn; - request->at(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); @@ -220,7 +344,7 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, void connector_socks::handle_socks_connect_write(const code& ec, size_t size, const finish_ptr& finish, const socket::ptr& socket, - const system::chunk_ptr& request) NOEXCEPT + const chunk_ptr& request) NOEXCEPT { BC_ASSERT(socket->stranded()); @@ -391,7 +515,6 @@ void connector_socks::socks_finish(const code& ec, const finish_ptr& finish, BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() -BC_POP_WARNING() } // namespace network } // namespace libbitcoin From a5eb9e2345066106639855bea15e28dc4924a7ac Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 16:04:57 -0500 Subject: [PATCH 06/19] Style. --- src/net/connector_socks.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 9db60c03c..dc17ac09a 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -95,35 +95,34 @@ connector_socks::connector_socks(const logger& log, asio::strand& strand, } // protected/override +// Caller can avoid proxied_ condition by using connector when not proxied. void connector_socks::start(const std::string& hostname, uint16_t port, const config::address& host, socket_handler&& handler) NOEXCEPT { - // Caller can avoid this condition by using connector when not proxied. - if (!proxied_) + if (proxied_) { - connector::start(hostname, port, host, std::move(handler)); + const auto& sox = socks5_.socks; + connector::start(sox.host(), sox.port(), host, std::move(handler)); return; } - // hostname and port are redundant with host. - connector::start(socks5_.socks.host(), socks5_.socks.port(), host, - std::move(handler)); + connector::start(hostname, port, host, std::move(handler)); } // protected/override +// Caller can avoid proxied_ condition by using connector when not proxied. void connector_socks::handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(stranded()); - // Caller can avoid this condition by using connector when not proxied. - if (!proxied_) + if (proxied_) { - connector::handle_connect(ec, finish, socket); + do_socks_greeting_write(ec, finish, socket); return; } - do_socks_greeting_write(ec, finish, socket); + connector::handle_connect(ec, finish, socket); } // socks5 handshake (private) From 08a88d056c85450b5e5aee8296d7b688da9f430d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 17:14:10 -0500 Subject: [PATCH 07/19] Make socks-specific error codes. --- include/bitcoin/network/error.hpp | 17 +++- src/error.cpp | 17 +++- test/error.cpp | 137 ++++++++++++++++++++++++++++-- 3 files changed, 160 insertions(+), 11 deletions(-) diff --git a/include/bitcoin/network/error.hpp b/include/bitcoin/network/error.hpp index b6445ec56..a861c3c1c 100644 --- a/include/bitcoin/network/error.hpp +++ b/include/bitcoin/network/error.hpp @@ -96,7 +96,6 @@ enum error_t : uint8_t address_in_use, resolve_failed, connect_failed, - authentication_failed, // heading read failures invalid_heading, @@ -127,6 +126,22 @@ enum error_t : uint8_t subscriber_stopped, desubscribed, + // socks5 + socks_method, + socks_username, + socks_password, + socks_server_name, + socks_authentication, + socks_disallowed, + socks_net_unreachable, + socks_host_unreachable, + socks_connection_refused, + socks_connection_expired, + socks_unsupported_command, + socks_unsupported_address, + socks_response_invalid, + socks_failure, + ////// http 4xx client error bad_request, ////unauthorized, diff --git a/src/error.cpp b/src/error.cpp index b6b12f4c0..a3ced6ef8 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -69,7 +69,6 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { address_in_use, "address already in use" }, { resolve_failed, "resolving hostname failed" }, { connect_failed, "unable to reach remote host" }, - { authentication_failed, "unable to authenticate" }, // heading read failures { invalid_heading, "invalid message heading" }, @@ -100,6 +99,22 @@ 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_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_response_invalid, "socks response invalid" }, + { socks_failure, "socks failure" }, + ////// http 4xx client error { bad_request, "bad request" }, ////{ unauthorized, "unauthorized" }, diff --git a/test/error.cpp b/test/error.cpp index 24bd41c39..8b4649910 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -303,15 +303,6 @@ BOOST_AUTO_TEST_CASE(error_t__code__connect_failed__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "unable to reach remote host"); } -BOOST_AUTO_TEST_CASE(error_t__code__authentication_failed__true_expected_message) -{ - constexpr auto value = error::authentication_failed; - const auto ec = code(value); - BOOST_REQUIRE(ec); - BOOST_REQUIRE(ec == value); - BOOST_REQUIRE_EQUAL(ec.message(), "unable to authenticate"); -} - // heading read failures BOOST_AUTO_TEST_CASE(error_t__code__invalid_heading__true_expected_message) @@ -509,6 +500,134 @@ 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_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_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"); +} + +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"); +} + // http 4xx client error BOOST_AUTO_TEST_CASE(error_t__code__bad_request__true_expected_message) From 28d9ba529f3be540116acb210a9a91081636dd62 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 17:14:18 -0500 Subject: [PATCH 08/19] Apply socks-specific error codes. --- .../bitcoin/network/net/connector_socks.hpp | 5 +- src/net/connector_socks.cpp | 63 ++++++++++++++----- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/include/bitcoin/network/net/connector_socks.hpp b/include/bitcoin/network/net/connector_socks.hpp index aad18a24d..365cbb6fe 100644 --- a/include/bitcoin/network/net/connector_socks.hpp +++ b/include/bitcoin/network/net/connector_socks.hpp @@ -52,10 +52,11 @@ class BCT_API connector_socks 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 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; diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index dc17ac09a..c5563c72e 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -82,6 +82,23 @@ enum socks : uint8_t address_ipv6 = 0x04 }; +// static +code connector_socks::socks_response(uint8_t value) NOEXCEPT +{ + switch (value) + { + case socks::success: return error::success; + 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: + case socks::failure: return error::socks_failure; + }; +} 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, @@ -174,7 +191,7 @@ void connector_socks::handle_socks_greeting_write(const code& ec, size_t size, if (size != sizeof(*greeting)) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::operation_failed, finish, socket); return; } @@ -207,10 +224,11 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, response->at(0) != socks::version || response->at(1) != method_) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::socks_method, finish, socket); return; } + // Bypass authentication. if (method_ == socks::method_clear) { do_socks_connect_write(finish, socket); @@ -218,14 +236,22 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, } const auto& username = socks5_.username; - const auto& password = socks5_.password; const auto username_length = username.size(); + + // Socks5 limits valid username length to one byte. + if (limit(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 username and password lengths to one byte. - if (limit(username_length) || limit(password_length)) + // Socks5 limits valid password length to one byte. + if (limit(password_length)) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::socks_password, finish, socket); return; } @@ -263,7 +289,7 @@ void connector_socks::handle_socks_authentication_write(const code& ec, if (size != authenticator->size()) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::operation_failed, finish, socket); return; } @@ -296,7 +322,7 @@ void connector_socks::handle_socks_authentication_read(const code& ec, response->at(0) != socks::method_basic_version || response->at(1) != socks::method_basic_success) { - do_socks_finish(error::authentication_failed, finish, socket); + do_socks_finish(error::socks_authentication, finish, socket); return; } @@ -315,7 +341,7 @@ void connector_socks::do_socks_connect_write(const finish_ptr& finish, // Socks5 limits valid host lengths to one byte. if (limit(host_length)) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::socks_server_name, finish, socket); return; } @@ -355,7 +381,7 @@ void connector_socks::handle_socks_connect_write(const code& ec, size_t size, if (size != request->size()) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::operation_failed, finish, socket); return; } @@ -386,10 +412,16 @@ void connector_socks::handle_socks_response_read(const code& ec, size_t size, // +----+-----+-------+------+ if (size != sizeof(*response) || response->at(0) != socks::version || - response->at(1) != socks::success || response->at(2) != socks::reserved) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::socks_response_invalid, finish, socket); + return; + } + + // Map response code to error code. + if (const auto code = socks_response(response->at(1))) + { + do_socks_finish(code, finish, socket); return; } @@ -432,7 +464,8 @@ void connector_socks::handle_socks_response_read(const code& ec, size_t size, } default: { - do_socks_finish(error::operation_failed, finish, socket); + // There are no other types defined. + do_socks_finish(error::socks_response_invalid, finish, socket); return; } } @@ -452,7 +485,7 @@ void connector_socks::handle_socks_length_read(const code& ec, size_t size, if (size != sizeof(*host_length)) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::socks_response_invalid, finish, socket); return; } @@ -484,7 +517,7 @@ void connector_socks::handle_socks_address_read(const code& ec, size_t size, // +----------+----------+ if (size != address->size()) { - do_socks_finish(error::connect_failed, finish, socket); + do_socks_finish(error::socks_response_invalid, finish, socket); return; } From b182adedc854f2dc6081af08fc6442ad032930fd Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 17:58:22 -0500 Subject: [PATCH 09/19] Fix using limit<> where should be is_limited<> (socsk5). --- src/net/connector_socks.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index c5563c72e..159f2e3f6 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -239,7 +239,7 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, const auto username_length = username.size(); // Socks5 limits valid username length to one byte. - if (limit(username_length)) + if (is_limited(username_length)) { do_socks_finish(error::socks_username, finish, socket); return; @@ -249,7 +249,7 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, const auto password_length = password.size(); // Socks5 limits valid password length to one byte. - if (limit(password_length)) + if (is_limited(password_length)) { do_socks_finish(error::socks_password, finish, socket); return; @@ -339,7 +339,7 @@ void connector_socks::do_socks_connect_write(const finish_ptr& finish, const auto host_length = host.length(); // Socks5 limits valid host lengths to one byte. - if (limit(host_length)) + if (is_limited(host_length)) { do_socks_finish(error::socks_server_name, finish, socket); return; From ac1ea11ff65928403f34cf30ef7f146f76a81473 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 18:54:22 -0500 Subject: [PATCH 10/19] Comments, style, logging. --- src/net/socket.cpp | 1 + src/sessions/session_manual.cpp | 1 - src/sessions/session_outbound.cpp | 17 +++++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/net/socket.cpp b/src/net/socket.cpp index 01a6b2182..7a9e87b65 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -623,6 +623,7 @@ void socket::handle_connect(const boost_code& ec, { BC_ASSERT(stranded()); + // For socks proxy this will be the local binding. authority_ = peer; // Outgoing connection requires address_ for .inbound() resolution. diff --git a/src/sessions/session_manual.cpp b/src/sessions/session_manual.cpp index b3cc282d9..00b65c664 100644 --- a/src/sessions/session_manual.cpp +++ b/src/sessions/session_manual.cpp @@ -132,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..cd38eed30 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,14 +215,19 @@ void session_outbound::handle_connect(const code& ec, return; } - if (ec == error::service_suspended) + // There was an error connecting a channel, so try again after delay. + + if (ec != error::connect_failed && + ec != error::operation_timeout && + ec != error::service_suspended) { - ////LOGS("Suspended outbound channel start."); + LOGS("Failed to connect outbound address: " << ec.message()); + + // Avoid tight loop with delay timer. defer(BIND(start_connect, _1)); return; } - // There was an error connecting a channel, so try again after delay. if (ec) { // Avoid tight loop with delay timer. @@ -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); From e7c20cca9bae792aea7b7c4f9ace500d37a7d6f0 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 4 Jan 2026 22:34:45 -0500 Subject: [PATCH 11/19] Add session logging for uncommon connection failures. --- src/sessions/session_outbound.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/sessions/session_outbound.cpp b/src/sessions/session_outbound.cpp index cd38eed30..e8274fec9 100644 --- a/src/sessions/session_outbound.cpp +++ b/src/sessions/session_outbound.cpp @@ -216,20 +216,15 @@ void session_outbound::handle_connect(const code& ec, } // There was an error connecting a channel, so try again after delay. - - if (ec != error::connect_failed && - ec != error::operation_timeout && - ec != error::service_suspended) - { - LOGS("Failed to connect outbound address: " << ec.message()); - - // Avoid tight loop with delay timer. - defer(BIND(start_connect, _1)); - return; - } - if (ec) { + if (ec != error::connect_failed && + ec != error::operation_timeout && + ec != error::service_suspended) + { + LOGS("Failed to connect outbound address: " << ec.message()); + } + // Avoid tight loop with delay timer. defer(BIND(start_connect, _1)); return; From b3843d810ecbecebaf1e43e7a286a61107e7b4b2 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 00:07:05 -0500 Subject: [PATCH 12/19] Add socket proxied tracking, to enable authority injection. --- include/bitcoin/network/net/socket.hpp | 8 ++++++-- src/net/socket.cpp | 27 ++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) 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/src/net/socket.cpp b/src/net/socket.cpp index 7a9e87b65..2bf9a176c 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,17 +620,22 @@ 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()); - // For socks proxy this will be the local binding. - 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() (trusting the proxy). + authority_ = proxied_ ? config::authority{ address_ } : + config::authority{ peer }; if (error::asio_is_canceled(ec)) { From da72a61692a6f6d52c5837c227ef239934cc2751 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 00:07:55 -0500 Subject: [PATCH 13/19] Add unknown socks error enum (unenumerated value from server). --- include/bitcoin/network/error.hpp | 3 ++- src/error.cpp | 3 ++- test/error.cpp | 21 +++++++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/include/bitcoin/network/error.hpp b/include/bitcoin/network/error.hpp index a861c3c1c..0cc468e57 100644 --- a/include/bitcoin/network/error.hpp +++ b/include/bitcoin/network/error.hpp @@ -132,6 +132,7 @@ enum error_t : uint8_t socks_password, socks_server_name, socks_authentication, + socks_failure, socks_disallowed, socks_net_unreachable, socks_host_unreachable, @@ -139,8 +140,8 @@ enum error_t : uint8_t socks_connection_expired, socks_unsupported_command, socks_unsupported_address, + socks_unassigned_failure, socks_response_invalid, - socks_failure, ////// http 4xx client error bad_request, diff --git a/src/error.cpp b/src/error.cpp index a3ced6ef8..fff2305fc 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -105,6 +105,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { 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" }, @@ -112,8 +113,8 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { 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" }, - { socks_failure, "socks failure" }, ////// http 4xx client error { bad_request, "bad request" }, diff --git a/test/error.cpp b/test/error.cpp index 8b4649910..3e1093f08 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -547,6 +547,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__socks_authentication__true_expected_message) 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; @@ -610,22 +619,22 @@ BOOST_AUTO_TEST_CASE(error_t__code__socks_unsupported_address__true_expected_mes BOOST_REQUIRE_EQUAL(ec.message(), "socks unsupported address"); } -BOOST_AUTO_TEST_CASE(error_t__code__socks_response_invalid__true_expected_message) +BOOST_AUTO_TEST_CASE(error_t__code__socks_unassigned_failure__true_expected_message) { - constexpr auto value = error::socks_response_invalid; + 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 response invalid"); + BOOST_REQUIRE_EQUAL(ec.message(), "socks unassigned failure"); } -BOOST_AUTO_TEST_CASE(error_t__code__socks_failure__true_expected_message) +BOOST_AUTO_TEST_CASE(error_t__code__socks_response_invalid__true_expected_message) { - constexpr auto value = error::socks_failure; + 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 failure"); + BOOST_REQUIRE_EQUAL(ec.message(), "socks response invalid"); } // http 4xx client error From c3819defb12f91ddce48e4bfb981c658218d5875 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 00:08:16 -0500 Subject: [PATCH 14/19] Add endpoint::is_address() method. --- include/bitcoin/network/config/endpoint.hpp | 4 ++++ src/config/endpoint.cpp | 6 ++++++ 2 files changed, 10 insertions(+) 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/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 { From 83a4c0d596005b9995d27ae3569871d871d05bdc Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 00:09:43 -0500 Subject: [PATCH 15/19] Add proxy authority injection to connectors. --- include/bitcoin/network/net/connector.hpp | 4 ++++ include/bitcoin/network/net/connector_socks.hpp | 1 + src/net/connector.cpp | 10 +++++++++- src/net/connector_socks.cpp | 14 ++++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/bitcoin/network/net/connector.hpp b/include/bitcoin/network/net/connector.hpp index 0f50b46e7..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; + /// 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; diff --git a/include/bitcoin/network/net/connector_socks.hpp b/include/bitcoin/network/net/connector_socks.hpp index 365cbb6fe..a42af6703 100644 --- a/include/bitcoin/network/net/connector_socks.hpp +++ b/include/bitcoin/network/net/connector_socks.hpp @@ -55,6 +55,7 @@ class BCT_API connector_socks static code socks_response(uint8_t value) NOEXCEPT; /// Connector overrides. + 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, diff --git a/src/net/connector.cpp b/src/net/connector.cpp index 264fdbd41..6611570b6 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 { @@ -102,6 +108,8 @@ void connector::connect(const authority& host, // TODO: this is getting a zero port for seeds (and maybe manual). // TODO: that results in the connection being interpreted as inbound. +// TODO: this results from the use of a DNS name, which does not convert to an +// TODO: address when host is passed to start(). // This used by seed, manual, and socks5 (endpoint from config). void connector::connect(const endpoint& host, socket_handler&& handler) NOEXCEPT @@ -133,7 +141,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( diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 159f2e3f6..36b45f3f5 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -99,6 +99,8 @@ code connector_socks::socks_response(uint8_t value) NOEXCEPT case socks::failure: return error::socks_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, @@ -112,11 +114,16 @@ connector_socks::connector_socks(const logger& log, asio::strand& strand, } // protected/override -// Caller can avoid proxied_ condition by using connector when not proxied. +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_) + if (proxied()) { const auto& sox = socks5_.socks; connector::start(sox.host(), sox.port(), host, std::move(handler)); @@ -127,13 +134,12 @@ void connector_socks::start(const std::string& hostname, uint16_t port, } // protected/override -// Caller can avoid proxied_ condition by using connector when not proxied. void connector_socks::handle_connect(const code& ec, const finish_ptr& finish, const socket::ptr& socket) NOEXCEPT { BC_ASSERT(stranded()); - if (proxied_) + if (proxied()) { do_socks_greeting_write(ec, finish, socket); return; From 76aff06f186f3aa1c2efe5d3c68f062e7ebd2da4 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 00:10:08 -0500 Subject: [PATCH 16/19] Handle unassigned error code from socks server. --- src/net/connector_socks.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 36b45f3f5..9fe66701a 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -60,6 +60,7 @@ enum socks : uint8_t connection_expired = 0x06, unsupported_command = 0x07, unsupported_address = 0x08, + unassigned = 0xff, // method (authentication) type method_none = 0xff, @@ -88,6 +89,7 @@ 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; @@ -95,8 +97,7 @@ code connector_socks::socks_response(uint8_t value) NOEXCEPT 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: - case socks::failure: return error::socks_failure; + default: return error::socks_unassigned_failure; }; } From f6920dd2ec5b372a0dd39e385fb4e298ba528367 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 00:16:36 -0500 Subject: [PATCH 17/19] Log suppressed connection failures in verbose mode. --- src/sessions/session_outbound.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sessions/session_outbound.cpp b/src/sessions/session_outbound.cpp index e8274fec9..ecb70bb53 100644 --- a/src/sessions/session_outbound.cpp +++ b/src/sessions/session_outbound.cpp @@ -218,9 +218,14 @@ void session_outbound::handle_connect(const code& ec, // 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) + 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()); } From 7182ed2ce172fa68efe7e7e6ad1c04db71b934ee Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 01:53:16 -0500 Subject: [PATCH 18/19] Comments on proxied fqdn. --- src/net/connector.cpp | 4 ---- src/net/connector_socks.cpp | 10 ++++++++++ src/net/socket.cpp | 4 +++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/net/connector.cpp b/src/net/connector.cpp index 6611570b6..3be9d8ecc 100644 --- a/src/net/connector.cpp +++ b/src/net/connector.cpp @@ -106,10 +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. -// TODO: this results from the use of a DNS name, which does not convert to an -// TODO: address when host is passed to start(). // This used by seed, manual, and socks5 (endpoint from config). void connector::connect(const endpoint& host, socket_handler&& handler) NOEXCEPT diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 9fe66701a..73feb457e 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -336,6 +336,16 @@ void connector_socks::handle_socks_authentication_read(const code& ec, 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 { diff --git a/src/net/socket.cpp b/src/net/socket.cpp index 2bf9a176c..7035c3a10 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -633,7 +633,9 @@ void socket::handle_connect(const boost_code& ec, const asio::endpoint& peer, BC_ASSERT(stranded()); // For socks proxy, peer will be the server's local binding. In this case - // authority_ is set to address_ as passed on start() (trusting the proxy). + // 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 }; From d34b353b506587d40c2cd65faf97f6c1cf3aeb2c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 5 Jan 2026 02:28:47 -0500 Subject: [PATCH 19/19] Remove dead code. --- src/net/connector_socks.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index 73feb457e..3f5f4f61b 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -41,7 +41,6 @@ using namespace system; using namespace std::placeholders; constexpr auto port_size = sizeof(uint16_t); -constexpr auto length_size = sizeof(uint8_t); enum socks : uint8_t {