diff --git a/contrib/epee/include/net/abstract_http_client.h b/contrib/epee/include/net/abstract_http_client.h index 29a7ce19b..8d08d5550 100644 --- a/contrib/epee/include/net/abstract_http_client.h +++ b/contrib/epee/include/net/abstract_http_client.h @@ -25,6 +25,7 @@ #pragma once +#include #include #include #include "http_auth.h" @@ -69,6 +70,7 @@ namespace http virtual bool connect(std::chrono::milliseconds timeout) = 0; virtual bool disconnect() = 0; virtual bool is_connected(bool *ssl = NULL) = 0; + virtual boost::system::error_code last_error(); virtual bool invoke(const boost::string_ref uri, const boost::string_ref method, const boost::string_ref body, std::chrono::milliseconds timeout, const http_response_info** ppresponse_info = NULL, const fields_list& additional_params = fields_list()) = 0; virtual bool invoke_get(const boost::string_ref uri, std::chrono::milliseconds timeout, const std::string& body = std::string(), const http_response_info** ppresponse_info = NULL, const fields_list& additional_params = fields_list()) = 0; virtual uint64_t get_bytes_sent() const = 0; diff --git a/contrib/epee/include/net/connection_basic.hpp b/contrib/epee/include/net/connection_basic.hpp index e3093de51..487affcb4 100644 --- a/contrib/epee/include/net/connection_basic.hpp +++ b/contrib/epee/include/net/connection_basic.hpp @@ -135,7 +135,7 @@ class connection_basic { // not-templated base class for rapid developmet of som bool handshake(boost::asio::ssl::stream_base::handshake_type type, boost::asio::const_buffer buffer = {}) { //m_state != nullptr verified in constructor - return m_state->ssl_options().handshake(socket_, type, buffer); + return !bool(m_state->ssl_options().handshake(socket_, type, buffer)); } template diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index ecbceb566..a0e861fbd 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -161,6 +161,12 @@ namespace net_utils return m_net_client.is_connected(ssl); } //--------------------------------------------------------------------------- + boost::system::error_code last_error() override final + { + CRITICAL_REGION_LOCAL(m_lock); + return m_net_client.last_error(); + } + //--------------------------------------------------------------------------- virtual bool handle_target_data(std::string& piece_of_transfer) override { CRITICAL_REGION_LOCAL(m_lock); diff --git a/contrib/epee/include/net/net_helper.h b/contrib/epee/include/net/net_helper.h index 3cb3d8c66..95ea800b3 100644 --- a/contrib/epee/include/net/net_helper.h +++ b/contrib/epee/include/net/net_helper.h @@ -63,15 +63,6 @@ namespace net_utils class blocked_mode_client { - enum try_connect_result_t - { - CONNECT_SUCCESS, - CONNECT_FAILURE, - CONNECT_NO_SSL, - }; - - - struct handler_obj { handler_obj(boost::system::error_code& error, size_t& bytes_transferred):ref_error(error), ref_bytes_transferred(bytes_transferred) @@ -91,6 +82,13 @@ namespace net_utils } }; + static boost::system::error_code guarantee_error(const boost::system::error_code error) + { + if (error) + return error; + return make_error_code(boost::system::errc::not_connected); + } + public: inline blocked_mode_client() : @@ -99,7 +97,7 @@ namespace net_utils m_ssl_socket(new boost::asio::ssl::stream(m_io_service, m_ctx)), m_connector(direct_connect{}), m_ssl_options(epee::net_utils::ssl_support_t::e_ssl_support_autodetect), - m_connected(false), + m_stream_error(make_error_code(boost::system::errc::not_connected)), m_deadline(m_io_service, std::chrono::steady_clock::time_point::max()), m_shutdowned(false), m_bytes_sent(0), @@ -142,7 +140,7 @@ namespace net_utils } inline - try_connect_result_t try_connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout) + boost::system::error_code try_connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout) { m_deadline.expires_from_now(timeout); boost::unique_future connection = m_connector(addr, port, m_deadline); @@ -159,79 +157,65 @@ namespace net_utils m_deadline.cancel(); if (m_ssl_socket->next_layer().is_open()) { - m_connected = true; + boost::system::error_code error{}; m_deadline.expires_at(std::chrono::steady_clock::time_point::max()); // SSL Options if (m_ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || m_ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { - if (!m_ssl_options.handshake(*m_ssl_socket, boost::asio::ssl::stream_base::client, {}, addr, timeout)) + if ((error = m_ssl_options.handshake(*m_ssl_socket, boost::asio::ssl::stream_base::client, {}, addr, timeout))) { - if (m_ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) - { - boost::system::error_code ignored_ec; - m_ssl_socket->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - m_ssl_socket->next_layer().close(); - m_connected = false; - return CONNECT_NO_SSL; - } - else - { - MWARNING("Failed to establish SSL connection"); - m_connected = false; - return CONNECT_FAILURE; - } + MWARNING("Failed to establish SSL connection"); + boost::system::error_code ignored_ec; + m_ssl_socket->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + m_ssl_socket->next_layer().close(); } } - return CONNECT_SUCCESS; - }else - { - MWARNING("Some problems at connect, expected open socket"); - return CONNECT_FAILURE; + return error; } + MWARNING("Some problems at connect, expected open socket"); + return boost::asio::error::not_connected; } inline bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout) { - m_connected = false; try { - m_ssl_socket->next_layer().close(); + disconnect(); // Set SSL options // disable sslv2 m_ssl_socket.reset(new boost::asio::ssl::stream(m_io_service, m_ctx)); - // Get a list of endpoints corresponding to the server name. - - try_connect_result_t try_connect_result = try_connect(addr, port, timeout); - if (try_connect_result == CONNECT_FAILURE) - return false; - if (m_ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) + boost::system::error_code error = try_connect(addr, port, timeout); + if (error && error != boost::asio::error::not_connected && m_ssl_options.support == ssl_support_t::e_ssl_support_autodetect) { - if (try_connect_result == CONNECT_NO_SSL) - { - MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL"); - m_ssl_options.support = epee::net_utils::ssl_support_t::e_ssl_support_disabled; - if (try_connect(addr, port, timeout) != CONNECT_SUCCESS) - return false; - } + MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL"); + m_ssl_options.support = ssl_support_t::e_ssl_support_disabled; + error = try_connect(addr, port, timeout); } + m_stream_error = error; } - catch(const boost::system::system_error& er) + catch (const boost::system::system_error& er) { + m_stream_error = guarantee_error(er.code()); + MDEBUG("Some problems at connect, message: " << er.what()); + } + catch(const std::exception& er) + { + m_stream_error = make_error_code(boost::system::errc::not_connected); MDEBUG("Some problems at connect, message: " << er.what()); - return false; } catch(...) { + m_stream_error = make_error_code(boost::system::errc::not_connected); MDEBUG("Some fatal problems."); - return false; } - - return true; + return !m_stream_error; } + + //! Change the connection routine (proxy, etc.) void set_connector(std::function connector) { @@ -243,9 +227,9 @@ namespace net_utils { try { - if(m_connected) + if(!last_error()) { - m_connected = false; + m_stream_error = make_error_code(boost::system::errc::not_connected); if(m_ssl_options) shutdown_ssl(); m_ssl_socket->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both); @@ -295,8 +279,8 @@ namespace net_utils if (ec) { + m_stream_error = ec; LOG_PRINT_L3("Problems at write: " << ec.message()); - m_connected = false; return false; }else { @@ -307,11 +291,13 @@ namespace net_utils catch(const boost::system::system_error& er) { + m_stream_error = guarantee_error(er.code()); LOG_ERROR("Some problems at connect, message: " << er.what()); return false; } catch(...) { + m_stream_error = make_error_code(boost::system::errc::not_connected); LOG_ERROR("Some fatal problems."); return false; } @@ -319,9 +305,11 @@ namespace net_utils return true; } + const boost::system::error_code& last_error() const noexcept { return m_stream_error; } + bool is_connected(bool *ssl = NULL) { - if (!m_connected || !m_ssl_socket->next_layer().is_open()) + if (last_error() || !m_ssl_socket->next_layer().is_open()) return false; if (ssl) *ssl = m_ssl_options.support != ssl_support_t::e_ssl_support_disabled; @@ -371,6 +359,7 @@ namespace net_utils if (ec) { + m_stream_error = ec; MTRACE("READ ENDS: Connection err_code " << ec.value()); if(ec == boost::asio::error::eof) { @@ -381,7 +370,6 @@ namespace net_utils } MDEBUG("Problems at read: " << ec.message()); - m_connected = false; return false; }else { @@ -399,24 +387,22 @@ namespace net_utils catch(const boost::system::system_error& er) { + m_stream_error = guarantee_error(er.code()); LOG_ERROR("Some problems at read, message: " << er.what()); - m_connected = false; - return false; } catch(...) { + m_stream_error = make_error_code(boost::system::errc::not_connected); LOG_ERROR("Some fatal problems at read."); - return false; } - - return false; } bool shutdown() { + m_stream_error = make_error_code(boost::system::errc::not_connected); m_deadline.cancel(); boost::system::error_code ec; if(m_ssl_options) @@ -431,7 +417,6 @@ namespace net_utils if(ec) MDEBUG("Problems at close: " << ec.message()); m_shutdowned = true; - m_connected = false; return true; } @@ -457,8 +442,8 @@ namespace net_utils // The deadline has passed. The socket is closed so that any outstanding // asynchronous operations are cancelled. This allows the blocked // connect(), read_line() or write_line() functions to return. + m_stream_error = make_error_code(boost::system::errc::timed_out); LOG_PRINT_L3("Timed out socket"); - m_connected = false; m_ssl_socket->next_layer().close(); // There is no longer an active deadline. The expiry is set to positive @@ -516,7 +501,7 @@ namespace net_utils std::shared_ptr> m_ssl_socket; std::function m_connector; ssl_options_t m_ssl_options; - bool m_connected; + boost::system::error_code m_stream_error; boost::asio::steady_timer m_deadline; std::atomic m_shutdowned; std::atomic m_bytes_sent; diff --git a/contrib/epee/include/net/net_ssl.h b/contrib/epee/include/net/net_ssl.h index c6ef925ba..2e552c679 100644 --- a/contrib/epee/include/net/net_ssl.h +++ b/contrib/epee/include/net/net_ssl.h @@ -133,9 +133,8 @@ namespace net_utils `verification == system_ca` the client also does a rfc2818 check to ensure that the server certificate is to the provided hostname. - \return True if the SSL handshake completes with peer verification - settings. */ - bool handshake( + \return Non-empty error_code on failure. */ + boost::system::error_code handshake( boost::asio::ssl::stream &socket, boost::asio::ssl::stream_base::handshake_type type, boost::asio::const_buffer buffer = {}, diff --git a/contrib/epee/src/abstract_http_client.cpp b/contrib/epee/src/abstract_http_client.cpp index 8897ad9ae..348c72bf4 100644 --- a/contrib/epee/src/abstract_http_client.cpp +++ b/contrib/epee/src/abstract_http_client.cpp @@ -133,6 +133,13 @@ namespace http { return false; } + + boost::system::error_code epee::net_utils::http::abstract_http_client::last_error() + { + if (is_connected()) + return boost::system::error_code{}; + return make_error_code(boost::system::errc::not_connected); + } } } } diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp index 0ad71d9c0..876362e8e 100644 --- a/contrib/epee/src/net_ssl.cpp +++ b/contrib/epee/src/net_ssl.cpp @@ -544,7 +544,7 @@ void ssl_options_t::configure( } } -bool ssl_options_t::handshake( +boost::system::error_code ssl_options_t::handshake( boost::asio::ssl::stream &socket, boost::asio::ssl::stream_base::handshake_type type, boost::asio::const_buffer buffer, @@ -636,12 +636,10 @@ bool ssl_options_t::handshake( const auto ec = start_handshake(); if (ec) - { MERROR("SSL handshake failed, connection dropped: " << ec.message()); - return false; - } - MDEBUG("SSL handshake success"); - return true; + else + MDEBUG("SSL handshake success"); + return ec; } std::string get_hr_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 6e5d0b1ec..40c1d587d 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -4554,7 +4554,8 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) if (!version) version = &version_; bool wallet_is_outdated = false, daemon_is_outdated = false; - if (!m_wallet->check_connection(version, NULL, 200000, &wallet_is_outdated, &daemon_is_outdated)) + boost::system::error_code err{}; + if (!m_wallet->check_connection(version, NULL, 200000, &wallet_is_outdated, &daemon_is_outdated, std::addressof(err))) { if (!silent) { @@ -4568,7 +4569,7 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) tr("Daemon is not up to date. " "Please make sure the daemon is running the latest version or change the daemon address using the 'set_daemon' command."); else - fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << + fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << " : " << err.message() << ". " << tr("Daemon either is not started or wrong port was passed. " "Please make sure daemon is running or change the daemon address using the 'set_daemon' command."); } @@ -9652,9 +9653,10 @@ bool simple_wallet::status(const std::vector &args) uint64_t local_height = m_wallet->get_blockchain_current_height(); uint32_t version = 0; bool ssl = false; - if (!m_wallet->check_connection(&version, &ssl)) + boost::system::error_code error{}; + if (!m_wallet->check_connection(&version, &ssl, 200000, nullptr, nullptr, std::addressof(error))) { - success_msg_writer() << "Refreshed " << local_height << "/?, no daemon connected"; + success_msg_writer() << "Refreshed " << local_height << "/?, no daemon connected: " << error.message(); return true; } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7f4dbbc79..3ce4eca51 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -2095,14 +2095,15 @@ bool WalletImpl::verifyMessageWithPublicKey(const std::string &message, const st bool WalletImpl::connectToDaemon() { - bool result = m_wallet->check_connection(NULL, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); + boost::system::error_code err{}; + bool result = m_wallet->check_connection(NULL, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS, NULL, NULL, std::addressof(err)); if (!result) { - setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address()); + setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address() + " : " + err.message()); } else { clearStatus(); // start refreshing here } - return result; + return !err; } Wallet::ConnectionStatus WalletImpl::connected() const diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 336f4e159..8fbf0ad17 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5863,7 +5863,7 @@ bool wallet2::prepare_file_names(const std::string& file_path) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated) +bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated, boost::system::error_code* error) { THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized); @@ -5874,6 +5874,8 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, b *version = 0; if (ssl) *ssl = false; + if (error) + *error = make_error_code(boost::system::errc::not_connected); return false; } @@ -5883,18 +5885,26 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, b { m_rpc_version = 0; m_node_rpc_proxy.invalidate(); - if (!m_http_client->connect(std::chrono::milliseconds(timeout))) - return false; - if(!m_http_client->is_connected(ssl)) + if (!m_http_client->connect(std::chrono::milliseconds(timeout)) || !m_http_client->is_connected(ssl)) + { + if (error) + *error = m_http_client->last_error(); return false; + } } } if (!m_rpc_version && !check_version(version, wallet_is_outdated, daemon_is_outdated)) + { + if (error) + *error = make_error_code(boost::system::errc::protocol_error); return false; + } if (version) *version = m_rpc_version; + if (error) + *error = boost::system::error_code{}; return true; } //---------------------------------------------------------------------------------------------------- @@ -11534,7 +11544,8 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) { uint32_t rpc_version; - THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address()); + boost::system::error_code error{}; + THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version, nullptr, 200000, nullptr, nullptr, std::addressof(error)), error::wallet_internal_error, "Failed to connect to daemon (" + get_daemon_address() + "): " + error.message()); COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; @@ -12059,7 +12070,8 @@ std::string wallet2::get_reserve_proof(const boost::optional #include #include +#include #include #include #include @@ -1113,7 +1114,7 @@ private: bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); std::vector create_unmixable_sweep_transactions(); void discard_unmixable_outputs(); - bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL); + bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL, boost::system::error_code* error = nullptr); bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated); bool check_hard_fork_version(cryptonote::network_type nettype, const std::vector> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated); void get_transfers(wallet2::transfer_container& incoming_transfers) const; diff --git a/tests/fuzz/http-client.cpp b/tests/fuzz/http-client.cpp index 629d012cb..ea68d18ef 100644 --- a/tests/fuzz/http-client.cpp +++ b/tests/fuzz/http-client.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include #include #include "include_base_utils.h" #include "file_io_utils.h" @@ -42,6 +43,7 @@ public: bool send(const boost::string_ref buff, std::chrono::milliseconds timeout) { return true; } bool send(const void* data, size_t sz) { return true; } bool is_connected() { return true; } + static boost::system::error_code last_error() noexcept { return {}; } bool recv(std::string& buff, std::chrono::milliseconds timeout) { buff = data; diff --git a/tests/unit_tests/epee_boosted_tcp_server.cpp b/tests/unit_tests/epee_boosted_tcp_server.cpp index d68f3c9bb..9b696bf6c 100644 --- a/tests/unit_tests/epee_boosted_tcp_server.cpp +++ b/tests/unit_tests/epee_boosted_tcp_server.cpp @@ -567,7 +567,7 @@ TEST(test_epee_connection, ssl_handshake) std::this_thread::sleep_for(std::chrono::milliseconds(50)); }); } - EXPECT_EQ( + EXPECT_NE( ssl_options.handshake( *ssl_socket, ssl_socket_t::server, @@ -575,7 +575,7 @@ TEST(test_epee_connection, ssl_handshake) {}, std::chrono::milliseconds(0) ), - false + boost::system::error_code{} ); ssl_socket->next_layer().close(); ssl_socket.reset();