// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. 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. // // 3. Neither the name of the copyright holder 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 HOLDER OR CONTRIBUTORS 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. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers // IP blocking adapted from Boolberry #pragma once #include #include #include #include #include "version.h" #include "string_tools.h" #include "common/util.h" #include "common/dns_utils.h" #include "net/net_helper.h" #include "math_helper.h" #include "p2p_protocol_defs.h" #include "net_peerlist_boost_serialization.h" #include "net/local_ip.h" #include "crypto/crypto.h" #include "storages/levin_abstract_invoke2.h" // We have to look for miniupnpc headers in different places, dependent on if its compiled or external #ifdef UPNP_STATIC #include #include #include #else #include "miniupnpc.h" #include "upnpcommands.h" #include "upnperrors.h" #endif #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.p2p" #define NET_MAKE_IP(b1,b2,b3,b4) ((LPARAM)(((DWORD)(b1)<<24)+((DWORD)(b2)<<16)+((DWORD)(b3)<<8)+((DWORD)(b4)))) #define MIN_WANTED_SEED_NODES 12 namespace nodetool { namespace { const int64_t default_limit_up = 2048; const int64_t default_limit_down = 8192; const command_line::arg_descriptor arg_p2p_bind_ip = {"p2p-bind-ip", "Interface for p2p network protocol", "0.0.0.0"}; const command_line::arg_descriptor arg_p2p_bind_port = { "p2p-bind-port" , "Port for p2p network protocol" , std::to_string(config::P2P_DEFAULT_PORT) }; const command_line::arg_descriptor arg_testnet_p2p_bind_port = { "testnet-p2p-bind-port" , "Port for testnet p2p network protocol" , std::to_string(config::testnet::P2P_DEFAULT_PORT) }; const command_line::arg_descriptor arg_p2p_external_port = {"p2p-external-port", "External port for p2p network protocol (if port forwarding used with NAT)", 0}; const command_line::arg_descriptor arg_p2p_allow_local_ip = {"allow-local-ip", "Allow local ip add to peer list, mostly in debug purposes"}; const command_line::arg_descriptor > arg_p2p_add_peer = {"add-peer", "Manually add peer to local peerlist"}; const command_line::arg_descriptor > arg_p2p_add_priority_node = {"add-priority-node", "Specify list of peers to connect to and attempt to keep the connection open"}; const command_line::arg_descriptor > arg_p2p_add_exclusive_node = {"add-exclusive-node", "Specify list of peers to connect to only." " If this option is given the options add-priority-node and seed-node are ignored"}; const command_line::arg_descriptor > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"}; const command_line::arg_descriptor arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true}; const command_line::arg_descriptor arg_no_igd = {"no-igd", "Disable UPnP port mapping"}; const command_line::arg_descriptor arg_offline = {"offline", "Do not listen for peers, nor connect to any"}; const command_line::arg_descriptor arg_out_peers = {"out-peers", "set max number of out peers", -1}; const command_line::arg_descriptor arg_tos_flag = {"tos-flag", "set TOS flag", -1}; const command_line::arg_descriptor arg_limit_rate_up = {"limit-rate-up", "set limit-rate-up [kB/s]", -1}; const command_line::arg_descriptor arg_limit_rate_down = {"limit-rate-down", "set limit-rate-down [kB/s]", -1}; const command_line::arg_descriptor arg_limit_rate = {"limit-rate", "set limit-rate [kB/s]", -1}; const command_line::arg_descriptor arg_save_graph = {"save-graph", "Save data for dr monero", false}; } //----------------------------------------------------------------------------------- template void node_server::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_p2p_bind_ip); command_line::add_arg(desc, arg_p2p_bind_port, false); command_line::add_arg(desc, arg_testnet_p2p_bind_port, false); command_line::add_arg(desc, arg_p2p_external_port); command_line::add_arg(desc, arg_p2p_allow_local_ip); command_line::add_arg(desc, arg_p2p_add_peer); command_line::add_arg(desc, arg_p2p_add_priority_node); command_line::add_arg(desc, arg_p2p_add_exclusive_node); command_line::add_arg(desc, arg_p2p_seed_node); command_line::add_arg(desc, arg_p2p_hide_my_port); command_line::add_arg(desc, arg_no_igd); command_line::add_arg(desc, arg_offline); command_line::add_arg(desc, arg_out_peers); command_line::add_arg(desc, arg_tos_flag); command_line::add_arg(desc, arg_limit_rate_up); command_line::add_arg(desc, arg_limit_rate_down); command_line::add_arg(desc, arg_limit_rate); command_line::add_arg(desc, arg_save_graph); } //----------------------------------------------------------------------------------- template bool node_server::init_config() { // TRY_ENTRY(); std::string state_file_path = m_config_folder + "/" + P2P_NET_DATA_FILENAME; std::ifstream p2p_data; p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::in); if(!p2p_data.fail()) { try { // first try reading in portable mode boost::archive::portable_binary_iarchive a(p2p_data); a >> *this; } catch (...) { // if failed, try reading in unportable mode boost::filesystem::copy_file(state_file_path, state_file_path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); p2p_data.close(); p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::in); if(!p2p_data.fail()) { try { boost::archive::binary_iarchive a(p2p_data); a >> *this; } catch (const std::exception &e) { MWARNING("Failed to load p2p config file, falling back to default config"); m_peerlist = peerlist_manager(); // it was probably half clobbered by the failed load make_default_config(); } } else { make_default_config(); } } }else { make_default_config(); } // always recreate a new peer id make_default_peer_id(); //at this moment we have hardcoded config m_config.m_net_config.handshake_interval = P2P_DEFAULT_HANDSHAKE_INTERVAL; m_config.m_net_config.packet_max_size = P2P_DEFAULT_PACKET_MAX_SIZE; //20 MB limit m_config.m_net_config.config_id = 0; // initial config m_config.m_net_config.connection_timeout = P2P_DEFAULT_CONNECTION_TIMEOUT; m_config.m_net_config.ping_connection_timeout = P2P_DEFAULT_PING_CONNECTION_TIMEOUT; m_config.m_net_config.send_peerlist_sz = P2P_DEFAULT_PEERS_IN_HANDSHAKE; m_config.m_support_flags = P2P_SUPPORT_FLAGS; m_first_connection_maker_call = true; CATCH_ENTRY_L0("node_server::init_config", false); return true; } //----------------------------------------------------------------------------------- template void node_server::for_each_connection(std::function f) { m_net_server.get_config_object().foreach_connection([&](p2p_connection_context& cntx){ return f(cntx, cntx.peer_id, cntx.support_flags); }); } //----------------------------------------------------------------------------------- template bool node_server::for_connection(const boost::uuids::uuid &connection_id, std::function f) { return m_net_server.get_config_object().for_connection(connection_id, [&](p2p_connection_context& cntx){ return f(cntx, cntx.peer_id, cntx.support_flags); }); } //----------------------------------------------------------------------------------- template bool node_server::is_remote_host_allowed(const epee::net_utils::network_address &address) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); auto it = m_blocked_hosts.find(address.host_str()); if(it == m_blocked_hosts.end()) return true; if(time(nullptr) >= it->second) { m_blocked_hosts.erase(it); MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); return true; } return false; } //----------------------------------------------------------------------------------- template bool node_server::make_default_peer_id() { m_config.m_peer_id = crypto::rand(); return true; } //----------------------------------------------------------------------------------- template bool node_server::make_default_config() { return make_default_peer_id(); } //----------------------------------------------------------------------------------- template bool node_server::block_host(const epee::net_utils::network_address &addr, time_t seconds) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); m_blocked_hosts[addr.host_str()] = time(nullptr) + seconds; // drop any connection to that IP std::list conns; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if (cntxt.m_remote_address.is_same_host(addr)) { conns.push_back(cntxt.m_connection_id); } return true; }); for (const auto &c: conns) m_net_server.get_config_object().close(c); MCLOG_CYAN(el::Level::Info, "global", "Host " << addr.host_str() << " blocked."); return true; } //----------------------------------------------------------------------------------- template bool node_server::unblock_host(const epee::net_utils::network_address &address) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); auto i = m_blocked_hosts.find(address.host_str()); if (i == m_blocked_hosts.end()) return false; m_blocked_hosts.erase(i); MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); return true; } //----------------------------------------------------------------------------------- template bool node_server::add_host_fail(const epee::net_utils::network_address &address) { CRITICAL_REGION_LOCAL(m_host_fails_score_lock); uint64_t fails = ++m_host_fails_score[address.host_str()]; MDEBUG("Host " << address.host_str() << " fail score=" << fails); if(fails > P2P_IP_FAILS_BEFORE_BLOCK) { auto it = m_host_fails_score.find(address.host_str()); CHECK_AND_ASSERT_MES(it != m_host_fails_score.end(), false, "internal error"); it->second = P2P_IP_FAILS_BEFORE_BLOCK/2; block_host(address); } return true; } //----------------------------------------------------------------------------------- template bool node_server::parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port) { return epee::net_utils::create_network_address(pe, node_addr, default_port); } //----------------------------------------------------------------------------------- template bool node_server::handle_command_line( const boost::program_options::variables_map& vm ) { auto p2p_bind_arg = m_testnet ? arg_testnet_p2p_bind_port : arg_p2p_bind_port; m_bind_ip = command_line::get_arg(vm, arg_p2p_bind_ip); m_port = command_line::get_arg(vm, p2p_bind_arg); m_external_port = command_line::get_arg(vm, arg_p2p_external_port); m_allow_local_ip = command_line::get_arg(vm, arg_p2p_allow_local_ip); m_no_igd = command_line::get_arg(vm, arg_no_igd); m_offline = command_line::get_arg(vm, arg_offline); if (command_line::has_arg(vm, arg_p2p_add_peer)) { std::vector perrs = command_line::get_arg(vm, arg_p2p_add_peer); for(const std::string& pr_str: perrs) { nodetool::peerlist_entry pe = AUTO_VAL_INIT(pe); pe.id = crypto::rand(); const uint16_t default_port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; bool r = parse_peer_from_string(pe.adr, pr_str, default_port); CHECK_AND_ASSERT_MES(r, false, "Failed to parse address from string: " << pr_str); m_command_line_peers.push_back(pe); } } if(command_line::has_arg(vm, arg_save_graph)) { set_save_graph(true); } if (command_line::has_arg(vm,arg_p2p_add_exclusive_node)) { if (!parse_peers_and_add_to_container(vm, arg_p2p_add_exclusive_node, m_exclusive_peers)) return false; } if (command_line::has_arg(vm, arg_p2p_add_priority_node)) { if (!parse_peers_and_add_to_container(vm, arg_p2p_add_priority_node, m_priority_peers)) return false; } if (command_line::has_arg(vm, arg_p2p_seed_node)) { if (!parse_peers_and_add_to_container(vm, arg_p2p_seed_node, m_seed_nodes)) return false; } if(command_line::has_arg(vm, arg_p2p_hide_my_port)) m_hide_my_port = true; if ( !set_max_out_peers(vm, command_line::get_arg(vm, arg_out_peers) ) ) return false; if ( !set_tos_flag(vm, command_line::get_arg(vm, arg_tos_flag) ) ) return false; if ( !set_rate_up_limit(vm, command_line::get_arg(vm, arg_limit_rate_up) ) ) return false; if ( !set_rate_down_limit(vm, command_line::get_arg(vm, arg_limit_rate_down) ) ) return false; if ( !set_rate_limit(vm, command_line::get_arg(vm, arg_limit_rate) ) ) return false; return true; } //----------------------------------------------------------------------------------- inline void append_net_address( std::vector & seed_nodes , std::string const & addr ) { using namespace boost::asio; size_t pos = addr.find_last_of(':'); CHECK_AND_ASSERT_MES_NO_RET(std::string::npos != pos && addr.length() - 1 != pos && 0 != pos, "Failed to parse seed address from string: '" << addr << '\''); std::string host = addr.substr(0, pos); std::string port = addr.substr(pos + 1); io_service io_srv; ip::tcp::resolver resolver(io_srv); ip::tcp::resolver::query query(host, port, boost::asio::ip::tcp::resolver::query::canonical_name); boost::system::error_code ec; ip::tcp::resolver::iterator i = resolver.resolve(query, ec); CHECK_AND_ASSERT_MES_NO_RET(!ec, "Failed to resolve host name '" << host << "': " << ec.message() << ':' << ec.value()); ip::tcp::resolver::iterator iend; for (; i != iend; ++i) { ip::tcp::endpoint endpoint = *i; if (endpoint.address().is_v4()) { epee::net_utils::network_address na(new epee::net_utils::ipv4_network_address(boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port())); seed_nodes.push_back(na); MINFO("Added seed node: " << na.str()); } else { MWARNING("IPv6 unsupported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); throw std::runtime_error("IPv6 unsupported"); } } } //----------------------------------------------------------------------------------- template std::set node_server::get_seed_nodes(bool testnet) const { std::set full_addrs; if (testnet) { full_addrs.insert("212.83.175.67:28080"); full_addrs.insert("5.9.100.248:28080"); full_addrs.insert("163.172.182.165:28080"); full_addrs.insert("195.154.123.123:28080"); full_addrs.insert("212.83.172.165:28080"); } else { full_addrs.insert("107.152.130.98:18080"); full_addrs.insert("212.83.175.67:18080"); full_addrs.insert("5.9.100.248:18080"); full_addrs.insert("163.172.182.165:18080"); full_addrs.insert("161.67.132.39:18080"); full_addrs.insert("198.74.231.92:18080"); full_addrs.insert("195.154.123.123:28080"); full_addrs.insert("212.83.172.165:28080"); } return full_addrs; } //----------------------------------------------------------------------------------- template bool node_server::init(const boost::program_options::variables_map& vm) { std::set full_addrs; m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); if (m_testnet) { memcpy(&m_network_id, &::config::testnet::NETWORK_ID, 16); full_addrs = get_seed_nodes(true); } else { memcpy(&m_network_id, &::config::NETWORK_ID, 16); // for each hostname in the seed nodes list, attempt to DNS resolve and // add the result addresses as seed nodes // TODO: at some point add IPv6 support, but that won't be relevant // for some time yet. std::vector> dns_results; dns_results.resize(m_seed_nodes_list.size()); std::list dns_threads; uint64_t result_index = 0; for (const std::string& addr_str : m_seed_nodes_list) { boost::thread th = boost::thread([=, &dns_results, &addr_str] { MDEBUG("dns_threads[" << result_index << "] created for: " << addr_str); // TODO: care about dnssec avail/valid bool avail, valid; std::vector addr_list; try { addr_list = tools::DNSResolver::instance().get_ipv4(addr_str, avail, valid); MDEBUG("dns_threads[" << result_index << "] DNS resolve done"); boost::this_thread::interruption_point(); } catch(const boost::thread_interrupted&) { // thread interruption request // even if we now have results, finish thread without setting // result variables, which are now out of scope in main thread MWARNING("dns_threads[" << result_index << "] interrupted"); return; } MINFO("dns_threads[" << result_index << "] addr_str: " << addr_str << " number of results: " << addr_list.size()); dns_results[result_index] = addr_list; }); dns_threads.push_back(std::move(th)); ++result_index; } MDEBUG("dns_threads created, now waiting for completion or timeout of " << CRYPTONOTE_DNS_TIMEOUT_MS << "ms"); boost::chrono::system_clock::time_point deadline = boost::chrono::system_clock::now() + boost::chrono::milliseconds(CRYPTONOTE_DNS_TIMEOUT_MS); uint64_t i = 0; for (boost::thread& th : dns_threads) { if (! th.try_join_until(deadline)) { MWARNING("dns_threads[" << i << "] timed out, sending interrupt"); th.interrupt(); } ++i; } i = 0; for (const auto& result : dns_results) { MDEBUG("DNS lookup for " << m_seed_nodes_list[i] << ": " << result.size() << " results"); // if no results for node, thread's lookup likely timed out if (result.size()) { for (const auto& addr_string : result) full_addrs.insert(addr_string + ":18080"); } ++i; } // append the fallback nodes if we have too few seed nodes to start with if (full_addrs.size() < MIN_WANTED_SEED_NODES) { if (full_addrs.empty()) MINFO("DNS seed node lookup either timed out or failed, falling back to defaults"); else MINFO("Not enough DNS seed nodes found, using fallback defaults too"); for (const auto &peer: get_seed_nodes(false)) full_addrs.insert(peer); } } for (const auto& full_addr : full_addrs) { MDEBUG("Seed node: " << full_addr); append_net_address(m_seed_nodes, full_addr); } MDEBUG("Number of seed nodes: " << m_seed_nodes.size()); bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); auto config_arg = m_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; m_config_folder = command_line::get_arg(vm, config_arg); if ((!m_testnet && m_port != std::to_string(::config::P2P_DEFAULT_PORT)) || (m_testnet && m_port != std::to_string(::config::testnet::P2P_DEFAULT_PORT))) { m_config_folder = m_config_folder + "/" + m_port; } res = init_config(); CHECK_AND_ASSERT_MES(res, false, "Failed to init config."); res = m_peerlist.init(m_allow_local_ip); CHECK_AND_ASSERT_MES(res, false, "Failed to init peerlist."); for(auto& p: m_command_line_peers) m_peerlist.append_with_peer_white(p); //only in case if we really sure that we have external visible ip m_have_address = true; m_ip_address = 0; m_last_stat_request_time = 0; //configure self m_net_server.set_threads_prefix("P2P"); m_net_server.get_config_object().m_pcommands_handler = this; m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT; m_net_server.set_connection_filter(this); // from here onwards, it's online stuff if (m_offline) return res; //try to bind MINFO("Binding on " << m_bind_ip << ":" << m_port); res = m_net_server.init_server(m_port, m_bind_ip); CHECK_AND_ASSERT_MES(res, false, "Failed to bind server"); m_listenning_port = m_net_server.get_binded_port(); MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listenning_port); if(m_external_port) MDEBUG("External port defined as " << m_external_port); // Add UPnP port mapping if(m_no_igd == false) { MDEBUG("Attempting to add IGD port mapping."); int result; #if MINIUPNPC_API_VERSION > 13 // default according to miniupnpc.h unsigned char ttl = 2; UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); #else UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); #endif UPNPUrls urls; IGDdatas igdData; char lanAddress[64]; result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); freeUPNPDevlist(deviceList); if (result != 0) { if (result == 1) { std::ostringstream portString; portString << m_listenning_port; // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); int portMappingResult; portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0"); if (portMappingResult != 0) { LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); } else { MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); } } else if (result == 2) { MWARNING("IGD was found but reported as not connected."); } else if (result == 3) { MWARNING("UPnP device was found but not recognized as IGD."); } else { MWARNING("UPNP_GetValidIGD returned an unknown result code."); } FreeUPNPUrls(&urls); } else { MINFO("No IGD was found."); } } return res; } //----------------------------------------------------------------------------------- template typename node_server::payload_net_handler& node_server::get_payload_object() { return m_payload_handler; } //----------------------------------------------------------------------------------- template bool node_server::run() { // creating thread to log number of connections mPeersLoggerThread.reset(new boost::thread([&]() { _note("Thread monitor number of peers - start"); while (!is_closing && !m_net_server.is_stop_signal_sent()) { // main loop of thread //number_of_peers = m_net_server.get_config_object().get_connections_count(); unsigned int number_of_peers = 0; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if (!cntxt.m_is_income) ++number_of_peers; return true; }); // lambda m_current_number_of_out_peers = number_of_peers; boost::this_thread::sleep_for(boost::chrono::seconds(1)); } // main loop of thread _note("Thread monitor number of peers - done"); })); // lambda //here you can set worker threads count int thrds_count = 10; m_net_server.add_idle_handler(boost::bind(&node_server::idle_worker, this), 1000); m_net_server.add_idle_handler(boost::bind(&t_payload_net_handler::on_idle, &m_payload_handler), 1000); boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); //go to loop MINFO("Run net_service loop( " << thrds_count << " threads)..."); if(!m_net_server.run_server(thrds_count, true, attrs)) { LOG_ERROR("Failed to run net tcp server!"); } MINFO("net_service loop stopped."); return true; } //----------------------------------------------------------------------------------- template uint64_t node_server::get_connections_count() { return m_net_server.get_config_object().get_connections_count(); } //----------------------------------------------------------------------------------- template bool node_server::deinit() { kill(); m_peerlist.deinit(); m_net_server.deinit_server(); return store_config(); } //----------------------------------------------------------------------------------- template bool node_server::store_config() { TRY_ENTRY(); if (!tools::create_directories_if_necessary(m_config_folder)) { MWARNING("Failed to create data directory: " << m_config_folder); return false; } std::string state_file_path = m_config_folder + "/" + P2P_NET_DATA_FILENAME; std::ofstream p2p_data; p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::out| std::ios::trunc); if(p2p_data.fail()) { MWARNING("Failed to save config to file " << state_file_path); return false; }; boost::archive::portable_binary_oarchive a(p2p_data); a << *this; return true; CATCH_ENTRY_L0("blockchain_storage::save", false); return true; } //----------------------------------------------------------------------------------- template bool node_server::send_stop_signal() { std::list connection_ids; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { connection_ids.push_back(cntxt.m_connection_id); return true; }); for (const auto &connection_id: connection_ids) m_net_server.get_config_object().close(connection_id); m_payload_handler.stop(); m_net_server.send_stop_signal(); MDEBUG("[node] Stop signal sent"); return true; } //----------------------------------------------------------------------------------- template bool node_server::do_handshake_with_peer(peerid_type& pi, p2p_connection_context& context_, bool just_take_peerlist) { typename COMMAND_HANDSHAKE::request arg; typename COMMAND_HANDSHAKE::response rsp; get_local_node_data(arg.node_data); m_payload_handler.get_payload_sync_data(arg.payload_data); epee::simple_event ev; std::atomic hsh_result(false); bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, m_net_server.get_config_object(), [this, &pi, &ev, &hsh_result, &just_take_peerlist](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) { epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();}); if(code < 0) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(rsp.node_data.network_id != m_network_id) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE Failed, wrong network! (" << epee::string_tools::get_str_from_guid_a(rsp.node_data.network_id) << "), closing connection."); return; } if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.node_data.local_time, context)) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); add_host_fail(context.m_remote_address); return; } hsh_result = true; if(!just_take_peerlist) { if(!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, true)) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE invoked, but process_payload_sync_data returned false, dropping connection."); hsh_result = false; return; } pi = context.peer_id = rsp.node_data.peer_id; m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address); if(rsp.node_data.peer_id == m_config.m_peer_id) { LOG_DEBUG_CC(context, "Connection to self detected, dropping connection"); hsh_result = false; return; } LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE INVOKED OK"); }else { LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); } }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); if(r) { ev.wait(); } if(!hsh_result) { LOG_WARNING_CC(context_, "COMMAND_HANDSHAKE Failed"); m_net_server.get_config_object().close(context_.m_connection_id); } else { try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) { flags_context.support_flags = support_flags; }); } return hsh_result; } //----------------------------------------------------------------------------------- template bool node_server::do_peer_timed_sync(const epee::net_utils::connection_context_base& context_, peerid_type peer_id) { typename COMMAND_TIMED_SYNC::request arg = AUTO_VAL_INIT(arg); m_payload_handler.get_payload_sync_data(arg.payload_data); bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_TIMED_SYNC::ID, arg, m_net_server.get_config_object(), [this](int code, const typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) { context.m_in_timedsync = false; if(code < 0) { LOG_WARNING_CC(context, "COMMAND_TIMED_SYNC invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.local_time, context)) { LOG_WARNING_CC(context, "COMMAND_TIMED_SYNC: failed to handle_remote_peerlist(...), closing connection."); m_net_server.get_config_object().close(context.m_connection_id ); add_host_fail(context.m_remote_address); } if(!context.m_is_income) m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address); m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false); }); if(!r) { LOG_WARNING_CC(context_, "COMMAND_TIMED_SYNC Failed"); return false; } return true; } //----------------------------------------------------------------------------------- template size_t node_server::get_random_index_with_fixed_probability(size_t max_index) { //divide by zero workaround if(!max_index) return 0; size_t x = crypto::rand()%(max_index+1); size_t res = (x*x*x)/(max_index*max_index); //parabola \/ MDEBUG("Random connection index=" << res << "(x="<< x << ", max_index=" << max_index << ")"); return res; } //----------------------------------------------------------------------------------- template bool node_server::is_peer_used(const peerlist_entry& peer) { if(m_config.m_peer_id == peer.id) return true;//dont make connections to ourself bool used = false; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr == cntxt.m_remote_address)) { used = true; return false;//stop enumerating } return true; }); return used; } //----------------------------------------------------------------------------------- template bool node_server::is_peer_used(const anchor_peerlist_entry& peer) { if(m_config.m_peer_id == peer.id) { return true;//dont make connections to ourself } bool used = false; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr == cntxt.m_remote_address)) { used = true; return false;//stop enumerating } return true; }); return used; } //----------------------------------------------------------------------------------- template bool node_server::is_addr_connected(const epee::net_utils::network_address& peer) { bool connected = false; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if(!cntxt.m_is_income && peer == cntxt.m_remote_address) { connected = true; return false;//stop enumerating } return true; }); return connected; } #define LOG_PRINT_CC_PRIORITY_NODE(priority, con, msg) \ do { \ if (priority) {\ LOG_INFO_CC(con, "[priority]" << msg); \ } else {\ LOG_INFO_CC(con, msg); \ } \ } while(0) template bool node_server::try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp) { if (m_current_number_of_out_peers == m_config.m_net_config.connections_count) // out peers limit { return false; } else if (m_current_number_of_out_peers > m_config.m_net_config.connections_count) { m_net_server.get_config_object().del_out_connections(1); m_current_number_of_out_peers --; // atomic variable, update time = 1s return false; } MDEBUG("Connecting to " << na.str() << "(peer_type=" << peer_type << ", last_seen: " << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") << ")..."); CHECK_AND_ASSERT_MES(na.get_type_id() == epee::net_utils::ipv4_network_address::ID, false, "Only IPv4 addresses are supported here"); const epee::net_utils::ipv4_network_address &ipv4 = na.as(); typename net_server::t_connection_context con = AUTO_VAL_INIT(con); bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), epee::string_tools::num_to_string_fast(ipv4.port()), m_config.m_net_config.connection_timeout, con); if(!res) { bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " << na.str() /*<< ", try " << try_count*/); //m_peerlist.set_peer_unreachable(pe); return false; } peerid_type pi = AUTO_VAL_INIT(pi); res = do_handshake_with_peer(pi, con, just_take_peerlist); if(!res) { bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " << na.str() /*<< ", try " << try_count*/); return false; } if(just_take_peerlist) { m_net_server.get_config_object().close(con.m_connection_id); LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK AND CLOSED."); return true; } peerlist_entry pe_local = AUTO_VAL_INIT(pe_local); pe_local.adr = na; pe_local.id = pi; time_t last_seen; time(&last_seen); pe_local.last_seen = static_cast(last_seen); m_peerlist.append_with_peer_white(pe_local); //update last seen and push it to peerlist manager anchor_peerlist_entry ape = AUTO_VAL_INIT(ape); ape.adr = na; ape.id = pi; ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr); m_peerlist.append_with_peer_anchor(ape); LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK."); return true; } template bool node_server::check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp) { LOG_PRINT_L1("Connecting to " << na.str() << "(last_seen: " << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") << ")..."); CHECK_AND_ASSERT_MES(na.get_type_id() == epee::net_utils::ipv4_network_address::ID, false, "Only IPv4 addresses are supported here"); const epee::net_utils::ipv4_network_address &ipv4 = na.as(); typename net_server::t_connection_context con = AUTO_VAL_INIT(con); bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), epee::string_tools::num_to_string_fast(ipv4.port()), m_config.m_net_config.connection_timeout, con); if (!res) { bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " << na.str()); return false; } peerid_type pi = AUTO_VAL_INIT(pi); res = do_handshake_with_peer(pi, con, true); if (!res) { bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " << na.str()); return false; } m_net_server.get_config_object().close(con.m_connection_id); LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK AND CLOSED."); return true; } #undef LOG_PRINT_CC_PRIORITY_NODE //----------------------------------------------------------------------------------- template bool node_server::is_addr_recently_failed(const epee::net_utils::network_address& addr) { CRITICAL_REGION_LOCAL(m_conn_fails_cache_lock); auto it = m_conn_fails_cache.find(addr); if(it == m_conn_fails_cache.end()) return false; if(time(NULL) - it->second > P2P_FAILED_ADDR_FORGET_SECONDS) return false; else return true; } //----------------------------------------------------------------------------------- template bool node_server::make_new_connection_from_anchor_peerlist(const std::vector& anchor_peerlist) { for (const auto& pe: anchor_peerlist) { _note("Considering connecting (out) to peer: " << peerid_type(pe.id) << " " << pe.adr.str()); if(is_peer_used(pe)) { _note("Peer is used"); continue; } if(!is_remote_host_allowed(pe.adr)) { continue; } if(is_addr_recently_failed(pe.adr)) { continue; } MDEBUG("Selected peer: " << peerid_to_string(pe.id) << " " << pe.adr.str() << "[peer_type=" << anchor << "] first_seen: " << epee::misc_utils::get_time_interval_string(time(NULL) - pe.first_seen)); if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, 0, anchor, pe.first_seen)) { _note("Handshake failed"); continue; } return true; } return false; } //----------------------------------------------------------------------------------- template bool node_server::make_new_connection_from_peerlist(bool use_white_list) { size_t local_peers_count = use_white_list ? m_peerlist.get_white_peers_count():m_peerlist.get_gray_peers_count(); if(!local_peers_count) return false;//no peers size_t max_random_index = std::min(local_peers_count -1, 20); std::set tried_peers; size_t try_count = 0; size_t rand_count = 0; while(rand_count < (max_random_index+1)*3 && try_count < 10 && !m_net_server.is_stop_signal_sent()) { ++rand_count; size_t random_index; if (use_white_list) { local_peers_count = m_peerlist.get_white_peers_count(); max_random_index = std::min(local_peers_count -1, 20); random_index = get_random_index_with_fixed_probability(max_random_index); } else { random_index = crypto::rand() % m_peerlist.get_gray_peers_count(); } CHECK_AND_ASSERT_MES(random_index < local_peers_count, false, "random_starter_index < peers_local.size() failed!!"); if(tried_peers.count(random_index)) continue; tried_peers.insert(random_index); peerlist_entry pe = AUTO_VAL_INIT(pe); bool r = use_white_list ? m_peerlist.get_white_peer_by_index(pe, random_index):m_peerlist.get_gray_peer_by_index(pe, random_index); CHECK_AND_ASSERT_MES(r, false, "Failed to get random peer from peerlist(white:" << use_white_list << ")"); ++try_count; _note("Considering connecting (out) to peer: " << peerid_to_string(pe.id) << " " << pe.adr.str()); if(is_peer_used(pe)) { _note("Peer is used"); continue; } if(!is_remote_host_allowed(pe.adr)) continue; if(is_addr_recently_failed(pe.adr)) continue; MDEBUG("Selected peer: " << peerid_to_string(pe.id) << " " << pe.adr.str() << "[peer_list=" << (use_white_list ? white : gray) << "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never")); if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, pe.last_seen, use_white_list ? white : gray)) { _note("Handshake failed"); continue; } return true; } return false; } //----------------------------------------------------------------------------------- template bool node_server::connect_to_seed() { if (m_seed_nodes.empty()) return true; size_t try_count = 0; size_t current_index = crypto::rand()%m_seed_nodes.size(); bool fallback_nodes_added = false; while(true) { if(m_net_server.is_stop_signal_sent()) return false; if(try_to_connect_and_handshake_with_new_peer(m_seed_nodes[current_index], true)) break; if(++try_count > m_seed_nodes.size()) { if (!fallback_nodes_added) { MWARNING("Failed to connect to any of seed peers, trying fallback seeds"); for (const auto &peer: get_seed_nodes(m_testnet)) { MDEBUG("Fallback seed node: " << peer); append_net_address(m_seed_nodes, peer); } fallback_nodes_added = true; // continue for another few cycles } else { MWARNING("Failed to connect to any of seed peers, continuing without seeds"); break; } } if(++current_index >= m_seed_nodes.size()) current_index = 0; } return true; } //----------------------------------------------------------------------------------- template bool node_server::connections_maker() { if (!connect_to_peerlist(m_exclusive_peers)) return false; if (!m_exclusive_peers.empty()) return true; size_t start_conn_count = get_outgoing_connections_count(); if(!m_peerlist.get_white_peers_count() && m_seed_nodes.size()) { if (!connect_to_seed()) return false; } if (!connect_to_peerlist(m_priority_peers)) return false; size_t expected_white_connections = (m_config.m_net_config.connections_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100; size_t conn_count = get_outgoing_connections_count(); if(conn_count < m_config.m_net_config.connections_count) { if(conn_count < expected_white_connections) { //start from anchor list if(!make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT)) return false; //then do white list if(!make_expected_connections_count(white, expected_white_connections)) return false; //then do grey list if(!make_expected_connections_count(gray, m_config.m_net_config.connections_count)) return false; }else { //start from grey list if(!make_expected_connections_count(gray, m_config.m_net_config.connections_count)) return false; //and then do white list if(!make_expected_connections_count(white, m_config.m_net_config.connections_count)) return false; } } if (start_conn_count == get_outgoing_connections_count() && start_conn_count < m_config.m_net_config.connections_count) { MINFO("Failed to connect to any, trying seeds"); if (!connect_to_seed()) return false; } return true; } //----------------------------------------------------------------------------------- template bool node_server::make_expected_connections_count(PeerType peer_type, size_t expected_connections) { if (m_offline) return true; std::vector apl; if (peer_type == anchor) { m_peerlist.get_and_empty_anchor_peerlist(apl); } size_t conn_count = get_outgoing_connections_count(); //add new connections from white peers while(conn_count < expected_connections) { if(m_net_server.is_stop_signal_sent()) return false; if (peer_type == anchor && !make_new_connection_from_anchor_peerlist(apl)) { break; } if (peer_type == white && !make_new_connection_from_peerlist(true)) { break; } if (peer_type == gray && !make_new_connection_from_peerlist(false)) { break; } conn_count = get_outgoing_connections_count(); } return true; } //----------------------------------------------------------------------------------- template size_t node_server::get_outgoing_connections_count() { size_t count = 0; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if(!cntxt.m_is_income) ++count; return true; }); return count; } //----------------------------------------------------------------------------------- template bool node_server::idle_worker() { m_peer_handshake_idle_maker_interval.do_call(boost::bind(&node_server::peer_sync_idle_maker, this)); m_connections_maker_interval.do_call(boost::bind(&node_server::connections_maker, this)); m_gray_peerlist_housekeeping_interval.do_call(boost::bind(&node_server::gray_peerlist_housekeeping, this)); m_peerlist_store_interval.do_call(boost::bind(&node_server::store_config, this)); return true; } //----------------------------------------------------------------------------------- template bool node_server::peer_sync_idle_maker() { MDEBUG("STARTED PEERLIST IDLE HANDSHAKE"); typedef std::list > local_connects_type; local_connects_type cncts; m_net_server.get_config_object().foreach_connection([&](p2p_connection_context& cntxt) { if(cntxt.peer_id && !cntxt.m_in_timedsync) { cntxt.m_in_timedsync = true; cncts.push_back(local_connects_type::value_type(cntxt, cntxt.peer_id));//do idle sync only with handshaked connections } return true; }); std::for_each(cncts.begin(), cncts.end(), [&](const typename local_connects_type::value_type& vl){do_peer_timed_sync(vl.first, vl.second);}); MDEBUG("FINISHED PEERLIST IDLE HANDSHAKE"); return true; } //----------------------------------------------------------------------------------- template bool node_server::fix_time_delta(std::list& local_peerlist, time_t local_time, int64_t& delta) { //fix time delta time_t now = 0; time(&now); delta = now - local_time; for(peerlist_entry& be: local_peerlist) { if(be.last_seen > local_time) { MWARNING("FOUND FUTURE peerlist for entry " << be.adr.str() << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time); return false; } be.last_seen += delta; } return true; } //----------------------------------------------------------------------------------- template bool node_server::handle_remote_peerlist(const std::list& peerlist, time_t local_time, const epee::net_utils::connection_context_base& context) { int64_t delta = 0; std::list peerlist_ = peerlist; if(!fix_time_delta(peerlist_, local_time, delta)) return false; LOG_DEBUG_CC(context, "REMOTE PEERLIST: TIME_DELTA: " << delta << ", remote peerlist size=" << peerlist_.size()); LOG_DEBUG_CC(context, "REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_)); return m_peerlist.merge_peerlist(peerlist_); } //----------------------------------------------------------------------------------- template bool node_server::get_local_node_data(basic_node_data& node_data) { time_t local_time; time(&local_time); node_data.local_time = local_time; node_data.peer_id = m_config.m_peer_id; if(!m_hide_my_port) node_data.my_port = m_external_port ? m_external_port : m_listenning_port; else node_data.my_port = 0; node_data.network_id = m_network_id; return true; } //----------------------------------------------------------------------------------- #ifdef ALLOW_DEBUG_COMMANDS template bool node_server::check_trust(const proof_of_trust& tr) { uint64_t local_time = time(NULL); uint64_t time_delata = local_time > tr.time ? local_time - tr.time: tr.time - local_time; if(time_delata > 24*60*60 ) { MWARNING("check_trust failed to check time conditions, local_time=" << local_time << ", proof_time=" << tr.time); return false; } if(m_last_stat_request_time >= tr.time ) { MWARNING("check_trust failed to check time conditions, last_stat_request_time=" << m_last_stat_request_time << ", proof_time=" << tr.time); return false; } if(m_config.m_peer_id != tr.peer_id) { MWARNING("check_trust failed: peer_id mismatch (passed " << tr.peer_id << ", expected " << m_config.m_peer_id<< ")"); return false; } crypto::public_key pk = AUTO_VAL_INIT(pk); epee::string_tools::hex_to_pod(::config::P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY, pk); crypto::hash h = get_proof_of_trust_hash(tr); if(!crypto::check_signature(h, pk, tr.sign)) { MWARNING("check_trust failed: sign check failed"); return false; } //update last request time m_last_stat_request_time = tr.time; return true; } //----------------------------------------------------------------------------------- template int node_server::handle_get_stat_info(int command, typename COMMAND_REQUEST_STAT_INFO::request& arg, typename COMMAND_REQUEST_STAT_INFO::response& rsp, p2p_connection_context& context) { if(!check_trust(arg.tr)) { drop_connection(context); return 1; } rsp.connections_count = m_net_server.get_config_object().get_connections_count(); rsp.incoming_connections_count = rsp.connections_count - get_outgoing_connections_count(); rsp.version = MONERO_VERSION_FULL; rsp.os_version = tools::get_os_version_string(); m_payload_handler.get_stat_info(rsp.payload_info); return 1; } //----------------------------------------------------------------------------------- template int node_server::handle_get_network_state(int command, COMMAND_REQUEST_NETWORK_STATE::request& arg, COMMAND_REQUEST_NETWORK_STATE::response& rsp, p2p_connection_context& context) { if(!check_trust(arg.tr)) { drop_connection(context); return 1; } m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { connection_entry ce; ce.adr = cntxt.m_remote_address; ce.id = cntxt.peer_id; ce.is_income = cntxt.m_is_income; rsp.connections_list.push_back(ce); return true; }); m_peerlist.get_peerlist_full(rsp.local_peerlist_gray, rsp.local_peerlist_white); rsp.my_id = m_config.m_peer_id; rsp.local_time = time(NULL); return 1; } //----------------------------------------------------------------------------------- template int node_server::handle_get_peer_id(int command, COMMAND_REQUEST_PEER_ID::request& arg, COMMAND_REQUEST_PEER_ID::response& rsp, p2p_connection_context& context) { rsp.my_id = m_config.m_peer_id; return 1; } #endif //----------------------------------------------------------------------------------- template int node_server::handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context) { rsp.support_flags = m_config.m_support_flags; return 1; } //----------------------------------------------------------------------------------- template void node_server::request_callback(const epee::net_utils::connection_context_base& context) { m_net_server.get_config_object().request_callback(context.m_connection_id); } //----------------------------------------------------------------------------------- template bool node_server::relay_notify_to_list(int command, const std::string& data_buff, const std::list &connections) { for(const auto& c_id: connections) { m_net_server.get_config_object().notify(command, data_buff, c_id); } return true; } //----------------------------------------------------------------------------------- template bool node_server::relay_notify_to_all(int command, const std::string& data_buff, const epee::net_utils::connection_context_base& context) { std::list connections; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if(cntxt.peer_id && context.m_connection_id != cntxt.m_connection_id) connections.push_back(cntxt.m_connection_id); return true; }); return relay_notify_to_list(command, data_buff, connections); } //----------------------------------------------------------------------------------- template void node_server::callback(p2p_connection_context& context) { m_payload_handler.on_callback(context); } //----------------------------------------------------------------------------------- template bool node_server::invoke_notify_to_peer(int command, const std::string& req_buff, const epee::net_utils::connection_context_base& context) { int res = m_net_server.get_config_object().notify(command, req_buff, context.m_connection_id); return res > 0; } //----------------------------------------------------------------------------------- template bool node_server::invoke_command_to_peer(int command, const std::string& req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context) { int res = m_net_server.get_config_object().invoke(command, req_buff, resp_buff, context.m_connection_id); return res > 0; } //----------------------------------------------------------------------------------- template bool node_server::drop_connection(const epee::net_utils::connection_context_base& context) { m_net_server.get_config_object().close(context.m_connection_id); return true; } //----------------------------------------------------------------------------------- template template bool node_server::try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb) { if(!node_data.my_port) return false; CHECK_AND_ASSERT_MES(context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::ID, false, "Only IPv4 addresses are supported here"); const epee::net_utils::network_address na = context.m_remote_address; uint32_t actual_ip = na.as().ip(); if(!m_peerlist.is_host_allowed(context.m_remote_address)) return false; std::string ip = epee::string_tools::get_ip_string_from_int32(actual_ip); std::string port = epee::string_tools::num_to_string_fast(node_data.my_port); epee::net_utils::network_address address(new epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)); peerid_type pr = node_data.peer_id; bool r = m_net_server.connect_async(ip, port, m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( const typename net_server::t_connection_context& ping_context, const boost::system::error_code& ec)->bool { if(ec) { LOG_WARNING_CC(ping_context, "back ping connect failed to " << address.str()); return false; } COMMAND_PING::request req; COMMAND_PING::response rsp; //vc2010 workaround /*std::string ip_ = ip; std::string port_=port; peerid_type pr_ = pr; auto cb_ = cb;*/ // GCC 5.1.0 gives error with second use of uint64_t (peerid_type) variable. peerid_type pr_ = pr; bool inv_call_res = epee::net_utils::async_invoke_remote_command2(ping_context.m_connection_id, COMMAND_PING::ID, req, m_net_server.get_config_object(), [=](int code, const COMMAND_PING::response& rsp, p2p_connection_context& context) { if(code <= 0) { LOG_WARNING_CC(ping_context, "Failed to invoke COMMAND_PING to " << address.str() << "(" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) { LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); m_net_server.get_config_object().close(ping_context.m_connection_id); return; } m_net_server.get_config_object().close(ping_context.m_connection_id); cb(); }); if(!inv_call_res) { LOG_WARNING_CC(ping_context, "back ping invoke failed to " << address.str()); m_net_server.get_config_object().close(ping_context.m_connection_id); return false; } return true; }); if(!r) { LOG_WARNING_CC(context, "Failed to call connect_async, network error."); } return r; } //----------------------------------------------------------------------------------- template bool node_server::try_get_support_flags(const p2p_connection_context& context, std::function f) { COMMAND_REQUEST_SUPPORT_FLAGS::request support_flags_request; bool r = epee::net_utils::async_invoke_remote_command2 ( context.m_connection_id, COMMAND_REQUEST_SUPPORT_FLAGS::ID, support_flags_request, m_net_server.get_config_object(), [=](int code, const typename COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context_) { if(code < 0) { LOG_WARNING_CC(context_, "COMMAND_REQUEST_SUPPORT_FLAGS invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } f(context_, rsp.support_flags); }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT ); return r; } //----------------------------------------------------------------------------------- template int node_server::handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) { if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, false)) { LOG_WARNING_CC(context, "Failed to process_payload_sync_data(), dropping connection"); drop_connection(context); return 1; } //fill response rsp.local_time = time(NULL); m_peerlist.get_peerlist_head(rsp.local_peerlist_new); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC"); return 1; } //----------------------------------------------------------------------------------- template int node_server::handle_handshake(int command, typename COMMAND_HANDSHAKE::request& arg, typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) { if(arg.node_data.network_id != m_network_id) { LOG_INFO_CC(context, "WRONG NETWORK AGENT CONNECTED! id=" << epee::string_tools::get_str_from_guid_a(arg.node_data.network_id)); drop_connection(context); add_host_fail(context.m_remote_address); return 1; } if(!context.m_is_income) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came not from incoming connection"); drop_connection(context); add_host_fail(context.m_remote_address); return 1; } if(context.peer_id) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but seems that connection already have associated peer_id (double COMMAND_HANDSHAKE?)"); drop_connection(context); return 1; } if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, true)) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but process_payload_sync_data returned false, dropping connection."); drop_connection(context); return 1; } if(has_too_many_connections(context.m_remote_address)) { LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << context.m_remote_address.host_str() << " REFUSED, too many connections from the same address"); drop_connection(context); return 1; } //associate peer_id with this connection context.peer_id = arg.node_data.peer_id; context.m_in_timedsync = false; if(arg.node_data.peer_id != m_config.m_peer_id && arg.node_data.my_port) { peerid_type peer_id_l = arg.node_data.peer_id; uint32_t port_l = arg.node_data.my_port; //try ping to be sure that we can add this peer to peer_list try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]() { CHECK_AND_ASSERT_MES(context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::ID, void(), "Only IPv4 addresses are supported here"); //called only(!) if success pinged, update local peerlist peerlist_entry pe; const epee::net_utils::network_address na = context.m_remote_address; pe.adr.reset(new epee::net_utils::ipv4_network_address(na.as().ip(), port_l)); time_t last_seen; time(&last_seen); pe.last_seen = static_cast(last_seen); pe.id = peer_id_l; this->m_peerlist.append_with_peer_white(pe); LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); }); } try_get_support_flags(context, [](p2p_connection_context& flags_context, const uint32_t& support_flags) { flags_context.support_flags = support_flags; }); //fill response m_peerlist.get_peerlist_head(rsp.local_peerlist_new); get_local_node_data(rsp.node_data); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); return 1; } //----------------------------------------------------------------------------------- template int node_server::handle_ping(int command, COMMAND_PING::request& arg, COMMAND_PING::response& rsp, p2p_connection_context& context) { LOG_DEBUG_CC(context, "COMMAND_PING"); rsp.status = PING_OK_RESPONSE_STATUS_TEXT; rsp.peer_id = m_config.m_peer_id; return 1; } //----------------------------------------------------------------------------------- template bool node_server::log_peerlist() { std::list pl_white; std::list pl_gray; m_peerlist.get_peerlist_full(pl_gray, pl_white); MINFO(ENDL << "Peerlist white:" << ENDL << print_peerlist_to_string(pl_white) << ENDL << "Peerlist gray:" << ENDL << print_peerlist_to_string(pl_gray) ); return true; } //----------------------------------------------------------------------------------- template bool node_server::log_connections() { MINFO("Connections: \r\n" << print_connections_container() ); return true; } //----------------------------------------------------------------------------------- template std::string node_server::print_connections_container() { std::stringstream ss; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { ss << cntxt.m_remote_address.str() << " \t\tpeer_id " << cntxt.peer_id << " \t\tconn_id " << epee::string_tools::get_str_from_guid_a(cntxt.m_connection_id) << (cntxt.m_is_income ? " INC":" OUT") << std::endl; return true; }); std::string s = ss.str(); return s; } //----------------------------------------------------------------------------------- template void node_server::on_connection_new(p2p_connection_context& context) { MINFO("["<< epee::net_utils::print_connection_context(context) << "] NEW CONNECTION"); } //----------------------------------------------------------------------------------- template void node_server::on_connection_close(p2p_connection_context& context) { if (!m_net_server.is_stop_signal_sent() && !context.m_is_income) { epee::net_utils::network_address na = AUTO_VAL_INIT(na); na = context.m_remote_address; m_peerlist.remove_from_peer_anchor(na); } m_payload_handler.on_connection_close(context); MINFO("["<< epee::net_utils::print_connection_context(context) << "] CLOSE CONNECTION"); } template bool node_server::is_priority_node(const epee::net_utils::network_address& na) { return (std::find(m_priority_peers.begin(), m_priority_peers.end(), na) != m_priority_peers.end()) || (std::find(m_exclusive_peers.begin(), m_exclusive_peers.end(), na) != m_exclusive_peers.end()); } template template bool node_server::connect_to_peerlist(const Container& peers) { for(const epee::net_utils::network_address& na: peers) { if(m_net_server.is_stop_signal_sent()) return false; if(is_addr_connected(na)) continue; try_to_connect_and_handshake_with_new_peer(na); } return true; } template template bool node_server::parse_peers_and_add_to_container(const boost::program_options::variables_map& vm, const command_line::arg_descriptor > & arg, Container& container) { std::vector perrs = command_line::get_arg(vm, arg); for(const std::string& pr_str: perrs) { epee::net_utils::network_address na = AUTO_VAL_INIT(na); const uint16_t default_port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; bool r = parse_peer_from_string(na, pr_str, default_port); CHECK_AND_ASSERT_MES(r, false, "Failed to parse address from string: " << pr_str); container.push_back(na); } return true; } template bool node_server::set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max) { if(max == -1) { m_config.m_net_config.connections_count = P2P_DEFAULT_CONNECTIONS_COUNT; return true; } m_config.m_net_config.connections_count = max; return true; } template void node_server::delete_connections(size_t count) { m_net_server.get_config_object().del_out_connections(count); } template bool node_server::set_tos_flag(const boost::program_options::variables_map& vm, int flag) { if(flag==-1){ return true; } epee::net_utils::connection >::set_tos_flag(flag); _dbg1("Set ToS flag " << flag); return true; } template bool node_server::set_rate_up_limit(const boost::program_options::variables_map& vm, int64_t limit) { this->islimitup=true; if (limit==-1) { limit=default_limit_up; this->islimitup=false; } limit *= 1024; epee::net_utils::connection >::set_rate_up_limit( limit ); MINFO("Set limit-up to " << limit/1024 << " kB/s"); return true; } template bool node_server::set_rate_down_limit(const boost::program_options::variables_map& vm, int64_t limit) { this->islimitdown=true; if(limit==-1) { limit=default_limit_down; this->islimitdown=false; } limit *= 1024; epee::net_utils::connection >::set_rate_down_limit( limit ); MINFO("Set limit-down to " << limit/1024 << " kB/s"); return true; } template bool node_server::set_rate_limit(const boost::program_options::variables_map& vm, int64_t limit) { int64_t limit_up = 0; int64_t limit_down = 0; if(limit == -1) { limit_up = default_limit_up * 1024; limit_down = default_limit_down * 1024; } else { limit_up = limit * 1024; limit_down = limit * 1024; } if(!this->islimitup) { epee::net_utils::connection >::set_rate_up_limit(limit_up); MINFO("Set limit-up to " << limit_up/1024 << " kB/s"); } if(!this->islimitdown) { epee::net_utils::connection >::set_rate_down_limit(limit_down); MINFO("Set limit-down to " << limit_down/1024 << " kB/s"); } return true; } template bool node_server::has_too_many_connections(const epee::net_utils::network_address &address) { const uint8_t max_connections = 1; uint8_t count = 0; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { if (cntxt.m_is_income && cntxt.m_remote_address.is_same_host(address)) { count++; if (count > max_connections) { return false; } } return true; }); return count > max_connections; } template bool node_server::gray_peerlist_housekeeping() { if (!m_exclusive_peers.empty()) return true; peerlist_entry pe = AUTO_VAL_INIT(pe); if (!m_peerlist.get_random_gray_peer(pe)) { return false; } bool success = check_connection_and_handshake_with_peer(pe.adr, pe.last_seen); if (!success) { m_peerlist.remove_from_peer_gray(pe); LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); return true; } m_peerlist.set_peer_just_seen(pe.id, pe.adr); LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); return true; } }