diff options
Diffstat (limited to 'tests/unit_tests/node_server.cpp')
-rw-r--r-- | tests/unit_tests/node_server.cpp | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp new file mode 100644 index 000000000..2c89323c7 --- /dev/null +++ b/tests/unit_tests/node_server.cpp @@ -0,0 +1,301 @@ +// Copyright (c) 2014-2019, 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 + +#include "gtest/gtest.h" +#include "cryptonote_core/cryptonote_core.h" +#include "p2p/net_node.h" +#include "p2p/net_node.inl" +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "cryptonote_protocol/cryptonote_protocol_handler.inl" + +#define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0} +#define MAKE_IPV4_ADDRESS_PORT(a,b,c,d,e) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),e} +#define MAKE_IPV4_SUBNET(a,b,c,d,e) epee::net_utils::ipv4_network_subnet{MAKE_IP(a,b,c,d),e} + +namespace cryptonote { + class blockchain_storage; +} + +class test_core +{ +public: + void on_synchronized(){} + void safesyncmode(const bool){} + uint64_t get_current_blockchain_height() const {return 1;} + void set_target_blockchain_height(uint64_t) {} + bool init(const boost::program_options::variables_map& vm) {return true ;} + bool deinit(){return true;} + bool get_short_chain_history(std::list<crypto::hash>& ids) const { return true; } + bool get_stat_info(cryptonote::core_stat_info& st_inf) const {return true;} + bool have_block(const crypto::hash& id) const {return true;} + void get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=crypto::null_hash;} + bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; } + bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; } + bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true) { return true; } + void pause_mine(){} + void resume_mine(){} + bool on_idle(){return true;} + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} + bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} + cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); } + bool get_test_drop_download() const {return true;} + bool get_test_drop_download_height() const {return true;} + bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks) { return true; } + bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } + uint64_t get_target_blockchain_height() const { return 1; } + size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } + virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {} + cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; } + bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; } + bool pool_has_tx(const crypto::hash &txid) const { return false; } + bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; } + bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; } + bool get_block_by_hash(const crypto::hash &h, cryptonote::block &blk, bool *orphan = NULL) const { return false; } + uint8_t get_ideal_hard_fork_version() const { return 0; } + uint8_t get_ideal_hard_fork_version(uint64_t height) const { return 0; } + uint8_t get_hard_fork_version(uint64_t height) const { return 0; } + uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return 0; } + cryptonote::difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return 0; } + bool fluffy_blocks_enabled() const { return false; } + uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights) { return 0; } + bool pad_transactions() { return false; } + uint32_t get_blockchain_pruning_seed() const { return 0; } + bool prune_blockchain(uint32_t pruning_seed = 0) { return true; } + bool is_within_compiled_block_hash_area(uint64_t height) const { return false; } + bool has_block_weights(uint64_t height, uint64_t nblocks) const { return false; } + void stop() {} +}; + +typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server; + +static bool is_blocked(Server &server, const epee::net_utils::network_address &address, time_t *t = NULL) +{ + std::map<std::string, time_t> hosts = server.get_blocked_hosts(); + for (auto rec: hosts) + { + if (rec.first == address.host_str()) + { + if (t) + *t = rec.second; + return true; + } + } + return false; +} + +TEST(ban, add) +{ + test_core pr_core; + cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL); + Server server(cprotocol); + cprotocol.set_p2p_endpoint(&server); + + // starts empty + ASSERT_TRUE(server.get_blocked_hosts().empty()); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // add an IP + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // add the same, should not change + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // remove an unblocked IP, should not change + ASSERT_FALSE(server.unblock_host(MAKE_IPV4_ADDRESS(1,2,3,5))); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // remove the IP, ends up empty + ASSERT_TRUE(server.unblock_host(MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.get_blocked_hosts().size() == 0); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // remove the IP from an empty list, still empty + ASSERT_FALSE(server.unblock_host(MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.get_blocked_hosts().size() == 0); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // add two for known amounts of time, they're both blocked + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4), 1)); + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,5), 3)); + ASSERT_TRUE(server.get_blocked_hosts().size() == 2); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + ASSERT_TRUE(server.unblock_host(MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.unblock_host(MAKE_IPV4_ADDRESS(1,2,3,5))); + + // these tests would need to call is_remote_ip_allowed, which is private +#if 0 + // after two seconds, the first IP is unblocked, but not the second yet + sleep(2); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + + // after two more seconds, the second IP is also unblocked + sleep(2); + ASSERT_TRUE(server.get_blocked_hosts().size() == 0); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); +#endif + + // add an IP again, then re-ban for longer, then shorter + time_t t; + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4), 2)); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4), &t)); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + ASSERT_TRUE(t >= 1); + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4), 9)); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4), &t)); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + ASSERT_TRUE(t >= 8); + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4), 5)); + ASSERT_TRUE(server.get_blocked_hosts().size() == 1); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4), &t)); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,5))); + ASSERT_TRUE(t >= 4); +} + +TEST(ban, limit) +{ + test_core pr_core; + cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL); + Server server(cprotocol); + cprotocol.set_p2p_endpoint(&server); + + // starts empty + ASSERT_TRUE(server.get_blocked_hosts().empty()); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4), std::numeric_limits<time_t>::max() - 1)); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS(1,2,3,4), 1)); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); +} + +TEST(ban, subnet) +{ + time_t seconds; + test_core pr_core; + cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL); + Server server(cprotocol); + cprotocol.set_p2p_endpoint(&server); + + ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,24), 10)); + ASSERT_TRUE(server.get_blocked_subnets().size() == 1); + ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,4), &seconds)); + ASSERT_TRUE(seconds >= 9); + ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,255), &seconds)); + ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,0), &seconds)); + ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,4,0), &seconds)); + ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,2,0), &seconds)); + ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,2,3,8,24))); + ASSERT_TRUE(server.get_blocked_subnets().size() == 0); + ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,255), &seconds)); + ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,0), &seconds)); + ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,8), 10)); + ASSERT_TRUE(server.get_blocked_subnets().size() == 1); + ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,255,3,255), &seconds)); + ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,0,3,255), &seconds)); + ASSERT_FALSE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,2,3,8,24))); + ASSERT_TRUE(server.get_blocked_subnets().size() == 1); + ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,8), 10)); + ASSERT_TRUE(server.get_blocked_subnets().size() == 1); + ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,255,0,0,8))); + ASSERT_TRUE(server.get_blocked_subnets().size() == 0); +} + +TEST(ban, ignores_port) +{ + time_t seconds; + test_core pr_core; + cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL); + Server server(cprotocol); + cprotocol.set_p2p_endpoint(&server); + + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS_PORT(1,2,3,4,5))); + ASSERT_TRUE(server.block_host(MAKE_IPV4_ADDRESS_PORT(1,2,3,4,5), std::numeric_limits<time_t>::max() - 1)); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS_PORT(1,2,3,4,5))); + ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS_PORT(1,2,3,4,6))); + ASSERT_TRUE(server.unblock_host(MAKE_IPV4_ADDRESS_PORT(1,2,3,4,5))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS_PORT(1,2,3,4,5))); + ASSERT_FALSE(is_blocked(server,MAKE_IPV4_ADDRESS_PORT(1,2,3,4,6))); +} + +TEST(node_server, bind_same_p2p_port) +{ + const auto new_node = []() -> std::unique_ptr<Server> { + test_core pr_core; + cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL); + std::unique_ptr<Server> server(new Server(cprotocol)); + cprotocol.set_p2p_endpoint(server.get()); + + return server; + }; + + const auto init = [](const std::unique_ptr<Server>& server, const char* port) -> bool { + boost::program_options::options_description desc_options("Command line options"); + cryptonote::core::init_options(desc_options); + Server::init_options(desc_options); + + const char *argv[2] = {nullptr, nullptr}; + boost::program_options::variables_map vm; + boost::program_options::store(boost::program_options::parse_command_line(1, argv, desc_options), vm); + + vm.find(nodetool::arg_p2p_bind_port.name)->second = boost::program_options::variable_value(std::string(port), false); + + boost::program_options::notify(vm); + + return server->init(vm); + }; + + constexpr char port[] = "48080"; + constexpr char port_another[] = "58080"; + + const auto node = new_node(); + EXPECT_TRUE(init(node, port)); + + EXPECT_FALSE(init(new_node(), port)); + EXPECT_TRUE(init(new_node(), port_another)); +} + +namespace nodetool { template class node_server<cryptonote::t_cryptonote_protocol_handler<test_core>>; } +namespace cryptonote { template class t_cryptonote_protocol_handler<test_core>; } |