diff options
42 files changed, 1264 insertions, 167 deletions
diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index c1aa0fe5f..b38ab5399 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -227,8 +227,12 @@ namespace net_utils std::map<std::string, t_connection_type> server_type_map; void create_server_type_map(); - bool init_server(uint32_t port, const std::string address = "0.0.0.0", ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect); - bool init_server(const std::string port, const std::string& address = "0.0.0.0", ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect); + bool init_server(uint32_t port, const std::string& address = "0.0.0.0", + uint32_t port_ipv6 = 0, const std::string& address_ipv6 = "::", bool use_ipv6 = false, bool require_ipv4 = true, + ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect); + bool init_server(const std::string port, const std::string& address = "0.0.0.0", + const std::string port_ipv6 = "", const std::string address_ipv6 = "::", bool use_ipv6 = false, bool require_ipv4 = true, + ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect); /// Run the server's io_service loop. bool run_server(size_t threads_count, bool wait = true, const boost::thread::attributes& attrs = boost::thread::attributes()); @@ -269,6 +273,7 @@ namespace net_utils } int get_binded_port(){return m_port;} + int get_binded_port_ipv6(){return m_port_ipv6;} long get_connections_count() const { @@ -339,7 +344,9 @@ namespace net_utils /// Run the server's io_service loop. bool worker_thread(); /// Handle completion of an asynchronous accept operation. - void handle_accept(const boost::system::error_code& e); + void handle_accept_ipv4(const boost::system::error_code& e); + void handle_accept_ipv6(const boost::system::error_code& e); + void handle_accept(const boost::system::error_code& e, bool ipv6 = false); bool is_thread_worker(); @@ -360,11 +367,16 @@ namespace net_utils /// Acceptor used to listen for incoming connections. boost::asio::ip::tcp::acceptor acceptor_; + boost::asio::ip::tcp::acceptor acceptor_ipv6; epee::net_utils::network_address default_remote; std::atomic<bool> m_stop_signal_sent; uint32_t m_port; + uint32_t m_port_ipv6; std::string m_address; + std::string m_address_ipv6; + bool m_use_ipv6; + bool m_require_ipv4; std::string m_thread_name_prefix; //TODO: change to enum server_type, now used size_t m_threads_count; std::vector<boost::shared_ptr<boost::thread> > m_threads; @@ -376,6 +388,8 @@ namespace net_utils /// The next connection to be accepted connection_ptr new_connection_; + connection_ptr new_connection_ipv6; + boost::mutex connections_mutex; std::set<connection_ptr> connections_; diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 0721366aa..19e9c9af9 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -145,10 +145,18 @@ PRAGMA_WARNING_DISABLE_VS(4355) boost::system::error_code ec; auto remote_ep = socket().remote_endpoint(ec); CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get remote endpoint: " << ec.message() << ':' << ec.value()); - CHECK_AND_NO_ASSERT_MES(remote_ep.address().is_v4(), false, "IPv6 not supported here"); + CHECK_AND_NO_ASSERT_MES(remote_ep.address().is_v4() || remote_ep.address().is_v6(), false, "only IPv4 and IPv6 supported here"); - const unsigned long ip_{boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong())}; - return start(is_income, is_multithreaded, ipv4_network_address{uint32_t(ip_), remote_ep.port()}); + if (remote_ep.address().is_v4()) + { + const unsigned long ip_ = boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong()); + return start(is_income, is_multithreaded, ipv4_network_address{uint32_t(ip_), remote_ep.port()}); + } + else + { + const auto ip_{remote_ep.address().to_v6()}; + return start(is_income, is_multithreaded, ipv6_network_address{ip_, remote_ep.port()}); + } CATCH_ENTRY_L0("connection<t_protocol_handler>::start()", false); } //--------------------------------------------------------------------------------- @@ -904,12 +912,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) m_io_service_local_instance(new worker()), io_service_(m_io_service_local_instance->io_service), acceptor_(io_service_), + acceptor_ipv6(io_service_), default_remote(), m_stop_signal_sent(false), m_port(0), m_threads_count(0), m_thread_index(0), m_connection_type( connection_type ), - new_connection_() + new_connection_(), + new_connection_ipv6() { create_server_type_map(); m_thread_name_prefix = "NET"; @@ -920,12 +930,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) m_state(boost::make_shared<typename connection<t_protocol_handler>::shared_state>()), io_service_(extarnal_io_service), acceptor_(io_service_), + acceptor_ipv6(io_service_), default_remote(), m_stop_signal_sent(false), m_port(0), m_threads_count(0), m_thread_index(0), m_connection_type(connection_type), - new_connection_() + new_connection_(), + new_connection_ipv6() { create_server_type_map(); m_thread_name_prefix = "NET"; @@ -947,29 +959,92 @@ PRAGMA_WARNING_DISABLE_VS(4355) } //--------------------------------------------------------------------------------- template<class t_protocol_handler> - bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string address, ssl_options_t ssl_options) + bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string& address, + uint32_t port_ipv6, const std::string& address_ipv6, bool use_ipv6, bool require_ipv4, + ssl_options_t ssl_options) { TRY_ENTRY(); m_stop_signal_sent = false; m_port = port; + m_port_ipv6 = port_ipv6; m_address = address; + m_address_ipv6 = address_ipv6; + m_use_ipv6 = use_ipv6; + m_require_ipv4 = require_ipv4; + if (ssl_options) m_state->configure_ssl(std::move(ssl_options)); - // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). - boost::asio::ip::tcp::resolver resolver(io_service_); - boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast<std::string>(port), boost::asio::ip::tcp::resolver::query::canonical_name); - boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); - acceptor_.open(endpoint.protocol()); - acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - acceptor_.bind(endpoint); - acceptor_.listen(); - boost::asio::ip::tcp::endpoint binded_endpoint = acceptor_.local_endpoint(); - m_port = binded_endpoint.port(); - MDEBUG("start accept"); - new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, m_state->ssl_options().support)); - acceptor_.async_accept(new_connection_->socket(), - boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept, this, - boost::asio::placeholders::error)); + + std::string ipv4_failed = ""; + std::string ipv6_failed = ""; + try + { + boost::asio::ip::tcp::resolver resolver(io_service_); + boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast<std::string>(port), boost::asio::ip::tcp::resolver::query::canonical_name); + boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); + acceptor_.open(endpoint.protocol()); + // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). + acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor_.bind(endpoint); + acceptor_.listen(); + boost::asio::ip::tcp::endpoint binded_endpoint = acceptor_.local_endpoint(); + m_port = binded_endpoint.port(); + MDEBUG("start accept (IPv4)"); + new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, m_state->ssl_options().support)); + acceptor_.async_accept(new_connection_->socket(), + boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept_ipv4, this, + boost::asio::placeholders::error)); + } + catch (const std::exception &e) + { + ipv4_failed = e.what(); + } + + if (ipv4_failed != "") + { + MERROR("Failed to bind IPv4: " << ipv4_failed); + if (require_ipv4) + { + throw std::runtime_error("Failed to bind IPv4 (set to required)"); + } + } + + if (use_ipv6) + { + try + { + if (port_ipv6 == 0) port_ipv6 = port; // default arg means bind to same port as ipv4 + boost::asio::ip::tcp::resolver resolver(io_service_); + boost::asio::ip::tcp::resolver::query query(address_ipv6, boost::lexical_cast<std::string>(port_ipv6), boost::asio::ip::tcp::resolver::query::canonical_name); + boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); + acceptor_ipv6.open(endpoint.protocol()); + // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). + acceptor_ipv6.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor_ipv6.set_option(boost::asio::ip::v6_only(true)); + acceptor_ipv6.bind(endpoint); + acceptor_ipv6.listen(); + boost::asio::ip::tcp::endpoint binded_endpoint = acceptor_ipv6.local_endpoint(); + m_port_ipv6 = binded_endpoint.port(); + MDEBUG("start accept (IPv6)"); + new_connection_ipv6.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, m_state->ssl_options().support)); + acceptor_ipv6.async_accept(new_connection_ipv6->socket(), + boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept_ipv6, this, + boost::asio::placeholders::error)); + } + catch (const std::exception &e) + { + ipv6_failed = e.what(); + } + } + + if (use_ipv6 && ipv6_failed != "") + { + MERROR("Failed to bind IPv6: " << ipv6_failed); + if (ipv4_failed != "") + { + throw std::runtime_error("Failed to bind IPv4 and IPv6"); + } + } return true; } @@ -988,15 +1063,23 @@ PRAGMA_WARNING_DISABLE_VS(4355) PUSH_WARNINGS DISABLE_GCC_WARNING(maybe-uninitialized) template<class t_protocol_handler> - bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, ssl_options_t ssl_options) + bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, + const std::string port_ipv6, const std::string address_ipv6, bool use_ipv6, bool require_ipv4, + ssl_options_t ssl_options) { uint32_t p = 0; + uint32_t p_ipv6 = 0; if (port.size() && !string_tools::get_xtype_from_string(p, port)) { MERROR("Failed to convert port no = " << port); return false; } - return this->init_server(p, address, std::move(ssl_options)); + + if (port_ipv6.size() && !string_tools::get_xtype_from_string(p_ipv6, port_ipv6)) { + MERROR("Failed to convert port no = " << port_ipv6); + return false; + } + return this->init_server(p, address, p_ipv6, address_ipv6, use_ipv6, require_ipv4, std::move(ssl_options)); } POP_WARNINGS //--------------------------------------------------------------------------------- @@ -1088,7 +1171,7 @@ POP_WARNINGS { //some problems with the listening socket ?.. _dbg1("Net service stopped without stop request, restarting..."); - if(!this->init_server(m_port, m_address)) + if(!this->init_server(m_port, m_address, m_port_ipv6, m_address_ipv6, m_use_ipv6, m_require_ipv4)) { _dbg1("Reiniting service failed, exit."); return false; @@ -1154,29 +1237,52 @@ POP_WARNINGS } //--------------------------------------------------------------------------------- template<class t_protocol_handler> - void boosted_tcp_server<t_protocol_handler>::handle_accept(const boost::system::error_code& e) + void boosted_tcp_server<t_protocol_handler>::handle_accept_ipv4(const boost::system::error_code& e) + { + this->handle_accept(e, false); + } + //--------------------------------------------------------------------------------- + template<class t_protocol_handler> + void boosted_tcp_server<t_protocol_handler>::handle_accept_ipv6(const boost::system::error_code& e) + { + this->handle_accept(e, true); + } + //--------------------------------------------------------------------------------- + template<class t_protocol_handler> + void boosted_tcp_server<t_protocol_handler>::handle_accept(const boost::system::error_code& e, bool ipv6) { MDEBUG("handle_accept"); + + boost::asio::ip::tcp::acceptor* current_acceptor = &acceptor_; + connection_ptr* current_new_connection = &new_connection_; + auto accept_function_pointer = &boosted_tcp_server<t_protocol_handler>::handle_accept_ipv4; + if (ipv6) + { + current_acceptor = &acceptor_ipv6; + current_new_connection = &new_connection_ipv6; + accept_function_pointer = &boosted_tcp_server<t_protocol_handler>::handle_accept_ipv6; + } + try { if (!e) { - if (m_connection_type == e_connection_type_RPC) { - const char *ssl_message = "unknown"; - switch (new_connection_->get_ssl_support()) - { - case epee::net_utils::ssl_support_t::e_ssl_support_disabled: ssl_message = "disabled"; break; - case epee::net_utils::ssl_support_t::e_ssl_support_enabled: ssl_message = "enabled"; break; - case epee::net_utils::ssl_support_t::e_ssl_support_autodetect: ssl_message = "autodetection"; break; - } - MDEBUG("New server for RPC connections, SSL " << ssl_message); - new_connection_->setRpcStation(); // hopefully this is not needed actually - } - connection_ptr conn(std::move(new_connection_)); - new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, conn->get_ssl_support())); - acceptor_.async_accept(new_connection_->socket(), - boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept, this, - boost::asio::placeholders::error)); + if (m_connection_type == e_connection_type_RPC) { + const char *ssl_message = "unknown"; + switch ((*current_new_connection)->get_ssl_support()) + { + case epee::net_utils::ssl_support_t::e_ssl_support_disabled: ssl_message = "disabled"; break; + case epee::net_utils::ssl_support_t::e_ssl_support_enabled: ssl_message = "enabled"; break; + case epee::net_utils::ssl_support_t::e_ssl_support_autodetect: ssl_message = "autodetection"; break; + } + MDEBUG("New server for RPC connections, SSL " << ssl_message); + (*current_new_connection)->setRpcStation(); // hopefully this is not needed actually + } + connection_ptr conn(std::move((*current_new_connection))); + (*current_new_connection).reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, conn->get_ssl_support())); + current_acceptor->async_accept((*current_new_connection)->socket(), + boost::bind(accept_function_pointer, this, + boost::asio::placeholders::error)); boost::asio::socket_base::keep_alive opt(true); conn->socket().set_option(opt); @@ -1208,10 +1314,10 @@ POP_WARNINGS assert(m_state != nullptr); // always set in constructor _erro("Some problems at accept: " << e.message() << ", connections_count = " << m_state->sock_count); misc_utils::sleep_no_w(100); - new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, new_connection_->get_ssl_support())); - acceptor_.async_accept(new_connection_->socket(), - boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept, this, - boost::asio::placeholders::error)); + (*current_new_connection).reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, (*current_new_connection)->get_ssl_support())); + current_acceptor->async_accept((*current_new_connection)->socket(), + boost::bind(accept_function_pointer, this, + boost::asio::placeholders::error)); } //--------------------------------------------------------------------------------- template<class t_protocol_handler> @@ -1345,23 +1451,84 @@ POP_WARNINGS epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); - ////////////////////////////////////////////////////////////////////////// + bool try_ipv6 = false; + boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); - boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); + boost::system::error_code resolve_error; + boost::asio::ip::tcp::resolver::iterator iterator; + try + { + //resolving ipv4 address as ipv6 throws, catch here and move on + iterator = resolver.resolve(query, resolve_error); + } + catch (const boost::system::system_error& e) + { + if (!m_use_ipv6 || (resolve_error != boost::asio::error::host_not_found && + resolve_error != boost::asio::error::host_not_found_try_again)) + { + throw; + } + try_ipv6 = true; + } + catch (...) + { + throw; + } + + std::string bind_ip_to_use; + boost::asio::ip::tcp::resolver::iterator end; if(iterator == end) { - _erro("Failed to resolve " << adr); - return false; + if (!m_use_ipv6) + { + _erro("Failed to resolve " << adr); + return false; + } + else + { + try_ipv6 = true; + MINFO("Resolving address as IPv4 failed, trying IPv6"); + } + } + else + { + bind_ip_to_use = bind_ip; } - ////////////////////////////////////////////////////////////////////////// + if (try_ipv6) + { + boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); + + iterator = resolver.resolve(query6, resolve_error); + + if(iterator == end) + { + _erro("Failed to resolve " << adr); + return false; + } + else + { + if (bind_ip == "0.0.0.0") + { + bind_ip_to_use = "::"; + } + else + { + bind_ip_to_use = ""; + } + + } + + } + + LOG_ERROR("Trying connect to " << adr << ":" << port << ", bind_ip = " << bind_ip_to_use); //boost::asio::ip::tcp::endpoint remote_endpoint(boost::asio::ip::address::from_string(addr.c_str()), port); boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); - auto try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip, conn_timeout, ssl_support); + auto try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip_to_use, conn_timeout, ssl_support); if (try_connect_result == CONNECT_FAILURE) return false; if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect && try_connect_result == CONNECT_NO_SSL) @@ -1369,7 +1536,7 @@ POP_WARNINGS // we connected, but could not connect with SSL, try without MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL"); new_connection_l->disable_ssl(); - try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip, conn_timeout, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip_to_use, conn_timeout, epee::net_utils::ssl_support_t::e_ssl_support_disabled); if (try_connect_result != CONNECT_SUCCESS) return false; } @@ -1409,17 +1576,59 @@ POP_WARNINGS epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); - ////////////////////////////////////////////////////////////////////////// + bool try_ipv6 = false; + boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); - boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); + boost::system::error_code resolve_error; + boost::asio::ip::tcp::resolver::iterator iterator; + try + { + //resolving ipv4 address as ipv6 throws, catch here and move on + iterator = resolver.resolve(query, resolve_error); + } + catch (const boost::system::system_error& e) + { + if (!m_use_ipv6 || (resolve_error != boost::asio::error::host_not_found && + resolve_error != boost::asio::error::host_not_found_try_again)) + { + throw; + } + try_ipv6 = true; + } + catch (...) + { + throw; + } + boost::asio::ip::tcp::resolver::iterator end; if(iterator == end) { - _erro("Failed to resolve " << adr); - return false; + if (!try_ipv6) + { + _erro("Failed to resolve " << adr); + return false; + } + else + { + MINFO("Resolving address as IPv4 failed, trying IPv6"); + } } - ////////////////////////////////////////////////////////////////////////// + + if (try_ipv6) + { + boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); + + iterator = resolver.resolve(query6, resolve_error); + + if(iterator == end) + { + _erro("Failed to resolve " << adr); + return false; + } + } + + boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); sock_.open(remote_endpoint.protocol()); diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl index ae8e43477..790d0f3b1 100644 --- a/contrib/epee/include/net/http_protocol_handler.inl +++ b/contrib/epee/include/net/http_protocol_handler.inl @@ -577,6 +577,10 @@ namespace net_utils if (query_info.m_http_method != http::http_method_options) { res = handle_request(query_info, response); + if (response.m_response_code == 500) + { + m_want_close = true; // close on all "Internal server error"s + } } else { diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 4b2053091..07ed8157b 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -71,7 +71,7 @@ MINFO(m_conn_context << "calling " << s_pattern); \ if(!callback_f(static_cast<command_type::request&>(req), static_cast<command_type::response&>(resp), &m_conn_context)) \ { \ - LOG_ERROR("Failed to " << #callback_f << "()"); \ + MERROR(m_conn_context << "Failed to " << #callback_f << "()"); \ response_info.m_response_code = 500; \ response_info.m_response_comment = "Internal Server Error"; \ return true; \ @@ -99,7 +99,7 @@ MINFO(m_conn_context << "calling " << s_pattern); \ if(!callback_f(static_cast<command_type::request&>(req), static_cast<command_type::response&>(resp), &m_conn_context)) \ { \ - LOG_ERROR("Failed to " << #callback_f << "()"); \ + MERROR(m_conn_context << "Failed to " << #callback_f << "()"); \ response_info.m_response_code = 500; \ response_info.m_response_comment = "Internal Server Error"; \ return true; \ diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h index fc2dcbf67..6cd19f17b 100644 --- a/contrib/epee/include/net/http_server_impl_base.h +++ b/contrib/epee/include/net/http_server_impl_base.h @@ -57,6 +57,7 @@ namespace epee {} bool init(std::function<void(size_t, uint8_t*)> rng, const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0", + const std::string& bind_ipv6_address = "::", bool use_ipv6 = false, bool require_ipv4 = true, std::vector<std::string> access_control_origins = std::vector<std::string>(), boost::optional<net_utils::http::login> user = boost::none, net_utils::ssl_options_t ssl_options = net_utils::ssl_support_t::e_ssl_support_autodetect) @@ -75,8 +76,12 @@ namespace epee m_net_server.get_config_object().m_user = std::move(user); - MGINFO("Binding on " << bind_ip << ":" << bind_port); - bool res = m_net_server.init_server(bind_port, bind_ip, std::move(ssl_options)); + MGINFO("Binding on " << bind_ip << " (IPv4):" << bind_port); + if (use_ipv6) + { + MGINFO("Binding on " << bind_ipv6_address << " (IPv6):" << bind_port); + } + bool res = m_net_server.init_server(bind_port, bind_ip, bind_port, bind_ipv6_address, use_ipv6, require_ipv4, std::move(ssl_options)); if(!res) { LOG_ERROR("Failed to bind server"); diff --git a/contrib/epee/include/net/local_ip.h b/contrib/epee/include/net/local_ip.h index 52c5855b9..7523f9d81 100644 --- a/contrib/epee/include/net/local_ip.h +++ b/contrib/epee/include/net/local_ip.h @@ -27,10 +27,38 @@ #pragma once +#include <string> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/asio/ip/address_v6.hpp> + namespace epee { namespace net_utils { + + inline + bool is_ipv6_local(const std::string& ip) + { + auto addr = boost::asio::ip::make_address_v6(ip); + + // ipv6 link-local unicast addresses are fe80::/10 + bool is_link_local = addr.is_link_local(); + + auto addr_bytes = addr.to_bytes(); + + // ipv6 unique local unicast addresses start with fc00::/7 -- (fcXX or fdXX) + bool is_unique_local_unicast = (addr_bytes[0] == 0xfc || addr_bytes[0] == 0xfd); + + return is_link_local || is_unique_local_unicast; + } + + inline + bool is_ipv6_loopback(const std::string& ip) + { + // ipv6 loopback is ::1 + return boost::asio::ip::address_v6::from_string(ip).is_loopback(); + } + inline bool is_ip_local(uint32_t ip) { diff --git a/contrib/epee/include/net/net_parse_helpers.h b/contrib/epee/include/net/net_parse_helpers.h index 708cce0ff..1d156d19c 100644 --- a/contrib/epee/include/net/net_parse_helpers.h +++ b/contrib/epee/include/net/net_parse_helpers.h @@ -94,7 +94,7 @@ namespace net_utils return true; } - inline + inline bool parse_uri(const std::string uri, http::uri_content& content) { @@ -128,11 +128,51 @@ namespace net_utils return true; } + inline + bool parse_url_ipv6(const std::string url_str, http::url_content& content) + { + STATIC_REGEXP_EXPR_1(rexp_match_uri, "^((.*?)://)?(\\[(.*)\\](:(\\d+))?)(.*)?", boost::regex::icase | boost::regex::normal); + // 12 3 4 5 6 7 - inline + content.port = 0; + boost::smatch result; + if(!(boost::regex_search(url_str, result, rexp_match_uri, boost::match_default) && result[0].matched)) + { + LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << rexp_match_uri); + //content.m_path = uri; + return false; + } + if(result[2].matched) + { + content.schema = result[2]; + } + if(result[4].matched) + { + content.host = result[4]; + } + else // if host not matched, matching should be considered failed + { + return false; + } + if(result[6].matched) + { + content.port = boost::lexical_cast<uint64_t>(result[6]); + } + if(result[7].matched) + { + content.uri = result[7]; + return parse_uri(result[7], content.m_uri_content); + } + + return true; + } + + inline bool parse_url(const std::string url_str, http::url_content& content) { + if (parse_url_ipv6(url_str, content)) return true; + ///iframe_test.html?api_url=http://api.vk.com/api.php&api_id=3289090&api_settings=1&viewer_id=562964060&viewer_type=0&sid=0aad8d1c5713130f9ca0076f2b7b47e532877424961367d81e7fa92455f069be7e21bc3193cbd0be11895&secret=368ebbc0ef&access_token=668bc03f43981d883f73876ffff4aa8564254b359cc745dfa1b3cde7bdab2e94105d8f6d8250717569c0a7&user_id=0&group_id=0&is_app_user=1&auth_key=d2f7a895ca5ff3fdb2a2a8ae23fe679a&language=0&parent_language=0&ad_info=ElsdCQBaQlxiAQRdFUVUXiN2AVBzBx5pU1BXIgZUJlIEAWcgAUoLQg==&referrer=unknown&lc_name=9834b6a3&hash= //STATIC_REGEXP_EXPR_1(rexp_match_uri, "^([^?#]*)(\\?([^#]*))?(#(.*))?", boost::regex::icase | boost::regex::normal); STATIC_REGEXP_EXPR_1(rexp_match_uri, "^((.*?)://)?(([^/:]*)(:(\\d+))?)(.*)?", boost::regex::icase | boost::regex::normal); diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index fce01311c..5ae3e53b3 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -31,6 +31,7 @@ #include <boost/uuid/uuid.hpp> #include <boost/asio/io_service.hpp> +#include <boost/asio/ip/address_v6.hpp> #include <typeinfo> #include <type_traits> #include "enums.h" @@ -154,6 +155,59 @@ namespace net_utils inline bool operator>=(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept { return !lhs.less(rhs); } + class ipv6_network_address + { + protected: + boost::asio::ip::address_v6 m_address; + uint16_t m_port; + + public: + ipv6_network_address() + : ipv6_network_address(boost::asio::ip::address_v6::loopback(), 0) + {} + + ipv6_network_address(const boost::asio::ip::address_v6& ip, uint16_t port) + : m_address(ip), m_port(port) + { + } + + bool equal(const ipv6_network_address& other) const noexcept; + bool less(const ipv6_network_address& other) const noexcept; + bool is_same_host(const ipv6_network_address& other) const noexcept + { return m_address == other.m_address; } + + boost::asio::ip::address_v6 ip() const noexcept { return m_address; } + uint16_t port() const noexcept { return m_port; } + std::string str() const; + std::string host_str() const; + bool is_loopback() const; + bool is_local() const; + static constexpr address_type get_type_id() noexcept { return address_type::ipv6; } + static constexpr zone get_zone() noexcept { return zone::public_; } + static constexpr bool is_blockable() noexcept { return true; } + + static const uint8_t ID = 2; + BEGIN_KV_SERIALIZE_MAP() + boost::asio::ip::address_v6::bytes_type bytes = this_ref.m_address.to_bytes(); + epee::serialization::selector<is_store>::serialize_t_val_as_blob(bytes, stg, hparent_section, "addr"); + const_cast<boost::asio::ip::address_v6&>(this_ref.m_address) = boost::asio::ip::address_v6(bytes); + KV_SERIALIZE(m_port) + END_KV_SERIALIZE_MAP() + }; + + inline bool operator==(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept + { return lhs.equal(rhs); } + inline bool operator!=(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept + { return !lhs.equal(rhs); } + inline bool operator<(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept + { return lhs.less(rhs); } + inline bool operator<=(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept + { return !rhs.less(lhs); } + inline bool operator>(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept + { return rhs.less(lhs); } + inline bool operator>=(const ipv6_network_address& lhs, const ipv6_network_address& rhs) noexcept + { return !lhs.less(rhs); } + class network_address { struct interface @@ -261,6 +315,8 @@ namespace net_utils { case address_type::ipv4: return this_ref.template serialize_addr<ipv4_network_address>(is_store_, stg, hparent_section); + case address_type::ipv6: + return this_ref.template serialize_addr<ipv6_network_address>(is_store_, stg, hparent_section); case address_type::tor: return this_ref.template serialize_addr<net::tor_address>(is_store_, stg, hparent_section); case address_type::i2p: diff --git a/contrib/epee/src/net_helper.cpp b/contrib/epee/src/net_helper.cpp index 3543f5716..719f1c8e0 100644 --- a/contrib/epee/src/net_helper.cpp +++ b/contrib/epee/src/net_helper.cpp @@ -11,10 +11,39 @@ namespace net_utils ////////////////////////////////////////////////////////////////////////// boost::asio::ip::tcp::resolver resolver(GET_IO_SERVICE(timeout)); boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), addr, port, boost::asio::ip::tcp::resolver::query::canonical_name); - boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); + + bool try_ipv6 = false; + boost::asio::ip::tcp::resolver::iterator iterator; boost::asio::ip::tcp::resolver::iterator end; - if(iterator == end) // Documentation states that successful call is guaranteed to be non-empty - throw boost::system::system_error{boost::asio::error::fault, "Failed to resolve " + addr}; + boost::system::error_code resolve_error; + try + { + iterator = resolver.resolve(query, resolve_error); + if(iterator == end) // Documentation states that successful call is guaranteed to be non-empty + { + // if IPv4 resolution fails, try IPv6. Unintentional outgoing IPv6 connections should only + // be possible if for some reason a hostname was given and that hostname fails IPv4 resolution, + // so at least for now there should not be a need for a flag "using ipv6 is ok" + try_ipv6 = true; + } + + } + catch (const boost::system::system_error& e) + { + if (resolve_error != boost::asio::error::host_not_found && + resolve_error != boost::asio::error::host_not_found_try_again) + { + throw; + } + try_ipv6 = true; + } + if (try_ipv6) + { + boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), addr, port, boost::asio::ip::tcp::resolver::query::canonical_name); + iterator = resolver.resolve(query6); + if (iterator == end) + throw boost::system::system_error{boost::asio::error::fault, "Failed to resolve " + addr}; + } ////////////////////////////////////////////////////////////////////////// diff --git a/contrib/epee/src/net_utils_base.cpp b/contrib/epee/src/net_utils_base.cpp index b7f07a23b..5cc49cc71 100644 --- a/contrib/epee/src/net_utils_base.cpp +++ b/contrib/epee/src/net_utils_base.cpp @@ -21,6 +21,19 @@ namespace epee { namespace net_utils bool ipv4_network_address::is_loopback() const { return net_utils::is_ip_loopback(ip()); } bool ipv4_network_address::is_local() const { return net_utils::is_ip_local(ip()); } + bool ipv6_network_address::equal(const ipv6_network_address& other) const noexcept + { return is_same_host(other) && port() == other.port(); } + + bool ipv6_network_address::less(const ipv6_network_address& other) const noexcept + { return is_same_host(other) ? port() < other.port() : m_address < other.m_address; } + + std::string ipv6_network_address::str() const + { return std::string("[") + host_str() + "]:" + std::to_string(port()); } + + std::string ipv6_network_address::host_str() const { return m_address.to_string(); } + bool ipv6_network_address::is_loopback() const { return m_address.is_loopback(); } + bool ipv6_network_address::is_local() const { return m_address.is_link_local(); } + bool ipv4_network_subnet::equal(const ipv4_network_subnet& other) const noexcept { return is_same_host(other) && m_mask == other.m_mask; } diff --git a/src/common/util.cpp b/src/common/util.cpp index db5aa3052..0fa9e8dc1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1068,6 +1068,23 @@ std::string get_nix_version_display_string() return std::string(buffer); } + std::string get_human_readable_timespan(uint64_t seconds) + { + if (seconds < 60) + return std::to_string(seconds) + " seconds"; + if (seconds < 3600) + return std::to_string((uint64_t)(seconds / 60)) + " minutes"; + if (seconds < 3600 * 24) + return std::to_string((uint64_t)(seconds / 3600)) + " hours"; + if (seconds < 3600 * 24 * 30.5) + return std::to_string((uint64_t)(seconds / (3600 * 24))) + " days"; + if (seconds < 3600 * 24 * 365.25) + return std::to_string((uint64_t)(seconds / (3600 * 24 * 30.5))) + " months"; + if (seconds < 3600 * 24 * 365.25 * 100) + return std::to_string((uint64_t)(seconds / (3600 * 24 * 30.5 * 365.25))) + " years"; + return "a long time"; + } + std::string get_human_readable_bytes(uint64_t bytes) { // Use 1024 for "kilo", 1024*1024 for "mega" and so on instead of the more modern and standard-conforming diff --git a/src/common/util.h b/src/common/util.h index f6d5c9b1f..b0f734eff 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -245,5 +245,7 @@ namespace tools std::string get_human_readable_timestamp(uint64_t ts); + std::string get_human_readable_timespan(uint64_t seconds); + std::string get_human_readable_bytes(uint64_t bytes); } diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 36b63f254..3d7200fae 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -48,6 +48,7 @@ namespace cryptonote bool m_overspend; bool m_fee_too_low; bool m_not_rct; + bool m_too_few_outputs; }; struct block_verification_context diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 56b6a63b7..b68bb41e1 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -128,6 +128,8 @@ #define P2P_SUPPORT_FLAG_FLUFFY_BLOCKS 0x01 #define P2P_SUPPORT_FLAGS P2P_SUPPORT_FLAG_FLUFFY_BLOCKS +#define RPC_IP_FAILS_BEFORE_BLOCK 3 + #define ALLOW_DEBUG_COMMANDS #define CRYPTONOTE_NAME "bitmonero" @@ -147,6 +149,7 @@ #define HF_VERSION_PER_BYTE_FEE 8 #define HF_VERSION_SMALLER_BP 10 #define HF_VERSION_LONG_TERM_BLOCK_WEIGHT 10 +#define HF_VERSION_MIN_2_OUTPUTS 12 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 31e2ac136..0ea81f19a 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2840,6 +2840,19 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, const uint8_t hf_version = m_hardfork->get_current_version(); + if (hf_version >= HF_VERSION_MIN_2_OUTPUTS) + { + if (tx.version >= 2) + { + if (tx.vout.size() < 2) + { + MERROR_VER("Tx " << get_transaction_hash(tx) << " has fewer than two outputs"); + tvc.m_too_few_outputs = true; + return false; + } + } + } + // from hard fork 2, we require mixin at least 2 unless one output cannot mix with 2 others // if one output cannot mix with 2 others, we accept at most 1 output that can mix if (hf_version >= 2) @@ -4840,9 +4853,9 @@ std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_ou return m_db->get_output_histogram(amounts, unlocked, recent_cutoff, min_count); } -std::list<std::pair<Blockchain::block_extended_info,std::vector<crypto::hash>>> Blockchain::get_alternative_chains() const +std::vector<std::pair<Blockchain::block_extended_info,std::vector<crypto::hash>>> Blockchain::get_alternative_chains() const { - std::list<std::pair<Blockchain::block_extended_info,std::vector<crypto::hash>>> chains; + std::vector<std::pair<Blockchain::block_extended_info,std::vector<crypto::hash>>> chains; blocks_ext_by_hash alt_blocks; alt_blocks.reserve(m_db->get_alt_block_count()); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index b1c23b704..d95f2dceb 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -950,9 +950,9 @@ namespace cryptonote /** * @brief returns a set of known alternate chains * - * @return a list of chains + * @return a vector of chains */ - std::list<std::pair<block_extended_info,std::vector<crypto::hash>>> get_alternative_chains() const; + std::vector<std::pair<block_extended_info,std::vector<crypto::hash>>> get_alternative_chains() const; void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const txpool_tx_meta_t &meta); void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 2ebd99904..a3a92ab60 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1844,7 +1844,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_block_rate() { - if (m_offline || m_nettype == FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height()) + if (m_offline || m_nettype == FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height() || m_target_blockchain_height == 0) { MDEBUG("Not checking block rate, offline or syncing"); return true; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 778d7b4d8..924447701 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -674,11 +674,38 @@ bool t_command_parser_executor::alt_chain_info(const std::vector<std::string>& a { if(args.size() > 1) { - std::cout << "usage: alt_chain_info [block_hash]" << std::endl; + std::cout << "usage: alt_chain_info [block_hash|>N|-N]" << std::endl; return false; } - return m_executor.alt_chain_info(args.size() == 1 ? args[0] : ""); + std::string tip; + size_t above = 0; + uint64_t last_blocks = 0; + if (args.size() == 1) + { + if (args[0].size() > 0 && args[0][0] == '>') + { + if (!epee::string_tools::get_xtype_from_string(above, args[0].c_str() + 1)) + { + std::cout << "invalid above parameter" << std::endl; + return false; + } + } + else if (args[0].size() > 0 && args[0][0] == '-') + { + if (!epee::string_tools::get_xtype_from_string(last_blocks, args[0].c_str() + 1)) + { + std::cout << "invalid last_blocks parameter" << std::endl; + return false; + } + } + else + { + tip = args[0]; + } + } + + return m_executor.alt_chain_info(tip, above, last_blocks); } bool t_command_parser_executor::print_blockchain_dynamic_stats(const std::vector<std::string>& args) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 1da6398b4..dbf0409e5 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -69,7 +69,7 @@ namespace { std::string id_str; std::string port_str; std::string elapsed = peer.last_seen == 0 ? "never" : epee::misc_utils::get_time_interval_string(now - last_seen); - std::string ip_str = epee::string_tools::get_ip_string_from_int32(peer.ip); + std::string ip_str = peer.ip != 0 ? epee::string_tools::get_ip_string_from_int32(peer.ip) : std::string("[") + peer.host + "]"; std::stringstream peer_id_str; peer_id_str << std::hex << std::setw(16) << peer.id; peer_id_str >> id_str; @@ -1841,7 +1841,7 @@ bool t_rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t cou return true; } -bool t_rpc_command_executor::alt_chain_info(const std::string &tip) +bool t_rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, uint64_t last_blocks) { cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; @@ -1878,16 +1878,31 @@ bool t_rpc_command_executor::alt_chain_info(const std::string &tip) if (tip.empty()) { - tools::msg_writer() << boost::lexical_cast<std::string>(res.chains.size()) << " alternate chains found:"; - for (const auto &chain: res.chains) + auto chains = res.chains; + std::sort(chains.begin(), chains.end(), [](const cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info &info0, cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info &info1){ return info0.height < info1.height; }); + std::vector<size_t> display; + for (size_t i = 0; i < chains.size(); ++i) { - uint64_t start_height = (chain.height - chain.length + 1); + const auto &chain = chains[i]; + if (chain.length <= above) + continue; + const uint64_t start_height = (chain.height - chain.length + 1); + if (last_blocks > 0 && ires.height - 1 - start_height >= last_blocks) + continue; + display.push_back(i); + } + tools::msg_writer() << boost::lexical_cast<std::string>(display.size()) << " alternate chains found:"; + for (const size_t idx: display) + { + const auto &chain = chains[idx]; + const uint64_t start_height = (chain.height - chain.length + 1); tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) << " deep), diff " << chain.wide_difficulty << ": " << chain.block_hash; } } else { + const uint64_t now = time(NULL); const auto i = std::find_if(res.chains.begin(), res.chains.end(), [&tip](cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info &info){ return info.block_hash == tip; }); if (i != res.chains.end()) { @@ -1899,6 +1914,49 @@ bool t_rpc_command_executor::alt_chain_info(const std::string &tip) for (const std::string &block_id: chain.block_hashes) tools::msg_writer() << " " << block_id; tools::msg_writer() << "Chain parent on main chain: " << chain.main_chain_parent_block; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request bhreq; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response bhres; + bhreq.hashes = chain.block_hashes; + bhreq.hashes.push_back(chain.main_chain_parent_block); + bhreq.fill_pow_hash = false; + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(bhreq, bhres, "getblockheaderbyhash", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_block_header_by_hash(bhreq, bhres, error_resp)) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + if (bhres.block_headers.size() != chain.length + 1) + { + tools::fail_msg_writer() << "Failed to get block header info for alt chain"; + return true; + } + uint64_t t0 = bhres.block_headers.front().timestamp, t1 = t0; + for (const cryptonote::block_header_response &block_header: bhres.block_headers) + { + t0 = std::min<uint64_t>(t0, block_header.timestamp); + t1 = std::max<uint64_t>(t1, block_header.timestamp); + } + const uint64_t dt = t1 - t0; + const uint64_t age = std::max(dt, t0 < now ? now - t0 : 0); + tools::msg_writer() << "Age: " << tools::get_human_readable_timespan(age); + if (chain.length > 1) + { + tools::msg_writer() << "Time span: " << tools::get_human_readable_timespan(dt); + cryptonote::difficulty_type start_difficulty = bhres.block_headers.back().difficulty; + if (start_difficulty > 0) + tools::msg_writer() << "Approximated " << 100.f * DIFFICULTY_TARGET_V2 * chain.length / dt << "% of network hash rate"; + else + tools::fail_msg_writer() << "Bad cmumulative difficulty reported by dameon"; + } } else tools::fail_msg_writer() << "Block hash " << tip << " is not the tip of any known alternate chain"; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 4622609ae..f3ed48319 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -145,7 +145,7 @@ public: bool print_coinbase_tx_sum(uint64_t height, uint64_t count); - bool alt_chain_info(const std::string &tip); + bool alt_chain_info(const std::string &tip, size_t above, uint64_t last_blocks); bool print_blockchain_dynamic_stats(uint64_t nblocks); diff --git a/src/net/parse.cpp b/src/net/parse.cpp index d93d7d352..7b74f2b75 100644 --- a/src/net/parse.cpp +++ b/src/net/parse.cpp @@ -34,28 +34,69 @@ namespace net { + void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port) + { + // require ipv6 address format "[addr:addr:addr:...:addr]:port" + if (address.find(']') != std::string::npos) + { + host = address.substr(1, address.rfind(']') - 1); + if ((host.size() + 2) < address.size()) + { + port = address.substr(address.rfind(':') + 1); + } + } + else + { + host = address.substr(0, address.rfind(':')); + if (host.size() < address.size()) + { + port = address.substr(host.size() + 1); + } + } + } + expect<epee::net_utils::network_address> get_network_address(const boost::string_ref address, const std::uint16_t default_port) { - const boost::string_ref host = address.substr(0, address.rfind(':')); + std::string host_str = ""; + std::string port_str = ""; + + bool ipv6 = false; + + get_network_address_host_and_port(std::string(address), host_str, port_str); - if (host.empty()) + boost::string_ref host_str_ref(host_str); + boost::string_ref port_str_ref(port_str); + + if (host_str.empty()) return make_error_code(net::error::invalid_host); - if (host.ends_with(".onion")) + if (host_str_ref.ends_with(".onion")) return tor_address::make(address, default_port); - if (host.ends_with(".i2p")) + if (host_str_ref.ends_with(".i2p")) return i2p_address::make(address, default_port); + boost::system::error_code ec; + boost::asio::ip::address_v6 v6 = boost::asio::ip::make_address_v6(host_str, ec); + ipv6 = !ec; + std::uint16_t port = default_port; - if (host.size() < address.size()) + if (port_str.size()) { - if (!epee::string_tools::get_xtype_from_string(port, std::string{address.substr(host.size() + 1)})) + if (!epee::string_tools::get_xtype_from_string(port, port_str)) return make_error_code(net::error::invalid_port); } - std::uint32_t ip = 0; - if (epee::string_tools::get_ip_int32_from_string(ip, std::string{host})) - return {epee::net_utils::ipv4_network_address{ip, port}}; + if (ipv6) + { + return {epee::net_utils::ipv6_network_address{v6, port}}; + } + else + { + std::uint32_t ip = 0; + if (epee::string_tools::get_ip_int32_from_string(ip, host_str)) + return {epee::net_utils::ipv4_network_address{ip, port}}; + } + return make_error_code(net::error::unsupported_address); } diff --git a/src/net/parse.h b/src/net/parse.h index 9f0d66ea6..0d8fda711 100644 --- a/src/net/parse.h +++ b/src/net/parse.h @@ -36,6 +36,8 @@ namespace net { + void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port); + /*! Identifies onion, i2p and IPv4 addresses and returns them as a generic `network_address`. If the type is unsupported, it might be a hostname, diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index a44297c17..bb51be242 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -108,10 +108,11 @@ namespace namespace nodetool { - const command_line::arg_descriptor<std::string> arg_p2p_bind_ip = {"p2p-bind-ip", "Interface for p2p network protocol", "0.0.0.0"}; + const command_line::arg_descriptor<std::string> arg_p2p_bind_ip = {"p2p-bind-ip", "Interface for p2p network protocol (IPv4)", "0.0.0.0"}; + const command_line::arg_descriptor<std::string> arg_p2p_bind_ipv6_address = {"p2p-bind-ipv6-address", "Interface for p2p network protocol (IPv6)", "::"}; const command_line::arg_descriptor<std::string, false, true, 2> arg_p2p_bind_port = { "p2p-bind-port" - , "Port for p2p network protocol" + , "Port for p2p network protocol (IPv4)" , std::to_string(config::P2P_DEFAULT_PORT) , {{ &cryptonote::arg_testnet_on, &cryptonote::arg_stagenet_on }} , [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string { @@ -122,6 +123,20 @@ namespace nodetool return val; } }; + const command_line::arg_descriptor<std::string, false, true, 2> arg_p2p_bind_port_ipv6 = { + "p2p-bind-port-ipv6" + , "Port for p2p network protocol (IPv6)" + , std::to_string(config::P2P_DEFAULT_PORT) + , {{ &cryptonote::arg_testnet_on, &cryptonote::arg_stagenet_on }} + , [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string { + if (testnet_stagenet[0] && defaulted) + return std::to_string(config::testnet::P2P_DEFAULT_PORT); + else if (testnet_stagenet[1] && defaulted) + return std::to_string(config::stagenet::P2P_DEFAULT_PORT); + return val; + } + }; + const command_line::arg_descriptor<uint32_t> 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<bool> arg_p2p_allow_local_ip = {"allow-local-ip", "Allow local ip add to peer list, mostly in debug purposes"}; const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_peer = {"add-peer", "Manually add peer to local peerlist"}; @@ -136,6 +151,8 @@ namespace nodetool const command_line::arg_descriptor<bool> arg_no_igd = {"no-igd", "Disable UPnP port mapping"}; const command_line::arg_descriptor<std::string> arg_igd = {"igd", "UPnP port mapping (disabled, enabled, delayed)", "delayed"}; + const command_line::arg_descriptor<bool> arg_p2p_use_ipv6 = {"p2p-use-ipv6", "Enable IPv6 for p2p", false}; + const command_line::arg_descriptor<bool> arg_p2p_require_ipv4 = {"p2p-require-ipv4", "Require successful IPv4 bind for p2p", true}; const command_line::arg_descriptor<int64_t> arg_out_peers = {"out-peers", "set max number of out peers", -1}; const command_line::arg_descriptor<int64_t> arg_in_peers = {"in-peers", "set max number of in peers", -1}; const command_line::arg_descriptor<int> arg_tos_flag = {"tos-flag", "set TOS flag", -1}; diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 0d95ceb99..cf6b2c67b 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -151,7 +151,9 @@ namespace nodetool : m_connect(nullptr), m_net_server(epee::net_utils::e_connection_type_P2P), m_bind_ip(), + m_bind_ipv6_address(), m_port(), + m_port_ipv6(), m_our_address(), m_peerlist(), m_config{}, @@ -167,7 +169,9 @@ namespace nodetool : m_connect(nullptr), m_net_server(public_service, epee::net_utils::e_connection_type_P2P), m_bind_ip(), + m_bind_ipv6_address(), m_port(), + m_port_ipv6(), m_our_address(), m_peerlist(), m_config{}, @@ -182,7 +186,9 @@ namespace nodetool connect_func* m_connect; net_server m_net_server; std::string m_bind_ip; + std::string m_bind_ipv6_address; std::string m_port; + std::string m_port_ipv6; epee::net_utils::network_address m_our_address; // in anonymity networks peerlist_manager m_peerlist; config m_config; @@ -357,7 +363,13 @@ namespace nodetool bool is_peer_used(const peerlist_entry& peer); bool is_peer_used(const anchor_peerlist_entry& peer); bool is_addr_connected(const epee::net_utils::network_address& peer); - void add_upnp_port_mapping(uint32_t port); + void add_upnp_port_mapping_impl(uint32_t port, bool ipv6=false); + void add_upnp_port_mapping_v4(uint32_t port); + void add_upnp_port_mapping_v6(uint32_t port); + void add_upnp_port_mapping(uint32_t port, bool ipv4=true, bool ipv6=false); + void delete_upnp_port_mapping_impl(uint32_t port, bool ipv6=false); + void delete_upnp_port_mapping_v4(uint32_t port); + void delete_upnp_port_mapping_v6(uint32_t port); void delete_upnp_port_mapping(uint32_t port); template<class t_callback> bool try_ping(basic_node_data& node_data, p2p_connection_context& context, const t_callback &cb); @@ -419,12 +431,15 @@ namespace nodetool bool m_have_address; bool m_first_connection_maker_call; uint32_t m_listening_port; + uint32_t m_listening_port_ipv6; uint32_t m_external_port; uint16_t m_rpc_port; bool m_allow_local_ip; bool m_hide_my_port; igd_t m_igd; bool m_offline; + bool m_use_ipv6; + bool m_require_ipv4; std::atomic<bool> is_closing; std::unique_ptr<boost::thread> mPeersLoggerThread; //critical_section m_connections_lock; @@ -485,7 +500,11 @@ namespace nodetool const int64_t default_limit_up = P2P_DEFAULT_LIMIT_RATE_UP; // kB/s const int64_t default_limit_down = P2P_DEFAULT_LIMIT_RATE_DOWN; // kB/s extern const command_line::arg_descriptor<std::string> arg_p2p_bind_ip; + extern const command_line::arg_descriptor<std::string> arg_p2p_bind_ipv6_address; extern const command_line::arg_descriptor<std::string, false, true, 2> arg_p2p_bind_port; + extern const command_line::arg_descriptor<std::string, false, true, 2> arg_p2p_bind_port_ipv6; + extern const command_line::arg_descriptor<bool> arg_p2p_use_ipv6; + extern const command_line::arg_descriptor<bool> arg_p2p_require_ipv4; extern const command_line::arg_descriptor<uint32_t> arg_p2p_external_port; extern const command_line::arg_descriptor<bool> arg_p2p_allow_local_ip; extern const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_peer; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 98484fe78..426767365 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -93,7 +93,11 @@ namespace nodetool void node_server<t_payload_net_handler>::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_ipv6_address); command_line::add_arg(desc, arg_p2p_bind_port, false); + command_line::add_arg(desc, arg_p2p_bind_port_ipv6, false); + command_line::add_arg(desc, arg_p2p_use_ipv6); + command_line::add_arg(desc, arg_p2p_require_ipv4); 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); @@ -341,7 +345,9 @@ namespace nodetool network_zone& public_zone = m_network_zones[epee::net_utils::zone::public_]; public_zone.m_connect = &public_connect; public_zone.m_bind_ip = command_line::get_arg(vm, arg_p2p_bind_ip); + public_zone.m_bind_ipv6_address = command_line::get_arg(vm, arg_p2p_bind_ipv6_address); public_zone.m_port = command_line::get_arg(vm, arg_p2p_bind_port); + public_zone.m_port_ipv6 = command_line::get_arg(vm, arg_p2p_bind_port_ipv6); public_zone.m_can_pingback = true; 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); @@ -375,6 +381,8 @@ namespace nodetool return false; } m_offline = command_line::get_arg(vm, cryptonote::arg_offline); + m_use_ipv6 = command_line::get_arg(vm, arg_p2p_use_ipv6); + m_require_ipv4 = command_line::get_arg(vm, arg_p2p_require_ipv4); if (command_line::has_arg(vm, arg_p2p_add_peer)) { @@ -518,12 +526,17 @@ namespace nodetool std::string host = addr; std::string port = std::to_string(default_port); - size_t pos = addr.find_last_of(':'); - if (std::string::npos != pos) + size_t colon_pos = addr.find_last_of(':'); + size_t dot_pos = addr.find_last_of('.'); + size_t square_brace_pos = addr.find('['); + + // IPv6 will have colons regardless. IPv6 and IPv4 address:port will have a colon but also either a . or a [ + // as IPv6 addresses specified as address:port are to be specified as "[addr:addr:...:addr]:port" + // One may also specify an IPv6 address as simply "[addr:addr:...:addr]" without the port; in that case + // the square braces will be stripped here. + if ((std::string::npos != colon_pos && std::string::npos != dot_pos) || std::string::npos != square_brace_pos) { - CHECK_AND_ASSERT_MES(addr.length() - 1 != pos && 0 != pos, false, "Failed to parse seed address from string: '" << addr << '\''); - host = addr.substr(0, pos); - port = addr.substr(pos + 1); + net::get_network_address_host_and_port(addr, host, port); } MINFO("Resolving node address: host=" << host << ", port=" << port); @@ -546,7 +559,9 @@ namespace nodetool } else { - MWARNING("IPv6 unsupported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); + epee::net_utils::network_address na{epee::net_utils::ipv6_network_address{endpoint.address().to_v6(), endpoint.port()}}; + seed_nodes.push_back(na); + MINFO("Added node: " << na.str()); } } return true; @@ -780,21 +795,40 @@ namespace nodetool if (!zone.second.m_bind_ip.empty()) { + std::string ipv6_addr = ""; + std::string ipv6_port = ""; zone.second.m_net_server.set_connection_filter(this); - MINFO("Binding on " << zone.second.m_bind_ip << ":" << zone.second.m_port); - res = zone.second.m_net_server.init_server(zone.second.m_port, zone.second.m_bind_ip, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + MINFO("Binding (IPv4) on " << zone.second.m_bind_ip << ":" << zone.second.m_port); + if (!zone.second.m_bind_ipv6_address.empty() && m_use_ipv6) + { + ipv6_addr = zone.second.m_bind_ipv6_address; + ipv6_port = zone.second.m_port_ipv6; + MINFO("Binding (IPv6) on " << zone.second.m_bind_ipv6_address << ":" << zone.second.m_port_ipv6); + } + res = zone.second.m_net_server.init_server(zone.second.m_port, zone.second.m_bind_ip, ipv6_port, ipv6_addr, m_use_ipv6, m_require_ipv4, epee::net_utils::ssl_support_t::e_ssl_support_disabled); CHECK_AND_ASSERT_MES(res, false, "Failed to bind server"); } } m_listening_port = public_zone.m_net_server.get_binded_port(); - MLOG_GREEN(el::Level::Info, "Net service bound to " << public_zone.m_bind_ip << ":" << m_listening_port); + MLOG_GREEN(el::Level::Info, "Net service bound (IPv4) to " << public_zone.m_bind_ip << ":" << m_listening_port); + if (m_use_ipv6) + { + m_listening_port_ipv6 = public_zone.m_net_server.get_binded_port_ipv6(); + MLOG_GREEN(el::Level::Info, "Net service bound (IPv6) to " << public_zone.m_bind_ipv6_address << ":" << m_listening_port_ipv6); + } if(m_external_port) MDEBUG("External port defined as " << m_external_port); // add UPnP port mapping if(m_igd == igd) - add_upnp_port_mapping(m_listening_port); + { + add_upnp_port_mapping_v4(m_listening_port); + if (m_use_ipv6) + { + add_upnp_port_mapping_v6(m_listening_port_ipv6); + } + } return res; } @@ -1997,19 +2031,43 @@ namespace nodetool if(!node_data.my_port) return false; - CHECK_AND_ASSERT_MES(context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id(), false, - "Only IPv4 addresses are supported here"); + bool address_ok = (context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id() || context.m_remote_address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()); + CHECK_AND_ASSERT_MES(address_ok, false, + "Only IPv4 or IPv6 addresses are supported here"); const epee::net_utils::network_address na = context.m_remote_address; - uint32_t actual_ip = na.as<const epee::net_utils::ipv4_network_address>().ip(); + std::string ip; + uint32_t ipv4_addr; + boost::asio::ip::address_v6 ipv6_addr; + bool is_ipv4; + if (na.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) + { + ipv4_addr = na.as<const epee::net_utils::ipv4_network_address>().ip(); + ip = epee::string_tools::get_ip_string_from_int32(ipv4_addr); + is_ipv4 = true; + } + else + { + ipv6_addr = na.as<const epee::net_utils::ipv6_network_address>().ip(); + ip = ipv6_addr.to_string(); + is_ipv4 = false; + } network_zone& zone = m_network_zones.at(na.get_zone()); if(!zone.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{epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)}; + + epee::net_utils::network_address address; + if (is_ipv4) + { + address = epee::net_utils::network_address{epee::net_utils::ipv4_network_address(ipv4_addr, node_data.my_port)}; + } + else + { + address = epee::net_utils::network_address{epee::net_utils::ipv6_network_address(ipv6_addr, node_data.my_port)}; + } peerid_type pr = node_data.peer_id; bool r = zone.m_net_server.connect_async(ip, port, zone.m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( const typename net_server::t_connection_context& ping_context, @@ -2193,12 +2251,19 @@ namespace nodetool //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::get_type_id(), void(), - "Only IPv4 addresses are supported here"); + CHECK_AND_ASSERT_MES((context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id() || context.m_remote_address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()), void(), + "Only IPv4 or IPv6 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 = epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l); + if (context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) + { + pe.adr = epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l); + } + else + { + pe.adr = epee::net_utils::ipv6_network_address(na.as<epee::net_utils::ipv6_network_address>().ip(), port_l); + } time_t last_seen; time(&last_seen); pe.last_seen = static_cast<int64_t>(last_seen); @@ -2565,16 +2630,19 @@ namespace nodetool } template<class t_payload_net_handler> - void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port) + void node_server<t_payload_net_handler>::add_upnp_port_mapping_impl(uint32_t port, bool ipv6) // if ipv6 false, do ipv4 { - MDEBUG("Attempting to add IGD port mapping."); + std::string ipversion = ipv6 ? "(IPv6)" : "(IPv4)"; + MDEBUG("Attempting to add IGD port mapping " << ipversion << "."); int result; + const int ipv6_arg = ipv6 ? 1 : 0; + #if MINIUPNPC_API_VERSION > 13 // default according to miniupnpc.h unsigned char ttl = 2; - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, ipv6_arg, ttl, &result); #else - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, ipv6_arg, &result); #endif UPNPUrls urls; IGDdatas igdData; @@ -2611,16 +2679,38 @@ namespace nodetool } template<class t_payload_net_handler> - void node_server<t_payload_net_handler>::delete_upnp_port_mapping(uint32_t port) + void node_server<t_payload_net_handler>::add_upnp_port_mapping_v4(uint32_t port) + { + add_upnp_port_mapping_impl(port, false); + } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::add_upnp_port_mapping_v6(uint32_t port) + { + add_upnp_port_mapping_impl(port, true); + } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port, bool ipv4, bool ipv6) + { + if (ipv4) add_upnp_port_mapping_v4(port); + if (ipv6) add_upnp_port_mapping_v6(port); + } + + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::delete_upnp_port_mapping_impl(uint32_t port, bool ipv6) { - MDEBUG("Attempting to delete IGD port mapping."); + std::string ipversion = ipv6 ? "(IPv6)" : "(IPv4)"; + MDEBUG("Attempting to delete IGD port mapping " << ipversion << "."); int result; + const int ipv6_arg = ipv6 ? 1 : 0; #if MINIUPNPC_API_VERSION > 13 // default according to miniupnpc.h unsigned char ttl = 2; - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, ipv6_arg, ttl, &result); #else - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, ipv6_arg, &result); #endif UPNPUrls urls; IGDdatas igdData; @@ -2653,6 +2743,25 @@ namespace nodetool } } + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::delete_upnp_port_mapping_v4(uint32_t port) + { + delete_upnp_port_mapping_impl(port, false); + } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::delete_upnp_port_mapping_v6(uint32_t port) + { + delete_upnp_port_mapping_impl(port, true); + } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::delete_upnp_port_mapping(uint32_t port) + { + delete_upnp_port_mapping_v4(port); + delete_upnp_port_mapping_v6(port); + } + template<typename t_payload_net_handler> boost::optional<p2p_connection_context_t<typename t_payload_net_handler::connection_context>> node_server<t_payload_net_handler>::socks_connect(network_zone& zone, const epee::net_utils::network_address& remote, epee::net_utils::ssl_support_t ssl_support) @@ -2671,13 +2780,34 @@ namespace nodetool boost::optional<p2p_connection_context_t<typename t_payload_net_handler::connection_context>> node_server<t_payload_net_handler>::public_connect(network_zone& zone, epee::net_utils::network_address const& na, epee::net_utils::ssl_support_t ssl_support) { - CHECK_AND_ASSERT_MES(na.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id(), boost::none, - "Only IPv4 addresses are supported here"); - const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); + bool is_ipv4 = na.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id(); + bool is_ipv6 = na.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id(); + CHECK_AND_ASSERT_MES(is_ipv4 || is_ipv6, boost::none, + "Only IPv4 or IPv6 addresses are supported here"); + + std::string address; + std::string port; + + if (is_ipv4) + { + const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); + address = epee::string_tools::get_ip_string_from_int32(ipv4.ip()); + port = epee::string_tools::num_to_string_fast(ipv4.port()); + } + else if (is_ipv6) + { + const epee::net_utils::ipv6_network_address &ipv6 = na.as<const epee::net_utils::ipv6_network_address>(); + address = ipv6.ip().to_string(); + port = epee::string_tools::num_to_string_fast(ipv6.port()); + } + else + { + LOG_ERROR("Only IPv4 or IPv6 addresses are supported here"); + return boost::none; + } typename net_server::t_connection_context con{}; - const bool res = zone.m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), - epee::string_tools::num_to_string_fast(ipv4.port()), + const bool res = zone.m_net_server.connect(address, port, zone.m_config.m_net_config.connection_timeout, con, "0.0.0.0", ssl_support); diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 32f30adca..05eb36e65 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -76,6 +76,9 @@ namespace boost case epee::net_utils::ipv4_network_address::get_type_id(): do_serialize<epee::net_utils::ipv4_network_address>(is_saving, a, na); break; + case epee::net_utils::ipv6_network_address::get_type_id(): + do_serialize<epee::net_utils::ipv6_network_address>(is_saving, a, na); + break; case net::tor_address::get_type_id(): do_serialize<net::tor_address>(is_saving, a, na); break; @@ -99,6 +102,34 @@ namespace boost } template <class Archive, class ver_type> + inline void serialize(Archive &a, boost::asio::ip::address_v6& v6, const ver_type ver) + { + if (typename Archive::is_saving()) + { + auto bytes = v6.to_bytes(); + for (auto &e: bytes) a & e; + } + else + { + boost::asio::ip::address_v6::bytes_type bytes; + for (auto &e: bytes) a & e; + v6 = boost::asio::ip::address_v6(bytes); + } + } + + template <class Archive, class ver_type> + inline void serialize(Archive &a, epee::net_utils::ipv6_network_address& na, const ver_type ver) + { + boost::asio::ip::address_v6 ip{na.ip()}; + uint16_t port{na.port()}; + a & ip; + a & port; + if (!typename Archive::is_saving()) + na = epee::net_utils::ipv6_network_address{ip, port}; + } + + + template <class Archive, class ver_type> inline void save(Archive& a, const net::tor_address& na, const ver_type) { const size_t length = std::strlen(na.host_str()); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d80219f1c..9aaaa026d 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -142,6 +142,7 @@ namespace cryptonote { m_restricted = restricted; m_net_server.set_threads_prefix("RPC"); + m_net_server.set_connection_filter(&m_p2p); auto rpc_config = cryptonote::rpc_args::process(vm, true); if (!rpc_config) @@ -161,7 +162,9 @@ namespace cryptonote auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( - rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) + rng, std::move(port), std::move(rpc_config->bind_ip), + std::move(rpc_config->bind_ipv6_address), std::move(rpc_config->use_ipv6), std::move(rpc_config->require_ipv4), + std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -173,6 +176,24 @@ namespace cryptonote } return true; } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::add_host_fail(const connection_context *ctx) + { + if(!ctx || !ctx->m_remote_address.is_blockable()) + return false; + + CRITICAL_REGION_LOCAL(m_host_fails_score_lock); + uint64_t fails = ++m_host_fails_score[ctx->m_remote_address.host_str()]; + MDEBUG("Host " << ctx->m_remote_address.host_str() << " fail score=" << fails); + if(fails > RPC_IP_FAILS_BEFORE_BLOCK) + { + auto it = m_host_fails_score.find(ctx->m_remote_address.host_str()); + CHECK_AND_ASSERT_MES(it != m_host_fails_score.end(), false, "internal error"); + it->second = RPC_IP_FAILS_BEFORE_BLOCK/2; + m_p2p.block_host(ctx->m_remote_address); + } + return true; + } #define CHECK_CORE_READY() do { if(!check_core_ready()){res.status = CORE_RPC_STATUS_BUSY;return true;} } while(0) //------------------------------------------------------------------------------------------------------------------------------ @@ -300,6 +321,7 @@ namespace cryptonote if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) { res.status = "Failed"; + add_host_fail(ctx); return false; } @@ -423,6 +445,7 @@ namespace cryptonote if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, res.start_height, res.current_height, false)) { res.status = "Failed"; + add_host_fail(ctx); return false; } @@ -603,7 +626,8 @@ namespace cryptonote return true; } const cryptonote::blobdata pruned = ss.str(); - sorted_txs.push_back(std::make_tuple(h, pruned, get_transaction_prunable_hash(tx), std::string(i->tx_blob, pruned.size()))); + const crypto::hash prunable_hash = tx.version == 1 ? crypto::null_hash : get_transaction_prunable_hash(tx); + sorted_txs.push_back(std::make_tuple(h, pruned, prunable_hash, std::string(i->tx_blob, pruned.size()))); missed_txs.erase(std::find(missed_txs.begin(), missed_txs.end(), h)); pool_tx_hashes.insert(h); const std::string hash_string = epee::string_tools::pod_to_hex(h); @@ -862,6 +886,8 @@ namespace cryptonote add_reason(reason, "fee too low"); if ((res.not_rct = tvc.m_not_rct)) add_reason(reason, "tx is not ringct"); + if ((res.too_few_outputs = tvc.m_too_few_outputs)) + add_reason(reason, "too few outputs"); const std::string punctuation = reason.empty() ? "" : ": "; if (tvc.m_verifivation_failed) { @@ -1026,6 +1052,11 @@ namespace cryptonote if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) + { + res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(), + entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + } else res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); } @@ -1036,6 +1067,9 @@ namespace cryptonote if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) + res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(), + entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); else res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); } @@ -1044,6 +1078,45 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_public_nodes(const COMMAND_RPC_GET_PUBLIC_NODES::request& req, COMMAND_RPC_GET_PUBLIC_NODES::response& res, const connection_context *ctx) + { + PERF_TIMER(on_get_public_nodes); + + COMMAND_RPC_GET_PEER_LIST::response peer_list_res; + const bool success = on_get_peer_list(COMMAND_RPC_GET_PEER_LIST::request(), peer_list_res, ctx); + res.status = peer_list_res.status; + if (!success) + { + return false; + } + if (res.status != CORE_RPC_STATUS_OK) + { + return true; + } + + const auto collect = [](const std::vector<peer> &peer_list, std::vector<public_node> &public_nodes) + { + for (const auto &entry : peer_list) + { + if (entry.rpc_port != 0) + { + public_nodes.emplace_back(entry); + } + } + }; + + if (req.white) + { + collect(peer_list_res.white_list, res.white); + } + if (req.gray) + { + collect(peer_list_res.gray_list, res.gray); + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx) { PERF_TIMER(on_set_log_hash_rate); @@ -1600,38 +1673,55 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH>(invoke_http_mode::JON_RPC, "getblockheaderbyhash", req, res, r)) return r; - crypto::hash block_hash; - bool hash_parsed = parse_hash256(req.hash, block_hash); - if(!hash_parsed) - { - error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; - error_resp.message = "Failed to parse hex representation of block hash. Hex = " + req.hash + '.'; - return false; - } - block blk; - bool orphan = false; - bool have_block = m_core.get_block_by_hash(block_hash, blk, &orphan); - if (!have_block) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.'; - return false; - } - if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen)) + auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool { + crypto::hash block_hash; + bool hash_parsed = parse_hash256(hash, block_hash); + if(!hash_parsed) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Failed to parse hex representation of block hash. Hex = " + hash + '.'; + return false; + } + block blk; + bool orphan = false; + bool have_block = m_core.get_block_by_hash(block_hash, blk, &orphan); + if (!have_block) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: can't get block by hash. Hash = " + hash + '.'; + return false; + } + if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; + return false; + } + uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, block_header, fill_pow_hash && !restricted); + if (!response_filled) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: can't produce valid response."; + return false; + } + return true; + }; + + const bool restricted = m_restricted && ctx; + if (!req.hash.empty()) { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; - return false; + if (!get(req.hash, req.fill_pow_hash, res.block_header, restricted, error_resp)) + return false; } - uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - const bool restricted = m_restricted && ctx; - bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash && !restricted); - if (!response_filled) + res.block_headers.reserve(req.hashes.size()); + for (const std::string &hash: req.hashes) { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: can't produce valid response."; - return false; + res.block_headers.push_back({}); + if (!get(hash, req.fill_pow_hash, res.block_headers.back(), restricted, error_resp)) + return false; } + res.status = CORE_RPC_STATUS_OK; return true; } @@ -2061,7 +2151,7 @@ namespace cryptonote PERF_TIMER(on_get_alternate_chains); try { - std::list<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); + std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); for (const auto &i: chains) { difficulty_type wdiff = i.first.cumulative_difficulty; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 663975617..e91d4c953 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -108,6 +108,7 @@ namespace cryptonote MAP_URI_AUTO_JON2_IF("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS, !m_restricted) MAP_URI_AUTO_JON2_IF("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC, !m_restricted) MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted) + MAP_URI_AUTO_JON2_IF("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_categories", on_set_log_categories, COMMAND_RPC_SET_LOG_CATEGORIES, !m_restricted) @@ -186,6 +187,7 @@ namespace cryptonote bool on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx = NULL); bool on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res, const connection_context *ctx = NULL); bool on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res, const connection_context *ctx = NULL); + bool on_get_public_nodes(const COMMAND_RPC_GET_PUBLIC_NODES::request& req, COMMAND_RPC_GET_PUBLIC_NODES::response& res, const connection_context *ctx = NULL); bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx = NULL); bool on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res, const connection_context *ctx = NULL); bool on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res, const connection_context *ctx = NULL); @@ -236,6 +238,7 @@ namespace cryptonote private: bool check_core_busy(); bool check_core_ready(); + bool add_host_fail(const connection_context *ctx); //utils uint64_t get_block_reward(const block& blk); @@ -256,6 +259,8 @@ private: bool m_was_bootstrap_ever_used; network_type m_nettype; bool m_restricted; + epee::critical_section m_host_fails_score_lock; + std::map<std::string, uint64_t> m_host_fails_score; }; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 571a71207..7ae0c77b2 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -87,7 +87,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 2 -#define CORE_RPC_VERSION_MINOR 7 +#define CORE_RPC_VERSION_MINOR 8 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -612,6 +612,7 @@ namespace cryptonote bool overspend; bool fee_too_low; bool not_rct; + bool too_few_outputs; bool sanity_check_failed; bool untrusted; @@ -627,6 +628,7 @@ namespace cryptonote KV_SERIALIZE(overspend) KV_SERIALIZE(fee_too_low) KV_SERIALIZE(not_rct) + KV_SERIALIZE(too_few_outputs) KV_SERIALIZE(sanity_check_failed) KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() @@ -1094,10 +1096,12 @@ namespace cryptonote struct request_t { std::string hash; + std::vector<std::string> hashes; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(hash) + KV_SERIALIZE(hashes) KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1107,10 +1111,12 @@ namespace cryptonote { std::string status; block_header_response block_header; + std::vector<block_header_response> block_headers; bool untrusted; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) + KV_SERIALIZE(block_headers) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() @@ -1200,6 +1206,9 @@ namespace cryptonote peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) : id(id), host(host), ip(0), port(0), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) {} + peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) + : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + {} peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) : id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) {} @@ -1239,6 +1248,54 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct public_node + { + std::string host; + uint64_t last_seen; + uint16_t rpc_port; + + public_node() = delete; + + public_node(const peer &peer) + : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port) + {} + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(host) + KV_SERIALIZE(last_seen) + KV_SERIALIZE(rpc_port) + END_KV_SERIALIZE_MAP() + }; + + struct COMMAND_RPC_GET_PUBLIC_NODES + { + struct request_t + { + bool gray; + bool white; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(gray, false) + KV_SERIALIZE_OPT(white, true) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string status; + std::vector<public_node> gray; + std::vector<public_node> white; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(gray) + KV_SERIALIZE(white) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + struct COMMAND_RPC_SET_LOG_HASH_RATE { struct request_t @@ -2095,7 +2152,7 @@ namespace cryptonote struct response_t { std::string status; - std::list<chain_info> chains; + std::vector<chain_info> chains; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 612b2cab6..890380dc8 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -343,6 +343,11 @@ namespace rpc if (!res.error_details.empty()) res.error_details += " and "; res.error_details = "tx is not ringct"; } + if (tvc.m_too_few_outputs) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "too few outputs"; + } if (res.error_details.empty()) { res.error_details = "an unknown issue was found with the transaction"; diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 4479bd1f1..68b33cb8c 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -90,6 +90,9 @@ namespace cryptonote rpc_args::descriptors::descriptors() : rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify IP to bind RPC server"), "127.0.0.1"}) + , rpc_bind_ipv6_address({"rpc-bind-ipv6-address", rpc_args::tr("Specify IPv6 address to bind RPC server"), "::1"}) + , rpc_use_ipv6({"rpc-use-ipv6", rpc_args::tr("Allow IPv6 for RPC"), false}) + , rpc_require_ipv4({"rpc-require-ipv4", rpc_args::tr("Require successful IPv4 bind for RPC"), true}) , rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true}) , confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")}) , rpc_access_control_origins({"rpc-access-control-origins", rpc_args::tr("Specify a comma separated list of origins to allow cross origin resource sharing"), ""}) @@ -108,6 +111,9 @@ namespace cryptonote { const descriptors arg{}; command_line::add_arg(desc, arg.rpc_bind_ip); + command_line::add_arg(desc, arg.rpc_bind_ipv6_address); + command_line::add_arg(desc, arg.rpc_use_ipv6); + command_line::add_arg(desc, arg.rpc_require_ipv4); command_line::add_arg(desc, arg.rpc_login); command_line::add_arg(desc, arg.confirm_external_bind); command_line::add_arg(desc, arg.rpc_access_control_origins); @@ -127,6 +133,9 @@ namespace cryptonote rpc_args config{}; config.bind_ip = command_line::get_arg(vm, arg.rpc_bind_ip); + config.bind_ipv6_address = command_line::get_arg(vm, arg.rpc_bind_ipv6_address); + config.use_ipv6 = command_line::get_arg(vm, arg.rpc_use_ipv6); + config.require_ipv4 = command_line::get_arg(vm, arg.rpc_require_ipv4); if (!config.bind_ip.empty()) { // always parse IP here for error consistency @@ -148,6 +157,34 @@ namespace cryptonote return boost::none; } } + if (!config.bind_ipv6_address.empty()) + { + // allow square braces, but remove them here if present + if (config.bind_ipv6_address.find('[') != std::string::npos) + { + config.bind_ipv6_address = config.bind_ipv6_address.substr(1, config.bind_ipv6_address.size() - 2); + } + + + // always parse IP here for error consistency + boost::system::error_code ec{}; + const auto parsed_ip = boost::asio::ip::address::from_string(config.bind_ipv6_address, ec); + if (ec) + { + LOG_ERROR(tr("Invalid IP address given for --") << arg.rpc_bind_ipv6_address.name); + return boost::none; + } + + if (!parsed_ip.is_loopback() && !command_line::get_arg(vm, arg.confirm_external_bind)) + { + LOG_ERROR( + "--" << arg.rpc_bind_ipv6_address.name << + tr(" permits inbound unencrypted external connections. Consider SSH tunnel or SSL proxy instead. Override with --") << + arg.confirm_external_bind.name + ); + return boost::none; + } + } const char *env_rpc_login = nullptr; const bool has_rpc_arg = command_line::has_arg(vm, arg.rpc_login); diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index 619f02b42..cd154a4d0 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -52,6 +52,9 @@ namespace cryptonote descriptors& operator=(descriptors&&) = delete; const command_line::arg_descriptor<std::string> rpc_bind_ip; + const command_line::arg_descriptor<std::string> rpc_bind_ipv6_address; + const command_line::arg_descriptor<bool> rpc_use_ipv6; + const command_line::arg_descriptor<bool> rpc_require_ipv4; const command_line::arg_descriptor<std::string> rpc_login; const command_line::arg_descriptor<bool> confirm_external_bind; const command_line::arg_descriptor<std::string> rpc_access_control_origins; @@ -76,6 +79,9 @@ namespace cryptonote static boost::optional<epee::net_utils::ssl_options_t> process_ssl(const boost::program_options::variables_map& vm, const bool any_cert_option = false); std::string bind_ip; + std::string bind_ipv6_address; + bool use_ipv6; + bool require_ipv4; std::vector<std::string> access_control_origins; boost::optional<tools::login> login; // currently `boost::none` if unspecified by user epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index fca14c8f8..10901bb51 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3972,7 +3972,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (m_restoring && m_generate_from_json.empty() && m_generate_from_device.empty()) { - m_wallet->explicit_refresh_from_block_height(!(command_line::is_arg_defaulted(vm, arg_restore_height) || + m_wallet->explicit_refresh_from_block_height(!(command_line::is_arg_defaulted(vm, arg_restore_height) && command_line::is_arg_defaulted(vm, arg_restore_date))); if (command_line::is_arg_defaulted(vm, arg_restore_height) && !command_line::is_arg_defaulted(vm, arg_restore_date)) { diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 03db385a4..140261f0b 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1705,7 +1705,9 @@ bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &va std::string WalletImpl::getCacheAttribute(const std::string &key) const { - return m_wallet->get_attribute(key); + std::string value; + m_wallet->get_attribute(key, value); + return value; } bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f25e9ad97..e44fea7d7 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -217,6 +217,8 @@ namespace add_reason(reason, "invalid input"); if (res.invalid_output) add_reason(reason, "invalid output"); + if (res.too_few_outputs) + add_reason(reason, "too few outputs"); if (res.too_big) add_reason(reason, "too big"); if (res.overspend) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 9d3605d11..844ecf90c 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -247,7 +247,9 @@ namespace tools m_net_server.set_threads_prefix("RPC"); auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( - rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), + rng, std::move(bind_port), std::move(rpc_config->bind_ip), + std::move(rpc_config->bind_ipv6_address), std::move(rpc_config->use_ipv6), std::move(rpc_config->require_ipv4), + std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) ); } @@ -4089,9 +4091,8 @@ namespace tools } } - er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; - er.message = std::string("Invalid address"); - return false; + res.valid = false; + return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx) diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 3c8cd9c1d..b109acf91 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -43,6 +43,7 @@ class MultisigTest(): self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5) self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5) self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5) + self.mine('4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW', 5) self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60) self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk') @@ -57,6 +58,12 @@ class MultisigTest(): self.import_multisig_info([0, 1, 2], 6) self.check_transaction(txid) + self.create_multisig_wallets(3, 3, '4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW') + self.import_multisig_info([2, 0, 1], 5) + txid = self.transfer([2, 1, 0]) + self.import_multisig_info([0, 2, 1], 6) + self.check_transaction(txid) + self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53') self.import_multisig_info([0, 2, 3], 5) txid = self.transfer([0, 2, 3]) diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index d9a6e592e..e3c01c27f 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -574,6 +574,7 @@ class TransferTest(): assert res.overspend == False assert res.fee_too_low == False assert res.not_rct == False + assert res.too_few_outputs == False res = daemon.get_transactions([txes[0][0]]) assert len(res.txs) >= 1 diff --git a/tests/functional_tests/validate_address.py b/tests/functional_tests/validate_address.py new file mode 100755 index 000000000..58748b0a2 --- /dev/null +++ b/tests/functional_tests/validate_address.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +# Copyright (c) 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. + +import time + +"""Test address validation RPC calls +""" + +from framework.wallet import Wallet + +class AddressValidationTest(): + def run_test(self): + self.create() + self.check_bad_addresses() + self.check_good_addresses() + self.check_openalias_addresses() + + def create(self): + print('Creating wallet') + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + self.wallet = Wallet() + # close the wallet if any, will throw if none is loaded + try: self.wallet.close_wallet() + except: pass + res = self.wallet.restore_deterministic_wallet(seed = seed) + assert res.address == address + assert res.seed == seed + + def check_bad_addresses(self): + print('Validating bad addresses') + bad_addresses = ['', 'a', '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWD9', ' ', '@', '42ey'] + for address in bad_addresses: + res = self.wallet.validate_address(address, any_net_type = False) + assert not res.valid + res = self.wallet.validate_address(address, any_net_type = True) + assert not res.valid + + def check_good_addresses(self): + print('Validating good addresses') + addresses = [ + [ 'mainnet', '', '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' ], + [ 'mainnet', '', '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' ], + [ 'testnet', '', '9ujeXrjzf7bfeK3KZdCqnYaMwZVFuXemPU8Ubw335rj2FN1CdMiWNyFV3ksEfMFvRp9L9qum5UxkP5rN9aLcPxbH1au4WAB' ], + [ 'stagenet', '', '53teqCAESLxeJ1REzGMAat1ZeHvuajvDiXqboEocPaDRRmqWoVPzy46GLo866qRFjbNhfkNckyhST3WEvBviDwpUDd7DSzB' ], + [ 'mainnet', 'i', '4BxSHvcgTwu25WooY4BVmgdcKwZu5EksVZSZkDd6ooxSVVqQ4ubxXkhLF6hEqtw96i9cf3cVfLw8UWe95bdDKfRQeYtPwLm1Jiw7AKt2LY' ], + [ 'mainnet', 's', '8AsN91rznfkBGTY8psSNkJBg9SZgxxGGRUhGwRptBhgr5XSQ1XzmA9m8QAnoxydecSh5aLJXdrgXwTDMMZ1AuXsN1EX5Mtm' ], + [ 'mainnet', 's', '86kKnBKFqzCLxtK1Jmx2BkNBDBSMDEVaRYMMyVbeURYDWs8uNGDZURKCA5yRcyMxHzPcmCf1q2fSdhQVcaKsFrtGRsdGfNk' ], + [ 'testnet', 'i', 'AApMA1VuhiCaHzr5X2KXi2Zc9oJ3VaGjkfChxxpRpxkyKf1NetvbRbQTbFMrGkr85DjnEH7JsBaoUFsgKwZnmtnVWnoB8MDotCsLb7eWwz' ], + [ 'testnet', 's', 'BdKg9udkvckC5T58a8Nmtb6BNsgRAxs7uA2D49sWNNX5HPW5Us6Wxu8QMXrnSx3xPBQQ2iu9kwEcRGAoiz6EPmcZKbF62GS' ], + [ 'testnet', 's', 'BcFvPa3fT4gVt5QyRDe5Vv7VtUFao9ci8NFEy3r254KF7R1N2cNB5FYhGvrHbMStv4D6VDzZ5xtxeKV8vgEPMnDcNFuwZb9' ], + [ 'stagenet', 'i', '5K8mwfjumVseCcQEjNbf59Um6R9NfVUNkHTLhhPCmNvgDLVS88YW5tScnm83rw9mfgYtchtDDTW5jEfMhygi27j1QYphX38hg6m4VMtN29' ], + [ 'stagenet', 's', '73LhUiix4DVFMcKhsPRG51QmCsv8dYYbL6GcQoLwEEFvPvkVvc7BhebfA4pnEFF9Lq66hwvLqBvpHjTcqvpJMHmmNjPPBqa' ], + [ 'stagenet', 's', '7A1Hr63MfgUa8pkWxueD5xBqhQczkusYiCMYMnJGcGmuQxa7aDBxN1G7iCuLCNB3VPeb2TW7U9FdxB27xKkWKfJ8VhUZthF' ], + ] + for any_net_type in [True, False]: + for address in addresses: + res = self.wallet.validate_address(address[2], any_net_type = any_net_type) + if any_net_type or address[0] == 'mainnet': + assert res.valid + assert res.integrated == (address[1] == 'i') + assert res.subaddress == (address[1] == 's') + assert res.nettype == address[0] + assert res.openalias_address == '' + else: + assert not res.valid + + def check_openalias_addresses(self): + print('Validating openalias addresses') + addresses = [ + ['donate@getmonero.org', '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A'] + ] + for address in addresses: + res = self.wallet.validate_address(address[0]) + assert not res.valid + res = self.wallet.validate_address(address[0], allow_openalias = True) + assert res.valid + assert not res.integrated + assert not res.subaddress + assert res.nettype == 'mainnet' + assert res.openalias_address == address[1] + +if __name__ == '__main__': + AddressValidationTest().run_test() diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp index c98696fbd..0724cd3e0 100644 --- a/tests/unit_tests/output_selection.cpp +++ b/tests/unit_tests/output_selection.cpp @@ -211,10 +211,10 @@ TEST(select_outputs, same_distribution) { const double diff = (double)output_norm[i] - (double)chain_norm[i]; double dev = fabs(2.0 * diff / (output_norm[i] + chain_norm[i])); - ASSERT_LT(dev, 0.1); + ASSERT_LT(dev, 0.15); avg_dev += dev; } avg_dev /= 100; MDEBUG("avg_dev: " << avg_dev); - ASSERT_LT(avg_dev, 0.015); + ASSERT_LT(avg_dev, 0.02); } diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index 18ce37221..20967030a 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -90,11 +90,12 @@ class Daemon(object): } return self.rpc.send_json_rpc_request(getlastblockheader) - def getblockheaderbyhash(self, hash): + def getblockheaderbyhash(self, hash = "", hashes = []): getblockheaderbyhash = { 'method': 'getblockheaderbyhash', 'params': { 'hash': hash, + 'hashes': hashes, }, 'jsonrpc': '2.0', 'id': '0' diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index 2e2650f92..36ff3644f 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -750,6 +750,19 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(set_log_categories) + def validate_address(self, address, any_net_type = False, allow_openalias = False): + validate_address = { + 'method': 'validate_address', + 'params': { + 'address': address, + 'any_net_type': any_net_type, + 'allow_openalias': allow_openalias, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(validate_address) + def get_version(self): get_version = { 'method': 'get_version', |