diff --git a/contrib/epee/src/net_utils_base.cpp b/contrib/epee/src/net_utils_base.cpp index 5cc49cc71..a2ca3d3d6 100644 --- a/contrib/epee/src/net_utils_base.cpp +++ b/contrib/epee/src/net_utils_base.cpp @@ -6,6 +6,17 @@ #include "string_tools.h" #include "net/local_ip.h" +static inline uint32_t make_address_v4_from_v6(const boost::asio::ip::address_v6& a) +{ + const auto &bytes = a.to_bytes(); + uint32_t v4 = 0; + v4 = (v4 << 8) | bytes[12]; + v4 = (v4 << 8) | bytes[13]; + v4 = (v4 << 8) | bytes[14]; + v4 = (v4 << 8) | bytes[15]; + return htonl(v4); +} + namespace epee { namespace net_utils { bool ipv4_network_address::equal(const ipv4_network_address& other) const noexcept @@ -83,8 +94,28 @@ namespace epee { namespace net_utils network_address::interface const* const other_self = other.self.get(); if (self_ == other_self) return true; if (!self_ || !other_self) return false; - if (typeid(*self_) != typeid(*other_self)) return false; - return self_->is_same_host(*other_self); + if (typeid(*self_) == typeid(*other_self)) + return self_->is_same_host(*other_self); + const auto this_id = get_type_id(); + if (this_id == ipv4_network_address::get_type_id() && other.get_type_id() == ipv6_network_address::get_type_id()) + { + const boost::asio::ip::address_v6 &actual_ip = other.as().ip(); + if (actual_ip.is_v4_mapped()) + { + const uint32_t v4ip = make_address_v4_from_v6(actual_ip); + return is_same_host(ipv4_network_address(v4ip, 0)); + } + } + else if (this_id == ipv6_network_address::get_type_id() && other.get_type_id() == ipv4_network_address::get_type_id()) + { + const boost::asio::ip::address_v6 &actual_ip = this->as().ip(); + if (actual_ip.is_v4_mapped()) + { + const uint32_t v4ip = make_address_v4_from_v6(actual_ip); + return other.is_same_host(ipv4_network_address(v4ip, 0)); + } + } + return false; } std::string print_connection_context(const connection_context_base& ctx) diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 0c3a94054..8d26e5638 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -43,7 +43,8 @@ namespace cryptonote { cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0), m_last_request_time(boost::date_time::not_a_date_time), m_callback_request_count(0), - m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_rpc_credits_per_hash(0), m_anchor(false) {} + m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_rpc_credits_per_hash(0), m_anchor(false), m_score(0), + m_expect_response(0) {} enum state { @@ -66,7 +67,8 @@ namespace cryptonote uint16_t m_rpc_port; uint32_t m_rpc_credits_per_hash; bool m_anchor; - //size_t m_score; TODO: add score calculations + int32_t m_score; + int m_expect_response; }; inline std::string get_protocol_state_string(cryptonote_connection_context::state s) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 61aac6d81..ee3a67198 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -148,6 +148,7 @@ namespace cryptonote void notify_new_stripe(cryptonote_connection_context &context, uint32_t stripe); void skip_unneeded_hashes(cryptonote_connection_context& context, bool check_block_queue) const; bool request_txpool_complement(cryptonote_connection_context &context); + void hit_score(cryptonote_connection_context &context, int32_t score); t_core& m_core; @@ -160,9 +161,10 @@ namespace cryptonote std::atomic m_ask_for_txpool_complement; boost::mutex m_sync_lock; block_queue m_block_queue; - epee::math_helper::once_a_time_seconds<30> m_idle_peer_kicker; + epee::math_helper::once_a_time_seconds<8> m_idle_peer_kicker; epee::math_helper::once_a_time_milliseconds<100> m_standby_checker; epee::math_helper::once_a_time_seconds<101> m_sync_search_checker; + epee::math_helper::once_a_time_seconds<43> m_bad_peer_checker; std::atomic m_max_out_peers; tools::PerformanceTimer m_sync_timer, m_add_timer; uint64_t m_last_add_end_time; @@ -183,6 +185,8 @@ namespace cryptonote double get_avg_block_size(); boost::circular_buffer m_avg_buffer = boost::circular_buffer(10); + boost::mutex m_bad_peer_check_lock; + template bool post_notify(typename t_parameter::request& arg, cryptonote_connection_context& context) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index a72b7db79..25b14d903 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -68,10 +68,12 @@ #define BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS 1000 #define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY (5 * 1000000) // microseconds #define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (30 * 1000000) // microseconds -#define IDLE_PEER_KICK_TIME (600 * 1000000) // microseconds +#define IDLE_PEER_KICK_TIME (240 * 1000000) // microseconds +#define NON_RESPONSIVE_PEER_KICK_TIME (20 * 1000000) // microseconds #define PASSIVE_PEER_KICK_TIME (60 * 1000000) // microseconds #define DROP_ON_SYNC_WEDGE_THRESHOLD (30 * 1000000000ull) // nanoseconds #define LAST_ACTIVITY_STALL_THRESHOLD (2.0f) // seconds +#define DROP_PEERS_ON_SCORE -2 namespace cryptonote { @@ -142,6 +144,8 @@ namespace cryptonote m_core.get_short_chain_history(r.block_ids); handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) r.prune = m_sync_pruned_blocks; + context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); + context.m_expect_response = NOTIFY_RESPONSE_CHAIN_ENTRY::ID; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify(r, context); MLOG_PEER_STATE("requesting chain"); @@ -326,6 +330,11 @@ namespace cryptonote } } + if (hshd.current_height < context.m_remote_blockchain_height) + { + MINFO(context << "Claims " << hshd.current_height << ", claimed " << context.m_remote_blockchain_height << " before"); + hit_score(context, 1); + } context.m_remote_blockchain_height = hshd.current_height; context.m_pruning_seed = hshd.pruning_seed; #ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED @@ -427,7 +436,7 @@ namespace cryptonote template int t_cryptonote_protocol_handler::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); + MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, context << "Received NOTIFY_NEW_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -487,6 +496,8 @@ namespace cryptonote m_core.get_short_chain_history(r.block_ids); r.prune = m_sync_pruned_blocks; handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) + context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); + context.m_expect_response = NOTIFY_RESPONSE_CHAIN_ENTRY::ID; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify(r, context); MLOG_PEER_STATE("requesting chain"); @@ -498,7 +509,7 @@ namespace cryptonote template int t_cryptonote_protocol_handler::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); + MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, context << "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -765,6 +776,8 @@ namespace cryptonote m_core.get_short_chain_history(r.block_ids); handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) r.prune = m_sync_pruned_blocks; + context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); + context.m_expect_response = NOTIFY_RESPONSE_CHAIN_ENTRY::ID; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify(r, context); MLOG_PEER_STATE("requesting chain"); @@ -1029,6 +1042,7 @@ namespace cryptonote drop_connection(context, false, false); return 1; } + context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); MLOG_P2P_MESSAGE("-->>NOTIFY_RESPONSE_GET_OBJECTS: blocks.size()=" << rsp.blocks.size() << ", rsp.m_current_blockchain_height=" << rsp.current_blockchain_height << ", missed_ids.size()=" << rsp.missed_ids.size()); @@ -1062,6 +1076,14 @@ namespace cryptonote boost::posix_time::ptime request_time = context.m_last_request_time; context.m_last_request_time = boost::date_time::not_a_date_time; + if (context.m_expect_response != NOTIFY_RESPONSE_GET_OBJECTS::ID) + { + LOG_ERROR_CCONTEXT("Got NOTIFY_RESPONSE_GET_OBJECTS out of the blue, dropping connection"); + drop_connection(context, true, false); + return 1; + } + context.m_expect_response = 0; + // calculate size of request size_t size = 0; size_t blocks_size = 0; @@ -1107,6 +1129,11 @@ namespace cryptonote return 1; } + if (arg.current_blockchain_height < context.m_remote_blockchain_height) + { + MINFO(context << "Claims " << arg.current_blockchain_height << ", claimed " << context.m_remote_blockchain_height << " before"); + hit_score(context, 1); + } context.m_remote_blockchain_height = arg.current_blockchain_height; if (context.m_remote_blockchain_height > m_core.get_target_blockchain_height()) m_core.set_target_blockchain_height(context.m_remote_blockchain_height); @@ -1666,24 +1693,45 @@ skip: bool t_cryptonote_protocol_handler::kick_idle_peers() { MTRACE("Checking for idle peers..."); + std::vector> idle_peers; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool { if (context.m_state == cryptonote_connection_context::state_synchronizing && context.m_last_request_time != boost::date_time::not_a_date_time) { const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); const boost::posix_time::time_duration dt = now - context.m_last_request_time; - if (dt.total_microseconds() > IDLE_PEER_KICK_TIME) + const auto ms = dt.total_microseconds(); + if (ms > IDLE_PEER_KICK_TIME || (context.m_expect_response && ms > NON_RESPONSIVE_PEER_KICK_TIME)) { - MINFO(context << " kicking idle peer, last update " << (dt.total_microseconds() / 1.e6) << " seconds ago"); - LOG_PRINT_CCONTEXT_L2("requesting callback"); - context.m_last_request_time = boost::date_time::not_a_date_time; - context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download - ++context.m_callback_request_count; - m_p2p->request_callback(context); + if (context.m_score-- >= 0) + { + MINFO(context << " kicking idle peer, last update " << (dt.total_microseconds() / 1.e6) << " seconds ago, expecting " << (int)context.m_expect_response); + LOG_PRINT_CCONTEXT_L2("requesting callback"); + context.m_last_request_time = boost::date_time::not_a_date_time; + context.m_expect_response = 0; + context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download + ++context.m_callback_request_count; + m_p2p->request_callback(context); + } + else + { + idle_peers.push_back(std::make_pair(context.m_connection_id, context.m_expect_response == 0 ? 1 : 5)); + } } } return true; }); + + for (const auto &e: idle_peers) + { + const auto &uuid = e.first; + m_p2p->for_connection(uuid, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ + MINFO(ctx << "dropping idle peer with negative score"); + drop_connection_with_score(ctx, e.second, false); + return true; + }); + } + return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -2206,6 +2254,7 @@ skip: } } context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); + context.m_expect_response = NOTIFY_RESPONSE_GET_OBJECTS::ID; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size() << "requested blocks count=" << count << " / " << count_limit << " from " << span.first << ", first hash " << req.blocks.front()); //epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size()); @@ -2296,6 +2345,7 @@ skip: //LOG_PRINT_CCONTEXT_L1("r = " << 200); context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); + context.m_expect_response = NOTIFY_RESPONSE_CHAIN_ENTRY::ID; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() << ", start_from_current_chain " << start_from_current_chain); post_notify(r, context); MLOG_PEER_STATE("requesting chain"); @@ -2321,7 +2371,7 @@ skip: } else { - MINFO(context << " we've reached this peer's blockchain height"); + MINFO(context << " we've reached this peer's blockchain height (theirs " << context.m_remote_blockchain_height << ", our target " << m_core.get_target_blockchain_height()); } } return true; @@ -2418,6 +2468,14 @@ skip: << ", m_start_height=" << arg.start_height << ", m_total_height=" << arg.total_height); MLOG_PEER_STATE("received chain"); + if (context.m_expect_response != NOTIFY_RESPONSE_CHAIN_ENTRY::ID) + { + LOG_ERROR_CCONTEXT("Got NOTIFY_RESPONSE_CHAIN_ENTRY out of the blue, dropping connection"); + drop_connection(context, true, false); + return 1; + } + context.m_expect_response = 0; + context.m_last_request_time = boost::date_time::not_a_date_time; m_sync_download_chain_size += arg.m_block_ids.size() * sizeof(crypto::hash); @@ -2448,6 +2506,11 @@ skip: drop_connection(context, false, false); return 1; } + if (arg.total_height < context.m_remote_blockchain_height) + { + MINFO(context << "Claims " << arg.total_height << ", claimed " << context.m_remote_blockchain_height << " before"); + hit_score(context, 1); + } context.m_remote_blockchain_height = arg.total_height; context.m_last_response_height = arg.start_height + arg.m_block_ids.size()-1; if(context.m_last_response_height > context.m_remote_blockchain_height) @@ -2564,6 +2627,19 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template + void t_cryptonote_protocol_handler::hit_score(cryptonote_connection_context &context, int32_t score) + { + if (score <= 0) + { + MERROR("Negative score hit"); + return; + } + context.m_score -= score; + if (context.m_score <= DROP_PEERS_ON_SCORE) + drop_connection_with_score(context, 5, false); + } + //------------------------------------------------------------------------------------------------------------------------ + template std::string t_cryptonote_protocol_handler::get_peers_overview() const { std::stringstream ss; diff --git a/src/p2p/net_peerlist.cpp b/src/p2p/net_peerlist.cpp index ce5c67fe5..42ab9727d 100644 --- a/src/p2p/net_peerlist.cpp +++ b/src/p2p/net_peerlist.cpp @@ -288,6 +288,19 @@ namespace nodetool copy_peers(peers.gray, m_peers_gray.get()); copy_peers(peers.anchor, m_peers_anchor.get()); } + + void peerlist_manager::evict_host_from_white_peerlist(const peerlist_entry& pr) + { + peers_indexed::index::type& sorted_index=m_peers_white.get(); + auto i = sorted_index.begin(); + while (i != sorted_index.end()) + { + if (i->adr.is_same_host(pr.adr)) + i = sorted_index.erase(i); + else + ++i; + } + } } BOOST_CLASS_VERSION(nodetool::peerlist_types, nodetool::CURRENT_PEERLIST_STORAGE_ARCHIVE_VER); diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 992462d0b..c794b0f3b 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -109,6 +109,7 @@ namespace nodetool bool get_white_peer_by_index(peerlist_entry& p, size_t i); bool get_gray_peer_by_index(peerlist_entry& p, size_t i); template bool foreach(bool white, const F &f); + void evict_host_from_white_peerlist(const peerlist_entry& pr); bool append_with_peer_white(const peerlist_entry& pr); bool append_with_peer_gray(const peerlist_entry& pr); bool append_with_peer_anchor(const anchor_peerlist_entry& ple); @@ -345,6 +346,7 @@ namespace nodetool if(by_addr_it_wt == m_peers_white.get().end()) { //put new record into white list + evict_host_from_white_peerlist(ple); m_peers_white.insert(ple); trim_white_peerlist(); }else