diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp index 04f967e155..0dd4db5ff8 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp @@ -40,32 +40,63 @@ namespace detail { return block_sync_rate_limit; } -} // namespace detail + /// @return host, port, remainder + inline std::tuple split_host_port_remainder(const std::string& peer_add) { + using std::string; + // host:port[:trx|:blk][:] + if (peer_add.empty()) return {}; - /// @return listen address and block sync rate limit (in bytes/sec) of address string - inline std::tuple parse_listen_address( const std::string& address ) { - auto listen_addr = address; - auto limit = std::string("0"); - auto last_colon_location = address.rfind(':'); - if( auto right_bracket_location = address.find(']'); right_bracket_location != address.npos ) { - if( std::count(address.begin()+right_bracket_location, address.end(), ':') > 1 ) { - listen_addr = std::string(address, 0, last_colon_location); - limit = std::string(address, last_colon_location+1); - } - } else { - if( auto colon_count = std::count(address.begin(), address.end(), ':'); colon_count > 1 ) { + string::size_type p = peer_add[0] == '[' ? peer_add.find(']') : 0; + if (p == 0) { + if( auto colon_count = std::count(peer_add.begin(), peer_add.end(), ':'); colon_count >= 7 ) { EOS_ASSERT( colon_count <= 2, chain::plugin_config_exception, - "Invalid address specification ${addr}; IPv6 addresses must be enclosed in square brackets.", ("addr", address)); - listen_addr = std::string(address, 0, last_colon_location); - limit = std::string(address, last_colon_location+1); + "Invalid address specification ${a}; IPv6 addresses must be enclosed in square brackets.", ("a", peer_add)); } } + string::size_type colon = p != string::npos ? peer_add.find(':', p) : string::npos; + if (colon == string::npos || colon == 0) { + return {}; + } + string::size_type colon2 = peer_add.find(':', colon + 1); + string host = (p > 0) ? peer_add.substr( 0, p+1 ) : peer_add.substr( 0, colon ); + string port = peer_add.substr( colon + 1, colon2 == string::npos ? string::npos : colon2 - (colon + 1)); + string remainder = colon2 == string::npos ? "" : peer_add.substr( colon2 + 1 ); + return {std::move(host), std::move(port), std::move(remainder)}; + } + +} // namespace detail + + /// @return host, port, type + inline std::tuple split_host_port_type(const std::string& peer_add) { + using std::string; + // host:port[:trx|:blk][:] // rate is discarded + if (peer_add.empty()) return {}; + + auto [host, port, remainder] = detail::split_host_port_remainder(peer_add); + if (host.empty()) return {}; + + string::size_type end = remainder.find_first_of( " :+=.,<>!$%^&(*)|-#@\t" ); // future proof by including most symbols without using regex + std::string type = remainder.substr(0, end); + + return {std::move(host), std::move(port), std::move(type)}; + } + + /// @return listen address, type [trx|blk], and block sync rate limit (in bytes/sec) of address string + inline std::tuple parse_listen_address( const std::string& address ) { + + auto [host, port, remainder] = detail::split_host_port_remainder(address); + auto listen_addr = host + ":" + port; + auto limit = remainder; + auto last_colon_location = remainder.rfind(':'); + if (last_colon_location != std::string::npos) { + limit = std::string(remainder, last_colon_location+1); + } auto block_sync_rate_limit = detail::parse_connection_rate_limit(limit); - return {listen_addr, block_sync_rate_limit}; + return {std::move(listen_addr), block_sync_rate_limit}; } - inline std::tuple split_host_port_type(const std::string& peer_add) { + inline std::tuple split_host_xport_type(const std::string& peer_add) { using std::string; // host:port:[|] if (peer_add.empty()) return {}; diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 2cfe3c69f7..c788970f9e 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -800,6 +800,7 @@ namespace eosio { const string& peer_address() const { return peer_addr; } // thread safe, const void set_connection_type( const string& peer_addr ); + void set_peer_connection_type( const string& peer_addr ); bool is_transactions_only_connection()const { return connection_type == transactions_only; } // thread safe, atomic bool is_blocks_only_connection()const { return connection_type == blocks_only; } bool is_transactions_connection() const { return connection_type != blocks_only; } // thread safe, atomic @@ -1286,6 +1287,29 @@ namespace eosio { } } + // called from connection strand + void connection::set_peer_connection_type( const std::string& peer_add ) { + // peer p2p-listen-endpoint received via handshake may indicate they do not want trx or blocks + auto [host, port, type] = net_utils::split_host_port_type(peer_add); + if (host.empty()) { + fc_dlog( logger, "Invalid peer address: ${a}", ("a", peer_add)); + } else if( type.empty() ) { + // peer asked for both, continue with p2p-peer-address type + } else if( type == "trx" ) { + if (connection_type == both) { // only switch to trx if p2p-peer-address didn't specify a connection type + fc_dlog( logger, "Setting peer connection - ${c} type for: ${peer} to transactions only", ("c", connection_id)("peer", peer_add) ); + connection_type = transactions_only; + } + } else if( type == "blk" ) { + if (connection_type == both) { // only switch to blocks if p2p-peer-address didn't specify a connection type + fc_dlog( logger, "Setting peer connection - ${c} type for: ${peer} to blocks only", ("c", connection_id)("peer", peer_add) ); + connection_type = blocks_only; + } + } else { + fc_dlog( logger, "Unknown peer connection - ${c} type: ${t}, for ${peer}", ("c", connection_id)("t", type)("peer", peer_add) ); + } + } + std::string connection::state_str(connection_state s) { switch (s) { case connection_state::connecting: @@ -3445,6 +3469,9 @@ namespace eosio { } } else { peer_dlog(this, "skipping duplicate check, addr == ${pa}, id = ${ni}", ("pa", peer_address())("ni", msg.node_id)); + + // check if peer requests no trx or no blocks + set_peer_connection_type(msg.p2p_address); } if( msg.chain_id != my_impl->chain_id ) { @@ -4226,9 +4253,11 @@ namespace eosio { if(hello.sig == chain::signature_type()) hello.token = sha256(); hello.p2p_address = listen_address; - if( is_transactions_only_connection() ) hello.p2p_address += ":trx"; + if( is_transactions_only_connection() && hello.p2p_address.find(":trx") == std::string::npos ) hello.p2p_address += ":trx"; // if we are not accepting transactions tell peer we are blocks only - if( is_blocks_only_connection() || !my_impl->p2p_accept_transactions ) hello.p2p_address += ":blk"; + if( is_blocks_only_connection() || !my_impl->p2p_accept_transactions ) + if (hello.p2p_address.find(":blk") == std::string::npos) + hello.p2p_address += ":blk"; if( !is_blocks_only_connection() && !my_impl->p2p_accept_transactions ) { peer_dlog( this, "p2p-accept-transactions=false inform peer blocks only connection ${a}", ("a", hello.p2p_address) ); } @@ -4257,15 +4286,17 @@ namespace eosio { void net_plugin::set_program_options( options_description& /*cli*/, options_description& cfg ) { cfg.add_options() - ( "p2p-listen-endpoint", bpo::value< vector >()->default_value( vector(1, string("0.0.0.0:9876:0")) ), "The actual host:port[:] used to listen for incoming p2p connections. May be used multiple times. " + ( "p2p-listen-endpoint", bpo::value< vector >()->default_value( vector(1, string("0.0.0.0:9876:0")) ), + "The actual host:port[:trx|:blk][:] used to listen for incoming p2p connections. May be used multiple times. " " The optional rate cap will limit per connection block sync bandwidth to the specified rate. Total " " allowed bandwidth is the rate-cap multiplied by the connection count limit. A number alone will be " " interpreted as bytes per second. The number may be suffixed with units. Supported units are: " " 'B/s', 'KB/s', 'MB/s, 'GB/s', 'TB/s', 'KiB/s', 'MiB/s', 'GiB/s', 'TiB/s'." " Transactions and blocks outside of sync mode are not throttled." + " The optional 'trx' and 'blk' indicates to peers that only transactions 'trx' or blocks 'blk' should be sent." " Examples:\n" " 192.168.0.100:9876:1MiB/s\n" - " node.eos.io:9876:1512KB/s\n" + " node.eos.io:9876:trx:1512KB/s\n" " node.eos.io:9876:0.5GB/s\n" " [2001:db8:85a3:8d3:1319:8a2e:370:7348]:9876:250KB/s") ( "p2p-server-address", bpo::value< vector >(), "An externally accessible host:port for identifying this node. Defaults to p2p-listen-endpoint. May be used as many times as p2p-listen-endpoint. If provided, the first address will be used in handshakes with other nodes. Otherwise the default is used.") diff --git a/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp b/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp index c288907908..d07d73322a 100644 --- a/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp +++ b/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp @@ -14,6 +14,19 @@ BOOST_AUTO_TEST_CASE(test_parse_rate_limit) { , "[::1]:9876:-250KB/s" , "0.0.0.0:9877:640Kb/s" , "0.0.0.0:9877:999999999999999999999999999TiB/s" + , "0.0.0.0:9876:trx" + , "0.0.0.0:9776:blk:0" + , "0.0.0.0:9877:trx:640KB/s" + , "192.168.0.1:9878:blk:20MiB/s" + , "localhost:9879:trx:0.5KB/s" + , "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9876:trx:250KB/s" + , "[::1]:9876:trx:250KB/s" + , "2001:db8:85a3:8d3:1319:8a2e:370:7348:9876:trx:250KB/s" + , "[::1]:9876:trx:-1KB/s" + , "0.0.0.0:9877:trx:640Kb/s" + , "0.0.0.0:9877:trx:999999999999999999999999999TiB/s" + , "0.0.0.0:9876:trx - 84c470d" + , "0.0.0.0:9877:trx:640KB/s - addition info" }; size_t which = 0; auto [listen_addr, block_sync_rate_limit] = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); @@ -49,4 +62,43 @@ BOOST_AUTO_TEST_CASE(test_parse_rate_limit) { BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception, [](const eosio::chain::plugin_config_exception& e) {return std::strstr(e.top_message().c_str(), "block sync rate limit specification overflowed");}); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9876"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9776"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9877"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 640000u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "192.168.0.1:9878"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 20971520u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "localhost:9879"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 500u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9876"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 250000u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "[::1]:9876"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 250000u); + BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception, + [](const eosio::chain::plugin_config_exception& e) + {return std::strstr(e.top_message().c_str(), "IPv6 addresses must be enclosed in square brackets");}); + BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception, + [](const eosio::chain::plugin_config_exception& e) + {return std::strstr(e.top_message().c_str(), "block sync rate limit must not be negative");}); + BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception, + [](const eosio::chain::plugin_config_exception& e) + {return std::strstr(e.top_message().c_str(), "invalid block sync rate limit specification");}); + BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception, + [](const eosio::chain::plugin_config_exception& e) + {return std::strstr(e.top_message().c_str(), "block sync rate limit specification overflowed");}); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9876"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]); + BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9877"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 640000u); }