/** @file @author from CrypoNote (see copyright below; Andrey N. Sabelnikov) @monero rfree @brief the connection templated-class for one peer connection */ // Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the Andrey N. Sabelnikov nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 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 <boost/foreach.hpp> #include <boost/uuid/random_generator.hpp> #include <boost/chrono.hpp> #include <boost/utility/value_init.hpp> #include <boost/asio/deadline_timer.hpp> #include <boost/date_time/posix_time/posix_time.hpp> // TODO #include <boost/thread/condition_variable.hpp> // TODO #include <boost/make_shared.hpp> #include <boost/thread.hpp> #include "warnings.h" #include "string_tools_lexical.h" #include "misc_language.h" #include <sstream> #include <iomanip> #include <algorithm> #include <functional> #include <random> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" #define AGGRESSIVE_TIMEOUT_THRESHOLD 120 // sockets #define NEW_CONNECTION_TIMEOUT_LOCAL 1200000 // 2 minutes #define NEW_CONNECTION_TIMEOUT_REMOTE 10000 // 10 seconds #define DEFAULT_TIMEOUT_MS_LOCAL 1800000 // 30 minutes #define DEFAULT_TIMEOUT_MS_REMOTE 300000 // 5 minutes #define TIMEOUT_EXTRA_MS_PER_BYTE 0.2 namespace epee { namespace net_utils { template<typename T> T& check_and_get(std::shared_ptr<T>& ptr) { CHECK_AND_ASSERT_THROW_MES(bool(ptr), "shared_state cannot be null"); return *ptr; } /************************************************************************/ /* */ /************************************************************************/ template<typename T> unsigned int connection<T>::host_count(int delta) { static std::mutex hosts_mutex; std::lock_guard<std::mutex> guard(hosts_mutex); static std::map<std::string, unsigned int> hosts; unsigned int &val = hosts[m_host]; if (delta > 0) MTRACE("New connection from host " << m_host << ": " << val); else if (delta < 0) MTRACE("Closed connection from host " << m_host << ": " << val); CHECK_AND_ASSERT_THROW_MES(delta >= 0 || val >= (unsigned)-delta, "Count would go negative"); CHECK_AND_ASSERT_THROW_MES(delta <= 0 || val <= std::numeric_limits<unsigned int>::max() - (unsigned)delta, "Count would wrap"); val += delta; return val; } template<typename T> typename connection<T>::duration_t connection<T>::get_default_timeout() { unsigned count{}; try { count = host_count(); } catch (...) {} const unsigned shift = ( connection_basic::get_state().sock_count > AGGRESSIVE_TIMEOUT_THRESHOLD ? std::min(std::max(count, 1u) - 1, 8u) : 0 ); return ( m_local ? std::chrono::milliseconds(DEFAULT_TIMEOUT_MS_LOCAL >> shift) : std::chrono::milliseconds(DEFAULT_TIMEOUT_MS_REMOTE >> shift) ); } template<typename T> typename connection<T>::duration_t connection<T>::get_timeout_from_bytes_read(size_t bytes) const { return std::chrono::duration_cast<connection<T>::duration_t>( std::chrono::duration<double, std::chrono::milliseconds::period>( bytes * TIMEOUT_EXTRA_MS_PER_BYTE ) ); } template<typename T> void connection<T>::state_status_check() { switch (m_state.status) { case status_t::RUNNING: interrupt(); break; case status_t::INTERRUPTED: on_interrupted(); break; case status_t::TERMINATING: on_terminating(); break; default: break; } } template<typename T> void connection<T>::start_timer(duration_t duration, bool add) { if (m_state.timers.general.wait_expire) { m_state.timers.general.cancel_expire = true; m_state.timers.general.reset_expire = true; ec_t ec; m_timers.general.expires_from_now( std::min( duration + (add ? m_timers.general.expires_from_now() : duration_t{}), get_default_timeout() ), ec ); } else { ec_t ec; m_timers.general.expires_from_now( std::min( duration + (add ? m_timers.general.expires_from_now() : duration_t{}), get_default_timeout() ), ec ); async_wait_timer(); } } template<typename T> void connection<T>::async_wait_timer() { if (m_state.timers.general.wait_expire) return; m_state.timers.general.wait_expire = true; auto self = connection<T>::shared_from_this(); m_timers.general.async_wait([this, self](const ec_t & ec){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.timers.general.wait_expire = false; if (m_state.timers.general.cancel_expire) { m_state.timers.general.cancel_expire = false; if (m_state.timers.general.reset_expire) { m_state.timers.general.reset_expire = false; async_wait_timer(); } else if (m_state.status == status_t::INTERRUPTED) on_interrupted(); else if (m_state.status == status_t::TERMINATING) on_terminating(); } else if (m_state.status == status_t::RUNNING) interrupt(); else if (m_state.status == status_t::INTERRUPTED) terminate(); }); } template<typename T> void connection<T>::cancel_timer() { if (!m_state.timers.general.wait_expire) return; m_state.timers.general.cancel_expire = true; m_state.timers.general.reset_expire = false; ec_t ec; m_timers.general.cancel(ec); } template<typename T> void connection<T>::start_handshake() { if (m_state.socket.wait_handshake) return; static_assert( epee::net_utils::get_ssl_magic_size() <= sizeof(m_state.data.read.buffer), "" ); auto self = connection<T>::shared_from_this(); if (!m_state.ssl.forced && !m_state.ssl.detected) { m_state.socket.wait_read = true; boost::asio::async_read( connection_basic::socket_.next_layer(), boost::asio::buffer( m_state.data.read.buffer.data(), m_state.data.read.buffer.size() ), boost::asio::transfer_exactly(epee::net_utils::get_ssl_magic_size()), m_strand.wrap( [this, self](const ec_t &ec, size_t bytes_transferred){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.wait_read = false; if (m_state.socket.cancel_read) { m_state.socket.cancel_read = false; state_status_check(); } else if (ec.value()) { terminate(); } else if ( !epee::net_utils::is_ssl( static_cast<const unsigned char *>( m_state.data.read.buffer.data() ), bytes_transferred ) ) { m_state.ssl.enabled = false; m_state.socket.handle_read = true; connection_basic::strand_.post( [this, self, bytes_transferred]{ bool success = m_handler.handle_recv( reinterpret_cast<char *>(m_state.data.read.buffer.data()), bytes_transferred ); std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.handle_read = false; if (m_state.status == status_t::INTERRUPTED) on_interrupted(); else if (m_state.status == status_t::TERMINATING) on_terminating(); else if (!success) interrupt(); else { start_read(); } } ); } else { m_state.ssl.detected = true; start_handshake(); } } ) ); return; } m_state.socket.wait_handshake = true; auto on_handshake = [this, self](const ec_t &ec, size_t bytes_transferred){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.wait_handshake = false; if (m_state.socket.cancel_handshake) { m_state.socket.cancel_handshake = false; state_status_check(); } else if (ec.value()) { ec_t ec; connection_basic::socket_.next_layer().shutdown( socket_t::shutdown_both, ec ); connection_basic::socket_.next_layer().close(ec); m_state.socket.connected = false; interrupt(); } else { m_state.ssl.handshaked = true; start_write(); start_read(); } }; const auto handshake = handshake_t::server; static_cast<shared_state&>( connection_basic::get_state() ).ssl_options().configure(connection_basic::socket_, handshake); m_strand.post( [this, self, on_handshake]{ connection_basic::socket_.async_handshake( handshake, boost::asio::buffer( m_state.data.read.buffer.data(), m_state.ssl.forced ? 0 : epee::net_utils::get_ssl_magic_size() ), m_strand.wrap(on_handshake) ); } ); } template<typename T> void connection<T>::start_read() { if (m_state.timers.throttle.in.wait_expire || m_state.socket.wait_read || m_state.socket.handle_read ) { return; } auto self = connection<T>::shared_from_this(); if (m_connection_type != e_connection_type_RPC) { auto calc_duration = []{ CRITICAL_REGION_LOCAL( network_throttle_manager_t::m_lock_get_global_throttle_in ); return std::chrono::duration_cast<connection<T>::duration_t>( std::chrono::duration<double, std::chrono::seconds::period>( std::min( network_throttle_manager_t::get_global_throttle_in( ).get_sleep_time_after_tick(1), 1.0 ) ) ); }; const auto duration = calc_duration(); if (duration > duration_t{}) { ec_t ec; m_timers.throttle.in.expires_from_now(duration, ec); m_state.timers.throttle.in.wait_expire = true; m_timers.throttle.in.async_wait([this, self](const ec_t &ec){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.timers.throttle.in.wait_expire = false; if (m_state.timers.throttle.in.cancel_expire) { m_state.timers.throttle.in.cancel_expire = false; state_status_check(); } else if (ec.value()) interrupt(); else start_read(); }); return; } } m_state.socket.wait_read = true; auto on_read = [this, self](const ec_t &ec, size_t bytes_transferred){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.wait_read = false; if (m_state.socket.cancel_read) { m_state.socket.cancel_read = false; state_status_check(); } else if (ec.value()) terminate(); else { { m_state.stat.in.throttle.handle_trafic_exact(bytes_transferred); const auto speed = m_state.stat.in.throttle.get_current_speed(); m_conn_context.m_current_speed_down = speed; m_conn_context.m_max_speed_down = std::max( m_conn_context.m_max_speed_down, speed ); { CRITICAL_REGION_LOCAL( network_throttle_manager_t::m_lock_get_global_throttle_in ); network_throttle_manager_t::get_global_throttle_in( ).handle_trafic_exact(bytes_transferred); } connection_basic::logger_handle_net_read(bytes_transferred); m_conn_context.m_last_recv = time(NULL); m_conn_context.m_recv_cnt += bytes_transferred; start_timer(get_timeout_from_bytes_read(bytes_transferred), true); } // Post handle_recv to a separate `strand_`, distinct from `m_strand` // which is listening for reads/writes. This avoids a circular dep. // handle_recv can queue many writes, and `m_strand` will process those // writes until the connection terminates without deadlocking waiting // for handle_recv. m_state.socket.handle_read = true; connection_basic::strand_.post( [this, self, bytes_transferred]{ bool success = m_handler.handle_recv( reinterpret_cast<char *>(m_state.data.read.buffer.data()), bytes_transferred ); std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.handle_read = false; if (m_state.status == status_t::INTERRUPTED) on_interrupted(); else if (m_state.status == status_t::TERMINATING) on_terminating(); else if (!success) interrupt(); else { start_read(); } } ); } }; if (!m_state.ssl.enabled) connection_basic::socket_.next_layer().async_read_some( boost::asio::buffer( m_state.data.read.buffer.data(), m_state.data.read.buffer.size() ), m_strand.wrap(on_read) ); else m_strand.post( [this, self, on_read]{ connection_basic::socket_.async_read_some( boost::asio::buffer( m_state.data.read.buffer.data(), m_state.data.read.buffer.size() ), m_strand.wrap(on_read) ); } ); } template<typename T> void connection<T>::start_write() { if (m_state.timers.throttle.out.wait_expire || m_state.socket.wait_write || m_state.data.write.queue.empty() || (m_state.ssl.enabled && !m_state.ssl.handshaked) ) { return; } auto self = connection<T>::shared_from_this(); if (m_connection_type != e_connection_type_RPC) { auto calc_duration = [this]{ CRITICAL_REGION_LOCAL( network_throttle_manager_t::m_lock_get_global_throttle_out ); return std::chrono::duration_cast<connection<T>::duration_t>( std::chrono::duration<double, std::chrono::seconds::period>( std::min( network_throttle_manager_t::get_global_throttle_out( ).get_sleep_time_after_tick( m_state.data.write.queue.back().size() ), 1.0 ) ) ); }; const auto duration = calc_duration(); if (duration > duration_t{}) { ec_t ec; m_timers.throttle.out.expires_from_now(duration, ec); m_state.timers.throttle.out.wait_expire = true; m_timers.throttle.out.async_wait([this, self](const ec_t &ec){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.timers.throttle.out.wait_expire = false; if (m_state.timers.throttle.out.cancel_expire) { m_state.timers.throttle.out.cancel_expire = false; state_status_check(); } else if (ec.value()) interrupt(); else start_write(); }); } } m_state.socket.wait_write = true; auto on_write = [this, self](const ec_t &ec, size_t bytes_transferred){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.wait_write = false; if (m_state.socket.cancel_write) { m_state.socket.cancel_write = false; m_state.data.write.queue.clear(); state_status_check(); } else if (ec.value()) { m_state.data.write.queue.clear(); interrupt(); } else { { m_state.stat.out.throttle.handle_trafic_exact(bytes_transferred); const auto speed = m_state.stat.out.throttle.get_current_speed(); m_conn_context.m_current_speed_up = speed; m_conn_context.m_max_speed_down = std::max( m_conn_context.m_max_speed_down, speed ); { CRITICAL_REGION_LOCAL( network_throttle_manager_t::m_lock_get_global_throttle_out ); network_throttle_manager_t::get_global_throttle_out( ).handle_trafic_exact(bytes_transferred); } connection_basic::logger_handle_net_write(bytes_transferred); m_conn_context.m_last_send = time(NULL); m_conn_context.m_send_cnt += bytes_transferred; start_timer(get_default_timeout(), true); } assert(bytes_transferred == m_state.data.write.queue.back().size()); m_state.data.write.queue.pop_back(); m_state.condition.notify_all(); start_write(); } }; if (!m_state.ssl.enabled) boost::asio::async_write( connection_basic::socket_.next_layer(), boost::asio::buffer( m_state.data.write.queue.back().data(), m_state.data.write.queue.back().size() ), m_strand.wrap(on_write) ); else m_strand.post( [this, self, on_write]{ boost::asio::async_write( connection_basic::socket_, boost::asio::buffer( m_state.data.write.queue.back().data(), m_state.data.write.queue.back().size() ), m_strand.wrap(on_write) ); } ); } template<typename T> void connection<T>::start_shutdown() { if (m_state.socket.wait_shutdown) return; auto self = connection<T>::shared_from_this(); m_state.socket.wait_shutdown = true; auto on_shutdown = [this, self](const ec_t &ec){ std::lock_guard<std::mutex> guard(m_state.lock); m_state.socket.wait_shutdown = false; if (m_state.socket.cancel_shutdown) { m_state.socket.cancel_shutdown = false; switch (m_state.status) { case status_t::RUNNING: interrupt(); break; case status_t::INTERRUPTED: terminate(); break; case status_t::TERMINATING: on_terminating(); break; default: break; } } else { terminate(); } }; m_strand.post( [this, self, on_shutdown]{ connection_basic::socket_.async_shutdown( m_strand.wrap(on_shutdown) ); } ); start_timer(get_default_timeout()); } template<typename T> void connection<T>::cancel_socket() { bool wait_socket = false; if (m_state.socket.wait_handshake) wait_socket = m_state.socket.cancel_handshake = true; if (m_state.timers.throttle.in.wait_expire) { m_state.timers.throttle.in.cancel_expire = true; ec_t ec; m_timers.throttle.in.cancel(ec); } if (m_state.socket.wait_read) wait_socket = m_state.socket.cancel_read = true; if (m_state.timers.throttle.out.wait_expire) { m_state.timers.throttle.out.cancel_expire = true; ec_t ec; m_timers.throttle.out.cancel(ec); } if (m_state.socket.wait_write) wait_socket = m_state.socket.cancel_write = true; if (m_state.socket.wait_shutdown) wait_socket = m_state.socket.cancel_shutdown = true; if (wait_socket) { ec_t ec; connection_basic::socket_.next_layer().cancel(ec); } } template<typename T> void connection<T>::cancel_handler() { if (m_state.protocol.released || m_state.protocol.wait_release) return; m_state.protocol.wait_release = true; m_state.lock.unlock(); m_handler.release_protocol(); m_state.lock.lock(); m_state.protocol.wait_release = false; m_state.protocol.released = true; if (m_state.status == status_t::INTERRUPTED) on_interrupted(); else if (m_state.status == status_t::TERMINATING) on_terminating(); } template<typename T> void connection<T>::interrupt() { if (m_state.status != status_t::RUNNING) return; m_state.status = status_t::INTERRUPTED; cancel_timer(); cancel_socket(); on_interrupted(); m_state.condition.notify_all(); cancel_handler(); } template<typename T> void connection<T>::on_interrupted() { assert(m_state.status == status_t::INTERRUPTED); if (m_state.timers.general.wait_expire) return; if (m_state.socket.wait_handshake) return; if (m_state.timers.throttle.in.wait_expire) return; if (m_state.socket.wait_read) return; if (m_state.socket.handle_read) return; if (m_state.timers.throttle.out.wait_expire) return; if (m_state.socket.wait_write) return; if (m_state.socket.wait_shutdown) return; if (m_state.protocol.wait_init) return; if (m_state.protocol.wait_callback) return; if (m_state.protocol.wait_release) return; if (m_state.socket.connected) { if (!m_state.ssl.enabled) { ec_t ec; connection_basic::socket_.next_layer().shutdown( socket_t::shutdown_both, ec ); connection_basic::socket_.next_layer().close(ec); m_state.socket.connected = false; m_state.status = status_t::WASTED; } else start_shutdown(); } else m_state.status = status_t::WASTED; } template<typename T> void connection<T>::terminate() { if (m_state.status != status_t::RUNNING && m_state.status != status_t::INTERRUPTED ) return; m_state.status = status_t::TERMINATING; cancel_timer(); cancel_socket(); on_terminating(); m_state.condition.notify_all(); cancel_handler(); } template<typename T> void connection<T>::on_terminating() { assert(m_state.status == status_t::TERMINATING); if (m_state.timers.general.wait_expire) return; if (m_state.socket.wait_handshake) return; if (m_state.timers.throttle.in.wait_expire) return; if (m_state.socket.wait_read) return; if (m_state.socket.handle_read) return; if (m_state.timers.throttle.out.wait_expire) return; if (m_state.socket.wait_write) return; if (m_state.socket.wait_shutdown) return; if (m_state.protocol.wait_init) return; if (m_state.protocol.wait_callback) return; if (m_state.protocol.wait_release) return; if (m_state.socket.connected) { ec_t ec; connection_basic::socket_.next_layer().shutdown( socket_t::shutdown_both, ec ); connection_basic::socket_.next_layer().close(ec); m_state.socket.connected = false; } m_state.status = status_t::WASTED; } template<typename T> bool connection<T>::send(epee::byte_slice message) { std::lock_guard<std::mutex> guard(m_state.lock); if (m_state.status != status_t::RUNNING || m_state.socket.wait_handshake) return false; // Wait for the write queue to fall below the max. If it doesn't after a // randomized delay, drop the connection. auto wait_consume = [this] { auto random_delay = []{ using engine = std::mt19937; std::random_device dev; std::seed_seq::result_type rand[ engine::state_size // Use complete bit space ]{}; std::generate_n(rand, engine::state_size, std::ref(dev)); std::seed_seq seed(rand, rand + engine::state_size); engine rng(seed); return std::chrono::milliseconds( std::uniform_int_distribution<>(5000, 6000)(rng) ); }; if (m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT) return true; m_state.data.write.wait_consume = true; bool success = m_state.condition.wait_for( m_state.lock, random_delay(), [this]{ return ( m_state.status != status_t::RUNNING || m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT ); } ); m_state.data.write.wait_consume = false; if (!success) { terminate(); return false; } else return m_state.status == status_t::RUNNING; }; auto wait_sender = [this] { m_state.condition.wait( m_state.lock, [this] { return ( m_state.status != status_t::RUNNING || !m_state.data.write.wait_consume ); } ); return m_state.status == status_t::RUNNING; }; if (!wait_sender()) return false; constexpr size_t CHUNK_SIZE = 32 * 1024; if (m_connection_type == e_connection_type_RPC || message.size() <= 2 * CHUNK_SIZE ) { if (!wait_consume()) return false; m_state.data.write.queue.emplace_front(std::move(message)); start_write(); } else { while (!message.empty()) { if (!wait_consume()) return false; m_state.data.write.queue.emplace_front( message.take_slice(CHUNK_SIZE) ); start_write(); } } m_state.condition.notify_all(); return true; } template<typename T> bool connection<T>::start_internal( bool is_income, bool is_multithreaded, boost::optional<network_address> real_remote ) { std::unique_lock<std::mutex> guard(m_state.lock); if (m_state.status != status_t::TERMINATED) return false; if (!real_remote) { ec_t ec; auto endpoint = connection_basic::socket_.next_layer().remote_endpoint( ec ); if (ec.value()) return false; real_remote = ( endpoint.address().is_v6() ? network_address{ ipv6_network_address{endpoint.address().to_v6(), endpoint.port()} } : network_address{ ipv4_network_address{ uint32_t{ boost::asio::detail::socket_ops::host_to_network_long( endpoint.address().to_v4().to_ulong() ) }, endpoint.port() } } ); } auto *filter = static_cast<shared_state&>( connection_basic::get_state() ).pfilter; if (filter && !filter->is_remote_host_allowed(*real_remote)) return false; ec_t ec; #if !defined(_WIN32) || !defined(__i686) connection_basic::socket_.next_layer().set_option( boost::asio::detail::socket_option::integer<IPPROTO_IP, IP_TOS>{ connection_basic::get_tos_flag() }, ec ); if (ec.value()) return false; #endif connection_basic::socket_.next_layer().set_option( boost::asio::ip::tcp::no_delay{false}, ec ); if (ec.value()) return false; connection_basic::m_is_multithreaded = is_multithreaded; m_conn_context.set_details( boost::uuids::random_generator()(), *real_remote, is_income, connection_basic::m_ssl_support == ssl_support_t::e_ssl_support_enabled ); m_host = real_remote->host_str(); try { host_count(1); } catch(...) { /* ignore */ } m_local = real_remote->is_loopback() || real_remote->is_local(); m_state.ssl.enabled = ( connection_basic::m_ssl_support != ssl_support_t::e_ssl_support_disabled ); m_state.ssl.forced = ( connection_basic::m_ssl_support == ssl_support_t::e_ssl_support_enabled ); m_state.socket.connected = true; m_state.status = status_t::RUNNING; start_timer( std::chrono::milliseconds( m_local ? NEW_CONNECTION_TIMEOUT_LOCAL : NEW_CONNECTION_TIMEOUT_REMOTE ) ); m_state.protocol.wait_init = true; guard.unlock(); m_handler.after_init_connection(); guard.lock(); m_state.protocol.wait_init = false; m_state.protocol.initialized = true; if (m_state.status == status_t::INTERRUPTED) on_interrupted(); else if (m_state.status == status_t::TERMINATING) on_terminating(); else if (!is_income || !m_state.ssl.enabled) start_read(); else start_handshake(); return true; } template<typename T> connection<T>::connection( io_context_t &io_context, std::shared_ptr<shared_state> shared_state, t_connection_type connection_type, ssl_support_t ssl_support ): connection( std::move(socket_t{io_context}), std::move(shared_state), connection_type, ssl_support ) { } template<typename T> connection<T>::connection( socket_t &&socket, std::shared_ptr<shared_state> shared_state, t_connection_type connection_type, ssl_support_t ssl_support ): connection_basic(std::move(socket), shared_state, ssl_support), m_handler(this, *shared_state, m_conn_context), m_connection_type(connection_type), m_io_context{GET_IO_SERVICE(connection_basic::socket_)}, m_strand{m_io_context}, m_timers{m_io_context} { } template<typename T> connection<T>::~connection() noexcept(false) { std::lock_guard<std::mutex> guard(m_state.lock); assert(m_state.status == status_t::TERMINATED || m_state.status == status_t::WASTED || m_io_context.stopped() ); if (m_state.status != status_t::WASTED) return; try { host_count(-1); } catch (...) { /* ignore */ } } template<typename T> bool connection<T>::start( bool is_income, bool is_multithreaded ) { return start_internal(is_income, is_multithreaded, {}); } template<typename T> bool connection<T>::start( bool is_income, bool is_multithreaded, network_address real_remote ) { return start_internal(is_income, is_multithreaded, real_remote); } template<typename T> void connection<T>::save_dbg_log() { std::lock_guard<std::mutex> guard(m_state.lock); std::string address; std::string port; ec_t ec; auto endpoint = connection_basic::socket().remote_endpoint(ec); if (ec.value()) { address = "<not connected>"; port = "<not connected>"; } else { address = endpoint.address().to_string(); port = std::to_string(endpoint.port()); } MDEBUG( " connection type " << std::to_string(m_connection_type) << " " << connection_basic::socket().local_endpoint().address().to_string() << ":" << connection_basic::socket().local_endpoint().port() << " <--> " << m_conn_context.m_remote_address.str() << " (via " << address << ":" << port << ")" ); } template<typename T> bool connection<T>::speed_limit_is_enabled() const { return m_connection_type != e_connection_type_RPC; } template<typename T> bool connection<T>::cancel() { return close(); } template<typename T> bool connection<T>::do_send(byte_slice message) { return send(std::move(message)); } template<typename T> bool connection<T>::send_done() { return true; } template<typename T> bool connection<T>::close() { std::lock_guard<std::mutex> guard(m_state.lock); if (m_state.status != status_t::RUNNING) return false; terminate(); return true; } template<typename T> bool connection<T>::call_run_once_service_io() { if(connection_basic::m_is_multithreaded) { if (!m_io_context.poll_one()) misc_utils::sleep_no_w(1); } else { if (!m_io_context.run_one()) return false; } return true; } template<typename T> bool connection<T>::request_callback() { std::lock_guard<std::mutex> guard(m_state.lock); if (m_state.status != status_t::RUNNING) return false; auto self = connection<T>::shared_from_this(); ++m_state.protocol.wait_callback; connection_basic::strand_.post([this, self]{ m_handler.handle_qued_callback(); std::lock_guard<std::mutex> guard(m_state.lock); --m_state.protocol.wait_callback; if (m_state.status == status_t::INTERRUPTED) on_interrupted(); else if (m_state.status == status_t::TERMINATING) on_terminating(); }); return true; } template<typename T> typename connection<T>::io_context_t &connection<T>::get_io_service() { return m_io_context; } template<typename T> bool connection<T>::add_ref() { try { auto self = connection<T>::shared_from_this(); std::lock_guard<std::mutex> guard(m_state.lock); this->self = std::move(self); ++m_state.protocol.reference_counter; return true; } catch (boost::bad_weak_ptr &exception) { return false; } } template<typename T> bool connection<T>::release() { connection_ptr self; std::lock_guard<std::mutex> guard(m_state.lock); if (!(--m_state.protocol.reference_counter)) self = std::move(this->self); return true; } template<typename T> void connection<T>::setRpcStation() { std::lock_guard<std::mutex> guard(m_state.lock); m_connection_type = e_connection_type_RPC; } template<class t_protocol_handler> boosted_tcp_server<t_protocol_handler>::boosted_tcp_server( t_connection_type connection_type ) : m_state(std::make_shared<typename connection<t_protocol_handler>::shared_state>()), m_io_service_local_instance(new worker()), io_service_(m_io_service_local_instance->io_service), acceptor_(io_service_), acceptor_ipv6(io_service_), default_remote(), m_stop_signal_sent(false), m_port(0), m_threads_count(0), m_thread_index(0), m_connection_type( connection_type ), new_connection_(), new_connection_ipv6() { create_server_type_map(); m_thread_name_prefix = "NET"; } template<class t_protocol_handler> boosted_tcp_server<t_protocol_handler>::boosted_tcp_server(boost::asio::io_service& extarnal_io_service, t_connection_type connection_type) : m_state(std::make_shared<typename connection<t_protocol_handler>::shared_state>()), io_service_(extarnal_io_service), acceptor_(io_service_), acceptor_ipv6(io_service_), default_remote(), m_stop_signal_sent(false), m_port(0), m_threads_count(0), m_thread_index(0), m_connection_type(connection_type), new_connection_(), new_connection_ipv6() { create_server_type_map(); m_thread_name_prefix = "NET"; } //--------------------------------------------------------------------------------- template<class t_protocol_handler> boosted_tcp_server<t_protocol_handler>::~boosted_tcp_server() { this->send_stop_signal(); timed_wait_server_stop(10000); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::create_server_type_map() { server_type_map["NET"] = e_connection_type_NET; server_type_map["RPC"] = e_connection_type_RPC; server_type_map["P2P"] = e_connection_type_P2P; } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string& address, uint32_t port_ipv6, const std::string& address_ipv6, bool use_ipv6, bool require_ipv4, ssl_options_t ssl_options) { TRY_ENTRY(); m_stop_signal_sent = false; m_port = port; m_port_ipv6 = port_ipv6; m_address = address; m_address_ipv6 = address_ipv6; m_use_ipv6 = use_ipv6; m_require_ipv4 = require_ipv4; if (ssl_options) m_state->configure_ssl(std::move(ssl_options)); std::string ipv4_failed = ""; std::string ipv6_failed = ""; try { boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast<std::string>(port), boost::asio::ip::tcp::resolver::query::canonical_name); boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); acceptor_.open(endpoint.protocol()); #if !defined(_WIN32) acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); #endif acceptor_.bind(endpoint); acceptor_.listen(); boost::asio::ip::tcp::endpoint binded_endpoint = acceptor_.local_endpoint(); m_port = binded_endpoint.port(); MDEBUG("start accept (IPv4)"); new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, m_state->ssl_options().support)); acceptor_.async_accept(new_connection_->socket(), boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept_ipv4, this, boost::asio::placeholders::error)); } catch (const std::exception &e) { ipv4_failed = e.what(); } if (ipv4_failed != "") { MERROR("Failed to bind IPv4: " << ipv4_failed); if (require_ipv4) { throw std::runtime_error("Failed to bind IPv4 (set to required)"); } } if (use_ipv6) { try { if (port_ipv6 == 0) port_ipv6 = port; // default arg means bind to same port as ipv4 boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(address_ipv6, boost::lexical_cast<std::string>(port_ipv6), boost::asio::ip::tcp::resolver::query::canonical_name); boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); acceptor_ipv6.open(endpoint.protocol()); #if !defined(_WIN32) acceptor_ipv6.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); #endif acceptor_ipv6.set_option(boost::asio::ip::v6_only(true)); acceptor_ipv6.bind(endpoint); acceptor_ipv6.listen(); boost::asio::ip::tcp::endpoint binded_endpoint = acceptor_ipv6.local_endpoint(); m_port_ipv6 = binded_endpoint.port(); MDEBUG("start accept (IPv6)"); new_connection_ipv6.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, m_state->ssl_options().support)); acceptor_ipv6.async_accept(new_connection_ipv6->socket(), boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept_ipv6, this, boost::asio::placeholders::error)); } catch (const std::exception &e) { ipv6_failed = e.what(); } } if (use_ipv6 && ipv6_failed != "") { MERROR("Failed to bind IPv6: " << ipv6_failed); if (ipv4_failed != "") { throw std::runtime_error("Failed to bind IPv4 and IPv6"); } } return true; } catch (const std::exception &e) { MFATAL("Error starting server: " << e.what()); return false; } catch (...) { MFATAL("Error starting server"); return false; } } //----------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, const std::string port_ipv6, const std::string address_ipv6, bool use_ipv6, bool require_ipv4, ssl_options_t ssl_options) { uint32_t p = 0; uint32_t p_ipv6 = 0; if (port.size() && !string_tools::get_xtype_from_string(p, port)) { MERROR("Failed to convert port no = " << port); return false; } if (port_ipv6.size() && !string_tools::get_xtype_from_string(p_ipv6, port_ipv6)) { MERROR("Failed to convert port no = " << port_ipv6); return false; } return this->init_server(p, address, p_ipv6, address_ipv6, use_ipv6, require_ipv4, std::move(ssl_options)); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::worker_thread() { TRY_ENTRY(); const uint32_t local_thr_index = m_thread_index++; // atomically increment, getting value before increment std::string thread_name = std::string("[") + m_thread_name_prefix; thread_name += boost::to_string(local_thr_index) + "]"; MLOG_SET_THREAD_NAME(thread_name); // _fact("Thread name: " << m_thread_name_prefix); while(!m_stop_signal_sent) { try { io_service_.run(); return true; } catch(const std::exception& ex) { _erro("Exception at server worker thread, what=" << ex.what()); } catch(...) { _erro("Exception at server worker thread, unknown execption"); } } //_info("Worker thread finished"); return true; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::worker_thread", false); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::set_threads_prefix(const std::string& prefix_name) { m_thread_name_prefix = prefix_name; auto it = server_type_map.find(m_thread_name_prefix); if (it==server_type_map.end()) throw std::runtime_error("Unknown prefix/server type:" + std::string(prefix_name)); auto connection_type = it->second; // the value of type MINFO("Set server type to: " << connection_type << " from name: " << m_thread_name_prefix << ", prefix_name = " << prefix_name); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::set_connection_filter(i_connection_filter* pfilter) { assert(m_state != nullptr); // always set in constructor m_state->pfilter = pfilter; } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::run_server(size_t threads_count, bool wait, const boost::thread::attributes& attrs) { TRY_ENTRY(); m_threads_count = threads_count; m_main_thread_id = boost::this_thread::get_id(); MLOG_SET_THREAD_NAME("[SRV_MAIN]"); while(!m_stop_signal_sent) { // Create a pool of threads to run all of the io_services. CRITICAL_REGION_BEGIN(m_threads_lock); for (std::size_t i = 0; i < threads_count; ++i) { boost::shared_ptr<boost::thread> thread(new boost::thread( attrs, boost::bind(&boosted_tcp_server<t_protocol_handler>::worker_thread, this))); _note("Run server thread name: " << m_thread_name_prefix); m_threads.push_back(thread); } CRITICAL_REGION_END(); // Wait for all threads in the pool to exit. if (wait) { _fact("JOINING all threads"); for (std::size_t i = 0; i < m_threads.size(); ++i) { m_threads[i]->join(); } _fact("JOINING all threads - almost"); m_threads.clear(); _fact("JOINING all threads - DONE"); } else { _dbg1("Reiniting OK."); return true; } if(wait && !m_stop_signal_sent) { //some problems with the listening socket ?.. _dbg1("Net service stopped without stop request, restarting..."); if(!this->init_server(m_port, m_address, m_port_ipv6, m_address_ipv6, m_use_ipv6, m_require_ipv4)) { _dbg1("Reiniting service failed, exit."); return false; }else { _dbg1("Reiniting OK."); } } } return true; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::run_server", false); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::is_thread_worker() { TRY_ENTRY(); CRITICAL_REGION_LOCAL(m_threads_lock); BOOST_FOREACH(boost::shared_ptr<boost::thread>& thp, m_threads) { if(thp->get_id() == boost::this_thread::get_id()) return true; } if(m_threads_count == 1 && boost::this_thread::get_id() == m_main_thread_id) return true; return false; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::is_thread_worker", false); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::timed_wait_server_stop(uint64_t wait_mseconds) { TRY_ENTRY(); boost::chrono::milliseconds ms(wait_mseconds); for (std::size_t i = 0; i < m_threads.size(); ++i) { if(m_threads[i]->joinable() && !m_threads[i]->try_join_for(ms)) { _dbg1("Interrupting thread " << m_threads[i]->native_handle()); m_threads[i]->interrupt(); } } return true; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::timed_wait_server_stop", false); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::send_stop_signal() { m_stop_signal_sent = true; typename connection<t_protocol_handler>::shared_state *state = static_cast<typename connection<t_protocol_handler>::shared_state*>(m_state.get()); state->stop_signal_sent = true; TRY_ENTRY(); connections_mutex.lock(); for (auto &c: connections_) { c->cancel(); } connections_.clear(); connections_mutex.unlock(); io_service_.stop(); CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::send_stop_signal()", void()); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::handle_accept_ipv4(const boost::system::error_code& e) { this->handle_accept(e, false); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::handle_accept_ipv6(const boost::system::error_code& e) { this->handle_accept(e, true); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> void boosted_tcp_server<t_protocol_handler>::handle_accept(const boost::system::error_code& e, bool ipv6) { MDEBUG("handle_accept"); boost::asio::ip::tcp::acceptor* current_acceptor = &acceptor_; connection_ptr* current_new_connection = &new_connection_; auto accept_function_pointer = &boosted_tcp_server<t_protocol_handler>::handle_accept_ipv4; if (ipv6) { current_acceptor = &acceptor_ipv6; current_new_connection = &new_connection_ipv6; accept_function_pointer = &boosted_tcp_server<t_protocol_handler>::handle_accept_ipv6; } try { if (!e) { if (m_connection_type == e_connection_type_RPC) { const char *ssl_message = "unknown"; switch ((*current_new_connection)->get_ssl_support()) { case epee::net_utils::ssl_support_t::e_ssl_support_disabled: ssl_message = "disabled"; break; case epee::net_utils::ssl_support_t::e_ssl_support_enabled: ssl_message = "enabled"; break; case epee::net_utils::ssl_support_t::e_ssl_support_autodetect: ssl_message = "autodetection"; break; } MDEBUG("New server for RPC connections, SSL " << ssl_message); (*current_new_connection)->setRpcStation(); // hopefully this is not needed actually } connection_ptr conn(std::move((*current_new_connection))); (*current_new_connection).reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, conn->get_ssl_support())); current_acceptor->async_accept((*current_new_connection)->socket(), boost::bind(accept_function_pointer, this, boost::asio::placeholders::error)); boost::asio::socket_base::keep_alive opt(true); conn->socket().set_option(opt); bool res; if (default_remote.get_type_id() == net_utils::address_type::invalid) res = conn->start(true, 1 < m_threads_count); else res = conn->start(true, 1 < m_threads_count, default_remote); if (!res) { conn->cancel(); return; } conn->save_dbg_log(); return; } else { MERROR("Error in boosted_tcp_server<t_protocol_handler>::handle_accept: " << e); } } catch (const std::exception &e) { MERROR("Exception in boosted_tcp_server<t_protocol_handler>::handle_accept: " << e.what()); } // error path, if e or exception assert(m_state != nullptr); // always set in constructor _erro("Some problems at accept: " << e.message() << ", connections_count = " << m_state->sock_count); misc_utils::sleep_no_w(100); (*current_new_connection).reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, (*current_new_connection)->get_ssl_support())); current_acceptor->async_accept((*current_new_connection)->socket(), boost::bind(accept_function_pointer, this, boost::asio::placeholders::error)); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::add_connection(t_connection_context& out, boost::asio::ip::tcp::socket&& sock, network_address real_remote, epee::net_utils::ssl_support_t ssl_support) { if(std::addressof(get_io_service()) == std::addressof(GET_IO_SERVICE(sock))) { connection_ptr conn(new connection<t_protocol_handler>(std::move(sock), m_state, m_connection_type, ssl_support)); if(conn->start(false, 1 < m_threads_count, std::move(real_remote))) { conn->get_context(out); conn->save_dbg_log(); return true; } } else { MWARNING(out << " was not added, socket/io_service mismatch"); } return false; } //--------------------------------------------------------------------------------- template<class t_protocol_handler> typename boosted_tcp_server<t_protocol_handler>::try_connect_result_t boosted_tcp_server<t_protocol_handler>::try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_support_t ssl_support) { TRY_ENTRY(); sock_.open(remote_endpoint.protocol()); if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" ) { boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(bind_ip.c_str()), 0); boost::system::error_code ec; sock_.bind(local_endpoint, ec); if (ec) { MERROR("Error binding to " << bind_ip << ": " << ec.message()); if (sock_.is_open()) sock_.close(); return CONNECT_FAILURE; } } /* NOTICE: be careful to make sync connection from event handler: in case if all threads suddenly do sync connect, there will be no thread to dispatch events from io service. */ boost::system::error_code ec = boost::asio::error::would_block; //have another free thread(s), work in wait mode, without event handling struct local_async_context { boost::system::error_code ec; boost::mutex connect_mut; boost::condition_variable cond; }; boost::shared_ptr<local_async_context> local_shared_context(new local_async_context()); local_shared_context->ec = boost::asio::error::would_block; boost::unique_lock<boost::mutex> lock(local_shared_context->connect_mut); auto connect_callback = [](boost::system::error_code ec_, boost::shared_ptr<local_async_context> shared_context) { shared_context->connect_mut.lock(); shared_context->ec = ec_; shared_context->cond.notify_one(); shared_context->connect_mut.unlock(); }; sock_.async_connect(remote_endpoint, std::bind<void>(connect_callback, std::placeholders::_1, local_shared_context)); while(local_shared_context->ec == boost::asio::error::would_block) { bool r = local_shared_context->cond.timed_wait(lock, boost::get_system_time() + boost::posix_time::milliseconds(conn_timeout)); if (m_stop_signal_sent) { if (sock_.is_open()) sock_.close(); return CONNECT_FAILURE; } if(local_shared_context->ec == boost::asio::error::would_block && !r) { //timeout sock_.close(); _dbg3("Failed to connect to " << adr << ":" << port << ", because of timeout (" << conn_timeout << ")"); return CONNECT_FAILURE; } } ec = local_shared_context->ec; if (ec || !sock_.is_open()) { _dbg3("Some problems at connect, message: " << ec.message()); if (sock_.is_open()) sock_.close(); return CONNECT_FAILURE; } _dbg3("Connected success to " << adr << ':' << port); const ssl_support_t ssl_support = new_connection_l->get_ssl_support(); if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { // Handshake MDEBUG("Handshaking SSL..."); if (!new_connection_l->handshake(boost::asio::ssl::stream_base::client)) { if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { boost::system::error_code ignored_ec; sock_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); sock_.close(); return CONNECT_NO_SSL; } MERROR("SSL handshake failed"); if (sock_.is_open()) sock_.close(); return CONNECT_FAILURE; } } return CONNECT_SUCCESS; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::try_connect", CONNECT_FAILURE); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::connect(const std::string& adr, const std::string& port, uint32_t conn_timeout, t_connection_context& conn_context, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support) { TRY_ENTRY(); connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) ); connections_mutex.lock(); connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); bool try_ipv6 = false; boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); boost::system::error_code resolve_error; boost::asio::ip::tcp::resolver::iterator iterator; try { //resolving ipv4 address as ipv6 throws, catch here and move on iterator = resolver.resolve(query, resolve_error); } catch (const boost::system::system_error& e) { if (!m_use_ipv6 || (resolve_error != boost::asio::error::host_not_found && resolve_error != boost::asio::error::host_not_found_try_again)) { throw; } try_ipv6 = true; } catch (...) { throw; } std::string bind_ip_to_use; boost::asio::ip::tcp::resolver::iterator end; if(iterator == end) { if (!m_use_ipv6) { _erro("Failed to resolve " << adr); return false; } else { try_ipv6 = true; MINFO("Resolving address as IPv4 failed, trying IPv6"); } } else { bind_ip_to_use = bind_ip; } if (try_ipv6) { boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); iterator = resolver.resolve(query6, resolve_error); if(iterator == end) { _erro("Failed to resolve " << adr); return false; } else { if (bind_ip == "0.0.0.0") { bind_ip_to_use = "::"; } else { bind_ip_to_use = ""; } } } MDEBUG("Trying to connect to " << adr << ":" << port << ", bind_ip = " << bind_ip_to_use); //boost::asio::ip::tcp::endpoint remote_endpoint(boost::asio::ip::address::from_string(addr.c_str()), port); boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); auto try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip_to_use, conn_timeout, ssl_support); if (try_connect_result == CONNECT_FAILURE) return false; if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect && try_connect_result == CONNECT_NO_SSL) { // we connected, but could not connect with SSL, try without MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL"); new_connection_l->disable_ssl(); try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip_to_use, conn_timeout, epee::net_utils::ssl_support_t::e_ssl_support_disabled); if (try_connect_result != CONNECT_SUCCESS) return false; } // start adds the connection to the config object's list, so we don't need to have it locally anymore connections_mutex.lock(); connections_.erase(new_connection_l); connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { new_connection_l->get_context(conn_context); //new_connection_l.reset(new connection<t_protocol_handler>(io_service_, m_config, m_sock_count, m_pfilter)); } else { assert(m_state != nullptr); // always set in constructor _erro("[sock " << new_connection_l->socket().native_handle() << "] Failed to start connection, connections_count = " << m_state->sock_count); } new_connection_l->save_dbg_log(); return r; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::connect", false); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> template<class t_callback> bool boosted_tcp_server<t_protocol_handler>::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, const t_callback &cb, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support) { TRY_ENTRY(); connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) ); connections_mutex.lock(); connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); bool try_ipv6 = false; boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); boost::system::error_code resolve_error; boost::asio::ip::tcp::resolver::iterator iterator; try { //resolving ipv4 address as ipv6 throws, catch here and move on iterator = resolver.resolve(query, resolve_error); } catch (const boost::system::system_error& e) { if (!m_use_ipv6 || (resolve_error != boost::asio::error::host_not_found && resolve_error != boost::asio::error::host_not_found_try_again)) { throw; } try_ipv6 = true; } catch (...) { throw; } boost::asio::ip::tcp::resolver::iterator end; if(iterator == end) { if (!try_ipv6) { _erro("Failed to resolve " << adr); return false; } else { MINFO("Resolving address as IPv4 failed, trying IPv6"); } } if (try_ipv6) { boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); iterator = resolver.resolve(query6, resolve_error); if(iterator == end) { _erro("Failed to resolve " << adr); return false; } } boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); sock_.open(remote_endpoint.protocol()); if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" ) { boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(bind_ip.c_str()), 0); boost::system::error_code ec; sock_.bind(local_endpoint, ec); if (ec) { MERROR("Error binding to " << bind_ip << ": " << ec.message()); if (sock_.is_open()) sock_.close(); return false; } } boost::shared_ptr<boost::asio::deadline_timer> sh_deadline(new boost::asio::deadline_timer(io_service_)); //start deadline sh_deadline->expires_from_now(boost::posix_time::milliseconds(conn_timeout)); sh_deadline->async_wait([=](const boost::system::error_code& error) { if(error != boost::asio::error::operation_aborted) { _dbg3("Failed to connect to " << adr << ':' << port << ", because of timeout (" << conn_timeout << ")"); new_connection_l->socket().close(); } }); //start async connect sock_.async_connect(remote_endpoint, [=](const boost::system::error_code& ec_) { t_connection_context conn_context = AUTO_VAL_INIT(conn_context); boost::system::error_code ignored_ec; boost::asio::ip::tcp::socket::endpoint_type lep = new_connection_l->socket().local_endpoint(ignored_ec); if(!ec_) {//success if(!sh_deadline->cancel()) { cb(conn_context, boost::asio::error::operation_aborted);//this mean that deadline timer already queued callback with cancel operation, rare situation }else { _dbg3("[sock " << new_connection_l->socket().native_handle() << "] Connected success to " << adr << ':' << port << " from " << lep.address().to_string() << ':' << lep.port()); // start adds the connection to the config object's list, so we don't need to have it locally anymore connections_mutex.lock(); connections_.erase(new_connection_l); connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { new_connection_l->get_context(conn_context); cb(conn_context, ec_); } else { _dbg3("[sock " << new_connection_l->socket().native_handle() << "] Failed to start connection to " << adr << ':' << port); cb(conn_context, boost::asio::error::fault); } } }else { _dbg3("[sock " << new_connection_l->socket().native_handle() << "] Failed to connect to " << adr << ':' << port << " from " << lep.address().to_string() << ':' << lep.port() << ": " << ec_.message() << ':' << ec_.value()); cb(conn_context, ec_); } }); return true; CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::connect_async", false); } } // namespace } // namespace